exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

test_wire_accounts.c (13256B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 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 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 General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file exchangedb/test_wire_accounts.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `wire_accounts`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_insert_wire(), #TALER_EXCHANGEDB_update_wire(),
     23  * #TALER_EXCHANGEDB_get_wire_timestamp() and
     24  * #TALER_EXCHANGEDB_iterate_wire_accounts().
     25  *
     26  * `wire_accounts` has no foreign keys -- it is the exchange's own list of
     27  * bank accounts, not the `wire_targets` list of everyone else's.  The
     28  * optional columns (conversion URL, gateways, bank label) are exercised
     29  * both set and NULL, since the iterator has to hand both back.
     30  */
     31 #include "test_common.h"
     32 #include "exchange-database/insert_wire.h"
     33 #include "exchange-database/update_wire.h"
     34 #include "exchange-database/get_wire_timestamp.h"
     35 #include "exchange-database/iterate_wire_accounts.h"
     36 
     37 
     38 /**
     39  * Closure for #account_cb().
     40  */
     41 struct AccountContext
     42 {
     43   /**
     44    * Account we are looking for.
     45    */
     46   const char *payto_uri;
     47 
     48   /**
     49    * How many accounts did the callback see in total?
     50    */
     51   unsigned int total;
     52 
     53   /**
     54    * How many times did we see @e payto_uri?
     55    */
     56   unsigned int matched;
     57 
     58   /**
     59    * Set if the account was reported with data we did not store.
     60    */
     61   bool mismatch;
     62 
     63   /**
     64    * Set if the account was reported with a conversion URL.
     65    */
     66   bool have_conversion_url;
     67 
     68   /**
     69    * Set if the account was reported with a bank label.
     70    */
     71   bool have_bank_label;
     72 
     73   /**
     74    * Priority reported for the account.
     75    */
     76   int64_t priority;
     77 };
     78 
     79 
     80 /**
     81  * Callback for #TALER_EXCHANGEDB_iterate_wire_accounts().
     82  *
     83  * @param cls a `struct AccountContext *`
     84  * @param payto_uri the account
     85  * @param conversion_url conversion service, NULL if none
     86  * @param open_banking_gateway open banking gateway, NULL if none
     87  * @param prepared_transfer_url transfer gateway, NULL if none
     88  * @param debit_restrictions debit restrictions on the account
     89  * @param credit_restrictions credit restrictions on the account
     90  * @param master_sig master signature over the account
     91  * @param bank_label label for the UI, NULL if none
     92  * @param priority ordering hint for the UI
     93  */
     94 static void
     95 account_cb (void *cls,
     96             const struct TALER_FullPayto payto_uri,
     97             const char *conversion_url,
     98             const char *open_banking_gateway,
     99             const char *prepared_transfer_url,
    100             const json_t *debit_restrictions,
    101             const json_t *credit_restrictions,
    102             const struct TALER_MasterSignatureP *master_sig,
    103             const char *bank_label,
    104             int64_t priority)
    105 {
    106   struct AccountContext *ctx = cls;
    107 
    108   (void) open_banking_gateway;
    109   (void) prepared_transfer_url;
    110   (void) master_sig;
    111   ctx->total++;
    112   if (0 != strcmp (payto_uri.full_payto,
    113                    ctx->payto_uri))
    114     return;
    115   ctx->matched++;
    116   ctx->have_conversion_url = (NULL != conversion_url);
    117   ctx->have_bank_label = (NULL != bank_label);
    118   ctx->priority = priority;
    119   if ( (! json_is_array (debit_restrictions)) ||
    120        (! json_is_array (credit_restrictions)) )
    121     ctx->mismatch = true;
    122 }
    123 
    124 
    125 /**
    126  * Nothing is known about an account that was never inserted, and updating
    127  * it does not create it.
    128  *
    129  * @param pg the database context
    130  * @return 0 on success
    131  */
    132 static int
    133 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    134 {
    135   struct TALER_FullPayto payto = {
    136     .full_payto = (char *) "payto://x-taler-bank/localhost/wa-none"
    137   };
    138   struct GNUNET_TIME_Timestamp last_date;
    139   struct TALER_MasterSignatureP master_sig;
    140   json_t *empty = json_array ();
    141 
    142   TDB_FILL (master_sig,
    143             1);
    144   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    145             TALER_EXCHANGEDB_get_wire_timestamp (pg,
    146                                                  payto,
    147                                                  &last_date),
    148             json_decref (empty));
    149   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    150             TALER_EXCHANGEDB_update_wire (pg,
    151                                           payto,
    152                                           NULL,
    153                                           NULL,
    154                                           NULL,
    155                                           empty,
    156                                           empty,
    157                                           GNUNET_TIME_timestamp_get (),
    158                                           &master_sig,
    159                                           NULL,
    160                                           0,
    161                                           true),
    162             json_decref (empty));
    163   FAILIF_C (0 != TDB_count (pg,
    164                             "FROM wire_accounts"),
    165             json_decref (empty));
    166   json_decref (empty);
    167   return 0;
    168 }
    169 
    170 
    171 /**
    172  * Insert an account with all optional columns set and read it back.
    173  *
    174  * @param pg the database context
    175  * @return 0 on success
    176  */
    177 static int
    178 check_insert_full (struct TALER_EXCHANGEDB_PostgresContext *pg)
    179 {
    180   const char *uri = "payto://x-taler-bank/localhost/wa-full";
    181   struct TALER_FullPayto payto = {
    182     .full_payto = (char *) uri
    183   };
    184   struct GNUNET_TIME_Timestamp start = GNUNET_TIME_timestamp_get ();
    185   struct GNUNET_TIME_Timestamp last_date;
    186   struct TALER_MasterSignatureP master_sig;
    187   struct AccountContext ctx;
    188   json_t *restrictions;
    189 
    190   TDB_FILL (master_sig,
    191             2);
    192   restrictions = json_pack ("[{s:s}]",
    193                             "type",
    194                             "deny");
    195   GNUNET_assert (NULL != restrictions);
    196   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    197             TALER_EXCHANGEDB_insert_wire (pg,
    198                                           payto,
    199                                           "https://conversion.example/",
    200                                           "https://obg.example/",
    201                                           "https://ptu.example/",
    202                                           restrictions,
    203                                           restrictions,
    204                                           start,
    205                                           &master_sig,
    206                                           "My Bank",
    207                                           7),
    208             json_decref (restrictions));
    209   json_decref (restrictions);
    210   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    211           TALER_EXCHANGEDB_get_wire_timestamp (pg,
    212                                                payto,
    213                                                &last_date));
    214   FAILIF (GNUNET_TIME_timestamp_cmp (last_date,
    215                                      !=,
    216                                      start));
    217   memset (&ctx,
    218           0,
    219           sizeof (ctx));
    220   ctx.payto_uri = uri;
    221   FAILIF (0 >=
    222           TALER_EXCHANGEDB_iterate_wire_accounts (pg,
    223                                                   &account_cb,
    224                                                   &ctx));
    225   FAILIF (1 != ctx.matched);
    226   FAILIF (ctx.mismatch);
    227   FAILIF (! ctx.have_conversion_url);
    228   FAILIF (! ctx.have_bank_label);
    229   FAILIF (7 != ctx.priority);
    230   return 0;
    231 }
    232 
    233 
    234 /**
    235  * Insert an account with every optional column left out; the iterator has
    236  * to hand the NULLs back as NULL.
    237  *
    238  * @param pg the database context
    239  * @return 0 on success
    240  */
    241 static int
    242 check_insert_minimal (struct TALER_EXCHANGEDB_PostgresContext *pg)
    243 {
    244   const char *uri = "payto://x-taler-bank/localhost/wa-min";
    245   struct TALER_FullPayto payto = {
    246     .full_payto = (char *) uri
    247   };
    248   struct TALER_MasterSignatureP master_sig;
    249   struct AccountContext ctx;
    250   json_t *empty = json_array ();
    251 
    252   TDB_FILL (master_sig,
    253             3);
    254   GNUNET_assert (NULL != empty);
    255   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    256             TALER_EXCHANGEDB_insert_wire (pg,
    257                                           payto,
    258                                           NULL,
    259                                           NULL,
    260                                           NULL,
    261                                           empty,
    262                                           empty,
    263                                           GNUNET_TIME_timestamp_get (),
    264                                           &master_sig,
    265                                           NULL,
    266                                           -3),
    267             json_decref (empty));
    268   json_decref (empty);
    269   memset (&ctx,
    270           0,
    271           sizeof (ctx));
    272   ctx.payto_uri = uri;
    273   FAILIF (0 >=
    274           TALER_EXCHANGEDB_iterate_wire_accounts (pg,
    275                                                   &account_cb,
    276                                                   &ctx));
    277   FAILIF (1 != ctx.matched);
    278   FAILIF (ctx.mismatch);
    279   FAILIF (ctx.have_conversion_url);
    280   FAILIF (ctx.have_bank_label);
    281   FAILIF (-3 != ctx.priority);
    282   FAILIF (2 != ctx.total);
    283   return 0;
    284 }
    285 
    286 
    287 /**
    288  * Disabling an account takes it out of the iterator but leaves the row.
    289  *
    290  * @param pg the database context
    291  * @return 0 on success
    292  */
    293 static int
    294 check_disable (struct TALER_EXCHANGEDB_PostgresContext *pg)
    295 {
    296   const char *uri = "payto://x-taler-bank/localhost/wa-min";
    297   struct TALER_FullPayto payto = {
    298     .full_payto = (char *) uri
    299   };
    300   struct GNUNET_TIME_Timestamp change = GNUNET_TIME_timestamp_get ();
    301   struct GNUNET_TIME_Timestamp last_date;
    302   struct AccountContext ctx;
    303 
    304   /* a disabled account may be stored without restrictions and signature */
    305   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    306           TALER_EXCHANGEDB_update_wire (pg,
    307                                         payto,
    308                                         NULL,
    309                                         NULL,
    310                                         NULL,
    311                                         NULL,
    312                                         NULL,
    313                                         change,
    314                                         NULL,
    315                                         NULL,
    316                                         0,
    317                                         false));
    318   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    319           TALER_EXCHANGEDB_get_wire_timestamp (pg,
    320                                                payto,
    321                                                &last_date));
    322   FAILIF (GNUNET_TIME_timestamp_cmp (last_date,
    323                                      !=,
    324                                      change));
    325   memset (&ctx,
    326           0,
    327           sizeof (ctx));
    328   ctx.payto_uri = uri;
    329   FAILIF (0 >=
    330           TALER_EXCHANGEDB_iterate_wire_accounts (pg,
    331                                                   &account_cb,
    332                                                   &ctx));
    333   FAILIF (0 != ctx.matched);
    334   FAILIF (1 != ctx.total);
    335   FAILIF (2 != TDB_count (pg,
    336                           "FROM wire_accounts"));
    337 
    338   /* re-enabling brings it back, with the new details */
    339   {
    340     struct TALER_MasterSignatureP master_sig;
    341     json_t *empty = json_array ();
    342 
    343     TDB_FILL (master_sig,
    344               4);
    345     GNUNET_assert (NULL != empty);
    346     FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    347               TALER_EXCHANGEDB_update_wire (pg,
    348                                             payto,
    349                                             "https://conv2.example/",
    350                                             NULL,
    351                                             NULL,
    352                                             empty,
    353                                             empty,
    354                                             change,
    355                                             &master_sig,
    356                                             "Relabelled",
    357                                             11,
    358                                             true),
    359               json_decref (empty));
    360     json_decref (empty);
    361   }
    362   memset (&ctx,
    363           0,
    364           sizeof (ctx));
    365   ctx.payto_uri = uri;
    366   FAILIF (0 >=
    367           TALER_EXCHANGEDB_iterate_wire_accounts (pg,
    368                                                   &account_cb,
    369                                                   &ctx));
    370   FAILIF (1 != ctx.matched);
    371   FAILIF (! ctx.have_conversion_url);
    372   FAILIF (! ctx.have_bank_label);
    373   FAILIF (11 != ctx.priority);
    374   return 0;
    375 }
    376 
    377 
    378 /**
    379  * The checks to run, in order.
    380  */
    381 static const struct TDB_Test tests[] = {
    382   { "wire-accounts-empty",
    383     &check_empty },
    384   { "wire-accounts-insert-full",
    385     &check_insert_full },
    386   { "wire-accounts-insert-minimal",
    387     &check_insert_minimal },
    388   { "wire-accounts-disable",
    389     &check_disable },
    390   { NULL, NULL }
    391 };
    392 
    393 
    394 int
    395 main (int argc,
    396       char *const *argv)
    397 {
    398   return TDB_main (argc,
    399                    argv,
    400                    "test-wire-accounts",
    401                    "Tests for the exchangedb `wire_accounts' table",
    402                    tests);
    403 }
    404 
    405 
    406 /* end of test_wire_accounts.c */