LCOV - code coverage report
Current view: top level - backend - test_merchant_kyccheck.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 55.8 % 154 86
Test Date: 2026-09-11 17:45:24 Functions: 50.0 % 4 2

            Line data    Source code
       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 backend/test_merchant_kyccheck.c
      18              :  * @brief Exercise refresh scheduling and database-backed status transitions.
      19              :  */
      20              : /* Inject successive /keys snapshots while using the real database helpers. */
      21              : #define TALER_MERCHANTDB_get_exchange_keys test_get_exchange_keys
      22              : #define main kyccheck_main
      23              : #include "taler-merchant-kyccheck.c"
      24              : #undef main
      25              : #undef TALER_MERCHANTDB_get_exchange_keys
      26              : 
      27              : static struct TALER_EXCHANGE_Keys *next_keys;
      28              : 
      29              : 
      30              : enum GNUNET_DB_QueryStatus
      31            0 : test_get_exchange_keys (struct TALER_MERCHANTDB_PostgresContext *db,
      32              :                         const char *exchange_url,
      33              :                         struct GNUNET_TIME_Absolute *first_retry,
      34              :                         struct TALER_EXCHANGE_Keys **keys)
      35              : {
      36              :   (void) db;
      37            0 :   GNUNET_assert (0 == strcmp (exchange_url, next_keys->exchange_url));
      38            0 :   *first_retry = GNUNET_TIME_UNIT_ZERO_ABS;
      39            0 :   *keys = TALER_EXCHANGE_keys_incref (next_keys);
      40            0 :   return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
      41              : }
      42              : 
      43              : 
      44              : /* Called by the HTTP fixture with its disposable database configuration. */
      45              : static void
      46            0 : check_database_transition (void *cls)
      47              : {
      48            0 :   const char *mode = cls;
      49            0 :   struct TALER_EXCHANGE_WireAccount wa = {
      50              :     .fpayto_uri = {
      51              :       .full_payto = "payto://x-taler-bank/localhost/exchange?receiver-name=Exchange"
      52              :     }
      53              :   };
      54            0 :   struct TALER_EXCHANGE_Keys eligible = {
      55              :     .exchange_url = "http://localhost:1/",
      56              :     .accounts = &wa,
      57              :     .accounts_len = 1,
      58              :     .rc = 1
      59              :   };
      60            0 :   struct TALER_EXCHANGE_Keys ineligible = {
      61            0 :     .exchange_url = eligible.exchange_url,
      62              :     .rc = 1
      63              :   };
      64            0 :   struct Account a = {
      65              :     .instance_id = "test-1",
      66              :     .merchant_account_uri = {
      67              :       .full_payto = "payto://x-taler-bank/localhost/account-1?receiver-name=Test"
      68              :     }
      69              :   };
      70              : 
      71            0 :   pg = TALER_MERCHANTDB_connect (cfg);
      72            0 :   GNUNET_assert (NULL != pg);
      73            0 :   memset (&a.h_wire, 42, sizeof (a.h_wire));
      74            0 :   if (0 == strcmp (mode, "tos-conflict"))
      75              :   {
      76            0 :     struct Exchange e = { .keys = &eligible };
      77            0 :     struct Inquiry i = {
      78              :       .a = &a,
      79              :       .e = &e,
      80            0 :       .tos_etag = GNUNET_strdup ("v2")
      81              :     };
      82            0 :     const struct TALER_EXCHANGE_PostKycUploadResponse response = {
      83              :       .hr.http_status = MHD_HTTP_CONFLICT
      84              :     };
      85              : 
      86            0 :     active_inquiries = 1;
      87            0 :     tos_upload_cb (&i, &response);
      88            0 :     GNUNET_assert (0 == active_inquiries);
      89            0 :     GNUNET_assert (NULL != i.task);
      90            0 :     GNUNET_SCHEDULER_cancel (i.task);
      91              :   }
      92              :   else
      93              :   {
      94            0 :     struct Account inactive = {
      95              :       .instance_id = "test-2",
      96              :       .merchant_account_uri = a.merchant_account_uri,
      97              :       .h_wire = a.h_wire,
      98              :       .account_gen = 1 /* differs from database_gen; discovery removed it */
      99              :     };
     100              : 
     101            0 :     GNUNET_assert (0 == strcmp (mode, "eligibility"));
     102            0 :     inquiry_map = GNUNET_CONTAINER_multihashmap_create (8, GNUNET_YES);
     103            0 :     ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &rc);
     104            0 :     rc = GNUNET_CURL_gnunet_rc_create (ctx);
     105            0 :     a_head = a_tail = &a;
     106            0 :     GNUNET_CONTAINER_DLL_insert (a_head, a_tail, &inactive);
     107              :     /* Pending discovery must not turn an unindexed account ineligible. */
     108            0 :     force_check_now (NULL, "test-2", eligible.exchange_url, &a.h_wire);
     109            0 :     next_keys = &eligible;
     110            0 :     find_keys (eligible.exchange_url);
     111            0 :     GNUNET_assert (NULL != a.i_head);
     112            0 :     GNUNET_assert (a.i_head->kyc_ok);
     113            0 :     next_keys = &ineligible;
     114            0 :     find_keys (eligible.exchange_url);
     115            0 :     GNUNET_assert (NULL == a.i_head);
     116            0 :     GNUNET_assert (0 == GNUNET_CONTAINER_multihashmap_size (inquiry_map));
     117              :     /* Repeated refreshes while ineligible must not recreate an inquiry. */
     118            0 :     force_check_now (NULL, a.instance_id, eligible.exchange_url, &a.h_wire);
     119            0 :     GNUNET_assert (NULL == a.i_head);
     120            0 :     next_keys = &eligible;
     121            0 :     find_keys (eligible.exchange_url);
     122            0 :     GNUNET_assert (NULL != a.i_head);
     123            0 :     GNUNET_assert (TALER_EC_MERCHANT_PRIVATE_ACCOUNT_NOT_ELIGIBLE_FOR_EXCHANGE
     124              :                    == a.i_head->last_ec);
     125            0 :     GNUNET_assert (0 == a.i_head->backoff.rel_value_us);
     126            0 :     GNUNET_assert (NULL != a.i_head->kyc);
     127            0 :     GNUNET_assert (! a.i_head->not_first_time);
     128              :     /* Cancel before performing network I/O; keep the persisted status for
     129              :        the fixture's HTTP assertion. */
     130            0 :     stop_inquiries (&a);
     131            0 :     TALER_EXCHANGE_keys_decref (e_head->keys);
     132            0 :     GNUNET_free (e_head);
     133            0 :     e_tail = NULL;
     134            0 :     a_head = a_tail = NULL;
     135            0 :     GNUNET_CONTAINER_multihashmap_destroy (inquiry_map);
     136            0 :     GNUNET_CURL_gnunet_rc_destroy (rc);
     137            0 :     GNUNET_CURL_fini (ctx);
     138              :   }
     139            0 :   GNUNET_assert (EXIT_SUCCESS == global_ret);
     140            0 :   TALER_MERCHANTDB_disconnect (pg);
     141            0 :   GNUNET_SCHEDULER_shutdown ();
     142            0 : }
     143              : 
     144              : 
     145              : static void
     146            1 : check_scheduling (void *cls)
     147              : {
     148            1 :   struct TALER_EXCHANGE_Keys keys = {
     149              :     .exchange_url = "http://localhost:1/"
     150              :   };
     151            1 :   struct Exchange e = { .keys = &keys };
     152            1 :   struct Account a = {
     153              :     .instance_id = "test",
     154              :     .merchant_account_uri = { .full_payto = "payto://x-taler-bank/localhost/test" }
     155              :   };
     156            1 :   struct Inquiry *i = GNUNET_new (struct Inquiry);
     157              :   struct GNUNET_HashCode other;
     158            1 :   uint64_t serial = GNUNET_htonll (42);
     159            1 :   uint64_t serial2 = GNUNET_htonll (43);
     160              : 
     161              :   (void) cls;
     162            1 :   inquiry_map = GNUNET_CONTAINER_multihashmap_create (8, GNUNET_YES);
     163            1 :   refresh_map = GNUNET_CONTAINER_multihashmap_create (8, GNUNET_YES);
     164            1 :   i->a = &a;
     165            1 :   i->e = &e;
     166            1 :   inquiry_key (a.instance_id, &a.h_wire, keys.exchange_url, &i->key);
     167            1 :   inquiry_key ("different-instance", &a.h_wire, keys.exchange_url, &other);
     168            1 :   GNUNET_assert (0 != GNUNET_memcmp (&i->key, &other));
     169            1 :   GNUNET_CONTAINER_DLL_insert (a.i_head, a.i_tail, i);
     170            1 :   GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (
     171              :                    inquiry_map, &i->key, i,
     172              :                    GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
     173              : 
     174              :   /* Repeated notifications for an instance are processed once per pending
     175              :      batch, and distinct instances retain their own requests. */
     176            1 :   update_forced (NULL, &serial, sizeof (serial));
     177            1 :   update_forced (NULL, &serial, sizeof (serial));
     178            1 :   update_forced (NULL, &serial2, sizeof (serial2));
     179            1 :   GNUNET_assert (2 == GNUNET_CONTAINER_multihashmap_size (refresh_map));
     180            1 :   GNUNET_assert (42 == refresh_head->merchant_serial);
     181            1 :   GNUNET_assert (43 == refresh_tail->merchant_serial);
     182            1 :   update_forced (NULL, NULL, 0);
     183            1 :   update_forced (NULL, &serial, 1);
     184            1 :   serial = 0;
     185            1 :   update_forced (NULL, &serial, sizeof (serial));
     186            1 :   GNUNET_assert (2 == GNUNET_CONTAINER_multihashmap_size (refresh_map));
     187            1 :   GNUNET_assert (EXIT_SUCCESS == global_ret);
     188            1 :   GNUNET_SCHEDULER_cancel (refresh_task);
     189            1 :   refresh_task = NULL;
     190              : 
     191              :   /* Indexed lookup affects only the selected inquiry, with one pending task. */
     192            1 :   force_check_now (NULL, "different-instance", keys.exchange_url, &a.h_wire);
     193            1 :   GNUNET_assert (NULL == i->task);
     194            1 :   force_check_now (NULL, a.instance_id, keys.exchange_url, &a.h_wire);
     195            1 :   GNUNET_assert (NULL != i->task);
     196            1 :   force_check_now (NULL, a.instance_id, keys.exchange_url, &a.h_wire);
     197            1 :   GNUNET_assert (NULL != i->task);
     198            1 :   GNUNET_SCHEDULER_cancel (i->task);
     199            1 :   i->task = NULL;
     200              : 
     201              :   /* All stages of an active exchange interaction share the same slot. The
     202              :      sentinels are never dereferenced and are cleared before cleanup. */
     203            1 :   i->kyc = (void *) i;
     204            1 :   request_inquiry (i);
     205            1 :   GNUNET_assert (NULL == i->task);
     206            1 :   i->kyc = NULL;
     207            1 :   i->kyc_info = (void *) i;
     208            1 :   request_inquiry (i);
     209            1 :   GNUNET_assert (NULL == i->task);
     210            1 :   i->kyc_info = NULL;
     211            1 :   i->tos_upload = (void *) i;
     212            1 :   request_inquiry (i);
     213            1 :   GNUNET_assert (NULL == i->task);
     214            1 :   i->tos_upload = NULL;
     215              : 
     216            1 :   active_inquiries = OPEN_INQUIRY_LIMIT;
     217            1 :   inquiry_work (i);
     218            1 :   GNUNET_assert (i->limited && at_limit);
     219            1 :   request_inquiry (i);
     220            1 :   GNUNET_assert (NULL == i->task);
     221            1 :   GNUNET_assert (i->limited);
     222              : 
     223              :   /* Releasing slots resumes the limited inquiry exactly once. Cancel the
     224              :      newly created HTTP operation before the scheduler performs network I/O. */
     225            1 :   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &rc);
     226            1 :   rc = GNUNET_CURL_gnunet_rc_create (ctx);
     227            1 :   a_head = &a;
     228            1 :   a_tail = &a;
     229            1 :   active_inquiries = OPEN_INQUIRY_LIMIT / 2;
     230            1 :   end_inquiry ();
     231            1 :   GNUNET_assert (! i->limited);
     232            1 :   GNUNET_assert (NULL != i->kyc);
     233            1 :   GNUNET_assert (OPEN_INQUIRY_LIMIT / 2 == active_inquiries);
     234            1 :   request_inquiry (i);
     235            1 :   GNUNET_assert (NULL == i->task);
     236            1 :   stop_inquiry (i);
     237            1 :   GNUNET_assert (0 == GNUNET_CONTAINER_multihashmap_size (inquiry_map));
     238            1 :   GNUNET_CURL_gnunet_rc_destroy (rc);
     239            1 :   GNUNET_CURL_fini (ctx);
     240            3 :   while (NULL != refresh_head)
     241              :   {
     242            2 :     struct Refresh *r = refresh_head;
     243              : 
     244            2 :     GNUNET_CONTAINER_DLL_remove (refresh_head, refresh_tail, r);
     245            2 :     GNUNET_free (r);
     246              :   }
     247            1 :   GNUNET_CONTAINER_multihashmap_destroy (refresh_map);
     248            1 :   GNUNET_CONTAINER_multihashmap_destroy (inquiry_map);
     249            1 :   a_head = a_tail = NULL;
     250            1 :   GNUNET_SCHEDULER_shutdown ();
     251            1 : }
     252              : 
     253              : 
     254              : int
     255            1 : main (int argc, char *const argv[])
     256              : {
     257            1 :   GNUNET_log_setup (argv[0], "WARNING", NULL);
     258            1 :   if (3 == argc)
     259              :   {
     260              :     struct GNUNET_CONFIGURATION_Handle *config =
     261            0 :       GNUNET_CONFIGURATION_create (TALER_MERCHANT_project_data ());
     262              : 
     263            0 :     GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_load (config, argv[1]));
     264            0 :     cfg = config;
     265            0 :     GNUNET_SCHEDULER_run (&check_database_transition, (void *) argv[2]);
     266            0 :     GNUNET_CONFIGURATION_destroy (config);
     267            0 :     return global_ret;
     268              :   }
     269            1 :   GNUNET_assert (1 == argc);
     270            1 :   GNUNET_SCHEDULER_run (&check_scheduling, NULL);
     271            1 :   return 0;
     272              : }
        

Generated by: LCOV version 2.0-1