taler-rust

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

routine.rs (44931B)


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