LCOV - code coverage report
Current view: top level - exchangedb - test_purse_requests.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 100.0 % 81 81
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_purse_requests.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `purse_requests`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_insert_purse_request(),
      23              :  * #TALER_EXCHANGEDB_get_purse_request(),
      24              :  * #TALER_EXCHANGEDB_get_purse_by_merge_pub(),
      25              :  * #TALER_EXCHANGEDB_get_purse() and
      26              :  * #TALER_EXCHANGEDB_iterate_purse_requests_above_serial_id().
      27              :  *
      28              :  * `purse_requests` has no foreign keys -- a purse is created before
      29              :  * anything is paid into it.  The interesting case of the insert is the
      30              :  * conflict: re-creating a purse with the same key but different meta data
      31              :  * is reported through @e in_conflict rather than as an error.
      32              :  */
      33              : #include "test_common.h"
      34              : #include "exchange-database/get_purse.h"
      35              : #include "exchange-database/get_purse_by_merge_pub.h"
      36              : #include "exchange-database/get_purse_request.h"
      37              : #include "exchange-database/insert_purse_request.h"
      38              : #include "exchange-database/iterate_purse_requests_above_serial_id.h"
      39              : 
      40              : 
      41              : /**
      42              :  * Build a timestamp from a number of seconds since the epoch.
      43              :  *
      44              :  * @param secs seconds since the epoch
      45              :  * @return the timestamp
      46              :  */
      47              : static struct GNUNET_TIME_Timestamp
      48            3 : ts (uint64_t secs)
      49              : {
      50            3 :   struct GNUNET_TIME_Absolute abs = {
      51            3 :     .abs_value_us = secs * 1000LLU * 1000LLU
      52              :   };
      53              : 
      54            3 :   return GNUNET_TIME_absolute_to_timestamp (abs);
      55              : }
      56              : 
      57              : 
      58              : /**
      59              :  * Closure for #purse_cb().
      60              :  */
      61              : struct PurseContext
      62              : {
      63              :   /**
      64              :    * How many rows did the callback see?
      65              :    */
      66              :   unsigned int total;
      67              : 
      68              :   /**
      69              :    * Stop after this many rows; 0 for no limit.
      70              :    */
      71              :   unsigned int stop_after;
      72              : 
      73              :   /**
      74              :    * Purse we are looking for, NULL to match nothing.
      75              :    */
      76              :   const struct TALER_PurseContractPublicKeyP *purse_pub;
      77              : 
      78              :   /**
      79              :    * How many times did we see it?
      80              :    */
      81              :   unsigned int matched;
      82              : 
      83              :   /**
      84              :    * Target amount reported for it.
      85              :    */
      86              :   struct TALER_Amount amount;
      87              : 
      88              :   /**
      89              :    * Age limit reported for it.
      90              :    */
      91              :   uint32_t age_limit;
      92              : };
      93              : 
      94              : 
      95              : /**
      96              :  * Callback for #TALER_EXCHANGEDB_iterate_purse_requests_above_serial_id().
      97              :  *
      98              :  * @param cls a `struct PurseContext *`
      99              :  * @param rowid row of the purse
     100              :  * @param purse_pub the purse
     101              :  * @param merge_pub its merge capability key
     102              :  * @param purse_creation when it was created
     103              :  * @param purse_expiration when it expires
     104              :  * @param h_contract_terms contract it is for
     105              :  * @param age_limit age limit on deposits into it
     106              :  * @param target_amount how much it is meant to hold
     107              :  * @param purse_sig signature over its meta data
     108              :  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
     109              :  */
     110              : static enum GNUNET_GenericReturnValue
     111            3 : purse_cb (void *cls,
     112              :           uint64_t rowid,
     113              :           const struct TALER_PurseContractPublicKeyP *purse_pub,
     114              :           const struct TALER_PurseMergePublicKeyP *merge_pub,
     115              :           struct GNUNET_TIME_Timestamp purse_creation,
     116              :           struct GNUNET_TIME_Timestamp purse_expiration,
     117              :           const struct TALER_PrivateContractHashP *h_contract_terms,
     118              :           uint32_t age_limit,
     119              :           const struct TALER_Amount *target_amount,
     120              :           const struct TALER_PurseContractSignatureP *purse_sig)
     121              : {
     122            3 :   struct PurseContext *ctx = cls;
     123              : 
     124              :   (void) rowid;
     125              :   (void) merge_pub;
     126              :   (void) purse_creation;
     127              :   (void) purse_expiration;
     128              :   (void) h_contract_terms;
     129              :   (void) purse_sig;
     130            3 :   ctx->total++;
     131            3 :   if ( (NULL != ctx->purse_pub) &&
     132            2 :        (0 == GNUNET_memcmp (purse_pub,
     133              :                             ctx->purse_pub)) )
     134              :   {
     135            1 :     ctx->matched++;
     136            1 :     ctx->amount = *target_amount;
     137            1 :     ctx->age_limit = age_limit;
     138              :   }
     139            3 :   if ( (0 != ctx->stop_after) &&
     140            1 :        (ctx->total >= ctx->stop_after) )
     141            1 :     return GNUNET_SYSERR;
     142            2 :   return GNUNET_OK;
     143              : }
     144              : 
     145              : 
     146              : /**
     147              :  * Nothing is known while the table is empty.
     148              :  *
     149              :  * @param pg the database context
     150              :  * @return 0 on success
     151              :  */
     152              : static int
     153            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     154              : {
     155              :   struct TALER_PurseContractPublicKeyP purse_pub;
     156              :   struct TALER_PurseMergePublicKeyP merge_pub;
     157              :   struct GNUNET_TIME_Timestamp purse_expiration;
     158              :   struct GNUNET_TIME_Timestamp purse_creation;
     159              :   struct GNUNET_TIME_Timestamp merge_timestamp;
     160              :   struct TALER_PrivateContractHashP h_contract_terms;
     161              :   struct TALER_Amount target_amount;
     162              :   struct TALER_Amount balance;
     163              :   struct TALER_PurseContractSignatureP purse_sig;
     164            1 :   struct PurseContext ctx = { 0 };
     165              :   uint32_t age_limit;
     166              :   bool purse_deleted;
     167              :   bool purse_refunded;
     168              : 
     169            1 :   TDB_FILL (purse_pub,
     170              :             1);
     171            1 :   TDB_FILL (merge_pub,
     172              :             1);
     173            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     174              :           TALER_EXCHANGEDB_get_purse_request (pg,
     175              :                                               &purse_pub,
     176              :                                               &merge_pub,
     177              :                                               &purse_expiration,
     178              :                                               &h_contract_terms,
     179              :                                               &age_limit,
     180              :                                               &target_amount,
     181              :                                               &balance,
     182              :                                               &purse_sig));
     183            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     184              :           TALER_EXCHANGEDB_get_purse_by_merge_pub (pg,
     185              :                                                    &merge_pub,
     186              :                                                    &purse_pub,
     187              :                                                    &purse_expiration,
     188              :                                                    &h_contract_terms,
     189              :                                                    &age_limit,
     190              :                                                    &target_amount,
     191              :                                                    &balance,
     192              :                                                    &purse_sig));
     193            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     194              :           TALER_EXCHANGEDB_get_purse (pg,
     195              :                                       &purse_pub,
     196              :                                       &purse_creation,
     197              :                                       &purse_expiration,
     198              :                                       &target_amount,
     199              :                                       &balance,
     200              :                                       &h_contract_terms,
     201              :                                       &merge_timestamp,
     202              :                                       &purse_deleted,
     203              :                                       &purse_refunded));
     204            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     205              :           TALER_EXCHANGEDB_iterate_purse_requests_above_serial_id (
     206              :             pg,
     207              :             0,
     208              :             &purse_cb,
     209              :             &ctx));
     210            1 :   FAILIF (0 != ctx.total);
     211            1 :   return 0;
     212              : }
     213              : 
     214              : 
     215              : /**
     216              :  * A created purse is found by its own key, by its merge key and by the
     217              :  * combined status lookup.
     218              :  *
     219              :  * @param pg the database context
     220              :  * @return 0 on success
     221              :  */
     222              : static int
     223            1 : check_create_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg)
     224              : {
     225              :   struct TDB_Purse purse;
     226              :   struct TALER_PurseContractPublicKeyP got_pub;
     227              :   struct TALER_PurseMergePublicKeyP got_merge;
     228              :   struct GNUNET_TIME_Timestamp purse_expiration;
     229              :   struct GNUNET_TIME_Timestamp purse_creation;
     230              :   struct GNUNET_TIME_Timestamp merge_timestamp;
     231              :   struct TALER_PrivateContractHashP h_contract_terms;
     232              :   struct TALER_Amount target_amount;
     233              :   struct TALER_Amount balance;
     234            1 :   struct TALER_Amount zero = TDB_amount ("0");
     235              :   struct TALER_PurseContractSignatureP purse_sig;
     236              :   uint32_t age_limit;
     237            1 :   bool purse_deleted = true;
     238            1 :   bool purse_refunded = true;
     239              : 
     240            1 :   TDB_purse (pg,
     241              :              10,
     242              :              "5",
     243              :              ts (1700000000),
     244              :              &purse);
     245            1 :   FAILIF (1 != TDB_count (pg,
     246              :                           "FROM purse_requests"));
     247              : 
     248            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     249              :           TALER_EXCHANGEDB_get_purse_request (pg,
     250              :                                               &purse.purse_pub,
     251              :                                               &got_merge,
     252              :                                               &purse_expiration,
     253              :                                               &h_contract_terms,
     254              :                                               &age_limit,
     255              :                                               &target_amount,
     256              :                                               &balance,
     257              :                                               &purse_sig));
     258            1 :   FAILIF (0 != GNUNET_memcmp (&got_merge,
     259              :                               &purse.merge_pub));
     260            1 :   FAILIF (0 != GNUNET_memcmp (&h_contract_terms,
     261              :                               &purse.h_contract_terms));
     262            1 :   FAILIF (0 != GNUNET_memcmp (&purse_sig,
     263              :                               &purse.purse_sig));
     264            1 :   FAILIF (GNUNET_TIME_timestamp_cmp (purse_expiration,
     265              :                                      !=,
     266              :                                      purse.purse_expiration));
     267            1 :   FAILIF (0 != TALER_amount_cmp (&target_amount,
     268              :                                  &purse.amount));
     269              :   /* nothing has been paid in yet */
     270            1 :   FAILIF (0 != TALER_amount_cmp (&balance,
     271              :                                  &zero));
     272            1 :   FAILIF (0 != age_limit);
     273              : 
     274            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     275              :           TALER_EXCHANGEDB_get_purse_by_merge_pub (pg,
     276              :                                                    &purse.merge_pub,
     277              :                                                    &got_pub,
     278              :                                                    &purse_expiration,
     279              :                                                    &h_contract_terms,
     280              :                                                    &age_limit,
     281              :                                                    &target_amount,
     282              :                                                    &balance,
     283              :                                                    &purse_sig));
     284            1 :   FAILIF (0 != GNUNET_memcmp (&got_pub,
     285              :                               &purse.purse_pub));
     286              : 
     287            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     288              :           TALER_EXCHANGEDB_get_purse (pg,
     289              :                                       &purse.purse_pub,
     290              :                                       &purse_creation,
     291              :                                       &purse_expiration,
     292              :                                       &target_amount,
     293              :                                       &balance,
     294              :                                       &h_contract_terms,
     295              :                                       &merge_timestamp,
     296              :                                       &purse_deleted,
     297              :                                       &purse_refunded));
     298            1 :   FAILIF (purse_deleted);
     299            1 :   FAILIF (purse_refunded);
     300            1 :   FAILIF (0 != TALER_amount_cmp (&balance,
     301              :                                  &zero));
     302              :   /* the purse has not been merged */
     303            1 :   FAILIF (! GNUNET_TIME_absolute_is_never (merge_timestamp.abs_time));
     304              : 
     305              :   /* a merge key nobody used finds nothing */
     306              :   {
     307              :     struct TALER_PurseMergePublicKeyP other;
     308              : 
     309            1 :     TDB_FILL (other,
     310              :               99);
     311            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     312              :             TALER_EXCHANGEDB_get_purse_by_merge_pub (pg,
     313              :                                                      &other,
     314              :                                                      &got_pub,
     315              :                                                      &purse_expiration,
     316              :                                                      &h_contract_terms,
     317              :                                                      &age_limit,
     318              :                                                      &target_amount,
     319              :                                                      &balance,
     320              :                                                      &purse_sig));
     321              :   }
     322            1 :   return 0;
     323              : }
     324              : 
     325              : 
     326              : /**
     327              :  * Re-creating a purse with the same key is idempotent if the meta data
     328              :  * matches and a conflict if it does not.
     329              :  *
     330              :  * @param pg the database context
     331              :  * @return 0 on success
     332              :  */
     333              : static int
     334            1 : check_conflict (struct TALER_EXCHANGEDB_PostgresContext *pg)
     335              : {
     336              :   struct TDB_Purse purse;
     337            1 :   struct TALER_Amount purse_fee = TDB_amount ("0");
     338            1 :   struct TALER_Amount other_amount = TDB_amount ("9");
     339            1 :   bool in_conflict = false;
     340              : 
     341              :   /* TDB_purse() with the same seed replays the identical request */
     342            1 :   TDB_purse (pg,
     343              :              10,
     344              :              "5",
     345              :              ts (1700000000),
     346              :              &purse);
     347            1 :   FAILIF (1 != TDB_count (pg,
     348              :                           "FROM purse_requests"));
     349              :   /* An identical replay is reported as "nothing to do".  Note that the
     350              :      function leaves @e in_conflict untouched on that path, so only the
     351              :      status says so. */
     352            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     353              :           TALER_EXCHANGEDB_insert_purse_request (
     354              :             pg,
     355              :             &purse.purse_pub,
     356              :             &purse.merge_pub,
     357              :             purse.purse_expiration,
     358              :             &purse.h_contract_terms,
     359              :             0,
     360              :             TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA,
     361              :             &purse_fee,
     362              :             &purse.amount,
     363              :             &purse.purse_sig,
     364              :             &in_conflict));
     365            1 :   FAILIF (in_conflict);
     366              : 
     367              :   /* the same purse key with a different target amount is a conflict */
     368            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     369              :           TALER_EXCHANGEDB_insert_purse_request (
     370              :             pg,
     371              :             &purse.purse_pub,
     372              :             &purse.merge_pub,
     373              :             purse.purse_expiration,
     374              :             &purse.h_contract_terms,
     375              :             0,
     376              :             TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA,
     377              :             &purse_fee,
     378              :             &other_amount,
     379              :             &purse.purse_sig,
     380              :             &in_conflict));
     381            1 :   FAILIF (! in_conflict);
     382            1 :   FAILIF (1 != TDB_count (pg,
     383              :                           "FROM purse_requests"));
     384            1 :   return 0;
     385              : }
     386              : 
     387              : 
     388              : /**
     389              :  * The iterator walks the table by serial and honours an aborting
     390              :  * callback.
     391              :  *
     392              :  * @param pg the database context
     393              :  * @return 0 on success
     394              :  */
     395              : static int
     396            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     397              : {
     398              :   struct TDB_Purse p2;
     399              :   struct PurseContext ctx;
     400              : 
     401            1 :   TDB_purse (pg,
     402              :              11,
     403              :              "3",
     404              :              ts (1700003600),
     405              :              &p2);
     406            1 :   FAILIF (2 != TDB_count (pg,
     407              :                           "FROM purse_requests"));
     408            1 :   memset (&ctx,
     409              :           0,
     410              :           sizeof (ctx));
     411            1 :   ctx.purse_pub = &p2.purse_pub;
     412            1 :   FAILIF (2 !=
     413              :           TALER_EXCHANGEDB_iterate_purse_requests_above_serial_id (
     414              :             pg,
     415              :             0,
     416              :             &purse_cb,
     417              :             &ctx));
     418            1 :   FAILIF (1 != ctx.matched);
     419            1 :   FAILIF (0 != TALER_amount_cmp (&ctx.amount,
     420              :                                  &p2.amount));
     421            1 :   FAILIF (0 != ctx.age_limit);
     422              : 
     423            1 :   memset (&ctx,
     424              :           0,
     425              :           sizeof (ctx));
     426            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     427              :           TALER_EXCHANGEDB_iterate_purse_requests_above_serial_id (
     428              :             pg,
     429              :             1000,
     430              :             &purse_cb,
     431              :             &ctx));
     432            1 :   FAILIF (0 != ctx.total);
     433              : 
     434            1 :   memset (&ctx,
     435              :           0,
     436              :           sizeof (ctx));
     437            1 :   ctx.stop_after = 1;
     438            1 :   FAILIF (2 !=
     439              :           TALER_EXCHANGEDB_iterate_purse_requests_above_serial_id (
     440              :             pg,
     441              :             0,
     442              :             &purse_cb,
     443              :             &ctx));
     444            1 :   FAILIF (1 != ctx.total);
     445            1 :   return 0;
     446              : }
     447              : 
     448              : 
     449              : /**
     450              :  * The checks to run, in order.
     451              :  */
     452              : static const struct TDB_Test tests[] = {
     453              :   { "purse-requests-empty",
     454              :     &check_empty },
     455              :   { "purse-requests-create-and-lookup",
     456              :     &check_create_and_lookup },
     457              :   { "purse-requests-conflict",
     458              :     &check_conflict },
     459              :   { "purse-requests-iterate",
     460              :     &check_iterate },
     461              :   { NULL, NULL }
     462              : };
     463              : 
     464              : 
     465              : int
     466            1 : main (int argc,
     467              :       char *const *argv)
     468              : {
     469            1 :   return TDB_main (argc,
     470              :                    argv,
     471              :                    "test-purse-requests",
     472              :                    "Tests for the exchangedb `purse_requests' table",
     473              :                    tests);
     474              : }
     475              : 
     476              : 
     477              : /* end of test_purse_requests.c */
        

Generated by: LCOV version 2.0-1