taler-rust

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

test.rs (7993B)


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