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

            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_aml_staff.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `aml_staff`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_insert_aml_officer(),
      23              :  * #TALER_EXCHANGEDB_get_aml_officer(),
      24              :  * #TALER_EXCHANGEDB_get_exists_aml_officer() and
      25              :  * #TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id().
      26              :  *
      27              :  * `aml_staff` has no foreign keys and is append-only: every status change
      28              :  * is a new row, and the current status is the newest one.  A change dated
      29              :  * before the one already on file is refused, which is what makes the
      30              :  * "previous_change" output of the insert worth having.
      31              :  */
      32              : #include "test_common.h"
      33              : #include "exchange-database/get_aml_officer.h"
      34              : #include "exchange-database/get_exists_aml_officer.h"
      35              : #include "exchange-database/insert_aml_officer.h"
      36              : #include "exchange-database/iterate_aml_staff_above_serial_id.h"
      37              : 
      38              : 
      39              : /**
      40              :  * Build a timestamp from a number of seconds since the epoch.
      41              :  *
      42              :  * @param secs seconds since the epoch
      43              :  * @return the timestamp
      44              :  */
      45              : static struct GNUNET_TIME_Timestamp
      46            8 : ts (uint64_t secs)
      47              : {
      48            8 :   struct GNUNET_TIME_Absolute abs = {
      49            8 :     .abs_value_us = secs * 1000LLU * 1000LLU
      50              :   };
      51              : 
      52            8 :   return GNUNET_TIME_absolute_to_timestamp (abs);
      53              : }
      54              : 
      55              : 
      56              : /**
      57              :  * Closure for #staff_cb().
      58              :  */
      59              : struct StaffContext
      60              : {
      61              :   /**
      62              :    * How many rows did the callback see?
      63              :    */
      64              :   unsigned int total;
      65              : 
      66              :   /**
      67              :    * Stop after this many rows; 0 for no limit.
      68              :    */
      69              :   unsigned int stop_after;
      70              : 
      71              :   /**
      72              :    * Officer we are looking for, NULL to match nothing.
      73              :    */
      74              :   const struct TALER_AmlOfficerPublicKeyP *decider_pub;
      75              : 
      76              :   /**
      77              :    * How many times did we see them?
      78              :    */
      79              :   unsigned int matched;
      80              : 
      81              :   /**
      82              :    * Active flag of the last matching row.
      83              :    */
      84              :   bool is_active;
      85              : 
      86              :   /**
      87              :    * Read-only flag of the last matching row.
      88              :    */
      89              :   bool read_only;
      90              : 
      91              :   /**
      92              :    * Name of the last matching row, owned by this struct.
      93              :    */
      94              :   char *name;
      95              : };
      96              : 
      97              : 
      98              : /**
      99              :  * Callback for #TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id().
     100              :  *
     101              :  * @param cls a `struct StaffContext *`
     102              :  * @param rowid row of the status change
     103              :  * @param decider_pub the officer
     104              :  * @param master_sig signature affirming the status
     105              :  * @param decider_name name of the officer
     106              :  * @param is_active whether they could act from then on
     107              :  * @param read_only whether their access was read-only
     108              :  * @param last_change when the change took effect
     109              :  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
     110              :  */
     111              : static enum GNUNET_GenericReturnValue
     112            8 : staff_cb (void *cls,
     113              :           uint64_t rowid,
     114              :           const struct TALER_AmlOfficerPublicKeyP *decider_pub,
     115              :           const struct TALER_MasterSignatureP *master_sig,
     116              :           const char *decider_name,
     117              :           bool is_active,
     118              :           bool read_only,
     119              :           struct GNUNET_TIME_Timestamp last_change)
     120              : {
     121            8 :   struct StaffContext *ctx = cls;
     122              : 
     123              :   (void) rowid;
     124              :   (void) master_sig;
     125              :   (void) last_change;
     126            8 :   ctx->total++;
     127            8 :   if ( (NULL != ctx->decider_pub) &&
     128            7 :        (0 == GNUNET_memcmp (decider_pub,
     129              :                             ctx->decider_pub)) )
     130              :   {
     131            4 :     ctx->matched++;
     132            4 :     ctx->is_active = is_active;
     133            4 :     ctx->read_only = read_only;
     134            4 :     GNUNET_free (ctx->name);
     135            4 :     ctx->name = GNUNET_strdup (decider_name);
     136              :   }
     137            8 :   if ( (0 != ctx->stop_after) &&
     138            1 :        (ctx->total >= ctx->stop_after) )
     139            1 :     return GNUNET_SYSERR;
     140            7 :   return GNUNET_OK;
     141              : }
     142              : 
     143              : 
     144              : /**
     145              :  * Record a status change for an officer.
     146              :  *
     147              :  * @param pg the database context
     148              :  * @param seed seed for the officer's key and the master signature
     149              :  * @param name name of the officer
     150              :  * @param is_active whether they may act
     151              :  * @param read_only whether their access is read-only
     152              :  * @param when when the change takes effect, in seconds since the epoch
     153              :  * @param[out] decider_pub set to the officer's key
     154              :  * @param[out] previous_change set to the time of the previous change
     155              :  * @return transaction status
     156              :  */
     157              : static enum GNUNET_DB_QueryStatus
     158            5 : add_officer (struct TALER_EXCHANGEDB_PostgresContext *pg,
     159              :              uint32_t seed,
     160              :              const char *name,
     161              :              bool is_active,
     162              :              bool read_only,
     163              :              uint64_t when,
     164              :              struct TALER_AmlOfficerPublicKeyP *decider_pub,
     165              :              struct GNUNET_TIME_Timestamp *previous_change)
     166              : {
     167              :   struct TALER_MasterSignatureP master_sig;
     168              : 
     169            5 :   TDB_fill (decider_pub,
     170              :             sizeof (*decider_pub),
     171              :             seed);
     172            5 :   TDB_FILL (master_sig,
     173              :             seed);
     174            5 :   return TALER_EXCHANGEDB_insert_aml_officer (pg,
     175              :                                               decider_pub,
     176              :                                               &master_sig,
     177              :                                               name,
     178              :                                               is_active,
     179              :                                               read_only,
     180              :                                               ts (when),
     181              :                                               previous_change);
     182              : }
     183              : 
     184              : 
     185              : /**
     186              :  * Nothing is known about an officer who was never appointed.
     187              :  *
     188              :  * @param pg the database context
     189              :  * @return 0 on success
     190              :  */
     191              : static int
     192            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     193              : {
     194              :   struct TALER_AmlOfficerPublicKeyP decider_pub;
     195              :   struct TALER_MasterSignatureP master_sig;
     196              :   struct GNUNET_TIME_Absolute last_change;
     197            1 :   struct StaffContext ctx = { 0 };
     198            1 :   char *decider_name = NULL;
     199              :   bool is_active;
     200              :   bool read_only;
     201              : 
     202            1 :   TDB_FILL (decider_pub,
     203              :             1);
     204            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     205              :           TALER_EXCHANGEDB_get_aml_officer (pg,
     206              :                                             &decider_pub,
     207              :                                             &master_sig,
     208              :                                             &decider_name,
     209              :                                             &is_active,
     210              :                                             &read_only,
     211              :                                             &last_change));
     212            1 :   FAILIF (NULL != decider_name);
     213            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     214              :           TALER_EXCHANGEDB_get_exists_aml_officer (pg,
     215              :                                                    &decider_pub,
     216              :                                                    &read_only));
     217            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     218              :           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
     219              :                                                               0,
     220              :                                                               &staff_cb,
     221              :                                                               &ctx));
     222            1 :   FAILIF (0 != ctx.total);
     223            1 :   return 0;
     224              : }
     225              : 
     226              : 
     227              : /**
     228              :  * Appointing an officer records them and makes them active.
     229              :  *
     230              :  * @param pg the database context
     231              :  * @return 0 on success
     232              :  */
     233              : static int
     234            1 : check_appoint (struct TALER_EXCHANGEDB_PostgresContext *pg)
     235              : {
     236              :   struct TALER_AmlOfficerPublicKeyP decider_pub;
     237              :   struct TALER_MasterSignatureP master_sig;
     238              :   struct TALER_MasterSignatureP expect_sig;
     239              :   struct GNUNET_TIME_Timestamp previous_change;
     240              :   struct GNUNET_TIME_Absolute last_change;
     241              :   struct StaffContext ctx;
     242            1 :   char *decider_name = NULL;
     243            1 :   bool is_active = false;
     244            1 :   bool read_only = true;
     245              : 
     246            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     247              :           add_officer (pg,
     248              :                        10,
     249              :                        "Alex Officer",
     250              :                        true,
     251              :                        false,
     252              :                        1600000000,
     253              :                        &decider_pub,
     254              :                        &previous_change));
     255            1 :   FAILIF (1 != TDB_count (pg,
     256              :                           "FROM aml_staff"));
     257              : 
     258            1 :   TDB_FILL (expect_sig,
     259              :             10);
     260            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     261              :           TALER_EXCHANGEDB_get_aml_officer (pg,
     262              :                                             &decider_pub,
     263              :                                             &master_sig,
     264              :                                             &decider_name,
     265              :                                             &is_active,
     266              :                                             &read_only,
     267              :                                             &last_change));
     268            1 :   FAILIF_C (0 != strcmp (decider_name,
     269              :                          "Alex Officer"),
     270              :             GNUNET_free (decider_name));
     271            1 :   GNUNET_free (decider_name);
     272            1 :   FAILIF (0 != GNUNET_memcmp (&master_sig,
     273              :                               &expect_sig));
     274            1 :   FAILIF (! is_active);
     275            1 :   FAILIF (read_only);
     276            1 :   FAILIF (last_change.abs_value_us !=
     277              :           ts (1600000000).abs_time.abs_value_us);
     278              : 
     279            1 :   read_only = true;
     280            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     281              :           TALER_EXCHANGEDB_get_exists_aml_officer (pg,
     282              :                                                    &decider_pub,
     283              :                                                    &read_only));
     284            1 :   FAILIF (read_only);
     285              : 
     286            1 :   memset (&ctx,
     287              :           0,
     288              :           sizeof (ctx));
     289            1 :   ctx.decider_pub = &decider_pub;
     290            1 :   FAILIF (0 >=
     291              :           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
     292              :                                                               0,
     293              :                                                               &staff_cb,
     294              :                                                               &ctx));
     295            1 :   FAILIF_C (1 != ctx.matched,
     296              :             GNUNET_free (ctx.name));
     297            1 :   FAILIF_C (0 != strcmp (ctx.name,
     298              :                          "Alex Officer"),
     299              :             GNUNET_free (ctx.name));
     300            1 :   GNUNET_free (ctx.name);
     301            1 :   return 0;
     302              : }
     303              : 
     304              : 
     305              : /**
     306              :  * A later change supersedes the earlier one; an earlier one is refused.
     307              :  *
     308              :  * @param pg the database context
     309              :  * @return 0 on success
     310              :  */
     311              : static int
     312            1 : check_history (struct TALER_EXCHANGEDB_PostgresContext *pg)
     313              : {
     314              :   struct TALER_AmlOfficerPublicKeyP decider_pub;
     315              :   struct TALER_MasterSignatureP master_sig;
     316              :   struct GNUNET_TIME_Timestamp previous_change;
     317              :   struct GNUNET_TIME_Absolute last_change;
     318              :   struct StaffContext ctx;
     319            1 :   char *decider_name = NULL;
     320            1 :   bool is_active = true;
     321            1 :   bool read_only = false;
     322              : 
     323              :   /* the officer is demoted to read-only */
     324            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     325              :           add_officer (pg,
     326              :                        10,
     327              :                        "Alex Officer",
     328              :                        true,
     329              :                        true,
     330              :                        1600003600,
     331              :                        &decider_pub,
     332              :                        &previous_change));
     333            1 :   FAILIF (GNUNET_TIME_timestamp_cmp (previous_change,
     334              :                                      !=,
     335              :                                      ts (1600000000)));
     336            1 :   FAILIF (2 != TDB_count (pg,
     337              :                           "FROM aml_staff"));
     338            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     339              :           TALER_EXCHANGEDB_get_aml_officer (pg,
     340              :                                             &decider_pub,
     341              :                                             &master_sig,
     342              :                                             &decider_name,
     343              :                                             &is_active,
     344              :                                             &read_only,
     345              :                                             &last_change));
     346            1 :   GNUNET_free (decider_name);
     347            1 :   FAILIF (! read_only);
     348            1 :   read_only = false;
     349            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     350              :           TALER_EXCHANGEDB_get_exists_aml_officer (pg,
     351              :                                                    &decider_pub,
     352              :                                                    &read_only));
     353            1 :   FAILIF (! read_only);
     354              : 
     355              :   /* A change dated before the current status is refused.  The call still
     356              :      reports ONE_RESULT -- the stored procedure always returns its row --
     357              :      and the refusal shows in @e previous_change, which comes back as the
     358              :      status that stayed in force rather than as the one being replaced. */
     359            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     360              :           add_officer (pg,
     361              :                        10,
     362              :                        "Alex Officer",
     363              :                        true,
     364              :                        false,
     365              :                        1600001800,
     366              :                        &decider_pub,
     367              :                        &previous_change));
     368            1 :   FAILIF (GNUNET_TIME_timestamp_cmp (previous_change,
     369              :                                      !=,
     370              :                                      ts (1600003600)));
     371            1 :   FAILIF (2 != TDB_count (pg,
     372              :                           "FROM aml_staff"));
     373              : 
     374              :   /* both rows are that officer's history */
     375            1 :   memset (&ctx,
     376              :           0,
     377              :           sizeof (ctx));
     378            1 :   ctx.decider_pub = &decider_pub;
     379            1 :   FAILIF (2 !=
     380              :           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
     381              :                                                               0,
     382              :                                                               &staff_cb,
     383              :                                                               &ctx));
     384            1 :   FAILIF_C (2 != ctx.matched,
     385              :             GNUNET_free (ctx.name));
     386            1 :   GNUNET_free (ctx.name);
     387            1 :   return 0;
     388              : }
     389              : 
     390              : 
     391              : /**
     392              :  * A deactivated officer is no longer active.
     393              :  *
     394              :  * @param pg the database context
     395              :  * @return 0 on success
     396              :  */
     397              : static int
     398            1 : check_deactivate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     399              : {
     400              :   struct TALER_AmlOfficerPublicKeyP decider_pub;
     401              :   struct GNUNET_TIME_Timestamp previous_change;
     402            1 :   bool read_only = false;
     403              : 
     404            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     405              :           add_officer (pg,
     406              :                        10,
     407              :                        "Alex Officer",
     408              :                        false,
     409              :                        false,
     410              :                        1600007200,
     411              :                        &decider_pub,
     412              :                        &previous_change));
     413            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     414              :           TALER_EXCHANGEDB_get_exists_aml_officer (pg,
     415              :                                                    &decider_pub,
     416              :                                                    &read_only));
     417            1 :   FAILIF (3 != TDB_count (pg,
     418              :                           "FROM aml_staff"));
     419            1 :   return 0;
     420              : }
     421              : 
     422              : 
     423              : /**
     424              :  * The iterator's serial bound and abort return behave as documented.
     425              :  *
     426              :  * @param pg the database context
     427              :  * @return 0 on success
     428              :  */
     429              : static int
     430            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     431              : {
     432              :   struct TALER_AmlOfficerPublicKeyP other;
     433              :   struct GNUNET_TIME_Timestamp previous_change;
     434              :   struct StaffContext ctx;
     435              : 
     436            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     437              :           add_officer (pg,
     438              :                        11,
     439              :                        "Bo Officer",
     440              :                        true,
     441              :                        true,
     442              :                        1600000000,
     443              :                        &other,
     444              :                        &previous_change));
     445            1 :   memset (&ctx,
     446              :           0,
     447              :           sizeof (ctx));
     448            1 :   ctx.decider_pub = &other;
     449            1 :   FAILIF (4 !=
     450              :           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
     451              :                                                               0,
     452              :                                                               &staff_cb,
     453              :                                                               &ctx));
     454            1 :   FAILIF_C (1 != ctx.matched,
     455              :             GNUNET_free (ctx.name));
     456            1 :   FAILIF_C (! ctx.read_only,
     457              :             GNUNET_free (ctx.name));
     458            1 :   GNUNET_free (ctx.name);
     459              : 
     460            1 :   memset (&ctx,
     461              :           0,
     462              :           sizeof (ctx));
     463            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     464              :           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
     465              :                                                               1000,
     466              :                                                               &staff_cb,
     467              :                                                               &ctx));
     468            1 :   FAILIF (0 != ctx.total);
     469              : 
     470              :   /* A callback that gives up stops the iteration.  Unlike most of the
     471              :      other iterators, this one reports how many rows the callback was
     472              :      actually shown rather than how many the query matched. */
     473            1 :   memset (&ctx,
     474              :           0,
     475              :           sizeof (ctx));
     476            1 :   ctx.stop_after = 1;
     477            1 :   FAILIF (1 !=
     478              :           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
     479              :                                                               0,
     480              :                                                               &staff_cb,
     481              :                                                               &ctx));
     482            1 :   FAILIF (1 != ctx.total);
     483            1 :   return 0;
     484              : }
     485              : 
     486              : 
     487              : /**
     488              :  * The checks to run, in order.
     489              :  */
     490              : static const struct TDB_Test tests[] = {
     491              :   { "aml-staff-empty",
     492              :     &check_empty },
     493              :   { "aml-staff-appoint",
     494              :     &check_appoint },
     495              :   { "aml-staff-history",
     496              :     &check_history },
     497              :   { "aml-staff-deactivate",
     498              :     &check_deactivate },
     499              :   { "aml-staff-iterate",
     500              :     &check_iterate },
     501              :   { NULL, NULL }
     502              : };
     503              : 
     504              : 
     505              : int
     506            1 : main (int argc,
     507              :       char *const *argv)
     508              : {
     509            1 :   return TDB_main (argc,
     510              :                    argv,
     511              :                    "test-aml-staff",
     512              :                    "Tests for the exchangedb `aml_staff' table",
     513              :                    tests);
     514              : }
     515              : 
     516              : 
     517              : /* end of test_aml_staff.c */
        

Generated by: LCOV version 2.0-1