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

Generated by: LCOV version 2.0-1