libeufin

Integration and sandbox testing for FinTech APIs and data formats
Log | Files | Refs | Submodules | README | LICENSE

lib.rs (15727B)


      1 /*
      2 * This file is part of LibEuFin.
      3 * Copyright (C) 2026 Taler Systems S.A.
      4 
      5 * LibEuFin is free software; you can redistribute it and/or modify
      6 * it under the terms of the GNU Affero General Public License as
      7 * published by the Free Software Foundation; either version 3, or
      8 * (at your option) any later version.
      9 
     10 * LibEuFin is distributed in the hope that it will be useful, but
     11 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     12 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
     13 * Public License for more details.
     14 
     15 * You should have received a copy of the GNU Affero General Public
     16 * License along with LibEuFin; see the file COPYING.  If not, see
     17 * <http://www.gnu.org/licenses/>
     18 */
     19 
     20 use taler_common::config::Config;
     21 
     22 #[cfg(test)]
     23 mod integration;
     24 
     25 pub fn full_cfg(base: &str, mem: &str) -> Config {
     26     let mut parser = taler_common::config::parser::Parser::empty();
     27     parser
     28         .load_env(libeufin_bank::constants::CONFIG_SOURCE)
     29         .unwrap();
     30     parser
     31         .load_env(libeufin_nexus::constants::CONFIG_SOURCE)
     32         .unwrap();
     33     parser
     34         .load_env(libeufin_ebisync::constants::CONFIG_SOURCE)
     35         .unwrap();
     36     parser.parse_file(base.into(), 0).unwrap();
     37     parser.parse_str(mem).unwrap();
     38     parser.finish()
     39 }
     40 
     41 #[cfg(test)]
     42 mod test {
     43     use std::{fs::Permissions, io::Write, os::unix::fs::PermissionsExt as _, path::PathBuf};
     44 
     45     use libeufin_ebics::keys::{BankKeys, ClientKeys, persist_bank_keys, persist_client_keys};
     46     use libeufin_nexus::config::{NexusCfg, NexusKeysCfg};
     47     use sqlx::PgPool;
     48     use taler_test_utils::cli::clap_parse;
     49 
     50     use crate::full_cfg;
     51 
     52     /** Test error format related to the keying process */
     53     #[tokio::test]
     54     async fn keys() {
     55         let nexus_cmds = ["ebics-submit", "ebics-fetch"];
     56         let nexus_all_cmds = ["ebics-submit", "ebics-fetch", "ebics-setup"];
     57         let sync_cmds = ["fetch"];
     58         let sync_all_cmds = ["fetch", "setup"];
     59         let cfg = full_cfg("conf/cli.conf", "");
     60         let cfg = NexusCfg::parse(&cfg).unwrap();
     61         let NexusKeysCfg { client, bank } = cfg.keys().unwrap();
     62 
     63         let c_path = &PathBuf::from(client);
     64         let b_path = &PathBuf::from(bank);
     65         std::fs::create_dir_all(c_path.parent().unwrap()).unwrap();
     66         std::fs::create_dir_all(b_path.parent().unwrap()).unwrap();
     67 
     68         let is_root = nix::unistd::Uid::effective().is_root();
     69 
     70         let check_cmds = async |msg: &str| {
     71             for cmd in nexus_cmds {
     72                 let tmp: libeufin_nexus::Args = clap_parse(cmd).unwrap();
     73                 let res = libeufin_nexus::run(&cfg.cfg, tmp.cmd).await.unwrap_err();
     74                 assert_eq!(
     75                     res.to_string(),
     76                     msg.replace("SETUPCMD", "libeufin-nexus ebics-setup")
     77                 );
     78             }
     79             for cmd in sync_cmds {
     80                 let tmp: libeufin_ebisync::Args = clap_parse(cmd).unwrap();
     81                 let res = libeufin_ebisync::run(&cfg.cfg, tmp.cmd).await.unwrap_err();
     82                 assert_eq!(
     83                     res.to_string(),
     84                     msg.replace("SETUPCMD", "libeufin-ebisync setup")
     85                 );
     86             }
     87         };
     88         let check_all_cmds = async |msg: &str| {
     89             for cmd in nexus_all_cmds {
     90                 let tmp: libeufin_nexus::Args = clap_parse(cmd).unwrap();
     91                 let res = libeufin_nexus::run(&cfg.cfg, tmp.cmd).await.unwrap_err();
     92                 assert_eq!(
     93                     res.to_string(),
     94                     msg.replace("SETUPCMD", "libeufin-nexus ebics-setup")
     95                 );
     96             }
     97             for cmd in sync_all_cmds {
     98                 let tmp: libeufin_ebisync::Args = clap_parse(cmd).unwrap();
     99                 let res = libeufin_ebisync::run(&cfg.cfg, tmp.cmd).await.unwrap_err();
    100                 assert_eq!(
    101                     res.to_string(),
    102                     msg.replace("SETUPCMD", "libeufin-ebisync setup")
    103                 );
    104             }
    105         };
    106 
    107         // Missing client keys
    108         std::fs::remove_file(c_path).ok();
    109         check_cmds(&format!(
    110             "Missing client private keys file at '{client}', run 'SETUPCMD' first"
    111         ))
    112         .await;
    113         // Empty client file
    114         let mut cf = std::fs::File::create(c_path).unwrap();
    115         check_all_cmds(&format!(
    116             "Could not read client private keys at '{client}': unexpected end of file"
    117         ))
    118         .await;
    119         // Bad client json
    120         cf.write_all(b"CORRUPTION").unwrap();
    121         check_all_cmds(&format!(
    122             "Could not read client private keys at '{client}': invalid data"
    123         ))
    124         .await;
    125         if !is_root {
    126             // Missing permission
    127             cf.set_permissions(Permissions::from_mode(0o000)).unwrap();
    128             check_all_cmds(&format!(
    129                 "Could not read client private keys at '{client}': permission denied"
    130             ))
    131             .await;
    132         }
    133         // Unfinished client
    134         let client_keys = ClientKeys::generate().unwrap();
    135         persist_client_keys(&client_keys, c_path).unwrap();
    136         check_cmds("Unsubmitted client private keys, run 'SETUPCMD' first").await;
    137 
    138         // Missing bank keys
    139         persist_client_keys(
    140             &ClientKeys {
    141                 submitted_ini: true,
    142                 submitted_hia: true,
    143                 ..client_keys
    144             },
    145             c_path,
    146         )
    147         .unwrap();
    148         std::fs::remove_file(b_path).ok();
    149         check_cmds(&format!(
    150             "Missing bank public keys file at '{bank}', run 'SETUPCMD' first"
    151         ))
    152         .await;
    153         // Empty bank file
    154         let mut bf = std::fs::File::create(b_path).unwrap();
    155         check_all_cmds(&format!(
    156             "Could not read bank public keys at '{bank}': unexpected end of file"
    157         ))
    158         .await;
    159         // Bad bank json
    160         bf.write_all(b"CORRUPTION").unwrap();
    161         check_all_cmds(&format!(
    162             "Could not read bank public keys at '{bank}': invalid data"
    163         ))
    164         .await;
    165         if !is_root {
    166             // Missing permission
    167             bf.set_permissions(Permissions::from_mode(0o000)).unwrap();
    168             check_all_cmds(&format!(
    169                 "Could not read bank public keys at '{bank}': permission denied"
    170             ))
    171             .await;
    172         }
    173         // Unfinished bank
    174         let bank_keys = BankKeys::generate();
    175         persist_bank_keys(&bank_keys, b_path).unwrap();
    176         check_cmds("Unaccepted bank public keys, run 'SETUPCMD' until accepting the bank keys")
    177             .await;
    178     }
    179 
    180     #[tokio::test]
    181     async fn migration() {
    182         let db = PgPool::connect("postgres:///taler_rust_check")
    183             .await
    184             .unwrap();
    185 
    186         let execute = async |sql: &str| {
    187             sqlx::raw_sql(sql).execute(&db).await.unwrap();
    188         };
    189         let execute_fs = async |file: &str| {
    190             dbg!(file);
    191             sqlx::raw_sql(
    192                 &std::fs::read_to_string(format!("../database-versioning/{file}")).unwrap(),
    193             )
    194             .execute(&db)
    195             .await
    196             .unwrap();
    197         };
    198 
    199         // Drop current schemas
    200         execute_fs("libeufin-bank-drop.sql").await;
    201         execute_fs("libeufin-nexus-drop.sql").await;
    202 
    203         // Versioning
    204         execute_fs("versioning.sql").await;
    205 
    206         // libeufin-bank
    207         execute_fs("libeufin-bank-0001.sql").await;
    208         execute("
    209             INSERT INTO libeufin_bank.customers (login, password_hash) VALUES
    210                 ('account_1', 'fack_hash'),
    211                 ('account_2', 'fack_hash'),
    212                 ('account_3', 'fack_hash'),
    213                 ('account_4', 'fack_hash');
    214             INSERT INTO libeufin_bank.bank_accounts (internal_payto_uri, owning_customer_id) VALUES
    215                 ('payto_1', 1),
    216                 ('payto_2', 2),
    217                 ('payto_3', 3),
    218                 ('payto_4', 4);
    219             INSERT INTO libeufin_bank.bank_account_transactions(creditor_payto_uri, creditor_name, debtor_payto_uri, debtor_name, subject, amount, transaction_date, direction, bank_account_id) VALUES
    220                 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1),
    221                 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1),
    222                 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1);
    223             INSERT INTO libeufin_bank.taler_exchange_incoming(reserve_pub, bank_transaction) VALUES
    224                 ('\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, 1),
    225                 ('\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, 2);
    226             INSERT INTO libeufin_bank.challenges(code, creation_date, expiration_date, retry_counter) VALUES
    227                 ('secret_code', 42, 42, 42),
    228                 ('secret_code', 42, 42, 42);
    229             INSERT INTO libeufin_bank.cashout_operations(request_uid, amount_debit, amount_credit, subject, creation_time, bank_account, challenge, local_transaction) VALUES
    230                 ('\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, (0, 0), (0, 0), 'subject', 42, 1, 1, 1),
    231                 ('\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, (0, 0), (0, 0), 'subject', 42, 1, 2, NULL);
    232             INSERT INTO libeufin_bank.taler_withdrawal_operations(withdrawal_uuid, amount, reserve_pub, wallet_bank_account) VALUES
    233                 (gen_random_uuid(), (0, 0), '\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, 1),
    234                 (gen_random_uuid(), (0, 0), '\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, 2);
    235         ").await;
    236         execute_fs("libeufin-bank-0002.sql").await;
    237         execute_fs("libeufin-bank-0003.sql").await;
    238         execute_fs("libeufin-bank-0004.sql").await;
    239         execute(
    240             "
    241             UPDATE libeufin_bank.bank_accounts SET min_cashout=(0, 1) WHERE bank_account_id=2;
    242             UPDATE libeufin_bank.bank_accounts SET min_cashout=(2, 300) WHERE bank_account_id IN (3, 4);
    243             ",
    244         )
    245         .await;
    246         execute_fs("libeufin-bank-0005.sql").await;
    247         execute_fs("libeufin-bank-0006.sql").await;
    248         execute_fs("libeufin-bank-0007.sql").await;
    249         execute_fs("libeufin-bank-0008.sql").await;
    250         execute_fs("libeufin-bank-0009.sql").await;
    251         execute_fs("libeufin-bank-0010.sql").await;
    252         execute_fs("libeufin-bank-0011.sql").await;
    253         execute_fs("libeufin-bank-0012.sql").await;
    254         execute(
    255             r#"
    256             INSERT INTO libeufin_bank.config(key, value) VALUES
    257                 ('cashin_ratio', '{"val": 1, "frac": 2}'::jsonb),
    258                 ('cashin_fee', '{"val": 3, "frac": 4}'::jsonb),
    259                 ('cashin_tiny_amount', '{"val": 5, "frac": 6}'::jsonb),
    260                 ('cashin_min_amount', '{"val": 7, "frac": 8}'::jsonb),
    261                 ('cashin_rounding_mode', '{"mode": "zero"}'::jsonb),
    262                 ('cashout_ratio', '{"val": 9, "frac": 10}'::jsonb),
    263                 ('cashout_fee', '{"val": 11, "frac": 12}'::jsonb),
    264                 ('cashout_tiny_amount', '{"val": 13, "frac": 14}'::jsonb),
    265                 ('cashout_min_amount', '{"val": 15, "frac": 16}'::jsonb),
    266                 ('cashout_rounding_mode', '{"mode": "nearest"}'::jsonb);
    267                 "#,
    268         )
    269         .await;
    270         execute_fs("libeufin-bank-0013.sql").await;
    271         assert!(
    272             sqlx::query_scalar::<_, bool>(
    273                 r#"
    274             SELECT value='{
    275                 "cashin": {
    276                     "fee": {
    277                         "val": 3,
    278                         "frac": 4
    279                     },
    280                     "ratio": {
    281                         "val": 1,
    282                         "frac": 2
    283                     },
    284                     "min_amount": {
    285                         "val": 7,
    286                         "frac": 8
    287                     },
    288                     "tiny_amount": {
    289                         "val": 5,
    290                         "frac": 6
    291                     },
    292                     "rounding_mode": "zero"
    293                 },
    294                 "cashout": {
    295                     "fee": {
    296                         "val": 11,
    297                         "frac": 12
    298                     },
    299                     "ratio": {
    300                         "val": 9,
    301                         "frac": 10
    302                     },
    303                     "min_amount": {
    304                         "val": 15,
    305                         "frac": 16
    306                     },
    307                     "tiny_amount": {
    308                         "val": 13,
    309                         "frac": 14
    310                     },
    311                     "rounding_mode": "nearest"
    312                 }
    313             }'::jsonb FROM libeufin_bank.config WHERE key='conversion_rate'
    314             "#
    315             )
    316             .fetch_one(&db)
    317             .await
    318             .unwrap()
    319         );
    320         execute_fs("libeufin-bank-0014.sql").await;
    321         execute_fs("libeufin-bank-0015.sql").await;
    322 
    323         // libeufin-nexus
    324         execute_fs("libeufin-nexus-0001.sql").await;
    325         execute(r#"
    326             INSERT INTO libeufin_nexus.outgoing_transactions(amount, execution_time, message_id) VALUES
    327                 ((0, 0), 42, 'id');
    328             INSERT INTO libeufin_nexus.initiated_outgoing_transactions(amount, wire_transfer_subject, initiation_time, credit_payto_uri, outgoing_transaction_id, request_uid) VALUES
    329                 ((0, 0), 'subject', 42, 'payto_0', 1, 'request_uid');
    330             "#,
    331         )
    332         .await;
    333         execute_fs("libeufin-nexus-0002.sql").await;
    334         execute_fs("libeufin-nexus-0003.sql").await;
    335         execute_fs("libeufin-nexus-0004.sql").await;
    336         execute_fs("libeufin-nexus-0005.sql").await;
    337         execute_fs("libeufin-nexus-0006.sql").await;
    338         execute(r#"
    339             INSERT INTO initiated_outgoing_transactions(amount, wire_transfer_subject, initiation_time, credit_payto_uri, outgoing_transaction_id, request_uid, order_id) VALUES
    340                 ((42, 0), 'subject', 0, 'credit_payto', NULL, 'TX0', 'ORDER0'),
    341                 ((41, 0), 'subject', 0, 'credit_payto', NULL, 'TX1', NULL);
    342             "#,
    343         )
    344         .await;
    345         execute_fs("libeufin-nexus-0007.sql").await;
    346         execute_fs("libeufin-nexus-0008.sql").await;
    347         execute_fs("libeufin-nexus-0009.sql").await;
    348         execute(r#"
    349             INSERT INTO libeufin_nexus.incoming_transactions(amount, subject, execution_time, debit_payto, bank_id) VALUES
    350                 ((1, 0), 'simple', 42, 'debit_payto', 'first'),
    351                 ((2, 0), 'reserve', 42, 'debit_payto', 'second'),
    352                 ((3, 0), 'kyc', 42, 'debit_payto', 'third'),
    353                 ((4, 0), 'simple', 42, 'debit_payto', gen_random_uuid()::text),
    354                 ((5, 0), 'reserve', 42, 'debit_payto', gen_random_uuid()::text),
    355                 ((6, 0), 'kyc', 42, 'debit_payto', gen_random_uuid()::text);
    356             INSERT INTO libeufin_nexus.talerable_incoming_transactions(incoming_transaction_id, type, reserve_public_key, account_pub) VALUES
    357                 (2, 'reserve', '\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, null),
    358                 (3, 'kyc', null, '\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea);
    359             "#,
    360         )
    361         .await;
    362         execute_fs("libeufin-nexus-0010.sql").await;
    363         execute_fs("libeufin-nexus-0011.sql").await;
    364         execute_fs("libeufin-nexus-0012.sql").await;
    365         execute_fs("libeufin-nexus-0013.sql").await;
    366         execute_fs("libeufin-nexus-0014.sql").await;
    367     }
    368 }