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

            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_global_fee.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `global_fee`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_insert_global_fee(),
      23              :  * #TALER_EXCHANGEDB_get_global_fee(),
      24              :  * #TALER_EXCHANGEDB_get_global_fee_by_time() and
      25              :  * #TALER_EXCHANGEDB_iterate_global_fees().
      26              :  *
      27              :  * `global_fee` has no foreign keys and, unlike `wire_fee`, no wire method
      28              :  * to key on -- the validity interval is all there is.  As with the wire
      29              :  * fees, the by-time lookup returns an invalid amount when the range covers
      30              :  * two different fee sets.
      31              :  */
      32              : #include "test_common.h"
      33              : #include "exchange-database/insert_global_fee.h"
      34              : #include "exchange-database/get_global_fee.h"
      35              : #include "exchange-database/get_global_fee_by_time.h"
      36              : #include "exchange-database/iterate_global_fees.h"
      37              : 
      38              : 
      39              : /**
      40              :  * One hour, in seconds.
      41              :  */
      42              : #define HOUR 3600
      43              : 
      44              : 
      45              : /**
      46              :  * Start of the first fee period, in seconds since the epoch.  It has to be
      47              :  * recent: #TALER_EXCHANGEDB_iterate_global_fees() only reports periods
      48              :  * that started within the last year.
      49              :  *
      50              :  * @return an hour ago, rounded down to a whole second
      51              :  */
      52              : static uint64_t
      53           20 : t0 (void)
      54              : {
      55              :   static uint64_t cached;
      56              : 
      57           20 :   if (0 == cached)
      58            1 :     cached = GNUNET_TIME_absolute_get ().abs_value_us
      59            1 :              / (1000LLU * 1000LLU) - HOUR;
      60           20 :   return cached;
      61              : }
      62              : 
      63              : 
      64              : /**
      65              :  * Convenience alias for the start of the first fee period.
      66              :  */
      67              : #define T0 (t0 ())
      68              : 
      69              : 
      70              : /**
      71              :  * Build a timestamp from a number of seconds since the epoch.
      72              :  *
      73              :  * @param secs seconds since the epoch
      74              :  * @return the timestamp
      75              :  */
      76              : static struct GNUNET_TIME_Timestamp
      77           20 : ts (uint64_t secs)
      78              : {
      79           20 :   struct GNUNET_TIME_Absolute abs = {
      80           20 :     .abs_value_us = secs * 1000LLU * 1000LLU
      81              :   };
      82              : 
      83           20 :   return GNUNET_TIME_absolute_to_timestamp (abs);
      84              : }
      85              : 
      86              : 
      87              : /**
      88              :  * Closure for #global_fee_cb().
      89              :  */
      90              : struct FeeContext
      91              : {
      92              :   /**
      93              :    * How many periods did the callback see?
      94              :    */
      95              :   unsigned int total;
      96              : 
      97              :   /**
      98              :    * Sum of the whole-unit parts of the history fees seen.
      99              :    */
     100              :   uint64_t value_sum;
     101              : 
     102              :   /**
     103              :    * Purse account limit of the last period seen.
     104              :    */
     105              :   uint32_t purse_account_limit;
     106              : };
     107              : 
     108              : 
     109              : /**
     110              :  * Callback for #TALER_EXCHANGEDB_iterate_global_fees().
     111              :  *
     112              :  * @param cls a `struct FeeContext *`
     113              :  * @param fees the fees of this period
     114              :  * @param purse_timeout when purses time out
     115              :  * @param history_expiration how long histories are kept
     116              :  * @param purse_account_limit free purses per account
     117              :  * @param start_date start of the period
     118              :  * @param end_date end of the period
     119              :  * @param master_sig signature over the period
     120              :  */
     121              : static void
     122            3 : global_fee_cb (void *cls,
     123              :                const struct TALER_GlobalFeeSet *fees,
     124              :                struct GNUNET_TIME_Relative purse_timeout,
     125              :                struct GNUNET_TIME_Relative history_expiration,
     126              :                uint32_t purse_account_limit,
     127              :                struct GNUNET_TIME_Timestamp start_date,
     128              :                struct GNUNET_TIME_Timestamp end_date,
     129              :                const struct TALER_MasterSignatureP *master_sig)
     130              : {
     131            3 :   struct FeeContext *ctx = cls;
     132              : 
     133              :   (void) purse_timeout;
     134              :   (void) history_expiration;
     135              :   (void) start_date;
     136              :   (void) end_date;
     137              :   (void) master_sig;
     138            3 :   ctx->total++;
     139            3 :   ctx->value_sum += fees->history.value;
     140            3 :   ctx->purse_account_limit = purse_account_limit;
     141            3 : }
     142              : 
     143              : 
     144              : /**
     145              :  * Insert a fee period.
     146              :  *
     147              :  * @param pg the database context
     148              :  * @param from start of the period
     149              :  * @param until end of the period
     150              :  * @param history history fee, e.g. "1"
     151              :  * @param limit free purses per account
     152              :  * @param seed seed for the master signature
     153              :  * @return transaction status
     154              :  */
     155              : static enum GNUNET_DB_QueryStatus
     156            3 : add_fee (struct TALER_EXCHANGEDB_PostgresContext *pg,
     157              :          uint64_t from,
     158              :          uint64_t until,
     159              :          const char *history,
     160              :          uint32_t limit,
     161              :          uint32_t seed)
     162              : {
     163              :   struct TALER_GlobalFeeSet fees;
     164              :   struct TALER_MasterSignatureP master_sig;
     165              : 
     166            3 :   fees.history = TDB_amount (history);
     167            3 :   fees.account = TDB_amount ("0.5");
     168            3 :   fees.purse = TDB_amount ("0.25");
     169            3 :   TDB_FILL (master_sig,
     170              :             seed);
     171            3 :   return TALER_EXCHANGEDB_insert_global_fee (pg,
     172              :                                              ts (from),
     173              :                                              ts (until),
     174              :                                              &fees,
     175              :                                              GNUNET_TIME_UNIT_HOURS,
     176              :                                              GNUNET_TIME_UNIT_DAYS,
     177              :                                              limit,
     178              :                                              &master_sig);
     179              : }
     180              : 
     181              : 
     182              : /**
     183              :  * No fees means no lookups succeed and the iterator sees nothing.
     184              :  *
     185              :  * @param pg the database context
     186              :  * @return 0 on success
     187              :  */
     188              : static int
     189            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     190              : {
     191              :   struct GNUNET_TIME_Timestamp start_date;
     192              :   struct GNUNET_TIME_Timestamp end_date;
     193              :   struct GNUNET_TIME_Relative purse_timeout;
     194              :   struct GNUNET_TIME_Relative history_expiration;
     195              :   struct TALER_GlobalFeeSet fees;
     196              :   struct TALER_MasterSignatureP master_sig;
     197            1 :   struct FeeContext ctx = { 0 };
     198              :   uint32_t purse_account_limit;
     199              : 
     200            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     201              :           TALER_EXCHANGEDB_get_global_fee (pg,
     202              :                                            ts (T0),
     203              :                                            &start_date,
     204              :                                            &end_date,
     205              :                                            &fees,
     206              :                                            &purse_timeout,
     207              :                                            &history_expiration,
     208              :                                            &purse_account_limit,
     209              :                                            &master_sig));
     210            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     211              :           TALER_EXCHANGEDB_get_global_fee_by_time (pg,
     212              :                                                    ts (T0),
     213              :                                                    ts (T0 + HOUR),
     214              :                                                    &fees,
     215              :                                                    &purse_timeout,
     216              :                                                    &history_expiration,
     217              :                                                    &purse_account_limit));
     218            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     219              :           TALER_EXCHANGEDB_iterate_global_fees (pg,
     220              :                                                 &global_fee_cb,
     221              :                                                 &ctx));
     222            1 :   FAILIF (0 != ctx.total);
     223            1 :   return 0;
     224              : }
     225              : 
     226              : 
     227              : /**
     228              :  * A single fee period is found inside its interval and nowhere else.
     229              :  *
     230              :  * @param pg the database context
     231              :  * @return 0 on success
     232              :  */
     233              : static int
     234            1 : check_single_period (struct TALER_EXCHANGEDB_PostgresContext *pg)
     235              : {
     236              :   struct GNUNET_TIME_Timestamp start_date;
     237              :   struct GNUNET_TIME_Timestamp end_date;
     238              :   struct GNUNET_TIME_Relative purse_timeout;
     239              :   struct GNUNET_TIME_Relative history_expiration;
     240              :   struct TALER_GlobalFeeSet fees;
     241            1 :   struct TALER_Amount expect_history = TDB_amount ("1");
     242              :   struct TALER_MasterSignatureP master_sig;
     243              :   struct TALER_MasterSignatureP expect_sig;
     244            1 :   uint32_t purse_account_limit = 0;
     245              : 
     246            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     247              :           add_fee (pg,
     248              :                    T0,
     249              :                    T0 + HOUR,
     250              :                    "1",
     251              :                    4,
     252              :                    20));
     253            1 :   TDB_FILL (expect_sig,
     254              :             20);
     255            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     256              :           TALER_EXCHANGEDB_get_global_fee (pg,
     257              :                                            ts (T0),
     258              :                                            &start_date,
     259              :                                            &end_date,
     260              :                                            &fees,
     261              :                                            &purse_timeout,
     262              :                                            &history_expiration,
     263              :                                            &purse_account_limit,
     264              :                                            &master_sig));
     265            1 :   FAILIF (GNUNET_TIME_timestamp_cmp (start_date,
     266              :                                      !=,
     267              :                                      ts (T0)));
     268            1 :   FAILIF (GNUNET_TIME_timestamp_cmp (end_date,
     269              :                                      !=,
     270              :                                      ts (T0 + HOUR)));
     271            1 :   FAILIF (0 != TALER_amount_cmp (&fees.history,
     272              :                                  &expect_history));
     273            1 :   FAILIF (4 != purse_account_limit);
     274            1 :   FAILIF (GNUNET_TIME_relative_cmp (purse_timeout,
     275              :                                     !=,
     276              :                                     GNUNET_TIME_UNIT_HOURS));
     277            1 :   FAILIF (GNUNET_TIME_relative_cmp (history_expiration,
     278              :                                     !=,
     279              :                                     GNUNET_TIME_UNIT_DAYS));
     280            1 :   FAILIF (0 != GNUNET_memcmp (&master_sig,
     281              :                               &expect_sig));
     282              : 
     283              :   /* the interval is half-open */
     284            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     285              :           TALER_EXCHANGEDB_get_global_fee (pg,
     286              :                                            ts (T0 + HOUR),
     287              :                                            &start_date,
     288              :                                            &end_date,
     289              :                                            &fees,
     290              :                                            &purse_timeout,
     291              :                                            &history_expiration,
     292              :                                            &purse_account_limit,
     293              :                                            &master_sig));
     294            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     295              :           TALER_EXCHANGEDB_get_global_fee (pg,
     296              :                                            ts (T0 - 1),
     297              :                                            &start_date,
     298              :                                            &end_date,
     299              :                                            &fees,
     300              :                                            &purse_timeout,
     301              :                                            &history_expiration,
     302              :                                            &purse_account_limit,
     303              :                                            &master_sig));
     304            1 :   return 0;
     305              : }
     306              : 
     307              : 
     308              : /**
     309              :  * Over a range with one fee set, get_global_fee_by_time() returns it; over
     310              :  * a range spanning two different ones it returns an invalid amount.
     311              :  *
     312              :  * @param pg the database context
     313              :  * @return 0 on success
     314              :  */
     315              : static int
     316            1 : check_by_time (struct TALER_EXCHANGEDB_PostgresContext *pg)
     317              : {
     318              :   struct GNUNET_TIME_Relative purse_timeout;
     319              :   struct GNUNET_TIME_Relative history_expiration;
     320              :   struct TALER_GlobalFeeSet fees;
     321            1 :   struct TALER_Amount expect_history = TDB_amount ("1");
     322              :   struct FeeContext ctx;
     323              :   uint32_t purse_account_limit;
     324              : 
     325            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     326              :           add_fee (pg,
     327              :                    T0 + HOUR,
     328              :                    T0 + 2 * HOUR,
     329              :                    "1",
     330              :                    4,
     331              :                    21));
     332            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     333              :           add_fee (pg,
     334              :                    T0 + 2 * HOUR,
     335              :                    T0 + 3 * HOUR,
     336              :                    "9",
     337              :                    4,
     338              :                    22));
     339              : 
     340              :   /* Two periods that agree: still the fee.  The status is the number of
     341              :      rows the range covered, so it is 2 rather than ONE_RESULT. */
     342            1 :   memset (&fees,
     343              :           0,
     344              :           sizeof (fees));
     345            1 :   FAILIF (2 !=
     346              :           TALER_EXCHANGEDB_get_global_fee_by_time (pg,
     347              :                                                    ts (T0),
     348              :                                                    ts (T0 + 2 * HOUR),
     349              :                                                    &fees,
     350              :                                                    &purse_timeout,
     351              :                                                    &history_expiration,
     352              :                                                    &purse_account_limit));
     353            1 :   FAILIF (0 != TALER_amount_cmp (&fees.history,
     354              :                                  &expect_history));
     355            1 :   FAILIF (4 != purse_account_limit);
     356              : 
     357            1 :   memset (&fees,
     358              :           0,
     359              :           sizeof (fees));
     360            1 :   FAILIF (0 >
     361              :           TALER_EXCHANGEDB_get_global_fee_by_time (pg,
     362              :                                                    ts (T0),
     363              :                                                    ts (T0 + 3 * HOUR),
     364              :                                                    &fees,
     365              :                                                    &purse_timeout,
     366              :                                                    &history_expiration,
     367              :                                                    &purse_account_limit));
     368            1 :   FAILIF (GNUNET_OK ==
     369              :           TALER_amount_is_valid (&fees.history));
     370              : 
     371            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     372              :           TALER_EXCHANGEDB_get_global_fee_by_time (pg,
     373              :                                                    ts (T0 + 10 * HOUR),
     374              :                                                    ts (T0 + 11 * HOUR),
     375              :                                                    &fees,
     376              :                                                    &purse_timeout,
     377              :                                                    &history_expiration,
     378              :                                                    &purse_account_limit));
     379              : 
     380            1 :   memset (&ctx,
     381              :           0,
     382              :           sizeof (ctx));
     383            1 :   FAILIF (0 >=
     384              :           TALER_EXCHANGEDB_iterate_global_fees (pg,
     385              :                                                 &global_fee_cb,
     386              :                                                 &ctx));
     387            1 :   FAILIF (3 != ctx.total);
     388            1 :   FAILIF (1 + 1 + 9 != ctx.value_sum);
     389            1 :   return 0;
     390              : }
     391              : 
     392              : 
     393              : /**
     394              :  * The checks to run, in order.
     395              :  */
     396              : static const struct TDB_Test tests[] = {
     397              :   { "global-fee-empty",
     398              :     &check_empty },
     399              :   { "global-fee-single-period",
     400              :     &check_single_period },
     401              :   { "global-fee-by-time",
     402              :     &check_by_time },
     403              :   { NULL, NULL }
     404              : };
     405              : 
     406              : 
     407              : int
     408            1 : main (int argc,
     409              :       char *const *argv)
     410              : {
     411            1 :   return TDB_main (argc,
     412              :                    argv,
     413              :                    "test-global-fee",
     414              :                    "Tests for the exchangedb `global_fee' table",
     415              :                    tests);
     416              : }
     417              : 
     418              : 
     419              : /* end of test_global_fee.c */
        

Generated by: LCOV version 2.0-1