taler-rust

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

routine.rs (47925B)


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