exchange

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

test_aml_staff.c (16670B)


      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 ts (uint64_t secs)
     47 {
     48   struct GNUNET_TIME_Absolute abs = {
     49     .abs_value_us = secs * 1000LLU * 1000LLU
     50   };
     51 
     52   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 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   struct StaffContext *ctx = cls;
    122 
    123   (void) rowid;
    124   (void) master_sig;
    125   (void) last_change;
    126   ctx->total++;
    127   if ( (NULL != ctx->decider_pub) &&
    128        (0 == GNUNET_memcmp (decider_pub,
    129                             ctx->decider_pub)) )
    130   {
    131     ctx->matched++;
    132     ctx->is_active = is_active;
    133     ctx->read_only = read_only;
    134     GNUNET_free (ctx->name);
    135     ctx->name = GNUNET_strdup (decider_name);
    136   }
    137   if ( (0 != ctx->stop_after) &&
    138        (ctx->total >= ctx->stop_after) )
    139     return GNUNET_SYSERR;
    140   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 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   TDB_fill (decider_pub,
    170             sizeof (*decider_pub),
    171             seed);
    172   TDB_FILL (master_sig,
    173             seed);
    174   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 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   struct StaffContext ctx = { 0 };
    198   char *decider_name = NULL;
    199   bool is_active;
    200   bool read_only;
    201 
    202   TDB_FILL (decider_pub,
    203             1);
    204   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   FAILIF (NULL != decider_name);
    213   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    214           TALER_EXCHANGEDB_get_exists_aml_officer (pg,
    215                                                    &decider_pub,
    216                                                    &read_only));
    217   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   FAILIF (0 != ctx.total);
    223   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 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   char *decider_name = NULL;
    243   bool is_active = false;
    244   bool read_only = true;
    245 
    246   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   FAILIF (1 != TDB_count (pg,
    256                           "FROM aml_staff"));
    257 
    258   TDB_FILL (expect_sig,
    259             10);
    260   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   FAILIF_C (0 != strcmp (decider_name,
    269                          "Alex Officer"),
    270             GNUNET_free (decider_name));
    271   GNUNET_free (decider_name);
    272   FAILIF (0 != GNUNET_memcmp (&master_sig,
    273                               &expect_sig));
    274   FAILIF (! is_active);
    275   FAILIF (read_only);
    276   FAILIF (last_change.abs_value_us !=
    277           ts (1600000000).abs_time.abs_value_us);
    278 
    279   read_only = true;
    280   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    281           TALER_EXCHANGEDB_get_exists_aml_officer (pg,
    282                                                    &decider_pub,
    283                                                    &read_only));
    284   FAILIF (read_only);
    285 
    286   memset (&ctx,
    287           0,
    288           sizeof (ctx));
    289   ctx.decider_pub = &decider_pub;
    290   FAILIF (0 >=
    291           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
    292                                                               0,
    293                                                               &staff_cb,
    294                                                               &ctx));
    295   FAILIF_C (1 != ctx.matched,
    296             GNUNET_free (ctx.name));
    297   FAILIF_C (0 != strcmp (ctx.name,
    298                          "Alex Officer"),
    299             GNUNET_free (ctx.name));
    300   GNUNET_free (ctx.name);
    301   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 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   char *decider_name = NULL;
    320   bool is_active = true;
    321   bool read_only = false;
    322 
    323   /* the officer is demoted to read-only */
    324   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   FAILIF (GNUNET_TIME_timestamp_cmp (previous_change,
    334                                      !=,
    335                                      ts (1600000000)));
    336   FAILIF (2 != TDB_count (pg,
    337                           "FROM aml_staff"));
    338   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   GNUNET_free (decider_name);
    347   FAILIF (! read_only);
    348   read_only = false;
    349   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    350           TALER_EXCHANGEDB_get_exists_aml_officer (pg,
    351                                                    &decider_pub,
    352                                                    &read_only));
    353   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   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   FAILIF (GNUNET_TIME_timestamp_cmp (previous_change,
    369                                      !=,
    370                                      ts (1600003600)));
    371   FAILIF (2 != TDB_count (pg,
    372                           "FROM aml_staff"));
    373 
    374   /* both rows are that officer's history */
    375   memset (&ctx,
    376           0,
    377           sizeof (ctx));
    378   ctx.decider_pub = &decider_pub;
    379   FAILIF (2 !=
    380           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
    381                                                               0,
    382                                                               &staff_cb,
    383                                                               &ctx));
    384   FAILIF_C (2 != ctx.matched,
    385             GNUNET_free (ctx.name));
    386   GNUNET_free (ctx.name);
    387   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 check_deactivate (struct TALER_EXCHANGEDB_PostgresContext *pg)
    399 {
    400   struct TALER_AmlOfficerPublicKeyP decider_pub;
    401   struct GNUNET_TIME_Timestamp previous_change;
    402   bool read_only = false;
    403 
    404   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   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    414           TALER_EXCHANGEDB_get_exists_aml_officer (pg,
    415                                                    &decider_pub,
    416                                                    &read_only));
    417   FAILIF (3 != TDB_count (pg,
    418                           "FROM aml_staff"));
    419   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 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   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   memset (&ctx,
    446           0,
    447           sizeof (ctx));
    448   ctx.decider_pub = &other;
    449   FAILIF (4 !=
    450           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
    451                                                               0,
    452                                                               &staff_cb,
    453                                                               &ctx));
    454   FAILIF_C (1 != ctx.matched,
    455             GNUNET_free (ctx.name));
    456   FAILIF_C (! ctx.read_only,
    457             GNUNET_free (ctx.name));
    458   GNUNET_free (ctx.name);
    459 
    460   memset (&ctx,
    461           0,
    462           sizeof (ctx));
    463   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   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   memset (&ctx,
    474           0,
    475           sizeof (ctx));
    476   ctx.stop_after = 1;
    477   FAILIF (1 !=
    478           TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id (pg,
    479                                                               0,
    480                                                               &staff_cb,
    481                                                               &ctx));
    482   FAILIF (1 != ctx.total);
    483   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 main (int argc,
    507       char *const *argv)
    508 {
    509   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 */