exchange

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

test_legitimization_processes.c (19210B)


      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_legitimization_processes.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `legitimization_processes`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_insert_legitimization_process(),
     23  * #TALER_EXCHANGEDB_update_legitimization_process_by_row(),
     24  * #TALER_EXCHANGEDB_insert_kyc_failure(),
     25  * #TALER_EXCHANGEDB_insert_aml_program_failure(),
     26  * #TALER_EXCHANGEDB_get_legitimization_process_by_account(),
     27  * #TALER_EXCHANGEDB_get_pending_legitimization_process(),
     28  * #TALER_EXCHANGEDB_get_kyc_provider_account() and
     29  * #TALER_EXCHANGEDB_iterate_kyc_references().
     30  *
     31  * A row of `legitimization_processes` is one run of one KYC provider
     32  * against one account, working on one measure of a
     33  * `legitimization_measures` set.  The checks therefore create an account
     34  * and a measure set first, and then walk a process from "started" through
     35  * "redirect known" to "finished".
     36  */
     37 #include "test_common.h"
     38 #include "exchange-database/do_trigger_kyc_rule_for_account.h"
     39 #include "exchange-database/get_kyc_provider_account.h"
     40 #include "exchange-database/get_legitimization_process_by_account.h"
     41 #include "exchange-database/get_pending_legitimization_process.h"
     42 #include "exchange-database/insert_aml_program_failure.h"
     43 #include "exchange-database/insert_kyc_failure.h"
     44 #include "exchange-database/insert_legitimization_process.h"
     45 #include "exchange-database/iterate_kyc_references.h"
     46 #include "exchange-database/update_legitimization_process_by_row.h"
     47 
     48 
     49 /**
     50  * Account the checks run processes against.
     51  */
     52 static struct TDB_Account account;
     53 
     54 
     55 /**
     56  * Measure set the processes work on.
     57  */
     58 static uint64_t measure_row;
     59 
     60 
     61 /**
     62  * Row of the process check_start() created.
     63  */
     64 static uint64_t process_row;
     65 
     66 
     67 /**
     68  * Closure for #reference_cb().
     69  */
     70 struct ReferenceContext
     71 {
     72   /**
     73    * How many references did the callback see?
     74    */
     75   unsigned int total;
     76 
     77   /**
     78    * Provider we are looking for, NULL to match nothing.
     79    */
     80   const char *provider;
     81 
     82   /**
     83    * How many times did we see it?
     84    */
     85   unsigned int matched;
     86 
     87   /**
     88    * Provider account id reported for it, owned by this struct.
     89    */
     90   char *user_id;
     91 
     92   /**
     93    * Provider legitimization id reported for it, owned by this struct.
     94    */
     95   char *legi_id;
     96 };
     97 
     98 
     99 /**
    100  * Callback for #TALER_EXCHANGEDB_iterate_kyc_references().
    101  *
    102  * @param cls a `struct ReferenceContext *`
    103  * @param kyc_provider_name provider that ran the process
    104  * @param provider_user_id account id at the provider
    105  * @param legi_id legitimization id at the provider
    106  */
    107 static void
    108 reference_cb (void *cls,
    109               const char *kyc_provider_name,
    110               const char *provider_user_id,
    111               const char *legi_id)
    112 {
    113   struct ReferenceContext *ctx = cls;
    114 
    115   ctx->total++;
    116   if ( (NULL != ctx->provider) &&
    117        (0 == strcmp (kyc_provider_name,
    118                      ctx->provider)) )
    119   {
    120     ctx->matched++;
    121     GNUNET_free (ctx->user_id);
    122     GNUNET_free (ctx->legi_id);
    123     ctx->user_id = (NULL == provider_user_id)
    124       ? NULL
    125       : GNUNET_strdup (provider_user_id);
    126     ctx->legi_id = (NULL == legi_id)
    127       ? NULL
    128       : GNUNET_strdup (legi_id);
    129   }
    130 }
    131 
    132 
    133 /**
    134  * Put a measure on our account, creating the account on the way.
    135  *
    136  * @param pg the database context
    137  */
    138 static void
    139 setup_account (struct TALER_EXCHANGEDB_PostgresContext *pg)
    140 {
    141   json_t *jmeasures;
    142   uint64_t row = 0;
    143   bool bad_kyc_auth;
    144 
    145   if (0 != measure_row)
    146     return;
    147   TDB_account (pg,
    148                10,
    149                &account);
    150   jmeasures = GNUNET_JSON_PACK (
    151     GNUNET_JSON_pack_array_steal (
    152       "measures",
    153       json_pack ("[{s:s,s:s,s:s}]",
    154                  "check_name",
    155                  "verify-id",
    156                  "prog_name",
    157                  "skip",
    158                  "context",
    159                  "none")),
    160     GNUNET_JSON_pack_bool ("is_and_combinator",
    161                            true));
    162   GNUNET_assert (NULL != jmeasures);
    163   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    164                  TALER_EXCHANGEDB_do_trigger_kyc_rule_for_account (
    165                    pg,
    166                    account.payto,
    167                    &account.h_normalized,
    168                    NULL,
    169                    NULL,
    170                    jmeasures,
    171                    1,
    172                    &row,
    173                    &bad_kyc_auth));
    174   json_decref (jmeasures);
    175   GNUNET_assert (0 != row);
    176   measure_row = row;
    177 }
    178 
    179 
    180 /**
    181  * Nothing is known while the table is empty.
    182  *
    183  * @param pg the database context
    184  * @return 0 on success
    185  */
    186 static int
    187 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    188 {
    189   struct TALER_NormalizedPaytoHashP h_payto;
    190   struct GNUNET_TIME_Absolute expiration;
    191   struct ReferenceContext ctx = { 0 };
    192   char *provider_account_id = NULL;
    193   char *provider_legitimization_id = NULL;
    194   char *redirect_url = NULL;
    195   uint64_t row;
    196   bool is_wallet;
    197 
    198   setup_account (pg);
    199   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    200           TALER_EXCHANGEDB_get_legitimization_process_by_account (
    201             pg,
    202             "test-provider",
    203             &account.h_normalized,
    204             &row,
    205             &expiration,
    206             &provider_account_id,
    207             &provider_legitimization_id,
    208             &is_wallet));
    209   FAILIF (NULL != provider_account_id);
    210   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    211           TALER_EXCHANGEDB_get_pending_legitimization_process (
    212             pg,
    213             &account.h_normalized,
    214             "test-provider",
    215             &redirect_url));
    216   FAILIF (NULL != redirect_url);
    217   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    218           TALER_EXCHANGEDB_get_kyc_provider_account (pg,
    219                                                      "test-provider",
    220                                                      "legi-1",
    221                                                      &h_payto,
    222                                                      &is_wallet,
    223                                                      &row));
    224   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    225           TALER_EXCHANGEDB_iterate_kyc_references (pg,
    226                                                    &account.h_normalized,
    227                                                    &reference_cb,
    228                                                    &ctx));
    229   FAILIF (0 != ctx.total);
    230   return 0;
    231 }
    232 
    233 
    234 /**
    235  * Starting a process records it and makes it findable by provider and by
    236  * the provider's own identifiers.
    237  *
    238  * @param pg the database context
    239  * @return 0 on success
    240  */
    241 static int
    242 check_start (struct TALER_EXCHANGEDB_PostgresContext *pg)
    243 {
    244   struct TALER_NormalizedPaytoHashP h_payto;
    245   struct GNUNET_TIME_Absolute expiration;
    246   struct ReferenceContext ctx;
    247   char *provider_account_id = NULL;
    248   char *provider_legitimization_id = NULL;
    249   uint64_t row = 0;
    250   bool is_wallet = true;
    251 
    252   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    253           TALER_EXCHANGEDB_insert_legitimization_process (
    254             pg,
    255             &account.h_normalized,
    256             0,
    257             measure_row,
    258             "test-provider",
    259             "provider-account-1",
    260             "legi-1",
    261             &process_row));
    262   FAILIF (0 == process_row);
    263   FAILIF (1 != TDB_count (pg,
    264                           "FROM legitimization_processes"));
    265 
    266   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    267           TALER_EXCHANGEDB_get_legitimization_process_by_account (
    268             pg,
    269             "test-provider",
    270             &account.h_normalized,
    271             &row,
    272             &expiration,
    273             &provider_account_id,
    274             &provider_legitimization_id,
    275             &is_wallet));
    276   FAILIF (row != process_row);
    277   FAILIF_C (0 != strcmp (provider_account_id,
    278                          "provider-account-1"),
    279             GNUNET_free (provider_account_id);
    280             GNUNET_free (provider_legitimization_id));
    281   FAILIF_C (0 != strcmp (provider_legitimization_id,
    282                          "legi-1"),
    283             GNUNET_free (provider_account_id);
    284             GNUNET_free (provider_legitimization_id));
    285   GNUNET_free (provider_account_id);
    286   GNUNET_free (provider_legitimization_id);
    287   FAILIF (is_wallet);
    288 
    289   /* another provider has no process on this account */
    290   provider_account_id = NULL;
    291   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    292           TALER_EXCHANGEDB_get_legitimization_process_by_account (
    293             pg,
    294             "other-provider",
    295             &account.h_normalized,
    296             &row,
    297             &expiration,
    298             &provider_account_id,
    299             &provider_legitimization_id,
    300             &is_wallet));
    301 
    302   /* the provider's own identifier resolves back to the account */
    303   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    304           TALER_EXCHANGEDB_get_kyc_provider_account (pg,
    305                                                      "test-provider",
    306                                                      "legi-1",
    307                                                      &h_payto,
    308                                                      &is_wallet,
    309                                                      &row));
    310   FAILIF (0 != GNUNET_memcmp (&h_payto,
    311                               &account.h_normalized));
    312   FAILIF (row != process_row);
    313   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    314           TALER_EXCHANGEDB_get_kyc_provider_account (pg,
    315                                                      "test-provider",
    316                                                      "legi-does-not-exist",
    317                                                      &h_payto,
    318                                                      &is_wallet,
    319                                                      &row));
    320 
    321   memset (&ctx,
    322           0,
    323           sizeof (ctx));
    324   ctx.provider = "test-provider";
    325   FAILIF (0 >=
    326           TALER_EXCHANGEDB_iterate_kyc_references (pg,
    327                                                    &account.h_normalized,
    328                                                    &reference_cb,
    329                                                    &ctx));
    330   FAILIF_C (1 != ctx.matched,
    331             GNUNET_free (ctx.user_id); GNUNET_free (ctx.legi_id));
    332   FAILIF_C (0 != strcmp (ctx.user_id,
    333                          "provider-account-1"),
    334             GNUNET_free (ctx.user_id); GNUNET_free (ctx.legi_id));
    335   GNUNET_free (ctx.user_id);
    336   GNUNET_free (ctx.legi_id);
    337   return 0;
    338 }
    339 
    340 
    341 /**
    342  * Updating a process records the redirect URL and can mark it finished.
    343  *
    344  * @param pg the database context
    345  * @return 0 on success
    346  */
    347 static int
    348 check_update (struct TALER_EXCHANGEDB_PostgresContext *pg)
    349 {
    350   struct GNUNET_TIME_Absolute expiration
    351     = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
    352   char *redirect_url = NULL;
    353 
    354   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    355           TALER_EXCHANGEDB_update_legitimization_process_by_row (
    356             pg,
    357             process_row,
    358             "test-provider",
    359             &account.h_normalized,
    360             "provider-account-1",
    361             "legi-1",
    362             "https://kyc.example/start",
    363             expiration,
    364             TALER_EC_NONE,
    365             NULL,
    366             false));
    367   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    368           TALER_EXCHANGEDB_get_pending_legitimization_process (
    369             pg,
    370             &account.h_normalized,
    371             "test-provider",
    372             &redirect_url));
    373   FAILIF (NULL == redirect_url);
    374   FAILIF_C (0 != strcmp (redirect_url,
    375                          "https://kyc.example/start"),
    376             GNUNET_free (redirect_url));
    377   GNUNET_free (redirect_url);
    378 
    379   /* a row that does not exist is not created */
    380   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    381           TALER_EXCHANGEDB_update_legitimization_process_by_row (
    382             pg,
    383             process_row + 1000,
    384             "test-provider",
    385             &account.h_normalized,
    386             "provider-account-1",
    387             "legi-1",
    388             "https://kyc.example/start",
    389             expiration,
    390             TALER_EC_NONE,
    391             NULL,
    392             false));
    393   FAILIF (1 != TDB_count (pg,
    394                           "FROM legitimization_processes"));
    395 
    396   /* marking it finished takes it out of the pending view */
    397   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    398           TALER_EXCHANGEDB_update_legitimization_process_by_row (
    399             pg,
    400             process_row,
    401             "test-provider",
    402             &account.h_normalized,
    403             "provider-account-1",
    404             "legi-1",
    405             "https://kyc.example/start",
    406             expiration,
    407             TALER_EC_NONE,
    408             NULL,
    409             true));
    410   redirect_url = NULL;
    411   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    412           TALER_EXCHANGEDB_get_pending_legitimization_process (
    413             pg,
    414             &account.h_normalized,
    415             "test-provider",
    416             &redirect_url));
    417   FAILIF (NULL != redirect_url);
    418   return 0;
    419 }
    420 
    421 
    422 /**
    423  * A failed KYC check marks the process finished with an error.
    424  *
    425  * @param pg the database context
    426  * @return 0 on success
    427  */
    428 static int
    429 check_kyc_failure (struct TALER_EXCHANGEDB_PostgresContext *pg)
    430 {
    431   uint64_t row = 0;
    432 
    433   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    434           TALER_EXCHANGEDB_insert_legitimization_process (
    435             pg,
    436             &account.h_normalized,
    437             1,
    438             measure_row,
    439             "failing-provider",
    440             "provider-account-2",
    441             "legi-2",
    442             &row));
    443   FAILIF (2 != TDB_count (pg,
    444                           "FROM legitimization_processes"));
    445   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    446           TALER_EXCHANGEDB_insert_kyc_failure (
    447             pg,
    448             row,
    449             &account.h_normalized,
    450             "failing-provider",
    451             "provider-account-2",
    452             "legi-2",
    453             "the provider said no",
    454             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY));
    455   FAILIF (1 != TDB_count (pg,
    456                           "FROM legitimization_processes"
    457                           " WHERE finished"
    458                           "   AND error_code=%u",
    459                           (unsigned int)
    460                           TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY));
    461   /* a row that does not exist is not created */
    462   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    463           TALER_EXCHANGEDB_insert_kyc_failure (
    464             pg,
    465             row + 1000,
    466             &account.h_normalized,
    467             "failing-provider",
    468             "provider-account-2",
    469             "legi-2",
    470             "the provider said no",
    471             TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY));
    472   FAILIF (2 != TDB_count (pg,
    473                           "FROM legitimization_processes"));
    474   return 0;
    475 }
    476 
    477 
    478 /**
    479  * A failed AML program marks its process finished with an error too.
    480  *
    481  * @param pg the database context
    482  * @return 0 on success
    483  */
    484 static int
    485 check_aml_program_failure (struct TALER_EXCHANGEDB_PostgresContext *pg)
    486 {
    487   uint64_t row = 0;
    488 
    489   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    490           TALER_EXCHANGEDB_insert_legitimization_process (
    491             pg,
    492             &account.h_normalized,
    493             2,
    494             measure_row,
    495             "aml-provider",
    496             "provider-account-3",
    497             "legi-3",
    498             &row));
    499   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    500           TALER_EXCHANGEDB_insert_aml_program_failure (
    501             pg,
    502             row,
    503             &account.h_normalized,
    504             "the AML program crashed",
    505             TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE));
    506   FAILIF (1 != TDB_count (pg,
    507                           "FROM legitimization_processes"
    508                           " WHERE finished"
    509                           "   AND error_code=%u",
    510                           (unsigned int)
    511                           TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE));
    512   /* Unlike insert_kyc_failure(), a row that does not exist is *created*
    513      here rather than ignored -- the stored procedure would rather keep a
    514      record of the failed AML program than lose it. */
    515   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    516           TALER_EXCHANGEDB_insert_aml_program_failure (
    517             pg,
    518             row + 1000,
    519             &account.h_normalized,
    520             "the AML program crashed",
    521             TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE));
    522   FAILIF (4 != TDB_count (pg,
    523                           "FROM legitimization_processes"));
    524   return 0;
    525 }
    526 
    527 
    528 /**
    529  * The reference listing reports every provider that worked on the
    530  * account.
    531  *
    532  * @param pg the database context
    533  * @return 0 on success
    534  */
    535 static int
    536 check_references (struct TALER_EXCHANGEDB_PostgresContext *pg)
    537 {
    538   struct ReferenceContext ctx;
    539 
    540   memset (&ctx,
    541           0,
    542           sizeof (ctx));
    543   ctx.provider = "aml-provider";
    544   FAILIF (0 >=
    545           TALER_EXCHANGEDB_iterate_kyc_references (pg,
    546                                                    &account.h_normalized,
    547                                                    &reference_cb,
    548                                                    &ctx));
    549   FAILIF_C (1 != ctx.matched,
    550             GNUNET_free (ctx.user_id); GNUNET_free (ctx.legi_id));
    551   FAILIF_C (4 != ctx.total,
    552             GNUNET_free (ctx.user_id); GNUNET_free (ctx.legi_id));
    553   GNUNET_free (ctx.user_id);
    554   GNUNET_free (ctx.legi_id);
    555 
    556   /* an account nobody checked has no references */
    557   {
    558     struct TALER_NormalizedPaytoHashP other;
    559 
    560     TDB_FILL (other,
    561               98);
    562     memset (&ctx,
    563             0,
    564             sizeof (ctx));
    565     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    566             TALER_EXCHANGEDB_iterate_kyc_references (pg,
    567                                                      &other,
    568                                                      &reference_cb,
    569                                                      &ctx));
    570     FAILIF (0 != ctx.total);
    571   }
    572   return 0;
    573 }
    574 
    575 
    576 /**
    577  * The checks to run, in order.
    578  */
    579 static const struct TDB_Test tests[] = {
    580   { "legitimization-processes-empty",
    581     &check_empty },
    582   { "legitimization-processes-start",
    583     &check_start },
    584   { "legitimization-processes-update",
    585     &check_update },
    586   { "legitimization-processes-kyc-failure",
    587     &check_kyc_failure },
    588   { "legitimization-processes-aml-program-failure",
    589     &check_aml_program_failure },
    590   { "legitimization-processes-references",
    591     &check_references },
    592   { NULL, NULL }
    593 };
    594 
    595 
    596 int
    597 main (int argc,
    598       char *const *argv)
    599 {
    600   int ret;
    601 
    602   ret = TDB_main (argc,
    603                   argv,
    604                   "test-legitimization-processes",
    605                   "Tests for the exchangedb `legitimization_processes' table",
    606                   tests);
    607   TDB_account_free (&account);
    608   return ret;
    609 }
    610 
    611 
    612 /* end of test_legitimization_processes.c */