taler-rust

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

api.rs (6348B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024, 2025, 2026 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 
     17 use axum::http::StatusCode;
     18 use common::setup;
     19 use jiff::Timestamp;
     20 use sqlx::{PgPool, Row, postgres::PgRow};
     21 use taler_api::db::TypeHelper as _;
     22 use taler_common::{
     23     api_common::{EddsaPublicKey, HashCode, ShortHashCode},
     24     api_revenue::RevenueConfig,
     25     api_transfer::WireTransferConfig,
     26     api_wire::{OutgoingHistory, TransferResponse, TransferState, WireConfig},
     27     db::IncomingType,
     28     error_code::ErrorCode,
     29     types::{amount::amount, payto::payto, url},
     30 };
     31 use taler_test_utils::{
     32     json,
     33     routine::{
     34         Status, admin_add_incoming_routine, registration_routine, revenue_routine,
     35         routine_pagination, transfer_routine,
     36     },
     37     server::TestServer as _,
     38 };
     39 use tracing::warn;
     40 
     41 use crate::common::db::{AddIncomingResult, add_incoming};
     42 
     43 mod common;
     44 
     45 #[tokio::test]
     46 async fn errors() {
     47     let (server, _) = setup().await;
     48     server
     49         .get("/unknown")
     50         .await
     51         .assert_error(ErrorCode::GENERIC_ENDPOINT_UNKNOWN);
     52     server
     53         .post("/taler-revenue/config")
     54         .await
     55         .assert_error(ErrorCode::GENERIC_METHOD_INVALID);
     56 }
     57 
     58 #[tokio::test]
     59 async fn config() {
     60     let (server, _) = setup().await;
     61     server
     62         .get("/taler-wire-gateway/config")
     63         .await
     64         .assert_ok_json::<WireConfig>();
     65     server
     66         .get("/taler-wire-transfer-gateway/config")
     67         .await
     68         .assert_ok_json::<WireTransferConfig>();
     69     server
     70         .get("/taler-revenue/config")
     71         .await
     72         .assert_ok_json::<RevenueConfig>();
     73 }
     74 
     75 #[tokio::test]
     76 async fn transfer() {
     77     let (server, _) = setup().await;
     78     transfer_routine(&server, TransferState::success, &payto("payto://test")).await;
     79 }
     80 
     81 #[tokio::test]
     82 async fn outgoing_history() {
     83     let (server, _) = setup().await;
     84     routine_pagination::<OutgoingHistory, _>(
     85         &server,
     86         "/taler-wire-gateway/history/outgoing",
     87         async |i| {
     88             server
     89                 .post("/taler-wire-gateway/transfer")
     90                 .json(&json!({
     91                     "request_uid": HashCode::rand(),
     92                     "amount": amount(format!("EUR:0.0{i}")),
     93                     "exchange_base_url": url("http://exchange.taler"),
     94                     "wtid": ShortHashCode::rand(),
     95                     "credit_account": url("payto://test"),
     96                 }))
     97                 .await
     98                 .assert_ok_json::<TransferResponse>();
     99         },
    100     )
    101     .await;
    102 }
    103 
    104 #[tokio::test]
    105 async fn admin_add_incoming() {
    106     let (server, _) = setup().await;
    107     admin_add_incoming_routine(&server, &payto("payto://test"), true).await;
    108 }
    109 
    110 #[tokio::test]
    111 async fn revenue() {
    112     let (server, _) = setup().await;
    113     revenue_routine(&server, &payto("payto://test"), true).await;
    114 }
    115 
    116 #[tokio::test]
    117 async fn account_check() {
    118     let (server, _) = setup().await;
    119     server
    120         .get("/taler-wire-gateway/account/check")
    121         .query("account", "payto://test")
    122         .await
    123         .assert_status(StatusCode::NOT_IMPLEMENTED);
    124 }
    125 
    126 async fn check_in(pool: &PgPool) -> Vec<Status> {
    127     sqlx::query(
    128             "
    129             SELECT pending_recurrent_in.authorization_pub IS NOT NULL, bounced.tx_in_id IS NOT NULL, type, taler_in.account_pub 
    130             FROM tx_in
    131                 LEFT JOIN taler_in USING (tx_in_id)
    132                 LEFT JOIN pending_recurrent_in USING (tx_in_id)
    133                 LEFT JOIN bounced USING (tx_in_id)
    134             ORDER BY tx_in.tx_in_id
    135         ",
    136         )
    137         .try_map(|r: PgRow| {
    138             Ok(
    139                 if r.try_get_flag(0)? {
    140                     Status::Pending
    141                 } else if r.try_get_flag(1)? {
    142                     Status::Bounced
    143                 } else {
    144                     match r.try_get(2)? {
    145                         None => Status::Simple,
    146                         Some(IncomingType::reserve) => Status::Reserve(r.try_get(3)?),
    147                         Some(IncomingType::kyc) => Status::Kyc(r.try_get(3)?),
    148                         Some(e) => unreachable!("{e:?}")
    149                     }
    150                 }
    151             )
    152         })
    153         .fetch_all(pool)
    154         .await
    155         .unwrap()
    156 }
    157 
    158 async fn register_mapped(pool: &PgPool, account_pub: &EddsaPublicKey) {
    159     let reason = match add_incoming(
    160         pool,
    161         &amount("EUR:42"),
    162         &payto("payto://test"),
    163         "lol",
    164         &Timestamp::now(),
    165         IncomingType::map,
    166         account_pub,
    167     )
    168     .await
    169     .unwrap()
    170     {
    171         AddIncomingResult::Success { .. } => return,
    172         AddIncomingResult::ReservePubReuse => "reserve pub reuse",
    173         AddIncomingResult::UnknownMapping => "unknown mapping",
    174         AddIncomingResult::MappingReuse => "mapping reuse",
    175     };
    176     warn!("Bounce {reason}");
    177     sqlx::query(
    178         "
    179             WITH tx_in AS (
    180                 INSERT INTO tx_in (
    181                     amount,
    182                     debit_payto,
    183                     created_at,
    184                     subject
    185                 ) VALUES (
    186                     (32, 0),
    187                     'payto',
    188                     0,
    189                     'subject'
    190                 ) RETURNING tx_in_id
    191             )
    192             INSERT INTO bounced (tx_in_id)
    193             SELECT tx_in_id FROM tx_in
    194         ",
    195     )
    196     .execute(pool)
    197     .await
    198     .unwrap();
    199 }
    200 
    201 #[tokio::test]
    202 async fn registration() {
    203     let (server, pool) = setup().await;
    204     registration_routine(
    205         &server,
    206         &payto("payto://test"),
    207         || check_in(&pool),
    208         |account_pub| {
    209             let account_pub = account_pub.clone();
    210             let pool = &pool;
    211             async move { register_mapped(pool, &account_pub).await }
    212         },
    213     )
    214     .await;
    215 }