taler-rust

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

routine.rs (50100B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2024-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 std::{
     18     fmt::Debug,
     19     future::Future,
     20     str::FromStr,
     21     sync::LazyLock,
     22     time::{Duration, Instant},
     23 };
     24 
     25 use aws_lc_rs::signature::{Ed25519KeyPair, KeyPair as _};
     26 use axum::{Router, http::StatusCode};
     27 use jiff::{SignedDuration, Timestamp};
     28 use serde::de::DeserializeOwned;
     29 use taler_api::subject::fmt_in_subject;
     30 use taler_common::{
     31     api::{
     32         EddsaPublicKey, EddsaSignature, HashCode, ShortHashCode,
     33         params::PageParams,
     34         prepared::{
     35             PublicKeyAlg, RegistrationRequest, RegistrationResponse, TransferSubject, TransferType,
     36             Unregistration,
     37         },
     38         revenue::RevenueIncomingHistory,
     39         wire::{
     40             IncomingBankTransaction, IncomingHistory, OutgoingHistory, TransferList,
     41             TransferRequest, TransferResponse, TransferState, TransferStatus,
     42         },
     43     },
     44     db::IncomingType,
     45     error_code::ErrorCode,
     46     types::{
     47         amount::{Amount, Currency, amount},
     48         base32::Base32,
     49         payto::PaytoURI,
     50         timestamp::TalerTimestamp,
     51         url,
     52     },
     53 };
     54 use tokio::time::sleep;
     55 
     56 use crate::{
     57     json,
     58     server::{TestResponse, TestServer as _},
     59 };
     60 
     61 static UNKNOWN: LazyLock<PaytoURI> = LazyLock::new(|| {
     62     PaytoURI::from_str("payto://malformed/unused?receiver-name=Malformed").unwrap()
     63 });
     64 
     65 pub trait Page: DeserializeOwned + Debug {
     66     fn ids(&self) -> Vec<i64>;
     67 }
     68 
     69 impl Page for IncomingHistory {
     70     fn ids(&self) -> Vec<i64> {
     71         self.incoming_transactions
     72             .iter()
     73             .map(|it| match it {
     74                 IncomingBankTransaction::Reserve { row_id, .. }
     75                 | IncomingBankTransaction::Wad { row_id, .. }
     76                 | IncomingBankTransaction::Kyc { row_id, .. } => *row_id as i64,
     77             })
     78             .collect()
     79     }
     80 }
     81 
     82 impl Page for OutgoingHistory {
     83     fn ids(&self) -> Vec<i64> {
     84         self.outgoing_transactions
     85             .iter()
     86             .map(|it| it.row_id as i64)
     87             .collect()
     88     }
     89 }
     90 
     91 impl Page for RevenueIncomingHistory {
     92     fn ids(&self) -> Vec<i64> {
     93         self.incoming_transactions
     94             .iter()
     95             .map(|it| it.row_id as i64)
     96             .collect()
     97     }
     98 }
     99 
    100 impl Page for TransferList {
    101     fn ids(&self) -> Vec<i64> {
    102         self.transfers.iter().map(|it| it.row_id as i64).collect()
    103     }
    104 }
    105 
    106 pub async fn latest_id<T: Page>(router: &Router) -> i64 {
    107     let res = router.get("?limit=-1").await;
    108     if res.status == StatusCode::NO_CONTENT {
    109         0
    110     } else {
    111         res.assert_ids::<T>(1)[0]
    112     }
    113 }
    114 
    115 pub async fn routine_pagination<T: Page>(
    116     server: &Router,
    117     mut register: Tasks<impl AsyncFnMut(usize)>,
    118 ) {
    119     // Check supported
    120     if !server.get("").await.is_implemented() {
    121         return;
    122     }
    123 
    124     // Check history is following specs
    125     let assert_history =
    126         async |args: &str, size: usize| server.get(format!("?{args}")).await.assert_ids::<T>(size);
    127     // Get latest registered id
    128     let latest_id = async || latest_id::<T>(server).await;
    129 
    130     for i in 0..20 {
    131         (register.lambda)(i).await;
    132     }
    133 
    134     let id = latest_id().await;
    135 
    136     // default
    137     assert_history("", 20).await;
    138 
    139     // forward range
    140     assert_history("limit=10", 10).await;
    141     assert_history("limit=10&offset=4", 10).await;
    142 
    143     // backward range
    144     assert_history("limit=-10", 10).await;
    145     assert_history(&format!("limit=-10&{}", id - 4), 10).await;
    146 }
    147 
    148 pub async fn assert_time<R: Debug>(range: std::ops::Range<u128>, task: impl Future<Output = R>) {
    149     let start = Instant::now();
    150     task.await;
    151     let elapsed = start.elapsed().as_millis();
    152     if !range.contains(&elapsed) {
    153         panic!("Expected to last {range:?} got {elapsed:?}")
    154     }
    155 }
    156 
    157 pub async fn routine_history<T: Page>(
    158     server: &Router,
    159     mut register: Tasks<impl AsyncFnMut(usize)>,
    160     mut ignore: Tasks<impl AsyncFnMut(usize)>,
    161 ) {
    162     // Check history is following specs
    163     macro_rules! assert_history {
    164         ($args:expr, $size:expr) => {
    165             async {
    166                 server
    167                     .get(&format!("?{}", $args))
    168                     .await
    169                     .assert_ids::<T>($size)
    170             }
    171         };
    172     }
    173     // Get latest registered id
    174     let latest_id = async || assert_history!("limit=-1", 1).await[0];
    175 
    176     // Check error when no transactions
    177     assert_history!("limit=7".to_owned(), 0).await;
    178 
    179     let mut register_iter = (0..register.len).peekable();
    180     let mut ignore_iter = (0..ignore.len).peekable();
    181     while register_iter.peek().is_some() || ignore_iter.peek().is_some() {
    182         if let Some(idx) = register_iter.next() {
    183             (register.lambda)(idx).await
    184         }
    185         if let Some(idx) = ignore_iter.next() {
    186             (ignore.lambda)(idx).await
    187         }
    188     }
    189     let nb_register = register.len;
    190     let nb_ignore = ignore.len;
    191     let nb_total = nb_register + nb_ignore;
    192 
    193     // Check ignored
    194     assert_history!(format_args!("limit={nb_total}"), nb_register).await;
    195     // Check skip ignored
    196     assert_history!(format_args!("limit={nb_register}"), nb_register).await;
    197 
    198     // Check no polling when we cannot have more transactions
    199     assert_time(
    200         0..200,
    201         assert_history!(
    202             format_args!("limit=-{}&timeout_ms=1000", nb_register + 1),
    203             nb_register
    204         ),
    205     )
    206     .await;
    207     // Check no polling when already find transactions even if less than delta
    208     assert_time(
    209         0..200,
    210         assert_history!(
    211             format_args!("limit={}&timeout_ms=1000", nb_register + 1),
    212             nb_register
    213         ),
    214     )
    215     .await;
    216 
    217     // Check polling
    218     let id = latest_id().await;
    219     tokio::join!(
    220         // Check polling succeed
    221         assert_time(
    222             100..400,
    223             assert_history!(format_args!("limit=2&offset={id}&timeout_ms=1000"), 1)
    224         ),
    225         assert_time(
    226             200..500,
    227             assert_history!(
    228                 format_args!(
    229                     "limit=1&offset={}&timeout_ms=200",
    230                     id as usize + nb_total * 3
    231                 ),
    232                 0
    233             )
    234         ),
    235         async {
    236             sleep(Duration::from_millis(100)).await;
    237             (register.lambda)(0).await
    238         }
    239     );
    240 
    241     // Test triggers
    242     for i in 0..register.len {
    243         let id = latest_id().await;
    244         tokio::join!(
    245             // Check polling succeed
    246             assert_time(
    247                 100..400,
    248                 assert_history!(format_args!("limit=7&offset={id}&timeout_ms=1000"), 1)
    249             ),
    250             async {
    251                 sleep(Duration::from_millis(100)).await;
    252                 (register.lambda)(i).await
    253             }
    254         );
    255     }
    256 
    257     // Test doesn't trigger
    258     let id = latest_id().await;
    259     tokio::join!(
    260         // Check polling succeed
    261         assert_time(
    262             200..500,
    263             assert_history!(format_args!("limit=7&offset={id}&timeout_ms=200"), 0)
    264         ),
    265         async {
    266             sleep(Duration::from_millis(100)).await;
    267             for i in 0..ignore.len {
    268                 (ignore.lambda)(i).await
    269             }
    270         }
    271     );
    272 
    273     routine_pagination::<T>(server, register).await;
    274 }
    275 
    276 impl TestResponse {
    277     #[track_caller]
    278     fn assert_ids<T: Page>(&self, size: usize) -> Vec<i64> {
    279         if size == 0 {
    280             self.assert_no_content();
    281             return vec![];
    282         }
    283         let body = self.assert_ok_json::<T>();
    284         let page = body.ids();
    285         let params = self.query::<PageParams>().check().unwrap();
    286 
    287         // testing the size is like expected
    288         assert_eq!(size, page.len(), "bad page length: {page:?}\n{body:?}");
    289         if params.limit < 0 {
    290             // testing that the first id is at most the 'offset' query param.
    291             assert!(
    292                 params
    293                     .offset
    294                     .map(|offset| page[0] <= offset)
    295                     .unwrap_or(true),
    296                 "bad page offset: {params:?} {page:?}"
    297             );
    298             // testing that the id decreases.
    299             assert!(
    300                 page.as_slice().is_sorted_by(|a, b| a > b),
    301                 "bad page order: {page:?}"
    302             )
    303         } else {
    304             // testing that the first id is at least the 'offset' query param.
    305             assert!(
    306                 params
    307                     .offset
    308                     .map(|offset| page[0] >= offset)
    309                     .unwrap_or(true),
    310                 "bad page offset: {params:?} {page:?}"
    311             );
    312             // testing that the id increases.
    313             assert!(page.as_slice().is_sorted(), "bad page order: {page:?}")
    314         }
    315         page
    316     }
    317 }
    318 
    319 // Get currency from config
    320 async fn get_currency(server: &Router) -> Currency {
    321     let config = server
    322         .get("/config")
    323         .await
    324         .assert_ok_json::<serde_json::Value>();
    325     let currency = config["currency"].as_str().unwrap();
    326     Currency::from_str(currency).unwrap()
    327 }
    328 
    329 /// Test standard behavior of the transfer endpoints
    330 pub async fn transfer_routine(
    331     wire_gateway: &Router,
    332     default_status: TransferState,
    333     credit_account: &PaytoURI,
    334 ) {
    335     let currency = &get_currency(wire_gateway).await;
    336     let default_amount = amount(format!("{currency}:42"));
    337     let request_uid = HashCode::rand();
    338     let wtid = ShortHashCode::rand();
    339     let valid_req = json!({
    340         "request_uid": request_uid,
    341         "amount": default_amount,
    342         "exchange_base_url": "http://exchange.taler/",
    343         "wtid": wtid,
    344         "credit_account": credit_account,
    345     });
    346 
    347     // Check empty db
    348     {
    349         wire_gateway.get("/transfers").await.assert_no_content();
    350         wire_gateway
    351             .get(format!("/transfers?status={}", default_status.as_ref()))
    352             .await
    353             .assert_no_content();
    354     }
    355 
    356     // TODO check subject formatting
    357 
    358     let routine = async |req: &TransferRequest| {
    359         // Check OK
    360         let first = wire_gateway
    361             .post("/transfer")
    362             .json(req)
    363             .await
    364             .assert_ok_json::<TransferResponse>();
    365         // Check idempotent
    366         let second = wire_gateway
    367             .post("/transfer")
    368             .json(req)
    369             .await
    370             .assert_ok_json::<TransferResponse>();
    371         assert_eq!(first.row_id, second.row_id);
    372         assert_eq!(first.timestamp, second.timestamp);
    373 
    374         // Check by id
    375         let tx = wire_gateway
    376             .get(format!("/transfers/{}", first.row_id))
    377             .await
    378             .assert_ok_json::<TransferStatus>();
    379         assert_eq!(default_status, tx.status);
    380         assert_eq!(default_amount, tx.amount);
    381         assert_eq!("http://exchange.taler/", tx.exchange_base_url);
    382         assert_eq!(req.wtid, tx.wtid);
    383         assert_eq!(first.timestamp, tx.timestamp);
    384         assert_eq!(req.metadata, tx.metadata);
    385         assert_eq!(credit_account, &tx.credit_account);
    386 
    387         // Check page
    388         let list = wire_gateway
    389             .get("/transfers?limit=-1")
    390             .await
    391             .assert_ok_json::<TransferList>();
    392         let tx = &list.transfers[0];
    393         assert_eq!(first.row_id, tx.row_id);
    394         assert_eq!(default_status, tx.status);
    395         assert_eq!(default_amount, tx.amount);
    396         assert_eq!(first.timestamp, tx.timestamp);
    397         assert_eq!(credit_account, &tx.credit_account);
    398     };
    399 
    400     let req = TransferRequest {
    401         request_uid,
    402         amount: default_amount,
    403         exchange_base_url: url("http://exchange.taler/"),
    404         metadata: None,
    405         wtid,
    406         credit_account: credit_account.clone(),
    407     };
    408     // Simple
    409     routine(&req).await;
    410     // With metadata
    411     routine(&TransferRequest {
    412         request_uid: HashCode::rand(),
    413         wtid: ShortHashCode::rand(),
    414         metadata: Some("test:medatata".into()),
    415         ..req
    416     })
    417     .await;
    418 
    419     // Check create transfer errors
    420     {
    421         // Check request uid reuse
    422         wire_gateway
    423             .post("/transfer")
    424             .json(json!(valid_req + {
    425                 "wtid": ShortHashCode::rand()
    426             }))
    427             .await
    428             .assert_error(ErrorCode::BANK_TRANSFER_REQUEST_UID_REUSED);
    429         // Check wtid reuse
    430         wire_gateway
    431             .post("/transfer")
    432             .json(json!(valid_req + {
    433                 "request_uid": HashCode::rand(),
    434             }))
    435             .await
    436             .assert_error(ErrorCode::BANK_TRANSFER_WTID_REUSED);
    437 
    438         // Check currency mismatch
    439         wire_gateway
    440             .post("/transfer")
    441             .json(json!(valid_req + {
    442                 "amount": "BAD:42"
    443             }))
    444             .await
    445             .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH);
    446 
    447         // Base Base32
    448         wire_gateway
    449             .post("/transfer")
    450             .json(json!(valid_req + {
    451                 "wtid": "I love chocolate"
    452             }))
    453             .await
    454             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    455         wire_gateway
    456             .post("/transfer")
    457             .json(json!(valid_req + {
    458                 "wtid": Base32::<31>::rand()
    459             }))
    460             .await
    461             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    462         wire_gateway
    463             .post("/transfer")
    464             .json(json!(valid_req + {
    465                 "request_uid": "I love chocolate"
    466             }))
    467             .await
    468             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    469         wire_gateway
    470             .post("/transfer")
    471             .json(json!(valid_req + {
    472                 "request_uid": Base32::<65>::rand()
    473             }))
    474             .await
    475             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    476 
    477         // Missing receiver-name
    478         let res = wire_gateway
    479             .post("/transfer")
    480             .json(json!(valid_req + {
    481                 "credit_account": credit_account.as_ref().as_str().split('?').next().unwrap()
    482             }))
    483             .await;
    484         if !res.status.is_success() {
    485             res.assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
    486         }
    487 
    488         // Unsupported payto kind
    489         wire_gateway
    490             .post("/transfer")
    491             .json(json!(valid_req + { "credit_account": *UNKNOWN }))
    492             .await
    493             .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
    494         // Malformed payto
    495         wire_gateway
    496             .post("/transfer")
    497             .json(json!(valid_req + { "credit_account": "http://email@test.com" }))
    498             .await
    499             .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    500 
    501         // Bad base URL
    502         for base_url in [
    503             "not-a-url",
    504             "file://not.http.com/",
    505             "no.transport.com/",
    506             "https://not.a/base/url",
    507         ] {
    508             wire_gateway
    509                 .post("/transfer")
    510                 .json(&json!(valid_req + { "exchange_base_url": base_url }))
    511                 .await
    512                 .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    513         }
    514 
    515         // Malformed metadata
    516         for metadata in ["bad_id", "bad id", "bad@id.com", &"A".repeat(41)] {
    517             wire_gateway
    518                 .post("/transfer")
    519                 .json(&json!(valid_req + { "metadata": metadata }))
    520                 .await
    521                 .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    522         }
    523     }
    524 
    525     // Check transfer by id errors
    526     {
    527         // Check unknown transaction
    528         wire_gateway
    529             .get("/transfers/42")
    530             .await
    531             .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND);
    532     }
    533 
    534     // Check transfer page
    535     {
    536         for _ in 0..4 {
    537             wire_gateway
    538                 .post("/transfer")
    539                 .json(&json!(valid_req + {
    540                     "request_uid": HashCode::rand(),
    541                     "wtid": ShortHashCode::rand(),
    542                 }))
    543                 .await
    544                 .assert_ok_json::<TransferResponse>();
    545         }
    546         {
    547             let list = wire_gateway
    548                 .get("/transfers")
    549                 .await
    550                 .assert_ok_json::<TransferList>();
    551             assert_eq!(list.transfers.len(), 6);
    552             assert_eq!(
    553                 list,
    554                 wire_gateway
    555                     .get(format!("/transfers?status={}", default_status.as_ref()))
    556                     .await
    557                     .assert_ok_json::<TransferList>()
    558             )
    559         }
    560 
    561         // Pagination test
    562         routine_pagination::<TransferList>(
    563             &wire_gateway.suffix("/transfers"),
    564             crate::tasks!({
    565                 wire_gateway
    566                     .post("/transfer")
    567                     .json(json!({
    568                         "request_uid": HashCode::rand(),
    569                         "amount": amount(format!("{currency}:0.1")),
    570                         "exchange_base_url": url("http://exchange.taler"),
    571                         "wtid": ShortHashCode::rand(),
    572                         "credit_account": credit_account,
    573                     }))
    574                     .await
    575                     .assert_ok_json::<TransferResponse>();
    576             }),
    577         )
    578         .await;
    579     }
    580 }
    581 
    582 async fn add_incoming_routine(
    583     wire_gateway: &Router,
    584     prepared_transfer: &Router,
    585     currency: &Currency,
    586     kind: IncomingType,
    587     debit_acount: &PaytoURI,
    588     credit_account: &PaytoURI,
    589 ) {
    590     let (path, key) = match kind {
    591         IncomingType::reserve => ("/admin/add-incoming", "reserve_pub"),
    592         IncomingType::kyc => ("/admin/add-kycauth", "account_pub"),
    593         IncomingType::map => ("/admin/add-mapped", "authorization_pub"),
    594     };
    595     let key_pair = Ed25519KeyPair::generate().unwrap();
    596     let pub_key = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap();
    597     // Valid
    598     let req = RegistrationRequest {
    599         credit_account: credit_account.clone(),
    600         r#type: TransferType::reserve,
    601         recurrent: false,
    602         credit_amount: Amount::new(currency, 44, 0),
    603         alg: PublicKeyAlg::EdDSA,
    604         account_pub: pub_key,
    605         authorization_pub: pub_key,
    606         authorization_sig: EddsaSignature::ZEROED,
    607     }
    608     .signed(&key_pair);
    609 
    610     prepared_transfer
    611         .post("/registration")
    612         .json(&req)
    613         .await
    614         .assert_ok_json::<RegistrationResponse>();
    615     let valid_req = json!({
    616         "amount": format!("{currency}:44"),
    617         key: pub_key,
    618         "debit_account": debit_acount,
    619     });
    620 
    621     // Check OK
    622     wire_gateway.post(path).json(&valid_req).await.assert_ok();
    623 
    624     match kind {
    625         IncomingType::reserve => {
    626             // Trigger conflict due to reused reserve_pub
    627             wire_gateway
    628                 .post(path)
    629                 .json(&json!(valid_req + {
    630                     "amount": format!("{currency}:44.1"),
    631                 }))
    632                 .await
    633                 .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT)
    634         }
    635         IncomingType::kyc => {
    636             // Non conflict on reuse
    637             wire_gateway.post(path).json(&valid_req).await.assert_ok();
    638         }
    639         IncomingType::map => {
    640             // Trigger conflict due to reused authorization_pub
    641             wire_gateway
    642                 .post(path)
    643                 .json(&valid_req)
    644                 .await
    645                 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
    646             // Trigger conflict due to unknown authorization_pub
    647             wire_gateway
    648                 .post(path)
    649                 .json(&json!(valid_req + {
    650                    key: EddsaPublicKey::rand()
    651                 }))
    652                 .await
    653                 .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_UNKNOWN);
    654         }
    655     }
    656 
    657     // Currency mismatch
    658     wire_gateway
    659         .post(path)
    660         .json(&json!(valid_req + { "amount": "BAD:33" }))
    661         .await
    662         .assert_error(ErrorCode::GENERIC_CURRENCY_MISMATCH);
    663 
    664     // Bad BASE32 reserve_pub
    665     wire_gateway
    666         .post(path)
    667         .json(json!(valid_req + { key: "I love chocolate" }))
    668         .await
    669         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    670     wire_gateway
    671         .post(path)
    672         .json(json!(valid_req + { key: Base32::<31>::rand() }))
    673         .await
    674         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    675 
    676     // Unsupported payto kind
    677     wire_gateway
    678         .post(path)
    679         .json(json!(valid_req + { "debit_account": *UNKNOWN }))
    680         .await
    681         .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
    682 
    683     // Malformed payto
    684     wire_gateway
    685         .post(path)
    686         .json(json!(valid_req + { "debit_account": "http://email@test.com" }))
    687         .await
    688         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
    689 }
    690 
    691 pub struct Tasks<F: AsyncFnMut(usize)> {
    692     pub len: usize,
    693     pub lambda: F,
    694 }
    695 
    696 #[macro_export]
    697 macro_rules! tasks {
    698     // Create new
    699     ( $( $(if $cond:expr =>)? $body:block ),* $(,)? ) => {
    700         $crate::tasks!(@build 0usize;
    701             $( $(if $cond =>)? $body ),*
    702         )
    703     };
    704 
    705     // Append to existing
    706     ( $existing:expr ; $( $(if $cond:expr =>)? $body:block ),* $(,)? ) => {{
    707         let mut existing = $existing;
    708         let mut extra = $crate::tasks![
    709             $( $(if $cond =>)? $body ),*
    710         ];
    711 
    712         $crate::routine::Tasks {
    713             len: existing.len + extra.len,
    714             lambda: async move |i: usize| {
    715                 if existing.len == 0 && extra.len == 0 {
    716                     return;
    717                 }
    718 
    719                 let i = i % (existing.len + extra.len);
    720 
    721                 if i < existing.len {
    722                     (existing.lambda)(i).await;
    723                 } else {
    724                     (extra.lambda)(i - existing.len).await;
    725                 }
    726             }
    727         }
    728     }};
    729 
    730     // Internal builder
    731     (@build $idx:expr; $( $(if $cond:expr =>)? $body:block ),* ) => {{
    732         let conditions = [ $( true $( && $cond )? ),* ];
    733         let len = conditions.iter().filter(|&&x| x).count();
    734 
    735         $crate::routine::Tasks {
    736             len,
    737             lambda: async move |i: usize| {
    738                 if len == 0 {
    739                     return;
    740                 }
    741 
    742                 let i = i % len;
    743                 let mut current = 0usize;
    744 
    745                 $crate::tasks!(
    746                     @dispatch i, current, conditions, 0usize;
    747                     $( $(if $cond =>)? $body ),*
    748                 );
    749 
    750                 let _ = (i, &mut current); // suppress lints
    751 
    752                 unreachable!()
    753             }
    754         }
    755     }};
    756 
    757     // Recursive dispatcher
    758     (@dispatch $i:expr, $current:ident, $conditions:ident, $idx:expr;) => {};
    759 
    760     (@dispatch
    761         $i:expr,
    762         $current:ident,
    763         $conditions:ident,
    764         $idx:expr;
    765         $(if $cond:expr =>)? $body:block
    766         $(, $($rest:tt)*)?
    767     ) => {{
    768         if $conditions[$idx] {
    769             if $i == $current {
    770                 (async $body).await;
    771                 return;
    772             }
    773             $current += 1;
    774         }
    775 
    776         $crate::tasks!(
    777             @dispatch
    778             $i,
    779             $current,
    780             $conditions,
    781             $idx + 1usize;
    782             $($($rest)*)?
    783         );
    784     }};
    785 }
    786 
    787 /// Test standard behavior of the revenue endpoints
    788 pub async fn revenue_routine(
    789     wire_gateway: &Router,
    790     revenue_api: &Router,
    791     debit_acount: &PaytoURI,
    792     register: Tasks<impl AsyncFnMut(usize)>,
    793     ignore: Tasks<impl AsyncFnMut(usize)>,
    794 ) {
    795     let currency = &get_currency(revenue_api).await;
    796     routine_history::<RevenueIncomingHistory>(
    797         &revenue_api.suffix("/history"),
    798         tasks!(register;
    799             {
    800                 wire_gateway
    801                         .post("/admin/add-incoming")
    802                         .json(json!({
    803                             "amount": format!("{currency}:1"),
    804                             "reserve_pub": EddsaPublicKey::rand(),
    805                             "debit_account": debit_acount,
    806                         }))
    807                         .await
    808                         .assert_ok_json::<TransferResponse>();
    809             },
    810             {
    811                 wire_gateway
    812                         .post("/admin/add-kycauth")
    813                         .json(json!({
    814                             "amount": format!("{currency}:2"),
    815                             "account_pub": EddsaPublicKey::rand(),
    816                             "debit_account": debit_acount,
    817                         }))
    818                         .await
    819                         .assert_ok_json::<TransferResponse>();
    820             }
    821         ),
    822         ignore,
    823     )
    824     .await;
    825 }
    826 
    827 /// Test standard behavior of the outgoing history endpoint
    828 pub async fn out_history_routine(
    829     wire_gateway: &Router,
    830     register: Tasks<impl AsyncFnMut(usize)>,
    831     ignore: Tasks<impl AsyncFnMut(usize)>,
    832 ) {
    833     routine_history::<OutgoingHistory>(&wire_gateway.suffix("/history/outgoing"), register, ignore)
    834         .await;
    835 }
    836 
    837 /// Test standard behavior of the incoming history endpoint
    838 pub async fn in_history_routine(
    839     wire_gateway: &Router,
    840     prepared_transfer: &Router,
    841     debit_account: &PaytoURI,
    842     credit_account: &PaytoURI,
    843     register: Tasks<impl AsyncFnMut(usize)>,
    844     ignored: Tasks<impl AsyncFnMut(usize)>,
    845 ) {
    846     let currency = &get_currency(wire_gateway).await;
    847     let mut key = Ed25519KeyPair::generate().unwrap();
    848 
    849     routine_history::<IncomingHistory>(
    850         &wire_gateway.suffix("/history/incoming"),
    851         tasks!(register;
    852             {
    853                 wire_gateway
    854                     .post("/admin/add-incoming")
    855                     .json(json!({
    856                         "amount": format!("{currency}:1"),
    857                         "reserve_pub": EddsaPublicKey::rand(),
    858                         "debit_account": debit_account,
    859                     }))
    860                     .await
    861                     .assert_ok_json::<TransferResponse>();
    862             },
    863             {
    864                 key = Ed25519KeyPair::generate().unwrap();
    865                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    866                 let reserve_pub = EddsaPublicKey::rand();
    867                 let amount = Amount::new(currency, 2, 0);
    868                 prepared_transfer
    869                     .post("/registration")
    870                     .json(
    871                         RegistrationRequest {
    872                             credit_account: credit_account.clone(),
    873                             r#type: TransferType::reserve,
    874                             recurrent: true,
    875                             credit_amount: amount,
    876                             alg: PublicKeyAlg::EdDSA,
    877                             account_pub: reserve_pub,
    878                             authorization_pub: auth_pub,
    879                             authorization_sig: EddsaSignature::ZEROED,
    880                         }
    881                         .signed(&key)
    882                     )
    883                     .await
    884                     .assert_ok_json::<RegistrationResponse>();
    885                 wire_gateway
    886                     .post("/admin/add-mapped")
    887                     .json(json!({
    888                         "amount": amount,
    889                         "authorization_pub": auth_pub,
    890                         "debit_account": debit_account,
    891                     }))
    892                     .await
    893                     .assert_ok_json::<TransferResponse>();
    894                 wire_gateway
    895                     .post("/admin/add-mapped")
    896                     .json(json!({
    897                         "amount": amount,
    898                         "authorization_pub": auth_pub,
    899                         "debit_account": debit_account,
    900                     }))
    901                     .await
    902                     .assert_ok_json::<TransferResponse>();
    903             },
    904             {
    905                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    906                 let reserve_pub = EddsaPublicKey::rand();
    907                 prepared_transfer
    908                     .post("/registration")
    909                     .json(
    910                         RegistrationRequest {
    911                             credit_account: credit_account.clone(),
    912                             r#type: TransferType::reserve,
    913                             recurrent: true,
    914                             credit_amount: Amount::new(currency, 3, 0),
    915                             alg: PublicKeyAlg::EdDSA,
    916                             account_pub: reserve_pub,
    917                             authorization_pub: auth_pub,
    918                             authorization_sig: EddsaSignature::ZEROED,
    919                         }
    920                         .signed(&key)
    921                     )
    922                     .await
    923                     .assert_ok_json::<RegistrationResponse>();
    924             },
    925             {
    926                 wire_gateway
    927                     .post("/admin/add-kycauth")
    928                     .json(json!({
    929                         "amount": format!("{currency}:4"),
    930                         "account_pub": EddsaPublicKey::rand(),
    931                         "debit_account": debit_account,
    932                     }))
    933                     .await
    934                     .assert_ok_json::<TransferResponse>();
    935             },
    936             {
    937                 key = Ed25519KeyPair::generate().unwrap();
    938                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    939                 let account_pub = EddsaPublicKey::rand();
    940                 let amount = Amount::new(currency, 5, 0);
    941                 prepared_transfer
    942                     .post("/registration")
    943                     .json(
    944                         RegistrationRequest {
    945                             credit_account: credit_account.clone(),
    946                             r#type: TransferType::kyc,
    947                             recurrent: true,
    948                             credit_amount: amount,
    949                             alg: PublicKeyAlg::EdDSA,
    950                             account_pub,
    951                             authorization_pub: auth_pub,
    952                             authorization_sig: EddsaSignature::ZEROED,
    953                         }
    954                         .signed(&key)
    955                     )
    956                     .await
    957                     .assert_ok_json::<RegistrationResponse>();
    958                 wire_gateway
    959                     .post("/admin/add-mapped")
    960                     .json(json!({
    961                         "amount": amount,
    962                         "authorization_pub": auth_pub,
    963                         "debit_account": debit_account,
    964                     }))
    965                     .await
    966                     .assert_ok_json::<TransferResponse>();
    967                 wire_gateway
    968                     .post("/admin/add-mapped")
    969                     .json(json!({
    970                         "amount": amount,
    971                         "authorization_pub": auth_pub,
    972                         "debit_account": debit_account,
    973                     }))
    974                     .await
    975                     .assert_ok_json::<TransferResponse>();
    976             },
    977             {
    978                 let auth_pub = EddsaPublicKey::try_from(key.public_key().as_ref()).unwrap();
    979                 let account_pub = EddsaPublicKey::rand();
    980                 prepared_transfer
    981                     .post("/registration")
    982                     .json(
    983                         RegistrationRequest {
    984                             credit_account: credit_account.clone(),
    985                             r#type: TransferType::kyc,
    986                             recurrent: true,
    987                             credit_amount: Amount::new(currency, 6, 0),
    988                             alg: PublicKeyAlg::EdDSA,
    989                             account_pub,
    990                             authorization_pub: auth_pub,
    991                             authorization_sig: EddsaSignature::ZEROED,
    992                         }
    993                         .signed(&key)
    994                     )
    995                     .await
    996                     .assert_ok_json::<RegistrationResponse>();
    997             }
    998         ),
    999         ignored,
   1000     )
   1001     .await;
   1002 }
   1003 
   1004 /// Test standard behavior of the admin add incoming endpoints
   1005 pub async fn admin_add_incoming_routine(
   1006     wire_gateway: &Router,
   1007     prepared_transfer: &Router,
   1008     debit_acount: &PaytoURI,
   1009     credit_account: &PaytoURI,
   1010 ) {
   1011     let currency = &get_currency(wire_gateway).await;
   1012     for kind in IncomingType::entries {
   1013         add_incoming_routine(
   1014             wire_gateway,
   1015             prepared_transfer,
   1016             currency,
   1017             *kind,
   1018             debit_acount,
   1019             credit_account,
   1020         )
   1021         .await;
   1022     }
   1023 }
   1024 
   1025 #[derive(Debug, PartialEq, Eq)]
   1026 pub enum Status {
   1027     Simple,
   1028     Pending,
   1029     Bounced,
   1030     Incomplete,
   1031     Reserve(EddsaPublicKey),
   1032     Kyc(EddsaPublicKey),
   1033 }
   1034 
   1035 /// Test standard registration behavior of the registration endpoints
   1036 pub async fn registration_routine<F1: Future<Output = Vec<Status>>>(
   1037     wire_gateway: &Router,
   1038     prepared_transfer: &Router,
   1039     debit_acount: &PaytoURI,
   1040     credit_account: &PaytoURI,
   1041     unknown_account: &PaytoURI,
   1042     mut in_status: impl FnMut() -> F1,
   1043 ) {
   1044     pub use Status::*;
   1045     let mut check_in = async |state: &[Status]| {
   1046         let current = in_status().await;
   1047         pretty_assertions::assert_eq!(state, current);
   1048     };
   1049 
   1050     let currency = &get_currency(wire_gateway).await;
   1051     let amount = amount(format!("{currency}:42"));
   1052     let key_pair1 = Ed25519KeyPair::generate().unwrap();
   1053     let auth_pub1 = EddsaPublicKey::try_from(key_pair1.public_key().as_ref()).unwrap();
   1054     let req = RegistrationRequest {
   1055         credit_account: credit_account.clone(),
   1056         r#type: TransferType::reserve,
   1057         recurrent: false,
   1058         credit_amount: amount,
   1059         alg: PublicKeyAlg::EdDSA,
   1060         account_pub: auth_pub1,
   1061         authorization_pub: auth_pub1,
   1062         authorization_sig: EddsaSignature::ZEROED,
   1063     };
   1064 
   1065     let register = async |auth_pub: &EddsaPublicKey| {
   1066         wire_gateway
   1067             .post("/admin/add-mapped")
   1068             .json(json!({
   1069                 "amount": format!("{currency}:42"),
   1070                 "authorization_pub": auth_pub,
   1071                 "debit_account": debit_acount,
   1072             }))
   1073             .await
   1074     };
   1075 
   1076     /* ----- Registration ----- */
   1077     let routine = async |ty: TransferType,
   1078                          account_pub: EddsaPublicKey,
   1079                          recurrent: bool,
   1080                          fmt: IncomingType| {
   1081         let req = RegistrationRequest {
   1082             r#type: ty,
   1083             account_pub,
   1084             recurrent,
   1085             ..req.clone()
   1086         }
   1087         .signed(&key_pair1);
   1088         // Valid
   1089         let res = prepared_transfer
   1090             .post("/registration")
   1091             .json(&req)
   1092             .await
   1093             .assert_ok_json::<RegistrationResponse>();
   1094 
   1095         // Idempotent
   1096         assert_eq!(
   1097             res,
   1098             prepared_transfer
   1099                 .post("/registration")
   1100                 .json(&req)
   1101                 .await
   1102                 .assert_ok_json::<RegistrationResponse>()
   1103         );
   1104 
   1105         assert!(!res.subjects.is_empty());
   1106 
   1107         for sub in res.subjects {
   1108             if let TransferSubject::Simple { subject, .. } = sub {
   1109                 assert_eq!(subject, fmt_in_subject(fmt, &auth_pub1).to_string());
   1110             };
   1111         }
   1112     };
   1113     for ty in [TransferType::reserve, TransferType::kyc] {
   1114         routine(ty, auth_pub1, false, ty.into()).await;
   1115         routine(ty, auth_pub1, true, IncomingType::map).await;
   1116     }
   1117 
   1118     let acc_pub1 = EddsaPublicKey::rand();
   1119     for ty in [TransferType::reserve, TransferType::kyc] {
   1120         routine(ty, acc_pub1, false, IncomingType::map).await;
   1121         routine(ty, acc_pub1, true, IncomingType::map).await;
   1122     }
   1123 
   1124     // Bad signature
   1125     prepared_transfer
   1126         .post("/registration")
   1127         .json(&req)
   1128         .await
   1129         .assert_error(ErrorCode::BANK_BAD_SIGNATURE);
   1130 
   1131     // Unknown account
   1132     prepared_transfer
   1133         .post("/registration")
   1134         .json(
   1135             RegistrationRequest {
   1136                 r#credit_account: unknown_account.clone(),
   1137                 ..req.clone()
   1138             }
   1139             .signed(&key_pair1),
   1140         )
   1141         .await
   1142         .assert_error(ErrorCode::BANK_UNKNOWN_CREDITOR);
   1143 
   1144     // Unsupported payto kind
   1145     prepared_transfer
   1146         .post("/registration")
   1147         .json(
   1148             RegistrationRequest {
   1149                 r#credit_account: UNKNOWN.clone(),
   1150                 ..req.clone()
   1151             }
   1152             .signed(&key_pair1),
   1153         )
   1154         .await
   1155         .assert_error(ErrorCode::GENERIC_PAYTO_URI_MALFORMED);
   1156 
   1157     // Malformed payto
   1158     prepared_transfer
   1159         .post("/registration")
   1160         .json(
   1161             RegistrationRequest {
   1162                 r#credit_account: unsafe { PaytoURI::from_raw("http://email@test.com") },
   1163                 ..req.clone()
   1164             }
   1165             .signed(&key_pair1),
   1166         )
   1167         .await
   1168         .assert_error(ErrorCode::GENERIC_JSON_INVALID);
   1169 
   1170     // Reserve pub reuse
   1171     prepared_transfer
   1172         .post("/registration")
   1173         .json(
   1174             RegistrationRequest {
   1175                 account_pub: acc_pub1,
   1176                 ..req.clone()
   1177             }
   1178             .signed(&key_pair1),
   1179         )
   1180         .await
   1181         .assert_ok_json::<RegistrationResponse>();
   1182     {
   1183         let key_pair = Ed25519KeyPair::generate().unwrap();
   1184         let auth_pub = EddsaPublicKey::try_from(key_pair.public_key().as_ref()).unwrap();
   1185         prepared_transfer
   1186             .post("/registration")
   1187             .json(
   1188                 RegistrationRequest {
   1189                     account_pub: acc_pub1,
   1190                     authorization_pub: auth_pub,
   1191                     ..req.clone()
   1192                 }
   1193                 .signed(&key_pair),
   1194             )
   1195             .await
   1196             .assert_error(ErrorCode::BANK_DUPLICATE_RESERVE_PUB_SUBJECT);
   1197     }
   1198 
   1199     // Non recurrent accept one then bounce
   1200     prepared_transfer
   1201         .post("/registration")
   1202         .json(
   1203             RegistrationRequest {
   1204                 account_pub: acc_pub1,
   1205                 ..req.clone()
   1206             }
   1207             .signed(&key_pair1),
   1208         )
   1209         .await
   1210         .assert_ok_json::<RegistrationResponse>();
   1211     register(&auth_pub1)
   1212         .await
   1213         .assert_ok_json::<TransferResponse>();
   1214     check_in(&[Reserve(acc_pub1)]).await;
   1215     register(&auth_pub1)
   1216         .await
   1217         .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
   1218 
   1219     // Again without using mapping
   1220     let acc_pub2 = EddsaPublicKey::rand();
   1221     prepared_transfer
   1222         .post("/registration")
   1223         .json(
   1224             RegistrationRequest {
   1225                 account_pub: acc_pub2,
   1226                 ..req.clone()
   1227             }
   1228             .signed(&key_pair1),
   1229         )
   1230         .await
   1231         .assert_ok_json::<RegistrationResponse>();
   1232     wire_gateway
   1233         .post("/admin/add-incoming")
   1234         .json(json!({
   1235             "amount": amount,
   1236             "reserve_pub": acc_pub2,
   1237             "debit_account": debit_acount,
   1238         }))
   1239         .await
   1240         .assert_ok();
   1241     register(&auth_pub1)
   1242         .await
   1243         .assert_error(ErrorCode::BANK_TRANSFER_MAPPING_REUSED);
   1244     check_in(&[Reserve(acc_pub1), Reserve(acc_pub2)]).await;
   1245 
   1246     // Recurrent accept one and delay others
   1247     let acc_pub3 = EddsaPublicKey::rand();
   1248     prepared_transfer
   1249         .post("/registration")
   1250         .json(
   1251             RegistrationRequest {
   1252                 account_pub: acc_pub3,
   1253                 recurrent: true,
   1254                 ..req.clone()
   1255             }
   1256             .signed(&key_pair1),
   1257         )
   1258         .await
   1259         .assert_ok_json::<RegistrationResponse>();
   1260     for _ in 0..5 {
   1261         register(&auth_pub1)
   1262             .await
   1263             .assert_ok_json::<TransferResponse>();
   1264     }
   1265     check_in(&[
   1266         Reserve(acc_pub1),
   1267         Reserve(acc_pub2),
   1268         Reserve(acc_pub3),
   1269         Pending,
   1270         Pending,
   1271         Pending,
   1272         Pending,
   1273     ])
   1274     .await;
   1275 
   1276     // Complete pending on recurrent update
   1277     let acc_pub4 = EddsaPublicKey::rand();
   1278     prepared_transfer
   1279         .post("/registration")
   1280         .json(
   1281             RegistrationRequest {
   1282                 r#type: TransferType::kyc,
   1283                 account_pub: acc_pub4,
   1284                 recurrent: true,
   1285                 ..req.clone()
   1286             }
   1287             .signed(&key_pair1),
   1288         )
   1289         .await
   1290         .assert_ok_json::<RegistrationResponse>();
   1291     prepared_transfer
   1292         .post("/registration")
   1293         .json(
   1294             RegistrationRequest {
   1295                 account_pub: acc_pub4,
   1296                 recurrent: true,
   1297                 ..req.clone()
   1298             }
   1299             .signed(&key_pair1),
   1300         )
   1301         .await
   1302         .assert_ok_json::<RegistrationResponse>();
   1303     check_in(&[
   1304         Reserve(acc_pub1),
   1305         Reserve(acc_pub2),
   1306         Reserve(acc_pub3),
   1307         Kyc(acc_pub4),
   1308         Reserve(acc_pub4),
   1309         Pending,
   1310         Pending,
   1311     ])
   1312     .await;
   1313 
   1314     // Kyc key reuse keep pending ones
   1315     wire_gateway
   1316         .post("/admin/add-kycauth")
   1317         .json(json!({
   1318             "amount": amount,
   1319             "account_pub": acc_pub4,
   1320             "debit_account": debit_acount,
   1321         }))
   1322         .await
   1323         .assert_ok_json::<TransferResponse>();
   1324     check_in(&[
   1325         Reserve(acc_pub1),
   1326         Reserve(acc_pub2),
   1327         Reserve(acc_pub3),
   1328         Kyc(acc_pub4),
   1329         Reserve(acc_pub4),
   1330         Pending,
   1331         Pending,
   1332         Kyc(acc_pub4),
   1333     ])
   1334     .await;
   1335 
   1336     // Switching to non recurrent cancel pending
   1337     let auth_pair = Ed25519KeyPair::generate().unwrap();
   1338     let auth_pub2 = EddsaPublicKey::try_from(auth_pair.public_key().as_ref()).unwrap();
   1339     prepared_transfer
   1340         .post("/registration")
   1341         .json(
   1342             RegistrationRequest {
   1343                 account_pub: auth_pub2,
   1344                 authorization_pub: auth_pub2,
   1345                 recurrent: true,
   1346                 ..req.clone()
   1347             }
   1348             .signed(&auth_pair),
   1349         )
   1350         .await
   1351         .assert_ok_json::<RegistrationResponse>();
   1352     for _ in 0..3 {
   1353         register(&auth_pub2)
   1354             .await
   1355             .assert_ok_json::<TransferResponse>();
   1356     }
   1357     check_in(&[
   1358         Reserve(acc_pub1),
   1359         Reserve(acc_pub2),
   1360         Reserve(acc_pub3),
   1361         Kyc(acc_pub4),
   1362         Reserve(acc_pub4),
   1363         Pending,
   1364         Pending,
   1365         Kyc(acc_pub4),
   1366         Reserve(auth_pub2),
   1367         Pending,
   1368         Pending,
   1369     ])
   1370     .await;
   1371     prepared_transfer
   1372         .post("/registration")
   1373         .json(
   1374             RegistrationRequest {
   1375                 r#type: TransferType::kyc,
   1376                 account_pub: auth_pub2,
   1377                 authorization_pub: auth_pub2,
   1378                 recurrent: false,
   1379                 ..req.clone()
   1380             }
   1381             .signed(&auth_pair),
   1382         )
   1383         .await
   1384         .assert_ok_json::<RegistrationResponse>();
   1385     check_in(&[
   1386         Reserve(acc_pub1),
   1387         Reserve(acc_pub2),
   1388         Reserve(acc_pub3),
   1389         Kyc(acc_pub4),
   1390         Reserve(acc_pub4),
   1391         Pending,
   1392         Pending,
   1393         Kyc(acc_pub4),
   1394         Reserve(auth_pub2),
   1395         Bounced,
   1396         Bounced,
   1397     ])
   1398     .await;
   1399 
   1400     // Recurrent reserve simple subject
   1401     let acc_pub5 = EddsaPublicKey::rand();
   1402     prepared_transfer
   1403         .post("/registration")
   1404         .json(
   1405             RegistrationRequest {
   1406                 account_pub: acc_pub5,
   1407                 authorization_pub: auth_pub2,
   1408                 recurrent: true,
   1409                 ..req.clone()
   1410             }
   1411             .signed(&auth_pair),
   1412         )
   1413         .await
   1414         .assert_ok_json::<RegistrationResponse>();
   1415     wire_gateway
   1416         .post("/admin/add-incoming")
   1417         .json(json!({
   1418             "amount": amount,
   1419             "reserve_pub": acc_pub5,
   1420             "debit_account": debit_acount,
   1421         }))
   1422         .await
   1423         .assert_ok();
   1424     register(&auth_pub2)
   1425         .await
   1426         .assert_ok_json::<TransferResponse>();
   1427     check_in(&[
   1428         Reserve(acc_pub1),
   1429         Reserve(acc_pub2),
   1430         Reserve(acc_pub3),
   1431         Kyc(acc_pub4),
   1432         Reserve(acc_pub4),
   1433         Pending,
   1434         Pending,
   1435         Kyc(acc_pub4),
   1436         Reserve(auth_pub2),
   1437         Bounced,
   1438         Bounced,
   1439         Reserve(acc_pub5),
   1440         Pending,
   1441     ])
   1442     .await;
   1443 
   1444     // Recurrent kyc simple subject
   1445     prepared_transfer
   1446         .post("/registration")
   1447         .json(
   1448             RegistrationRequest {
   1449                 r#type: TransferType::kyc,
   1450                 account_pub: acc_pub5,
   1451                 authorization_pub: auth_pub2,
   1452                 ..req.clone()
   1453             }
   1454             .signed(&auth_pair),
   1455         )
   1456         .await
   1457         .assert_ok_json::<RegistrationResponse>();
   1458     prepared_transfer
   1459         .post("/registration")
   1460         .json(
   1461             RegistrationRequest {
   1462                 r#type: TransferType::kyc,
   1463                 account_pub: acc_pub5,
   1464                 authorization_pub: auth_pub2,
   1465                 recurrent: true,
   1466                 ..req.clone()
   1467             }
   1468             .signed(&auth_pair),
   1469         )
   1470         .await
   1471         .assert_ok_json::<RegistrationResponse>();
   1472     let pair = Ed25519KeyPair::generate().unwrap();
   1473     prepared_transfer
   1474         .post("/registration")
   1475         .json(
   1476             RegistrationRequest {
   1477                 r#type: TransferType::kyc,
   1478                 account_pub: acc_pub5,
   1479                 authorization_pub: EddsaPublicKey::try_from(pair.public_key().as_ref()).unwrap(),
   1480                 recurrent: true,
   1481                 ..req.clone()
   1482             }
   1483             .signed(&pair),
   1484         )
   1485         .await
   1486         .assert_ok_json::<RegistrationResponse>();
   1487     wire_gateway
   1488         .post("/admin/add-kycauth")
   1489         .json(json!({
   1490             "amount": amount,
   1491             "account_pub": acc_pub5,
   1492             "debit_account": debit_acount,
   1493         }))
   1494         .await
   1495         .assert_ok();
   1496     for _ in 0..2 {
   1497         register(&auth_pub2)
   1498             .await
   1499             .assert_ok_json::<TransferResponse>();
   1500     }
   1501     check_in(&[
   1502         Reserve(acc_pub1),
   1503         Reserve(acc_pub2),
   1504         Reserve(acc_pub3),
   1505         Kyc(acc_pub4),
   1506         Reserve(acc_pub4),
   1507         Pending,
   1508         Pending,
   1509         Kyc(acc_pub4),
   1510         Reserve(auth_pub2),
   1511         Bounced,
   1512         Bounced,
   1513         Reserve(acc_pub5),
   1514         Bounced,
   1515         Kyc(acc_pub5),
   1516         Kyc(acc_pub5),
   1517         Pending,
   1518     ])
   1519     .await;
   1520 
   1521     // Kyc without using mapping
   1522 
   1523     /* ----- Unregistration ----- */
   1524     let un_req = Unregistration {
   1525         timestamp: TalerTimestamp::Timestamp(Timestamp::now()),
   1526         authorization_pub: auth_pub2,
   1527         authorization_sig: EddsaSignature::ZEROED,
   1528     };
   1529     let signed = un_req.clone().signed(&auth_pair);
   1530 
   1531     // Delete
   1532     prepared_transfer
   1533         .post("/unregistration")
   1534         .json(&signed)
   1535         .await
   1536         .assert_no_content();
   1537 
   1538     // Check bounce pending on deletion
   1539     check_in(&[
   1540         Reserve(acc_pub1),
   1541         Reserve(acc_pub2),
   1542         Reserve(acc_pub3),
   1543         Kyc(acc_pub4),
   1544         Reserve(acc_pub4),
   1545         Pending,
   1546         Pending,
   1547         Kyc(acc_pub4),
   1548         Reserve(auth_pub2),
   1549         Bounced,
   1550         Bounced,
   1551         Reserve(acc_pub5),
   1552         Bounced,
   1553         Kyc(acc_pub5),
   1554         Kyc(acc_pub5),
   1555         Bounced,
   1556     ])
   1557     .await;
   1558 
   1559     // Idempotent
   1560     prepared_transfer
   1561         .post("/unregistration")
   1562         .json(&signed)
   1563         .await
   1564         .assert_error(ErrorCode::BANK_TRANSACTION_NOT_FOUND);
   1565 
   1566     // Bad signature
   1567     prepared_transfer
   1568         .post("/unregistration")
   1569         .json(&un_req)
   1570         .await
   1571         .assert_error(ErrorCode::BANK_BAD_SIGNATURE);
   1572 
   1573     // Old timestamp
   1574     prepared_transfer
   1575         .post("/unregistration")
   1576         .json(
   1577             Unregistration {
   1578                 timestamp: TalerTimestamp::Timestamp(
   1579                     Timestamp::now() - SignedDuration::from_mins(10),
   1580                 ),
   1581                 ..un_req.clone()
   1582             }
   1583             .signed(&auth_pair),
   1584         )
   1585         .await
   1586         .assert_error(ErrorCode::BANK_OLD_TIMESTAMP);
   1587 
   1588     // Never timestamp
   1589     prepared_transfer
   1590         .post("/unregistration")
   1591         .json(
   1592             Unregistration {
   1593                 timestamp: TalerTimestamp::Never,
   1594                 ..un_req.clone()
   1595             }
   1596             .signed(&auth_pair),
   1597         )
   1598         .await
   1599         .assert_error(ErrorCode::BANK_OLD_TIMESTAMP);
   1600 
   1601     /* ----- API ----- */
   1602 
   1603     let history: Vec<_> = wire_gateway
   1604         .get("/history/incoming?limit=20")
   1605         .await
   1606         .assert_ok_json::<IncomingHistory>()
   1607         .incoming_transactions
   1608         .into_iter()
   1609         .map(|tx| {
   1610             let (acc_pub, auth_pub, auth_sig) = match tx {
   1611                 IncomingBankTransaction::Reserve {
   1612                     reserve_pub,
   1613                     authorization_pub,
   1614                     authorization_sig,
   1615                     ..
   1616                 } => (reserve_pub, authorization_pub, authorization_sig),
   1617                 IncomingBankTransaction::Wad { .. } => unreachable!(),
   1618                 IncomingBankTransaction::Kyc {
   1619                     account_pub,
   1620                     authorization_pub,
   1621                     authorization_sig,
   1622                     ..
   1623                 } => (account_pub, authorization_pub, authorization_sig),
   1624             };
   1625             assert_eq!(auth_pub.is_some(), auth_sig.is_some());
   1626             (acc_pub, auth_pub)
   1627         })
   1628         .collect();
   1629     pretty_assertions::assert_eq!(
   1630         history,
   1631         [
   1632             (acc_pub1, Some(auth_pub1)),
   1633             (acc_pub2, None),
   1634             (acc_pub3, Some(auth_pub1)),
   1635             (acc_pub4, Some(auth_pub1)),
   1636             (acc_pub4, Some(auth_pub1)),
   1637             (acc_pub4, None),
   1638             (auth_pub2, Some(auth_pub2)),
   1639             (acc_pub5, None),
   1640             (acc_pub5, None),
   1641             (acc_pub5, Some(auth_pub2)),
   1642         ]
   1643     )
   1644 }