taler-rust

GNU Taler code in Rust. Largely core banking integrations.
Log | Files | Refs | Submodules | README | LICENSE

test.rs (7972B)


      1 use std::sync::{Arc, LazyLock};
      2 
      3 use axum::{
      4     Router,
      5     http::{StatusCode, header},
      6 };
      7 use serde_json::json;
      8 use sqlx::{PgPool, Row as _, postgres::PgRow};
      9 use taler_common::{
     10     api::{
     11         HashCode, ShortHashCode,
     12         prepared::PreparedTransferConfig,
     13         revenue::RevenueConfig,
     14         wire::{TransferRequest, TransferResponse, TransferState, WireConfig},
     15     },
     16     db::IncomingType,
     17     error_code::ErrorCode,
     18     types::{
     19         amount::{Amount, Currency, amount},
     20         base32::Base32,
     21         payto::{PaytoURI, payto},
     22         url,
     23     },
     24 };
     25 use taler_test_utils::{
     26     db::db_test_setup_manual,
     27     routine::{
     28         Status, admin_add_incoming_routine, in_history_routine, out_history_routine,
     29         registration_routine, revenue_routine, transfer_routine,
     30     },
     31     server::TestServer,
     32     tasks,
     33 };
     34 use tokio::sync::watch::Sender;
     35 
     36 use crate::{
     37     api::TalerRouter,
     38     auth::AuthMethod,
     39     constants::MAX_BODY_LENGTH,
     40     db::TypeHelper as _,
     41     test::{api::TestApi, db::notification_listener},
     42 };
     43 
     44 mod api;
     45 mod db;
     46 
     47 fn test_api(pool: PgPool, currency: Currency) -> Router {
     48     let outgoing_channel = Sender::new(0);
     49     let incoming_channel = Sender::new(0);
     50     let wg = TestApi {
     51         currency,
     52         pool: pool.clone(),
     53         outgoing_channel: outgoing_channel.clone(),
     54         incoming_channel: incoming_channel.clone(),
     55     };
     56     tokio::spawn(notification_listener(
     57         pool,
     58         outgoing_channel,
     59         incoming_channel,
     60     ));
     61     let state = Arc::new(wg);
     62     Router::new()
     63         .wire_gateway(state.clone(), AuthMethod::None)
     64         .prepared_transfer(state.clone())
     65         .revenue(state, AuthMethod::None)
     66 }
     67 
     68 async fn setup() -> (Router, PgPool) {
     69     let (_, pool) = db_test_setup_manual("db".as_ref(), "taler-api").await;
     70     (
     71         test_api(pool.clone(), "EUR".parse().unwrap()).finalize(),
     72         pool,
     73     )
     74 }
     75 
     76 #[tokio::test]
     77 async fn body_parsing() {
     78     let (server, _) = setup().await;
     79     let normal_body = TransferRequest {
     80         request_uid: Base32::rand(),
     81         amount: Amount::zero(&Currency::EUR),
     82         exchange_base_url: url("https://test.com"),
     83         wtid: Base32::rand(),
     84         credit_account: payto("payto:://test?receiver-name=lol"),
     85         metadata: None,
     86     };
     87 
     88     // Check OK
     89     server
     90         .post("/taler-wire-gateway/transfer")
     91         .json(&normal_body)
     92         .deflate()
     93         .await
     94         .assert_ok_json::<TransferResponse>();
     95 
     96     // Headers check
     97     server
     98         .post("/taler-wire-gateway/transfer")
     99         .json(&normal_body)
    100         .remove(header::CONTENT_TYPE)
    101         .await
    102         .assert_error_status(
    103             ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED,
    104             StatusCode::UNSUPPORTED_MEDIA_TYPE,
    105         );
    106     server
    107         .post("/taler-wire-gateway/transfer")
    108         .json(&normal_body)
    109         .deflate()
    110         .remove(header::CONTENT_ENCODING)
    111         .await
    112         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    113     server
    114         .post("/taler-wire-gateway/transfer")
    115         .json(&normal_body)
    116         .header(header::CONTENT_TYPE, "invalid")
    117         .await
    118         .assert_error_status(
    119             ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED,
    120             StatusCode::UNSUPPORTED_MEDIA_TYPE,
    121         );
    122     server
    123         .post("/taler-wire-gateway/transfer")
    124         .json(&normal_body)
    125         .header(header::CONTENT_ENCODING, "deflate")
    126         .await
    127         .assert_error(ErrorCode::GENERIC_COMPRESSION_INVALID);
    128     server
    129         .post("/taler-wire-gateway/transfer")
    130         .json(&normal_body)
    131         .header(header::CONTENT_ENCODING, "invalid")
    132         .await
    133         .assert_error_status(
    134             ErrorCode::GENERIC_HTTP_HEADERS_MALFORMED,
    135             StatusCode::UNSUPPORTED_MEDIA_TYPE,
    136         );
    137 
    138     // Body size limit
    139     let huge_body = TransferRequest {
    140         credit_account: payto(format!(
    141             "payto:://test?message={:A<1$}",
    142             "payout", MAX_BODY_LENGTH
    143         )),
    144         ..normal_body
    145     };
    146     server
    147         .post("/taler-wire-gateway/transfer")
    148         .json(&huge_body)
    149         .await
    150         .assert_error(ErrorCode::GENERIC_UPLOAD_EXCEEDS_LIMIT);
    151     server
    152         .post("/taler-wire-gateway/transfer")
    153         .json(&huge_body)
    154         .deflate()
    155         .await
    156         .assert_error(ErrorCode::GENERIC_UPLOAD_EXCEEDS_LIMIT);
    157 }
    158 
    159 static PAYTO: LazyLock<PaytoURI> = LazyLock::new(|| payto("payto://test?receiver-name=Test"));
    160 
    161 #[tokio::test]
    162 async fn errors() {
    163     let (server, _) = setup().await;
    164     server
    165         .get("/unknown")
    166         .await
    167         .assert_error(ErrorCode::GENERIC_ENDPOINT_UNKNOWN);
    168     server
    169         .post("/taler-revenue/config")
    170         .await
    171         .assert_error(ErrorCode::GENERIC_METHOD_INVALID);
    172 }
    173 
    174 #[tokio::test]
    175 async fn config() {
    176     let (server, _) = setup().await;
    177     server
    178         .get("/taler-wire-gateway/config")
    179         .await
    180         .assert_ok_json::<WireConfig>();
    181     server
    182         .get("/taler-prepared-transfer/config")
    183         .await
    184         .assert_ok_json::<PreparedTransferConfig>();
    185     server
    186         .get("/taler-revenue/config")
    187         .await
    188         .assert_ok_json::<RevenueConfig>();
    189 }
    190 
    191 #[tokio::test]
    192 async fn transfer() {
    193     let (server, _) = setup().await;
    194     transfer_routine(&server, TransferState::success, &PAYTO).await;
    195 }
    196 
    197 #[tokio::test]
    198 async fn outgoing_history() {
    199     let (server, _) = &setup().await;
    200     out_history_routine(
    201         server,
    202         tasks!({
    203             server
    204                 .post("/taler-wire-gateway/transfer")
    205                 .json(json!({
    206                     "request_uid": HashCode::rand(),
    207                     "amount": amount("EUR:1"),
    208                     "exchange_base_url": url("http://exchange.taler"),
    209                     "wtid": ShortHashCode::rand(),
    210                     "credit_account": PAYTO.clone(),
    211                 }))
    212                 .await
    213                 .assert_ok_json::<TransferResponse>();
    214         }),
    215         tasks!(),
    216     )
    217     .await;
    218 }
    219 
    220 #[tokio::test]
    221 async fn admin_add_incoming() {
    222     let (server, _) = setup().await;
    223     admin_add_incoming_routine(&server, &PAYTO, true).await;
    224 }
    225 
    226 #[tokio::test]
    227 async fn in_history() {
    228     let (server, _) = setup().await;
    229     in_history_routine(&server, &PAYTO, true, tasks!(), tasks!()).await;
    230 }
    231 
    232 #[tokio::test]
    233 async fn revenue() {
    234     let (server, _) = setup().await;
    235     revenue_routine(&server, &PAYTO, true, tasks!(), tasks!()).await;
    236 }
    237 
    238 #[tokio::test]
    239 async fn account_check() {
    240     let (server, _) = setup().await;
    241     server
    242         .get("/taler-wire-gateway/account/check")
    243         .query("account", "payto://test")
    244         .await
    245         .assert_status(StatusCode::NOT_IMPLEMENTED);
    246 }
    247 
    248 async fn check_in(pool: &PgPool) -> Vec<Status> {
    249     sqlx::query(
    250             "
    251             SELECT pending_recurrent_in.authorization_pub IS NOT NULL, bounced.tx_in_id IS NOT NULL, type, taler_in.account_pub
    252             FROM tx_in
    253                 LEFT JOIN taler_in USING (tx_in_id)
    254                 LEFT JOIN pending_recurrent_in USING (tx_in_id)
    255                 LEFT JOIN bounced USING (tx_in_id)
    256             ORDER BY tx_in.tx_in_id
    257         ",
    258         )
    259         .try_map(|r: PgRow| {
    260             Ok(
    261                 if r.try_get_flag(0)? {
    262                     Status::Pending
    263                 } else if r.try_get_flag(1)? {
    264                     Status::Bounced
    265                 } else {
    266                     match r.try_get(2)? {
    267                         None => Status::Simple,
    268                         Some(IncomingType::reserve) => Status::Reserve(r.try_get(3)?),
    269                         Some(IncomingType::kyc) => Status::Kyc(r.try_get(3)?),
    270                         Some(e) => unreachable!("{e:?}")
    271                     }
    272                 }
    273             )
    274         })
    275         .fetch_all(pool)
    276         .await
    277         .unwrap()
    278 }
    279 
    280 #[tokio::test]
    281 async fn registration() {
    282     let (server, pool) = setup().await;
    283     registration_routine(&server, &PAYTO, || check_in(&pool)).await;
    284 }