LCOV - code coverage report
Current view: top level - exchangedb - test_kyc_events.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 84 84
Test Date: 2026-09-11 18:55:36 Functions: 100.0 % 7 7

            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 exchangedb/test_kyc_events.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `kyc_events`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_iterate_aml_statistics().
      23              :  *
      24              :  * `kyc_events` is a plain counter log: AML decisions may name events,
      25              :  * each of which becomes one row, and the statistics query groups those
      26              :  * rows by name over a time range.  Two of the names are special --
      27              :  * ACCOUNT_OPEN and ACCOUNT_IDLE also move the account's open/close time
      28              :  * in `kyc_targets` -- so those are checked as well.
      29              :  */
      30              : #include "test_common.h"
      31              : #include "exchange-database/insert_aml_decision.h"
      32              : #include "exchange-database/insert_aml_officer.h"
      33              : #include "exchange-database/iterate_aml_statistics.h"
      34              : 
      35              : 
      36              : /**
      37              :  * One second, in microseconds.
      38              :  */
      39              : #define SECOND 1000000LLU
      40              : 
      41              : 
      42              : /**
      43              :  * Times at which we take our three decisions, in seconds since the
      44              :  * epoch.
      45              :  */
      46              : #define T1 1600000000
      47              : #define T2 1600003600
      48              : #define T3 1600007200
      49              : 
      50              : 
      51              : /**
      52              :  * Account the decisions are about.
      53              :  */
      54              : static struct TDB_Account account;
      55              : 
      56              : 
      57              : /**
      58              :  * Officer taking the decisions.
      59              :  */
      60              : static struct TALER_AmlOfficerPublicKeyP officer_pub;
      61              : 
      62              : 
      63              : /**
      64              :  * Build a timestamp from a number of seconds since the epoch.
      65              :  *
      66              :  * @param secs seconds since the epoch
      67              :  * @return the timestamp
      68              :  */
      69              : static struct GNUNET_TIME_Timestamp
      70           19 : ts (uint64_t secs)
      71              : {
      72           19 :   struct GNUNET_TIME_Absolute abs = {
      73           19 :     .abs_value_us = secs * SECOND
      74              :   };
      75              : 
      76           19 :   return GNUNET_TIME_absolute_to_timestamp (abs);
      77              : }
      78              : 
      79              : 
      80              : /**
      81              :  * Closure for #statistics_cb().
      82              :  */
      83              : struct StatsContext
      84              : {
      85              :   /**
      86              :    * How many counters did the callback see?
      87              :    */
      88              :   unsigned int total;
      89              : 
      90              :   /**
      91              :    * Name we are interested in.
      92              :    */
      93              :   const char *name;
      94              : 
      95              :   /**
      96              :    * Count reported for @e name, UINT64_MAX if it was not reported.
      97              :    */
      98              :   uint64_t cnt;
      99              : };
     100              : 
     101              : 
     102              : /**
     103              :  * Callback for #TALER_EXCHANGEDB_iterate_aml_statistics().
     104              :  *
     105              :  * @param cls a `struct StatsContext *`
     106              :  * @param name name of the counter
     107              :  * @param cnt value of the counter
     108              :  */
     109              : static void
     110            5 : statistics_cb (void *cls,
     111              :                const char *name,
     112              :                uint64_t cnt)
     113              : {
     114            5 :   struct StatsContext *ctx = cls;
     115              : 
     116            5 :   ctx->total++;
     117            5 :   if ( (NULL != ctx->name) &&
     118            5 :        (0 == strcmp (name,
     119              :                      ctx->name)) )
     120            3 :     ctx->cnt = cnt;
     121            5 : }
     122              : 
     123              : 
     124              : /**
     125              :  * Take an AML decision that triggers @a events.
     126              :  *
     127              :  * @param pg the database context
     128              :  * @param seed seed for the officer's signature
     129              :  * @param when when the decision is taken, in seconds since the epoch
     130              :  * @param num_events length of @a events
     131              :  * @param events the events to trigger
     132              :  * @return transaction status
     133              :  */
     134              : static enum GNUNET_DB_QueryStatus
     135            4 : decide (struct TALER_EXCHANGEDB_PostgresContext *pg,
     136              :         uint32_t seed,
     137              :         uint64_t when,
     138              :         size_t num_events,
     139              :         const char *events[static num_events])
     140            4 : {
     141              :   struct TALER_AmlOfficerSignatureP decider_sig;
     142            4 :   struct TALER_FullPayto null_payto = { NULL };
     143              :   struct GNUNET_TIME_Timestamp last_date;
     144              :   json_t *new_rules;
     145              :   bool invalid_officer;
     146              :   bool unknown_account;
     147              :   bool is_wallet;
     148              :   uint64_t lms;
     149              :   enum GNUNET_DB_QueryStatus qs;
     150              : 
     151            4 :   TDB_fill (&decider_sig,
     152              :             sizeof (decider_sig),
     153              :             seed);
     154            4 :   new_rules = GNUNET_JSON_PACK (
     155              :     GNUNET_JSON_pack_array_steal ("rules",
     156              :                                   json_array ()));
     157            4 :   GNUNET_assert (NULL != new_rules);
     158            4 :   qs = TALER_EXCHANGEDB_insert_aml_decision (
     159              :     pg,
     160              :     null_payto,
     161              :     &account.h_normalized,
     162              :     ts (when),
     163              :     GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS),
     164              :     NULL,
     165              :     new_rules,
     166              :     false,
     167              :     NULL,
     168              :     NULL,
     169              :     "statistics",
     170              :     &officer_pub,
     171              :     &decider_sig,
     172              :     num_events,
     173              :     events,
     174              :     NULL,
     175              :     0,
     176              :     NULL,
     177              :     NULL,
     178            4 :     GNUNET_TIME_UNIT_ZERO_TS,
     179              :     &invalid_officer,
     180              :     &unknown_account,
     181              :     &last_date,
     182              :     &lms,
     183              :     &is_wallet);
     184            4 :   json_decref (new_rules);
     185            4 :   GNUNET_assert (! invalid_officer);
     186            4 :   return qs;
     187              : }
     188              : 
     189              : 
     190              : /**
     191              :  * No counters while the table is empty.
     192              :  *
     193              :  * @param pg the database context
     194              :  * @return 0 on success
     195              :  */
     196              : static int
     197            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     198              : {
     199            1 :   const char *names[] = { "ACCOUNT_OPEN", "sar-filed" };
     200              :   struct TALER_MasterSignatureP master_sig;
     201              :   struct GNUNET_TIME_Timestamp previous_change;
     202            1 :   struct StatsContext ctx = { 0 };
     203              : 
     204            1 :   TDB_account (pg,
     205              :                10,
     206              :                &account);
     207            1 :   TDB_FILL (officer_pub,
     208              :             20);
     209            1 :   TDB_FILL (master_sig,
     210              :             21);
     211            1 :   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     212              :                  TALER_EXCHANGEDB_insert_aml_officer (pg,
     213              :                                                       &officer_pub,
     214              :                                                       &master_sig,
     215              :                                                       "Alex Officer",
     216              :                                                       true,
     217              :                                                       false,
     218              :                                                       ts (T1 - 1),
     219              :                                                       &previous_change));
     220            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     221              :           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
     222              :                                                    2,
     223              :                                                    names,
     224              :                                                    ts (0),
     225              :                                                    ts (T3 + 1),
     226              :                                                    &statistics_cb,
     227              :                                                    &ctx));
     228            1 :   FAILIF (0 != ctx.total);
     229            1 :   return 0;
     230              : }
     231              : 
     232              : 
     233              : /**
     234              :  * Decisions record their events, and the two account-lifecycle events
     235              :  * also move the account's open and close time.
     236              :  *
     237              :  * @param pg the database context
     238              :  * @return 0 on success
     239              :  */
     240              : static int
     241            1 : check_record (struct TALER_EXCHANGEDB_PostgresContext *pg)
     242              : {
     243            1 :   const char *open_and_sar[] = { "ACCOUNT_OPEN", "sar-filed" };
     244            1 :   const char *sar[] = { "sar-filed" };
     245            1 :   const char *idle[] = { "ACCOUNT_IDLE" };
     246            1 :   const char *none[1] = { NULL };
     247              : 
     248            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     249              :           decide (pg,
     250              :                   30,
     251              :                   T1,
     252              :                   2,
     253              :                   open_and_sar));
     254            1 :   FAILIF (2 != TDB_count (pg,
     255              :                           "FROM kyc_events"));
     256            1 :   FAILIF (1 != TDB_count (pg,
     257              :                           "FROM kyc_targets"
     258              :                           " WHERE open_time=%llu"
     259              :                           "   AND close_time IS NULL",
     260              :                           (unsigned long long) (T1 * SECOND)));
     261              : 
     262            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     263              :           decide (pg,
     264              :                   31,
     265              :                   T2,
     266              :                   1,
     267              :                   sar));
     268            1 :   FAILIF (3 != TDB_count (pg,
     269              :                           "FROM kyc_events"));
     270              : 
     271            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     272              :           decide (pg,
     273              :                   32,
     274              :                   T3,
     275              :                   1,
     276              :                   idle));
     277            1 :   FAILIF (4 != TDB_count (pg,
     278              :                           "FROM kyc_events"));
     279            1 :   FAILIF (1 != TDB_count (pg,
     280              :                           "FROM kyc_targets"
     281              :                           " WHERE close_time=%llu",
     282              :                           (unsigned long long) (T3 * SECOND)));
     283              : 
     284              :   /* a decision without events records none */
     285            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     286              :           decide (pg,
     287              :                   33,
     288              :                   T3 + 1,
     289              :                   0,
     290              :                   none));
     291            1 :   FAILIF (4 != TDB_count (pg,
     292              :                           "FROM kyc_events"));
     293            1 :   return 0;
     294              : }
     295              : 
     296              : 
     297              : /**
     298              :  * The statistics group the events by name over the requested range.
     299              :  *
     300              :  * @param pg the database context
     301              :  * @return 0 on success
     302              :  */
     303              : static int
     304            1 : check_statistics (struct TALER_EXCHANGEDB_PostgresContext *pg)
     305              : {
     306            1 :   const char *all[] = { "ACCOUNT_OPEN", "ACCOUNT_IDLE", "sar-filed" };
     307            1 :   const char *sar[] = { "sar-filed" };
     308            1 :   const char *unknown[] = { "no-such-event" };
     309              :   struct StatsContext ctx;
     310              : 
     311              :   /* over the whole range every requested name that occurred is
     312              :      reported, once */
     313            1 :   memset (&ctx,
     314              :           0,
     315              :           sizeof (ctx));
     316            1 :   ctx.name = "sar-filed";
     317            1 :   ctx.cnt = UINT64_MAX;
     318            1 :   FAILIF (0 >=
     319              :           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
     320              :                                                    3,
     321              :                                                    all,
     322              :                                                    ts (0),
     323              :                                                    ts (T3 + 2),
     324              :                                                    &statistics_cb,
     325              :                                                    &ctx));
     326            1 :   FAILIF (3 != ctx.total);
     327            1 :   FAILIF (2 != ctx.cnt);
     328              : 
     329              :   /* a name that was not asked for is not reported, even though it
     330              :      happened */
     331            1 :   memset (&ctx,
     332              :           0,
     333              :           sizeof (ctx));
     334            1 :   ctx.name = "sar-filed";
     335            1 :   ctx.cnt = UINT64_MAX;
     336            1 :   FAILIF (0 >=
     337              :           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
     338              :                                                    1,
     339              :                                                    sar,
     340              :                                                    ts (0),
     341              :                                                    ts (T3 + 2),
     342              :                                                    &statistics_cb,
     343              :                                                    &ctx));
     344            1 :   FAILIF (1 != ctx.total);
     345            1 :   FAILIF (2 != ctx.cnt);
     346              : 
     347              :   /* the start of the range is inclusive, the end is exclusive */
     348            1 :   memset (&ctx,
     349              :           0,
     350              :           sizeof (ctx));
     351            1 :   ctx.name = "sar-filed";
     352            1 :   ctx.cnt = UINT64_MAX;
     353            1 :   FAILIF (0 >=
     354              :           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
     355              :                                                    1,
     356              :                                                    sar,
     357              :                                                    ts (T2),
     358              :                                                    ts (T3),
     359              :                                                    &statistics_cb,
     360              :                                                    &ctx));
     361            1 :   FAILIF (1 != ctx.total);
     362            1 :   FAILIF (1 != ctx.cnt);
     363              : 
     364            1 :   memset (&ctx,
     365              :           0,
     366              :           sizeof (ctx));
     367            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     368              :           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
     369              :                                                    1,
     370              :                                                    sar,
     371              :                                                    ts (T1),
     372              :                                                    ts (T1),
     373              :                                                    &statistics_cb,
     374              :                                                    &ctx));
     375            1 :   FAILIF (0 != ctx.total);
     376              : 
     377              :   /* a range after everything that happened is empty */
     378            1 :   memset (&ctx,
     379              :           0,
     380              :           sizeof (ctx));
     381            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     382              :           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
     383              :                                                    3,
     384              :                                                    all,
     385              :                                                    ts (T3 + 2),
     386              :                                                    ts (T3 + 100),
     387              :                                                    &statistics_cb,
     388              :                                                    &ctx));
     389            1 :   FAILIF (0 != ctx.total);
     390              : 
     391              :   /* and so is a name nobody ever recorded */
     392            1 :   memset (&ctx,
     393              :           0,
     394              :           sizeof (ctx));
     395            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     396              :           TALER_EXCHANGEDB_iterate_aml_statistics (pg,
     397              :                                                    1,
     398              :                                                    unknown,
     399              :                                                    ts (0),
     400              :                                                    ts (T3 + 2),
     401              :                                                    &statistics_cb,
     402              :                                                    &ctx));
     403            1 :   FAILIF (0 != ctx.total);
     404            1 :   return 0;
     405              : }
     406              : 
     407              : 
     408              : /**
     409              :  * The checks to run, in order.
     410              :  */
     411              : static const struct TDB_Test tests[] = {
     412              :   { "kyc-events-empty",
     413              :     &check_empty },
     414              :   { "kyc-events-record",
     415              :     &check_record },
     416              :   { "kyc-events-statistics",
     417              :     &check_statistics },
     418              :   { NULL, NULL }
     419              : };
     420              : 
     421              : 
     422              : int
     423            1 : main (int argc,
     424              :       char *const *argv)
     425              : {
     426              :   int ret;
     427              : 
     428            1 :   ret = TDB_main (argc,
     429              :                   argv,
     430              :                   "test-kyc-events",
     431              :                   "Tests for the exchangedb `kyc_events' table",
     432              :                   tests);
     433            1 :   TDB_account_free (&account);
     434            1 :   return ret;
     435              : }
     436              : 
     437              : 
     438              : /* end of test_kyc_events.c */
        

Generated by: LCOV version 2.0-1