taler-rust

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

commit 3322693545f8648b7e8dd9b881d0827b737680a5
parent dae0531936b449deef1242dfcbcea994de9e3fe2
Author: Antoine A <>
Date:   Thu, 26 Mar 2026 16:19:22 +0100

common: clean tests

Diffstat:
Mcommon/failure-injection/Cargo.toml | 7+++++--
Mcommon/http-client/Cargo.toml | 3+++
Mcommon/taler-api/Cargo.toml | 3+++
Mcommon/taler-build/Cargo.toml | 8++++++--
Mcommon/taler-common/Cargo.toml | 3+++
Mcommon/taler-test-utils/Cargo.toml | 5++++-
Mtaler-cyclos/Cargo.toml | 3+++
Mtaler-cyclos/src/api.rs | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dtaler-cyclos/tests/api.rs | 235-------------------------------------------------------------------------------
Mtaler-magnet-bank/Cargo.toml | 3+++
Rtaler-magnet-bank/tests/fixtures/setup.json -> taler-magnet-bank/fixtures/setup.json | 0
Mtaler-magnet-bank/src/api.rs | 213+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtaler-magnet-bank/src/setup.rs | 2+-
Dtaler-magnet-bank/tests/api.rs | 222-------------------------------------------------------------------------------
14 files changed, 468 insertions(+), 463 deletions(-)

diff --git a/common/failure-injection/Cargo.toml b/common/failure-injection/Cargo.toml @@ -7,5 +7,9 @@ homepage.workspace = true repository.workspace = true license-file.workspace = true +[lib] +test = false +doctest = false + [dependencies] -thiserror.workspace = true -\ No newline at end of file +thiserror.workspace = true diff --git a/common/http-client/Cargo.toml b/common/http-client/Cargo.toml @@ -7,6 +7,9 @@ homepage.workspace = true repository.workspace = true license-file.workspace = true +[lib] +doctest = false + [dependencies] serde_json = { workspace = true, features = ["raw_value"] } serde.workspace = true diff --git a/common/taler-api/Cargo.toml b/common/taler-api/Cargo.toml @@ -7,6 +7,9 @@ homepage.workspace = true repository.workspace = true license-file.workspace = true +[lib] +doctest = false + [dependencies] listenfd = "1.0.0" dashmap = "6.1" diff --git a/common/taler-build/Cargo.toml b/common/taler-build/Cargo.toml @@ -5,4 +5,8 @@ edition.workspace = true authors.workspace = true homepage.workspace = true repository.workspace = true -license-file.workspace = true -\ No newline at end of file +license-file.workspace = true + +[lib] +test = false +doctest = false +\ No newline at end of file diff --git a/common/taler-common/Cargo.toml b/common/taler-common/Cargo.toml @@ -7,6 +7,9 @@ homepage.workspace = true repository.workspace = true license-file.workspace = true +[lib] +doctest = false + [dependencies] glob = "0.3" indexmap = "2.7" diff --git a/common/taler-test-utils/Cargo.toml b/common/taler-test-utils/Cargo.toml @@ -7,10 +7,13 @@ homepage.workspace = true repository.workspace = true license-file.workspace = true +[lib] +test = false +doctest = false + [dependencies] tower = "0.5" flate2 = { version = "1.0", features = ["zlib-rs"], default-features = false } -uuid = { version = "1", features = ["v4"] } axum.workspace = true tokio.workspace = true serde_json.workspace = true diff --git a/taler-cyclos/Cargo.toml b/taler-cyclos/Cargo.toml @@ -8,6 +8,9 @@ homepage.workspace = true repository.workspace = true license-file.workspace = true +[lib] +doctest = false + [dependencies] sqlx.workspace = true serde_json = { workspace = true, features = ["raw_value"] } diff --git a/taler-cyclos/src/api.rs b/taler-cyclos/src/api.rs @@ -294,3 +294,227 @@ impl WireTransferGateway for CyclosApi { } } } + +#[cfg(test)] +mod test { + use std::{ + str::FromStr as _, + sync::{Arc, LazyLock}, + }; + + use compact_str::CompactString; + use jiff::Timestamp; + use sqlx::{PgPool, Row as _, postgres::PgRow}; + use taler_api::{ + api::TalerRouter as _, + auth::AuthMethod, + db::TypeHelper as _, + subject::{IncomingSubject, OutgoingSubject}, + }; + use taler_common::{ + api_common::EddsaPublicKey, + api_revenue::RevenueConfig, + api_transfer::WireTransferConfig, + api_wire::{OutgoingHistory, TransferState, WireConfig}, + db::IncomingType, + types::{ + amount::{Currency, decimal}, + payto::{PaytoURI, payto}, + }, + }; + use taler_test_utils::{ + Router, + db::db_test_setup, + routine::{ + Status, admin_add_incoming_routine, registration_routine, revenue_routine, + routine_pagination, transfer_routine, + }, + server::TestServer as _, + }; + + use crate::{ + api::CyclosApi, + constants::CONFIG_SOURCE, + db::{self, AddIncomingResult, TxIn, TxOutKind}, + }; + + static ACCOUNT: LazyLock<PaytoURI> = + LazyLock::new(|| payto("payto://cyclos/localhost/7762070814178012479?receiver-name=name")); + + async fn setup() -> (Router, PgPool) { + let (_, pool) = db_test_setup(CONFIG_SOURCE).await; + let api = Arc::new( + CyclosApi::start( + pool.clone(), + CompactString::const_new("localhost"), + ACCOUNT.clone(), + Currency::from_str("TEST").unwrap(), + ) + .await, + ); + let server = Router::new() + .wire_gateway(api.clone(), AuthMethod::None) + .wire_transfer_gateway(api.clone()) + .revenue(api, AuthMethod::None) + .finalize(); + + (server, pool) + } + + #[tokio::test] + async fn config() { + let (server, _) = setup().await; + server + .get("/taler-wire-gateway/config") + .await + .assert_ok_json::<WireConfig>(); + server + .get("/taler-wire-transfer-gateway/config") + .await + .assert_ok_json::<WireTransferConfig>(); + server + .get("/taler-revenue/config") + .await + .assert_ok_json::<RevenueConfig>(); + } + + #[tokio::test] + async fn transfer() { + let (server, _) = setup().await; + transfer_routine(&server, TransferState::pending, &ACCOUNT).await; + } + + #[tokio::test] + async fn outgoing_history() { + let (server, pool) = setup().await; + routine_pagination::<OutgoingHistory, _>( + &server, + "/taler-wire-gateway/history/outgoing", + |it| { + it.outgoing_transactions + .into_iter() + .map(|it| *it.row_id as i64) + .collect() + }, + async |_, i| { + db::register_tx_out( + &mut pool.acquire().await.unwrap(), + &db::TxOut { + transfer_id: i as i64, + tx_id: if i % 2 == 0 { + Some((i % 2) as i64) + } else { + None + }, + amount: decimal("10"), + subject: "subject".to_owned(), + creditor_id: 31000163100000000, + creditor_name: "Name".into(), + valued_at: Timestamp::now(), + }, + &TxOutKind::Talerable(OutgoingSubject::rand()), + &Timestamp::now(), + ) + .await + .unwrap(); + }, + ) + .await; + } + + #[tokio::test] + async fn admin_add_incoming() { + let (server, _) = setup().await; + admin_add_incoming_routine(&server, &ACCOUNT, true).await; + } + + #[tokio::test] + async fn revenue() { + let (server, _) = setup().await; + revenue_routine(&server, &ACCOUNT, true).await; + } + + async fn check_in(pool: &PgPool) -> Vec<Status> { + sqlx::query( + " + SELECT pending_recurrent_in.authorization_pub IS NOT NULL, bounced.tx_in_id IS NOT NULL, type, metadata + FROM tx_in + LEFT JOIN taler_in USING (tx_in_id) + LEFT JOIN pending_recurrent_in USING (tx_in_id) + LEFT JOIN bounced USING (tx_in_id) + ORDER BY tx_in.tx_in_id + ", + ) + .try_map(|r: PgRow| { + Ok( + if r.try_get_flag(0)? { + Status::Pending + } else if r.try_get_flag(1)? { + Status::Bounced + } else { + match r.try_get(2)? { + None => Status::Simple, + Some(IncomingType::reserve) => Status::Reserve(r.try_get(3)?), + Some(IncomingType::kyc) => Status::Kyc(r.try_get(3)?), + Some(e) => unreachable!("{e:?}") + } + } + ) + }) + .fetch_all(pool) + .await + .unwrap() + } + + pub async fn test_in(pool: &PgPool, key: EddsaPublicKey) { + let tx = TxIn { + transfer_id: rand::random_range(10..10000), + tx_id: None, + amount: decimal("12"), + subject: String::new(), + debtor_id: rand::random_range(10..10000), + debtor_name: "Name".into(), + valued_at: Timestamp::now(), + }; + let mut db = pool.acquire().await.unwrap(); + let reason = match db::register_tx_in( + &mut db, + &tx, + &Some(IncomingSubject::Map(key)), + &Timestamp::now(), + ) + .await + .unwrap() + { + AddIncomingResult::Success { .. } => return, + AddIncomingResult::ReservePubReuse => "reserve pub reuse", + AddIncomingResult::UnknownMapping => "unknown mapping", + AddIncomingResult::MappingReuse => "mapping reuse", + }; + db::register_bounced_tx_in( + &mut db, + &tx, + rand::random_range(10..10000), + reason, + &Timestamp::now(), + ) + .await + .unwrap(); + } + + #[tokio::test] + async fn registration() { + let (server, pool) = setup().await; + registration_routine( + &server, + &ACCOUNT, + || check_in(&pool), + |account_pub| { + let account_pub = account_pub.clone(); + let pool = &pool; + async move { test_in(pool, account_pub).await } + }, + ) + .await; + } +} diff --git a/taler-cyclos/tests/api.rs b/taler-cyclos/tests/api.rs @@ -1,235 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2025, 2026 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ - -use std::{ - str::FromStr, - sync::{Arc, LazyLock}, -}; - -use compact_str::CompactString; -use jiff::Timestamp; -use sqlx::{PgPool, Row as _, postgres::PgRow}; -use taler_api::{ - api::TalerRouter, - auth::AuthMethod, - db::TypeHelper as _, - subject::{IncomingSubject, OutgoingSubject}, -}; -use taler_common::{ - api_common::EddsaPublicKey, - api_revenue::RevenueConfig, - api_transfer::WireTransferConfig, - api_wire::{OutgoingHistory, TransferState, WireConfig}, - db::IncomingType, - types::{ - amount::{Currency, decimal}, - payto::{PaytoURI, payto}, - }, -}; -use taler_cyclos::{ - api::CyclosApi, - constants::CONFIG_SOURCE, - db::{self, AddIncomingResult, TxIn, TxOutKind}, -}; -use taler_test_utils::{ - Router, - db::db_test_setup, - routine::{ - Status, admin_add_incoming_routine, registration_routine, revenue_routine, - routine_pagination, transfer_routine, - }, - server::TestServer, -}; - -static ACCOUNT: LazyLock<PaytoURI> = - LazyLock::new(|| payto("payto://cyclos/localhost/7762070814178012479?receiver-name=name")); - -async fn setup() -> (Router, PgPool) { - let (_, pool) = db_test_setup(CONFIG_SOURCE).await; - let api = Arc::new( - CyclosApi::start( - pool.clone(), - CompactString::const_new("localhost"), - ACCOUNT.clone(), - Currency::from_str("TEST").unwrap(), - ) - .await, - ); - let server = Router::new() - .wire_gateway(api.clone(), AuthMethod::None) - .wire_transfer_gateway(api.clone()) - .revenue(api, AuthMethod::None) - .finalize(); - - (server, pool) -} - -#[tokio::test] -async fn config() { - let (server, _) = setup().await; - server - .get("/taler-wire-gateway/config") - .await - .assert_ok_json::<WireConfig>(); - server - .get("/taler-wire-transfer-gateway/config") - .await - .assert_ok_json::<WireTransferConfig>(); - server - .get("/taler-revenue/config") - .await - .assert_ok_json::<RevenueConfig>(); -} - -#[tokio::test] -async fn transfer() { - let (server, _) = setup().await; - transfer_routine(&server, TransferState::pending, &ACCOUNT).await; -} - -#[tokio::test] -async fn outgoing_history() { - let (server, pool) = setup().await; - routine_pagination::<OutgoingHistory, _>( - &server, - "/taler-wire-gateway/history/outgoing", - |it| { - it.outgoing_transactions - .into_iter() - .map(|it| *it.row_id as i64) - .collect() - }, - async |_, i| { - db::register_tx_out( - &mut pool.acquire().await.unwrap(), - &db::TxOut { - transfer_id: i as i64, - tx_id: if i % 2 == 0 { - Some((i % 2) as i64) - } else { - None - }, - amount: decimal("10"), - subject: "subject".to_owned(), - creditor_id: 31000163100000000, - creditor_name: "Name".into(), - valued_at: Timestamp::now(), - }, - &TxOutKind::Talerable(OutgoingSubject::rand()), - &Timestamp::now(), - ) - .await - .unwrap(); - }, - ) - .await; -} - -#[tokio::test] -async fn admin_add_incoming() { - let (server, _) = setup().await; - admin_add_incoming_routine(&server, &ACCOUNT, true).await; -} - -#[tokio::test] -async fn revenue() { - let (server, _) = setup().await; - revenue_routine(&server, &ACCOUNT, true).await; -} - -async fn check_in(pool: &PgPool) -> Vec<Status> { - sqlx::query( - " - SELECT pending_recurrent_in.authorization_pub IS NOT NULL, bounced.tx_in_id IS NOT NULL, type, metadata - FROM tx_in - LEFT JOIN taler_in USING (tx_in_id) - LEFT JOIN pending_recurrent_in USING (tx_in_id) - LEFT JOIN bounced USING (tx_in_id) - ORDER BY tx_in.tx_in_id - ", - ) - .try_map(|r: PgRow| { - Ok( - if r.try_get_flag(0)? { - Status::Pending - } else if r.try_get_flag(1)? { - Status::Bounced - } else { - match r.try_get(2)? { - None => Status::Simple, - Some(IncomingType::reserve) => Status::Reserve(r.try_get(3)?), - Some(IncomingType::kyc) => Status::Kyc(r.try_get(3)?), - Some(e) => unreachable!("{e:?}") - } - } - ) - }) - .fetch_all(pool) - .await - .unwrap() -} - -pub async fn test_in(pool: &PgPool, key: EddsaPublicKey) { - let tx = TxIn { - transfer_id: rand::random_range(10..10000), - tx_id: None, - amount: decimal("12"), - subject: String::new(), - debtor_id: rand::random_range(10..10000), - debtor_name: "Name".into(), - valued_at: Timestamp::now(), - }; - let mut db = pool.acquire().await.unwrap(); - let reason = match db::register_tx_in( - &mut db, - &tx, - &Some(IncomingSubject::Map(key)), - &Timestamp::now(), - ) - .await - .unwrap() - { - AddIncomingResult::Success { .. } => return, - AddIncomingResult::ReservePubReuse => "reserve pub reuse", - AddIncomingResult::UnknownMapping => "unknown mapping", - AddIncomingResult::MappingReuse => "mapping reuse", - }; - db::register_bounced_tx_in( - &mut db, - &tx, - rand::random_range(10..10000), - reason, - &Timestamp::now(), - ) - .await - .unwrap(); -} - -#[tokio::test] -async fn registration() { - let (server, pool) = setup().await; - registration_routine( - &server, - &ACCOUNT, - || check_in(&pool), - |account_pub| { - let account_pub = account_pub.clone(); - let pool = &pool; - async move { test_in(pool, account_pub).await } - }, - ) - .await; -} diff --git a/taler-magnet-bank/Cargo.toml b/taler-magnet-bank/Cargo.toml @@ -8,6 +8,9 @@ homepage.workspace = true repository.workspace = true license-file.workspace = true +[lib] +doctest = false + [dependencies] form_urlencoded = "1.2" percent-encoding = "2.3" diff --git a/taler-magnet-bank/tests/fixtures/setup.json b/taler-magnet-bank/fixtures/setup.json diff --git a/taler-magnet-bank/src/api.rs b/taler-magnet-bank/src/api.rs @@ -269,3 +269,216 @@ impl WireTransferGateway for MagnetApi { } } } + +#[cfg(test)] +mod test { + + use std::sync::{Arc, LazyLock}; + + use crate::{ + FullHuPayto, + api::MagnetApi, + constants::CONFIG_SOURCE, + db::{self, AddIncomingResult, TxIn, TxOutKind}, + magnet_api::types::TxStatus, + magnet_payto, + }; + + use jiff::{Timestamp, Zoned}; + use sqlx::{PgPool, Row as _, postgres::PgRow}; + use taler_api::{ + api::TalerRouter as _, + auth::AuthMethod, + db::TypeHelper as _, + subject::{IncomingSubject, OutgoingSubject}, + }; + use taler_common::{ + api_common::EddsaPublicKey, + api_revenue::RevenueConfig, + api_transfer::WireTransferConfig, + api_wire::{OutgoingHistory, TransferState, WireConfig}, + db::IncomingType, + types::{ + amount::amount, + payto::{PaytoURI, payto}, + }, + }; + use taler_test_utils::{ + Router, + db::db_test_setup, + routine::{ + Status, admin_add_incoming_routine, registration_routine, revenue_routine, + routine_pagination, transfer_routine, + }, + server::TestServer, + }; + + static PAYTO: LazyLock<FullHuPayto> = LazyLock::new(|| { + magnet_payto("payto://iban/HU02162000031000164800000000?receiver-name=name") + }); + static ACCOUNT: LazyLock<PaytoURI> = LazyLock::new(|| PAYTO.as_payto()); + + async fn setup() -> (Router, PgPool) { + let (_, pool) = db_test_setup(CONFIG_SOURCE).await; + let api = Arc::new(MagnetApi::start(pool.clone(), ACCOUNT.clone()).await); + let server = Router::new() + .wire_gateway(api.clone(), AuthMethod::None) + .wire_transfer_gateway(api.clone()) + .revenue(api, AuthMethod::None) + .finalize(); + + (server, pool) + } + + #[tokio::test] + async fn config() { + let (server, _) = setup().await; + server + .get("/taler-wire-gateway/config") + .await + .assert_ok_json::<WireConfig>(); + server + .get("/taler-wire-transfer-gateway/config") + .await + .assert_ok_json::<WireTransferConfig>(); + server + .get("/taler-revenue/config") + .await + .assert_ok_json::<RevenueConfig>(); + } + + #[tokio::test] + async fn transfer() { + let (server, _) = setup().await; + transfer_routine( + &server, + TransferState::pending, + &payto("payto://iban/HU02162000031000164800000000?receiver-name=name"), + ) + .await; + } + + #[tokio::test] + async fn outgoing_history() { + let (server, pool) = setup().await; + routine_pagination::<OutgoingHistory, _>( + &server, + "/taler-wire-gateway/history/outgoing", + |it| { + it.outgoing_transactions + .into_iter() + .map(|it| *it.row_id as i64) + .collect() + }, + async |_, i| { + let mut conn = pool.acquire().await.unwrap(); + let now = Zoned::now().date(); + db::register_tx_out( + &mut conn, + &db::TxOut { + code: i as u64, + amount: amount("EUR:10"), + subject: "subject".into(), + creditor: PAYTO.clone(), + value_date: now, + status: TxStatus::Completed, + }, + &TxOutKind::Talerable(OutgoingSubject::rand()), + &Timestamp::now(), + ) + .await + .unwrap(); + }, + ) + .await; + } + + #[tokio::test] + async fn admin_add_incoming() { + let (server, _) = setup().await; + admin_add_incoming_routine(&server, &ACCOUNT, true).await; + } + + #[tokio::test] + async fn revenue() { + let (server, _) = setup().await; + revenue_routine(&server, &ACCOUNT, true).await; + } + + async fn check_in(pool: &PgPool) -> Vec<Status> { + sqlx::query( + " + SELECT pending_recurrent_in.authorization_pub IS NOT NULL, initiated_id IS NOT NULL, type, metadata + FROM tx_in + LEFT JOIN taler_in USING (tx_in_id) + LEFT JOIN pending_recurrent_in USING (tx_in_id) + LEFT JOIN bounced USING (tx_in_id) + ORDER BY tx_in.tx_in_id + ", + ) + .try_map(|r: PgRow| { + Ok( + if r.try_get_flag(0)? { + Status::Pending + } else if r.try_get_flag(1)? { + Status::Bounced + } else { + match r.try_get(2)? { + None => Status::Simple, + Some(IncomingType::reserve) => Status::Reserve(r.try_get(3)?), + Some(IncomingType::kyc) => Status::Kyc(r.try_get(3)?), + Some(e) => unreachable!("{e:?}") + } + } + ) + }) + .fetch_all(pool) + .await + .unwrap() + } + + pub async fn test_in(pool: &PgPool, key: EddsaPublicKey) { + let tx = TxIn { + code: rand::random_range(10..10000), + amount: amount("EUR:12"), + subject: Box::default(), + debtor: PAYTO.clone(), + value_date: Zoned::now().date(), + status: TxStatus::Completed, + }; + let mut db = pool.acquire().await.unwrap(); + let reason = match db::register_tx_in( + &mut db, + &tx, + &Some(IncomingSubject::Map(key)), + &Timestamp::now(), + ) + .await + .unwrap() + { + AddIncomingResult::Success { .. } => return, + AddIncomingResult::ReservePubReuse => "reserve pub reuse", + AddIncomingResult::UnknownMapping => "unknown mapping", + AddIncomingResult::MappingReuse => "mapping reuse", + }; + db::register_bounce_tx_in(&mut db, &tx, reason, &Timestamp::now()) + .await + .unwrap(); + } + + #[tokio::test] + async fn registration() { + let (server, pool) = setup().await; + registration_routine( + &server, + &ACCOUNT, + || check_in(&pool), + |account_pub| { + let account_pub = account_pub.clone(); + let pool = &pool; + async move { test_in(pool, account_pub).await } + }, + ) + .await; + } +} diff --git a/taler-magnet-bank/src/setup.rs b/taler-magnet-bank/src/setup.rs @@ -220,7 +220,7 @@ mod test { #[test] fn keys_files() { // Load JSON file - let content: KeysFile = json_file::load("tests/fixtures/setup.json").unwrap(); + let content: KeysFile = json_file::load("./fixtures/setup.json").unwrap(); // Check full assert!(content.access_token.is_some()); let key = content.signing_key.clone().unwrap(); diff --git a/taler-magnet-bank/tests/api.rs b/taler-magnet-bank/tests/api.rs @@ -1,222 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2025, 2026 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ - -use std::sync::{Arc, LazyLock}; - -use jiff::{Timestamp, Zoned}; -use sqlx::{PgPool, Row as _, postgres::PgRow}; -use taler_api::{ - api::TalerRouter as _, - auth::AuthMethod, - db::TypeHelper as _, - subject::{IncomingSubject, OutgoingSubject}, -}; -use taler_common::{ - api_common::EddsaPublicKey, - api_revenue::RevenueConfig, - api_transfer::WireTransferConfig, - api_wire::{OutgoingHistory, TransferState, WireConfig}, - db::IncomingType, - types::{ - amount::amount, - payto::{PaytoURI, payto}, - }, -}; -use taler_magnet_bank::{ - FullHuPayto, - api::MagnetApi, - constants::CONFIG_SOURCE, - db::{self, AddIncomingResult, TxIn, TxOutKind}, - magnet_api::types::TxStatus, - magnet_payto, -}; -use taler_test_utils::{ - Router, - db::db_test_setup, - routine::{ - Status, admin_add_incoming_routine, registration_routine, revenue_routine, - routine_pagination, transfer_routine, - }, - server::TestServer, -}; - -static PAYTO: LazyLock<FullHuPayto> = - LazyLock::new(|| magnet_payto("payto://iban/HU02162000031000164800000000?receiver-name=name")); -static ACCOUNT: LazyLock<PaytoURI> = LazyLock::new(|| PAYTO.as_payto()); - -async fn setup() -> (Router, PgPool) { - let (_, pool) = db_test_setup(CONFIG_SOURCE).await; - let api = Arc::new(MagnetApi::start(pool.clone(), ACCOUNT.clone()).await); - let server = Router::new() - .wire_gateway(api.clone(), AuthMethod::None) - .wire_transfer_gateway(api.clone()) - .revenue(api, AuthMethod::None) - .finalize(); - - (server, pool) -} - -#[tokio::test] -async fn config() { - let (server, _) = setup().await; - server - .get("/taler-wire-gateway/config") - .await - .assert_ok_json::<WireConfig>(); - server - .get("/taler-wire-transfer-gateway/config") - .await - .assert_ok_json::<WireTransferConfig>(); - server - .get("/taler-revenue/config") - .await - .assert_ok_json::<RevenueConfig>(); -} - -#[tokio::test] -async fn transfer() { - let (server, _) = setup().await; - transfer_routine( - &server, - TransferState::pending, - &payto("payto://iban/HU02162000031000164800000000?receiver-name=name"), - ) - .await; -} - -#[tokio::test] -async fn outgoing_history() { - let (server, pool) = setup().await; - routine_pagination::<OutgoingHistory, _>( - &server, - "/taler-wire-gateway/history/outgoing", - |it| { - it.outgoing_transactions - .into_iter() - .map(|it| *it.row_id as i64) - .collect() - }, - async |_, i| { - let mut conn = pool.acquire().await.unwrap(); - let now = Zoned::now().date(); - db::register_tx_out( - &mut conn, - &db::TxOut { - code: i as u64, - amount: amount("EUR:10"), - subject: "subject".into(), - creditor: PAYTO.clone(), - value_date: now, - status: TxStatus::Completed, - }, - &TxOutKind::Talerable(OutgoingSubject::rand()), - &Timestamp::now(), - ) - .await - .unwrap(); - }, - ) - .await; -} - -#[tokio::test] -async fn admin_add_incoming() { - let (server, _) = setup().await; - admin_add_incoming_routine(&server, &ACCOUNT, true).await; -} - -#[tokio::test] -async fn revenue() { - let (server, _) = setup().await; - revenue_routine(&server, &ACCOUNT, true).await; -} - -async fn check_in(pool: &PgPool) -> Vec<Status> { - sqlx::query( - " - SELECT pending_recurrent_in.authorization_pub IS NOT NULL, initiated_id IS NOT NULL, type, metadata - FROM tx_in - LEFT JOIN taler_in USING (tx_in_id) - LEFT JOIN pending_recurrent_in USING (tx_in_id) - LEFT JOIN bounced USING (tx_in_id) - ORDER BY tx_in.tx_in_id - ", - ) - .try_map(|r: PgRow| { - Ok( - if r.try_get_flag(0)? { - Status::Pending - } else if r.try_get_flag(1)? { - Status::Bounced - } else { - match r.try_get(2)? { - None => Status::Simple, - Some(IncomingType::reserve) => Status::Reserve(r.try_get(3)?), - Some(IncomingType::kyc) => Status::Kyc(r.try_get(3)?), - Some(e) => unreachable!("{e:?}") - } - } - ) - }) - .fetch_all(pool) - .await - .unwrap() -} - -pub async fn test_in(pool: &PgPool, key: EddsaPublicKey) { - let tx = TxIn { - code: rand::random_range(10..10000), - amount: amount("EUR:12"), - subject: Box::default(), - debtor: PAYTO.clone(), - value_date: Zoned::now().date(), - status: TxStatus::Completed, - }; - let mut db = pool.acquire().await.unwrap(); - let reason = match db::register_tx_in( - &mut db, - &tx, - &Some(IncomingSubject::Map(key)), - &Timestamp::now(), - ) - .await - .unwrap() - { - AddIncomingResult::Success { .. } => return, - AddIncomingResult::ReservePubReuse => "reserve pub reuse", - AddIncomingResult::UnknownMapping => "unknown mapping", - AddIncomingResult::MappingReuse => "mapping reuse", - }; - db::register_bounce_tx_in(&mut db, &tx, reason, &Timestamp::now()) - .await - .unwrap(); -} - -#[tokio::test] -async fn registration() { - let (server, pool) = setup().await; - registration_routine( - &server, - &ACCOUNT, - || check_in(&pool), - |account_pub| { - let account_pub = account_pub.clone(); - let pool = &pool; - async move { test_in(pool, account_pub).await } - }, - ) - .await; -}