exchange

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

test_auditordb_regression.c (10296B)


      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 auditordb/test_auditordb_regression.c
     18  * @brief regression tests for individual auditor DB operations
     19  * @author Christian Grothoff
     20  *
     21  * Each test function in this file pins down the behaviour of one
     22  * auditor DB operation that was found to be wrong.  Keep them small
     23  * and independent: the file is meant to grow one function per fixed
     24  * defect.
     25  */
     26 #include <gnunet/gnunet_db_lib.h>
     27 #include "auditordb_lib.h"
     28 #include "auditor-database/create_tables.h"
     29 #include "auditor-database/drop_tables.h"
     30 #include "auditor-database/get_auditor_progress.h"
     31 #include "auditor-database/get_balance.h"
     32 #include "auditor-database/insert_auditor_progress.h"
     33 #include "auditor-database/insert_balance.h"
     34 #include "auditor-database/insert_purse_not_closed_inconsistencies.h"
     35 #include "auditor-database/iterate_purse_not_closed_inconsistencies.h"
     36 #include "auditor-database/preflight.h"
     37 #include "auditor-database/start.h"
     38 
     39 /**
     40  * Currency we use, must match CURRENCY in "test-auditor-db-postgres.conf".
     41  */
     42 #define CURRENCY "EUR"
     43 
     44 /**
     45  * Complain and fail the test if @a cond is true.
     46  */
     47 #define FAILIF(cond)                            \
     48         do {                                    \
     49           if (! (cond)) break;                  \
     50           GNUNET_break (0);                     \
     51           return GNUNET_SYSERR;                 \
     52         } while (0)
     53 
     54 
     55 /**
     56  * Global result from the testcase.
     57  */
     58 static int result = -1;
     59 
     60 /**
     61  * Database connection under test.
     62  */
     63 static struct TALER_AUDITORDB_PostgresContext *pg;
     64 
     65 
     66 /**
     67  * Parse @a str into @a amount, asserting success.
     68  *
     69  * @param str amount to parse, without currency
     70  * @param[out] amount where to write the result
     71  */
     72 static void
     73 amount (const char *str,
     74         struct TALER_Amount *amount)
     75 {
     76   char buf[128];
     77 
     78   GNUNET_snprintf (buf,
     79                    sizeof (buf),
     80                    "%s:%s",
     81                    CURRENCY,
     82                    str);
     83   GNUNET_assert (GNUNET_OK ==
     84                  TALER_string_to_amount (buf,
     85                                          amount));
     86 }
     87 
     88 
     89 /**
     90  * A-1: neither TALER_AUDITORDB_get_balance() nor
     91  * TALER_AUDITORDB_get_auditor_progress() can report "key not on file"
     92  * (auditor_do_get_balance() emits one row per key either way), so an
     93  * auditor helper cannot know whether to insert or to update.  Both
     94  * operations must therefore store the value they are given no matter
     95  * which one the caller picks.
     96  *
     97  * @return #GNUNET_OK on success
     98  */
     99 static enum GNUNET_GenericReturnValue
    100 test_checkpoint_upsert (void)
    101 {
    102   struct TALER_Amount one;
    103   struct TALER_Amount two;
    104   struct TALER_Amount got;
    105   uint64_t off;
    106 
    107   amount ("1",
    108           &one);
    109   amount ("2",
    110           &two);
    111 
    112   /* insert_balance() on a key that does not exist yet must create it. */
    113   FAILIF (0 >
    114           TALER_AUDITORDB_insert_balance (pg,
    115                                           "a1-update-only",
    116                                           &one,
    117                                           NULL));
    118   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    119           TALER_AUDITORDB_get_balance (pg,
    120                                        "a1-update-only",
    121                                        &got,
    122                                        NULL));
    123   FAILIF (0 !=
    124           TALER_amount_cmp (&got,
    125                             &one));
    126 
    127   /* insert_balance() on a key that already exists must overwrite it. */
    128   FAILIF (0 >
    129           TALER_AUDITORDB_insert_balance (pg,
    130                                           "a1-insert-twice",
    131                                           &one,
    132                                           NULL));
    133   FAILIF (0 >
    134           TALER_AUDITORDB_insert_balance (pg,
    135                                           "a1-insert-twice",
    136                                           &two,
    137                                           NULL));
    138   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    139           TALER_AUDITORDB_get_balance (pg,
    140                                        "a1-insert-twice",
    141                                        &got,
    142                                        NULL));
    143   FAILIF (0 !=
    144           TALER_amount_cmp (&got,
    145                             &two));
    146 
    147   /* Same two properties for the progress points. */
    148   FAILIF (0 >
    149           TALER_AUDITORDB_insert_auditor_progress (pg,
    150                                                    "a1-update-only",
    151                                                    42,
    152                                                    NULL));
    153   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    154           TALER_AUDITORDB_get_auditor_progress (pg,
    155                                                 "a1-update-only",
    156                                                 &off,
    157                                                 NULL));
    158   FAILIF (42 != off);
    159 
    160   FAILIF (0 >
    161           TALER_AUDITORDB_insert_auditor_progress (pg,
    162                                                    "a1-insert-twice",
    163                                                    42,
    164                                                    NULL));
    165   FAILIF (0 >
    166           TALER_AUDITORDB_insert_auditor_progress (pg,
    167                                                    "a1-insert-twice",
    168                                                    43,
    169                                                    NULL));
    170   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    171           TALER_AUDITORDB_get_auditor_progress (pg,
    172                                                 "a1-insert-twice",
    173                                                 &off,
    174                                                 NULL));
    175   FAILIF (43 != off);
    176   return GNUNET_OK;
    177 }
    178 
    179 
    180 /**
    181  * Callback for #test_purse_not_closed_dedup(), keeps the last row seen.
    182  *
    183  * @param cls a `struct TALER_AUDITORDB_PurseNotClosedInconsistencies *`
    184  * @param dc the row
    185  * @return #GNUNET_OK
    186  */
    187 static enum GNUNET_GenericReturnValue
    188 purse_not_closed_cb (
    189   void *cls,
    190   const struct TALER_AUDITORDB_PurseNotClosedInconsistencies *dc)
    191 {
    192   struct TALER_AUDITORDB_PurseNotClosedInconsistencies *last = cls;
    193 
    194   *last = *dc;
    195   return GNUNET_OK;
    196 }
    197 
    198 
    199 /**
    200  * A-5: the purse auditor re-reports every expired purse on every round.
    201  * Without dedup on purse_pub the report table grows without bound.
    202  *
    203  * @return #GNUNET_OK on success
    204  */
    205 static enum GNUNET_GenericReturnValue
    206 test_purse_not_closed_dedup (void)
    207 {
    208   struct TALER_AUDITORDB_PurseNotClosedInconsistencies pnc = {
    209     .expiration_date = GNUNET_TIME_absolute_get ()
    210   };
    211   struct TALER_AUDITORDB_PurseNotClosedInconsistencies last = { 0 };
    212   struct TALER_Amount three;
    213 
    214   GNUNET_CRYPTO_random_block (&pnc.purse_pub,
    215                               sizeof (pnc.purse_pub));
    216   amount ("1",
    217           &pnc.amount);
    218   amount ("3",
    219           &three);
    220   for (unsigned int round = 0; round < 3; round++)
    221   {
    222     if (2 == round)
    223       pnc.amount = three;
    224     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    225             TALER_AUDITORDB_insert_purse_not_closed_inconsistencies (pg,
    226                                                                      &pnc));
    227   }
    228   /* Three rounds, one purse, one row -- carrying the latest amount. */
    229   FAILIF (1 !=
    230           TALER_AUDITORDB_iterate_purse_not_closed_inconsistencies (
    231             pg,
    232             1024,
    233             0,
    234             true,
    235             &purse_not_closed_cb,
    236             &last));
    237   FAILIF (0 !=
    238           TALER_amount_cmp (&last.amount,
    239                             &three));
    240   return GNUNET_OK;
    241 }
    242 
    243 
    244 /**
    245  * Main function that will be run by the scheduler.
    246  *
    247  * @param cls closure with the configuration
    248  */
    249 static void
    250 run (void *cls)
    251 {
    252   struct GNUNET_CONFIGURATION_Handle *cfg = cls;
    253 
    254   if (NULL ==
    255       (pg = TALER_AUDITORDB_connect (cfg)))
    256   {
    257     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    258                 "Failed to connect to database\n");
    259     result = 77;
    260     return;
    261   }
    262   GNUNET_assert (GNUNET_OK ==
    263                  TALER_AUDITORDB_preflight (pg));
    264   (void) TALER_AUDITORDB_drop_tables (pg,
    265                                       GNUNET_YES);
    266   if (GNUNET_OK !=
    267       TALER_AUDITORDB_create_tables (pg,
    268                                      false,
    269                                      0))
    270   {
    271     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    272                 "Failed to 'create_tables'\n");
    273     result = 77;
    274     goto unload;
    275   }
    276   if (GNUNET_SYSERR ==
    277       TALER_AUDITORDB_preflight (pg))
    278   {
    279     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    280                 "Failed preflight check\n");
    281     result = 77;
    282     goto drop;
    283   }
    284   if (GNUNET_OK !=
    285       TALER_AUDITORDB_start (pg,
    286                              "test-auditordb-regression"))
    287   {
    288     GNUNET_break (0);
    289     goto drop;
    290   }
    291   if (GNUNET_OK !=
    292       test_checkpoint_upsert ())
    293     goto rollback;
    294   if (GNUNET_OK !=
    295       test_purse_not_closed_dedup ())
    296     goto rollback;
    297   result = 0;
    298   GNUNET_break (0 <=
    299                 TALER_AUDITORDB_commit (pg));
    300   goto drop;
    301 rollback:
    302   TALER_AUDITORDB_rollback (pg);
    303 drop:
    304   GNUNET_break (GNUNET_OK ==
    305                 TALER_AUDITORDB_drop_tables (pg,
    306                                              GNUNET_YES));
    307 unload:
    308   TALER_AUDITORDB_disconnect (pg);
    309   pg = NULL;
    310 }
    311 
    312 
    313 int
    314 main (int argc,
    315       char *const argv[])
    316 {
    317   struct GNUNET_CONFIGURATION_Handle *cfg;
    318 
    319   (void) argc;
    320   result = -1;
    321   GNUNET_log_setup (argv[0],
    322                     "WARNING",
    323                     NULL);
    324   cfg = GNUNET_CONFIGURATION_create (TALER_AUDITOR_project_data ());
    325   if (GNUNET_OK !=
    326       GNUNET_CONFIGURATION_parse (cfg,
    327                                   "test-auditor-db-postgres.conf"))
    328   {
    329     GNUNET_break (0);
    330     return 2;
    331   }
    332   GNUNET_SCHEDULER_run (&run,
    333                         cfg);
    334   GNUNET_CONFIGURATION_destroy (cfg);
    335   return result;
    336 }