taler-rust

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

routine.rs (50961B)


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