libeufin

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

lib.rs (15824B)


      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::{AssertSqlSafe, 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(AssertSqlSafe(sql))
    188                 .execute(&db)
    189                 .await
    190                 .unwrap();
    191         };
    192         let execute_fs = async |file: &str| {
    193             dbg!(file);
    194             sqlx::raw_sql(AssertSqlSafe(
    195                 std::fs::read_to_string(format!("../database-versioning/{file}")).unwrap(),
    196             ))
    197             .execute(&db)
    198             .await
    199             .unwrap();
    200         };
    201 
    202         // Drop current schemas
    203         execute_fs("libeufin-bank-drop.sql").await;
    204         execute_fs("libeufin-nexus-drop.sql").await;
    205 
    206         // Versioning
    207         execute_fs("versioning.sql").await;
    208 
    209         // libeufin-bank
    210         execute_fs("libeufin-bank-0001.sql").await;
    211         execute("
    212             INSERT INTO libeufin_bank.customers (login, password_hash) VALUES
    213                 ('account_1', 'fack_hash'),
    214                 ('account_2', 'fack_hash'),
    215                 ('account_3', 'fack_hash'),
    216                 ('account_4', 'fack_hash');
    217             INSERT INTO libeufin_bank.bank_accounts (internal_payto_uri, owning_customer_id) VALUES
    218                 ('payto_1', 1),
    219                 ('payto_2', 2),
    220                 ('payto_3', 3),
    221                 ('payto_4', 4);
    222             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
    223                 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1),
    224                 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1),
    225                 ('payto_1', 'account_1', 'payto_2', 'account_2', 'subject', (0, 0), 42, 'credit', 1);
    226             INSERT INTO libeufin_bank.taler_exchange_incoming(reserve_pub, bank_transaction) VALUES
    227                 ('\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, 1),
    228                 ('\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, 2);
    229             INSERT INTO libeufin_bank.challenges(code, creation_date, expiration_date, retry_counter) VALUES
    230                 ('secret_code', 42, 42, 42),
    231                 ('secret_code', 42, 42, 42);
    232             INSERT INTO libeufin_bank.cashout_operations(request_uid, amount_debit, amount_credit, subject, creation_time, bank_account, challenge, local_transaction) VALUES
    233                 ('\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, (0, 0), (0, 0), 'subject', 42, 1, 1, 1),
    234                 ('\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, (0, 0), (0, 0), 'subject', 42, 1, 2, NULL);
    235             INSERT INTO libeufin_bank.taler_withdrawal_operations(withdrawal_uuid, amount, reserve_pub, wallet_bank_account) VALUES
    236                 (gen_random_uuid(), (0, 0), '\\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, 1),
    237                 (gen_random_uuid(), (0, 0), '\\xa605637a4852684e4957e6177f41311eacf8661a6a74b90178c487fe347b9918'::bytea, 2);
    238         ").await;
    239         execute_fs("libeufin-bank-0002.sql").await;
    240         execute_fs("libeufin-bank-0003.sql").await;
    241         execute_fs("libeufin-bank-0004.sql").await;
    242         execute(
    243             "
    244             UPDATE libeufin_bank.bank_accounts SET min_cashout=(0, 1) WHERE bank_account_id=2;
    245             UPDATE libeufin_bank.bank_accounts SET min_cashout=(2, 300) WHERE bank_account_id IN (3, 4);
    246             ",
    247         )
    248         .await;
    249         execute_fs("libeufin-bank-0005.sql").await;
    250         execute_fs("libeufin-bank-0006.sql").await;
    251         execute_fs("libeufin-bank-0007.sql").await;
    252         execute_fs("libeufin-bank-0008.sql").await;
    253         execute_fs("libeufin-bank-0009.sql").await;
    254         execute_fs("libeufin-bank-0010.sql").await;
    255         execute_fs("libeufin-bank-0011.sql").await;
    256         execute_fs("libeufin-bank-0012.sql").await;
    257         execute(
    258             r#"
    259             INSERT INTO libeufin_bank.config(key, value) VALUES
    260                 ('cashin_ratio', '{"val": 1, "frac": 2}'::jsonb),
    261                 ('cashin_fee', '{"val": 3, "frac": 4}'::jsonb),
    262                 ('cashin_tiny_amount', '{"val": 5, "frac": 6}'::jsonb),
    263                 ('cashin_min_amount', '{"val": 7, "frac": 8}'::jsonb),
    264                 ('cashin_rounding_mode', '{"mode": "zero"}'::jsonb),
    265                 ('cashout_ratio', '{"val": 9, "frac": 10}'::jsonb),
    266                 ('cashout_fee', '{"val": 11, "frac": 12}'::jsonb),
    267                 ('cashout_tiny_amount', '{"val": 13, "frac": 14}'::jsonb),
    268                 ('cashout_min_amount', '{"val": 15, "frac": 16}'::jsonb),
    269                 ('cashout_rounding_mode', '{"mode": "nearest"}'::jsonb);
    270                 "#,
    271         )
    272         .await;
    273         execute_fs("libeufin-bank-0013.sql").await;
    274         assert!(
    275             sqlx::query_scalar::<_, bool>(
    276                 r#"
    277             SELECT value='{
    278                 "cashin": {
    279                     "fee": {
    280                         "val": 3,
    281                         "frac": 4
    282                     },
    283                     "ratio": {
    284                         "val": 1,
    285                         "frac": 2
    286                     },
    287                     "min_amount": {
    288                         "val": 7,
    289                         "frac": 8
    290                     },
    291                     "tiny_amount": {
    292                         "val": 5,
    293                         "frac": 6
    294                     },
    295                     "rounding_mode": "zero"
    296                 },
    297                 "cashout": {
    298                     "fee": {
    299                         "val": 11,
    300                         "frac": 12
    301                     },
    302                     "ratio": {
    303                         "val": 9,
    304                         "frac": 10
    305                     },
    306                     "min_amount": {
    307                         "val": 15,
    308                         "frac": 16
    309                     },
    310                     "tiny_amount": {
    311                         "val": 13,
    312                         "frac": 14
    313                     },
    314                     "rounding_mode": "nearest"
    315                 }
    316             }'::jsonb FROM libeufin_bank.config WHERE key='conversion_rate'
    317             "#
    318             )
    319             .fetch_one(&db)
    320             .await
    321             .unwrap()
    322         );
    323         execute_fs("libeufin-bank-0014.sql").await;
    324         execute_fs("libeufin-bank-0015.sql").await;
    325 
    326         // libeufin-nexus
    327         execute_fs("libeufin-nexus-0001.sql").await;
    328         execute(r#"
    329             INSERT INTO libeufin_nexus.outgoing_transactions(amount, execution_time, message_id) VALUES
    330                 ((0, 0), 42, 'id');
    331             INSERT INTO libeufin_nexus.initiated_outgoing_transactions(amount, wire_transfer_subject, initiation_time, credit_payto_uri, outgoing_transaction_id, request_uid) VALUES
    332                 ((0, 0), 'subject', 42, 'payto_0', 1, 'request_uid');
    333             "#,
    334         )
    335         .await;
    336         execute_fs("libeufin-nexus-0002.sql").await;
    337         execute_fs("libeufin-nexus-0003.sql").await;
    338         execute_fs("libeufin-nexus-0004.sql").await;
    339         execute_fs("libeufin-nexus-0005.sql").await;
    340         execute_fs("libeufin-nexus-0006.sql").await;
    341         execute(r#"
    342             INSERT INTO initiated_outgoing_transactions(amount, wire_transfer_subject, initiation_time, credit_payto_uri, outgoing_transaction_id, request_uid, order_id) VALUES
    343                 ((42, 0), 'subject', 0, 'credit_payto', NULL, 'TX0', 'ORDER0'),
    344                 ((41, 0), 'subject', 0, 'credit_payto', NULL, 'TX1', NULL);
    345             "#,
    346         )
    347         .await;
    348         execute_fs("libeufin-nexus-0007.sql").await;
    349         execute_fs("libeufin-nexus-0008.sql").await;
    350         execute_fs("libeufin-nexus-0009.sql").await;
    351         execute(r#"
    352             INSERT INTO libeufin_nexus.incoming_transactions(amount, subject, execution_time, debit_payto, bank_id) VALUES
    353                 ((1, 0), 'simple', 42, 'debit_payto', 'first'),
    354                 ((2, 0), 'reserve', 42, 'debit_payto', 'second'),
    355                 ((3, 0), 'kyc', 42, 'debit_payto', 'third'),
    356                 ((4, 0), 'simple', 42, 'debit_payto', gen_random_uuid()::text),
    357                 ((5, 0), 'reserve', 42, 'debit_payto', gen_random_uuid()::text),
    358                 ((6, 0), 'kyc', 42, 'debit_payto', gen_random_uuid()::text);
    359             INSERT INTO libeufin_nexus.talerable_incoming_transactions(incoming_transaction_id, type, reserve_public_key, account_pub) VALUES
    360                 (2, 'reserve', '\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea, null),
    361                 (3, 'kyc', null, '\x6ca1ab1a76a484d7424064c51c49c1947405f42f7d185d052dbf6718d845ec6b'::bytea);
    362             "#,
    363         )
    364         .await;
    365         execute_fs("libeufin-nexus-0010.sql").await;
    366         execute_fs("libeufin-nexus-0011.sql").await;
    367         execute_fs("libeufin-nexus-0012.sql").await;
    368         execute_fs("libeufin-nexus-0013.sql").await;
    369         execute_fs("libeufin-nexus-0014.sql").await;
    370     }
    371 }