exchange

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

test_prewire.c (15267B)


      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_prewire.c
     18  * @brief tests for the exchangedb functions whose primary table is `prewire`
     19  * @author Christian Grothoff
     20  *
     21  * Covers #TALER_EXCHANGEDB_insert_prewire(),
     22  * #TALER_EXCHANGEDB_iterate_prewires(),
     23  * #TALER_EXCHANGEDB_update_to_prewire_finished() and
     24  * #TALER_EXCHANGEDB_update_to_prewire_failed().
     25  *
     26  * `prewire` has no foreign keys.  The table is a work queue: the iterator
     27  * only reports rows that are neither finished nor failed, which is what the
     28  * checks below are about.
     29  */
     30 #include "test_common.h"
     31 #include "exchange-database/insert_prewire.h"
     32 #include "exchange-database/iterate_prewires.h"
     33 #include "exchange-database/iterate_prewires_below_open_shards.h"
     34 #include "exchange-database/update_to_prewire_finished.h"
     35 #include "exchange-database/update_to_prewire_failed.h"
     36 
     37 
     38 /**
     39  * Closure for #prewire_cb().
     40  */
     41 struct PrewireContext
     42 {
     43   /**
     44    * How many rows did the callback see?
     45    */
     46   unsigned int total;
     47 
     48   /**
     49    * Row of the first entry seen.
     50    */
     51   uint64_t first_row;
     52 
     53   /**
     54    * Payload of the first entry seen, owned by this struct.
     55    */
     56   char *first_buf;
     57 
     58   /**
     59    * Number of bytes in @e first_buf.
     60    */
     61   size_t first_buf_size;
     62 
     63   /**
     64    * Wire method of the first entry seen, owned by this struct.
     65    */
     66   char *first_method;
     67 };
     68 
     69 
     70 /**
     71  * Callback for #TALER_EXCHANGEDB_iterate_prewires().
     72  *
     73  * @param cls a `struct PrewireContext *`
     74  * @param rowid row of the entry
     75  * @param wire_method wire method of the entry
     76  * @param buf payload of the entry
     77  * @param buf_size number of bytes in @a buf
     78  */
     79 static void
     80 prewire_cb (void *cls,
     81             uint64_t rowid,
     82             const char *wire_method,
     83             const char *buf,
     84             size_t buf_size)
     85 {
     86   struct PrewireContext *ctx = cls;
     87 
     88   if (0 == ctx->total++)
     89   {
     90     ctx->first_row = rowid;
     91     ctx->first_buf = GNUNET_memdup (buf,
     92                                     buf_size);
     93     ctx->first_buf_size = buf_size;
     94     ctx->first_method = GNUNET_strdup (wire_method);
     95   }
     96 }
     97 
     98 
     99 /**
    100  * Release what #prewire_cb() allocated.
    101  *
    102  * @param[in,out] ctx context to clean up
    103  */
    104 static void
    105 ctx_free (struct PrewireContext *ctx)
    106 {
    107   GNUNET_free (ctx->first_buf);
    108   GNUNET_free (ctx->first_method);
    109 }
    110 
    111 
    112 /**
    113  * Closure for #rows_cb().
    114  */
    115 struct RowsContext
    116 {
    117   /**
    118    * Rows seen, in the order the callback saw them.
    119    */
    120   uint64_t rows[8];
    121 
    122   /**
    123    * How many rows did the callback see?
    124    */
    125   unsigned int total;
    126 };
    127 
    128 
    129 static void
    130 rows_cb (void *cls,
    131          uint64_t rowid,
    132          const char *wire_method,
    133          const char *buf,
    134          size_t buf_size)
    135 {
    136   struct RowsContext *rc = cls;
    137 
    138   (void) wire_method;
    139   (void) buf;
    140   (void) buf_size;
    141   if (rc->total < sizeof (rc->rows) / sizeof (rc->rows[0]))
    142     rc->rows[rc->total] = rowid;
    143   rc->total++;
    144 }
    145 
    146 
    147 /**
    148  * An empty queue yields nothing, and marking rows that do not exist does
    149  * nothing.
    150  *
    151  * @param pg the database context
    152  * @return 0 on success
    153  */
    154 static int
    155 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    156 {
    157   struct PrewireContext ctx = { 0 };
    158 
    159   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    160           TALER_EXCHANGEDB_iterate_prewires (pg,
    161                                              0,
    162                                              10,
    163                                              &prewire_cb,
    164                                              &ctx));
    165   FAILIF (0 != ctx.total);
    166   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    167           TALER_EXCHANGEDB_update_to_prewire_finished (pg,
    168                                                        1));
    169   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    170           TALER_EXCHANGEDB_update_to_prewire_failed (pg,
    171                                                      1));
    172   return 0;
    173 }
    174 
    175 
    176 /**
    177  * Inserted rows come back in order, with their payload intact.
    178  *
    179  * @param pg the database context
    180  * @return 0 on success
    181  */
    182 static int
    183 check_insert_and_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
    184 {
    185   static const char buf1[] = "first-transfer";
    186   static const char buf2[] = "second-transfer";
    187   struct PrewireContext ctx;
    188   uint64_t row1;
    189 
    190   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    191           TALER_EXCHANGEDB_insert_prewire (pg,
    192                                            "x-taler-bank",
    193                                            buf1,
    194                                            sizeof (buf1)));
    195   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    196           TALER_EXCHANGEDB_insert_prewire (pg,
    197                                            "iban",
    198                                            buf2,
    199                                            sizeof (buf2)));
    200   memset (&ctx,
    201           0,
    202           sizeof (ctx));
    203   FAILIF (0 >=
    204           TALER_EXCHANGEDB_iterate_prewires (pg,
    205                                              0,
    206                                              10,
    207                                              &prewire_cb,
    208                                              &ctx));
    209   FAILIF_C (2 != ctx.total,
    210             ctx_free (&ctx));
    211   FAILIF_C (sizeof (buf1) != ctx.first_buf_size,
    212             ctx_free (&ctx));
    213   FAILIF_C (0 != memcmp (ctx.first_buf,
    214                          buf1,
    215                          sizeof (buf1)),
    216             ctx_free (&ctx));
    217   FAILIF_C (0 != strcmp (ctx.first_method,
    218                          "x-taler-bank"),
    219             ctx_free (&ctx));
    220   row1 = ctx.first_row;
    221   ctx_free (&ctx);
    222 
    223   /* the limit is honoured */
    224   memset (&ctx,
    225           0,
    226           sizeof (ctx));
    227   FAILIF (0 >=
    228           TALER_EXCHANGEDB_iterate_prewires (pg,
    229                                              0,
    230                                              1,
    231                                              &prewire_cb,
    232                                              &ctx));
    233   FAILIF_C (1 != ctx.total,
    234             ctx_free (&ctx));
    235   ctx_free (&ctx);
    236 
    237   /* ...and so is the starting row */
    238   memset (&ctx,
    239           0,
    240           sizeof (ctx));
    241   FAILIF (0 >=
    242           TALER_EXCHANGEDB_iterate_prewires (pg,
    243                                              row1 + 1,
    244                                              10,
    245                                              &prewire_cb,
    246                                              &ctx));
    247   FAILIF_C (1 != ctx.total,
    248             ctx_free (&ctx));
    249   FAILIF_C (0 != strcmp (ctx.first_method,
    250                          "iban"),
    251             ctx_free (&ctx));
    252   ctx_free (&ctx);
    253   return 0;
    254 }
    255 
    256 
    257 /**
    258  * Finished and failed rows drop out of the queue.
    259  *
    260  * @param pg the database context
    261  * @return 0 on success
    262  */
    263 static int
    264 check_finished_and_failed (struct TALER_EXCHANGEDB_PostgresContext *pg)
    265 {
    266   struct PrewireContext ctx;
    267   uint64_t row1;
    268   uint64_t row2;
    269 
    270   memset (&ctx,
    271           0,
    272           sizeof (ctx));
    273   FAILIF (0 >=
    274           TALER_EXCHANGEDB_iterate_prewires (pg,
    275                                              0,
    276                                              10,
    277                                              &prewire_cb,
    278                                              &ctx));
    279   FAILIF_C (2 != ctx.total,
    280             ctx_free (&ctx));
    281   row1 = ctx.first_row;
    282   ctx_free (&ctx);
    283 
    284   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    285           TALER_EXCHANGEDB_update_to_prewire_finished (pg,
    286                                                        row1));
    287   memset (&ctx,
    288           0,
    289           sizeof (ctx));
    290   FAILIF (0 >=
    291           TALER_EXCHANGEDB_iterate_prewires (pg,
    292                                              0,
    293                                              10,
    294                                              &prewire_cb,
    295                                              &ctx));
    296   FAILIF_C (1 != ctx.total,
    297             ctx_free (&ctx));
    298   row2 = ctx.first_row;
    299   ctx_free (&ctx);
    300   FAILIF (row2 == row1);
    301 
    302   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    303           TALER_EXCHANGEDB_update_to_prewire_failed (pg,
    304                                                      row2));
    305   memset (&ctx,
    306           0,
    307           sizeof (ctx));
    308   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    309           TALER_EXCHANGEDB_iterate_prewires (pg,
    310                                              0,
    311                                              10,
    312                                              &prewire_cb,
    313                                              &ctx));
    314   FAILIF_C (0 != ctx.total,
    315             ctx_free (&ctx));
    316   ctx_free (&ctx);
    317 
    318   /* both rows are still on file, one finished and one failed */
    319   FAILIF (2 != TDB_count (pg,
    320                           "FROM prewire"));
    321   FAILIF (1 != TDB_count (pg,
    322                           "FROM prewire WHERE finished"));
    323   FAILIF (1 != TDB_count (pg,
    324                           "FROM prewire WHERE failed"));
    325   /* marking again is still reported as a row touched */
    326   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    327           TALER_EXCHANGEDB_update_to_prewire_finished (pg,
    328                                                        row1));
    329   return 0;
    330 }
    331 
    332 
    333 /**
    334  * Unfinished rows below every open shard of the job are found; rows in
    335  * or above an open shard, finished and failed rows, and the shards of
    336  * other jobs do not count.
    337  *
    338  * @param pg the database context
    339  * @return 0 on success
    340  */
    341 static int
    342 check_below_open_shards (struct TALER_EXCHANGEDB_PostgresContext *pg)
    343 {
    344   static const char buf[] = "late-transfer";
    345   struct RowsContext rc;
    346   uint64_t r[4];
    347 
    348   for (unsigned int i = 0; i < 4; i++)
    349     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    350             TALER_EXCHANGEDB_insert_prewire (pg,
    351                                              "iban",
    352                                              buf,
    353                                              sizeof (buf)));
    354   /* the rows of the earlier checks are finished or failed */
    355   memset (&rc,
    356           0,
    357           sizeof (rc));
    358   FAILIF (0 >=
    359           TALER_EXCHANGEDB_iterate_prewires (pg,
    360                                              0,
    361                                              10,
    362                                              &rows_cb,
    363                                              &rc));
    364   FAILIF (4 != rc.total);
    365   memcpy (r,
    366           rc.rows,
    367           sizeof (r));
    368 
    369   /* without shards, nothing is below them */
    370   memset (&rc,
    371           0,
    372           sizeof (rc));
    373   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    374           TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
    375                                                                "transfer",
    376                                                                10,
    377                                                                &rows_cb,
    378                                                                &rc));
    379   FAILIF (0 != rc.total);
    380 
    381   /* r[0] and r[1] lie in a completed shard, r[2] and r[3] in an open
    382      one; an open shard of another job covers all of them */
    383   FAILIF (GNUNET_OK !=
    384           TDB_exec (pg,
    385                     "INSERT INTO work_shards"
    386                     " (job_name,last_attempt,start_row,end_row"
    387                     " ,completed,progress_row)"
    388                     " VALUES"
    389                     " ('transfer',0,0,%llu,TRUE,%llu)"
    390                     ",('transfer',0,%llu,%llu,FALSE,%llu)"
    391                     ",('other',0,0,%llu,FALSE,0);",
    392                     (unsigned long long) r[2],
    393                     (unsigned long long) r[2],
    394                     (unsigned long long) r[2],
    395                     (unsigned long long) (r[3] + 10),
    396                     (unsigned long long) (r[3] + 1),
    397                     (unsigned long long) (r[3] + 10)));
    398   memset (&rc,
    399           0,
    400           sizeof (rc));
    401   FAILIF (0 >=
    402           TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
    403                                                                "transfer",
    404                                                                10,
    405                                                                &rows_cb,
    406                                                                &rc));
    407   FAILIF (2 != rc.total);
    408   FAILIF (r[0] != rc.rows[0]);
    409   FAILIF (r[1] != rc.rows[1]);
    410 
    411   /* the limit is honoured */
    412   memset (&rc,
    413           0,
    414           sizeof (rc));
    415   FAILIF (0 >=
    416           TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
    417                                                                "transfer",
    418                                                                1,
    419                                                                &rows_cb,
    420                                                                &rc));
    421   FAILIF (1 != rc.total);
    422   FAILIF (r[0] != rc.rows[0]);
    423 
    424   /* finished rows drop out */
    425   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    426           TALER_EXCHANGEDB_update_to_prewire_finished (pg,
    427                                                        r[0]));
    428   memset (&rc,
    429           0,
    430           sizeof (rc));
    431   FAILIF (0 >=
    432           TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
    433                                                                "transfer",
    434                                                                10,
    435                                                                &rows_cb,
    436                                                                &rc));
    437   FAILIF (1 != rc.total);
    438   FAILIF (r[1] != rc.rows[0]);
    439 
    440   /* with no shard of the job open, everything below the highest shard's
    441      end counts */
    442   FAILIF (GNUNET_OK !=
    443           TDB_exec (pg,
    444                     "UPDATE work_shards"
    445                     " SET completed=TRUE"
    446                     "    ,progress_row=end_row"
    447                     " WHERE job_name='transfer';"));
    448   memset (&rc,
    449           0,
    450           sizeof (rc));
    451   FAILIF (0 >=
    452           TALER_EXCHANGEDB_iterate_prewires_below_open_shards (pg,
    453                                                                "transfer",
    454                                                                10,
    455                                                                &rows_cb,
    456                                                                &rc));
    457   FAILIF (3 != rc.total);
    458   FAILIF (r[1] != rc.rows[0]);
    459   FAILIF (r[2] != rc.rows[1]);
    460   FAILIF (r[3] != rc.rows[2]);
    461   return 0;
    462 }
    463 
    464 
    465 /**
    466  * The checks to run, in order.
    467  */
    468 static const struct TDB_Test tests[] = {
    469   { "prewire-empty",
    470     &check_empty },
    471   { "prewire-insert-and-iterate",
    472     &check_insert_and_iterate },
    473   { "prewire-finished-and-failed",
    474     &check_finished_and_failed },
    475   { "prewire-below-open-shards",
    476     &check_below_open_shards },
    477   { NULL, NULL }
    478 };
    479 
    480 
    481 int
    482 main (int argc,
    483       char *const *argv)
    484 {
    485   return TDB_main (argc,
    486                    argv,
    487                    "test-prewire",
    488                    "Tests for the exchangedb `prewire' table",
    489                    tests);
    490 }
    491 
    492 
    493 /* end of test_prewire.c */