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

            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_refunds.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `refunds`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_do_refund(), #TALER_EXCHANGEDB_insert_refund(),
      23              :  * #TALER_EXCHANGEDB_iterate_refunds_above_serial_id() and
      24              :  * #TALER_EXCHANGEDB_iterate_refunds_by_coin().
      25              :  *
      26              :  * `refunds` references `coin_deposits`, so every check deposits a coin
      27              :  * first with TDB_deposit().  do_refund() has three ways of declining:
      28              :  * no such deposit, the merchant was already paid, and a reused refund
      29              :  * transaction id.
      30              :  */
      31              : #include "test_common.h"
      32              : #include "exchange-database/do_refund.h"
      33              : #include "exchange-database/insert_refund.h"
      34              : #include "exchange-database/iterate_refunds_above_serial_id.h"
      35              : #include "exchange-database/iterate_refunds_by_coin.h"
      36              : #include "exchange-database/rollback.h"
      37              : #include "exchange-database/start.h"
      38              : 
      39              : 
      40              : /**
      41              :  * Account the merchants of the checks are paid at.
      42              :  */
      43              : static struct TDB_Account account;
      44              : 
      45              : 
      46              : /**
      47              :  * Denomination the checks deposit.
      48              :  */
      49              : static struct TDB_Denom denom;
      50              : 
      51              : 
      52              : /**
      53              :  * Build a timestamp from a number of seconds since the epoch.
      54              :  *
      55              :  * @param secs seconds since the epoch
      56              :  * @return the timestamp
      57              :  */
      58              : static struct GNUNET_TIME_Timestamp
      59            6 : ts (uint64_t secs)
      60              : {
      61            6 :   struct GNUNET_TIME_Absolute abs = {
      62            6 :     .abs_value_us = secs * 1000LLU * 1000LLU
      63              :   };
      64              : 
      65            6 :   return GNUNET_TIME_absolute_to_timestamp (abs);
      66              : }
      67              : 
      68              : 
      69              : /**
      70              :  * Closure for #refund_cb().
      71              :  */
      72              : struct RefundContext
      73              : {
      74              :   /**
      75              :    * How many rows did the callback see?
      76              :    */
      77              :   unsigned int total;
      78              : 
      79              :   /**
      80              :    * Stop after this many rows; 0 for no limit.
      81              :    */
      82              :   unsigned int stop_after;
      83              : 
      84              :   /**
      85              :    * Coin we are looking for, NULL to match nothing.
      86              :    */
      87              :   const struct TALER_CoinSpendPublicKeyP *coin_pub;
      88              : 
      89              :   /**
      90              :    * How many times did we see it?
      91              :    */
      92              :   unsigned int matched;
      93              : 
      94              :   /**
      95              :    * Amount reported for it.
      96              :    */
      97              :   struct TALER_Amount amount;
      98              : 
      99              :   /**
     100              :    * Refund transaction id reported for it.
     101              :    */
     102              :   uint64_t rtransaction_id;
     103              : 
     104              :   /**
     105              :    * Was the deposit fully refunded?
     106              :    */
     107              :   bool full_refund;
     108              : };
     109              : 
     110              : 
     111              : /**
     112              :  * Callback for #TALER_EXCHANGEDB_iterate_refunds_above_serial_id().
     113              :  *
     114              :  * @param cls a `struct RefundContext *`
     115              :  * @param rowid row of the refund
     116              :  * @param denom_pub denomination of the coin
     117              :  * @param coin_pub the refunded coin
     118              :  * @param merchant_pub merchant that refunded
     119              :  * @param merchant_sig signature over the refund
     120              :  * @param h_contract_terms contract the refund is for
     121              :  * @param rtransaction_id merchant's refund transaction id
     122              :  * @param full_refund whether the deposit is now fully refunded
     123              :  * @param amount_with_fee how much was refunded
     124              :  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
     125              :  */
     126              : static enum GNUNET_GenericReturnValue
     127            5 : refund_cb (void *cls,
     128              :            uint64_t rowid,
     129              :            const struct TALER_DenominationPublicKey *denom_pub,
     130              :            const struct TALER_CoinSpendPublicKeyP *coin_pub,
     131              :            const struct TALER_MerchantPublicKeyP *merchant_pub,
     132              :            const struct TALER_MerchantSignatureP *merchant_sig,
     133              :            const struct TALER_PrivateContractHashP *h_contract_terms,
     134              :            uint64_t rtransaction_id,
     135              :            bool full_refund,
     136              :            const struct TALER_Amount *amount_with_fee)
     137              : {
     138            5 :   struct RefundContext *ctx = cls;
     139              : 
     140              :   (void) rowid;
     141              :   (void) denom_pub;
     142              :   (void) merchant_pub;
     143              :   (void) merchant_sig;
     144              :   (void) h_contract_terms;
     145            5 :   ctx->total++;
     146            5 :   if ( (NULL != ctx->coin_pub) &&
     147            1 :        (0 == GNUNET_memcmp (coin_pub,
     148              :                             ctx->coin_pub)) )
     149              :   {
     150            1 :     ctx->matched++;
     151            1 :     ctx->amount = *amount_with_fee;
     152            1 :     ctx->rtransaction_id = rtransaction_id;
     153            1 :     ctx->full_refund = full_refund;
     154              :   }
     155            5 :   if ( (0 != ctx->stop_after) &&
     156            1 :        (ctx->total >= ctx->stop_after) )
     157            1 :     return GNUNET_SYSERR;
     158            4 :   return GNUNET_OK;
     159              : }
     160              : 
     161              : 
     162              : /**
     163              :  * Closure for #coin_refund_cb().
     164              :  */
     165              : struct CoinRefundContext
     166              : {
     167              :   /**
     168              :    * How many refunds did the callback see?
     169              :    */
     170              :   unsigned int total;
     171              : 
     172              :   /**
     173              :    * Sum of the whole-unit parts of the amounts seen.
     174              :    */
     175              :   uint64_t value_sum;
     176              : };
     177              : 
     178              : 
     179              : /**
     180              :  * Callback for #TALER_EXCHANGEDB_iterate_refunds_by_coin().
     181              :  *
     182              :  * @param cls a `struct CoinRefundContext *`
     183              :  * @param amount_with_fee how much was refunded
     184              :  * @return #GNUNET_OK
     185              :  */
     186              : static enum GNUNET_GenericReturnValue
     187            4 : coin_refund_cb (void *cls,
     188              :                 const struct TALER_Amount *amount_with_fee)
     189              : {
     190            4 :   struct CoinRefundContext *ctx = cls;
     191              : 
     192            4 :   ctx->total++;
     193            4 :   ctx->value_sum += amount_with_fee->value;
     194            4 :   return GNUNET_OK;
     195              : }
     196              : 
     197              : 
     198              : /**
     199              :  * Outcome of a refund request.
     200              :  */
     201              : struct RefundStatus
     202              : {
     203              :   /**
     204              :    * Was there no such deposit?
     205              :    */
     206              :   bool not_found;
     207              : 
     208              :   /**
     209              :    * Was the refund accepted?
     210              :    */
     211              :   bool refund_ok;
     212              : 
     213              :   /**
     214              :    * Was the merchant already paid?
     215              :    */
     216              :   bool gone;
     217              : 
     218              :   /**
     219              :    * Was the refund transaction id reused?
     220              :    */
     221              :   bool conflict;
     222              : };
     223              : 
     224              : 
     225              : /**
     226              :  * Refund part of a deposit.
     227              :  *
     228              :  * @param pg the database context
     229              :  * @param coin coin to refund
     230              :  * @param dep deposit to refund
     231              :  * @param seed seed for the merchant signature
     232              :  * @param rtransaction_id merchant's refund transaction id
     233              :  * @param amount how much to refund, e.g. "0.5"
     234              :  * @param[out] st set to the outcome
     235              :  * @return transaction status
     236              :  */
     237              : static enum GNUNET_DB_QueryStatus
     238            5 : run_refund (struct TALER_EXCHANGEDB_PostgresContext *pg,
     239              :             const struct TALER_CoinPublicInfo *coin,
     240              :             const struct TDB_Deposit *dep,
     241              :             uint32_t seed,
     242              :             uint64_t rtransaction_id,
     243              :             const char *amount,
     244              :             struct RefundStatus *st)
     245              : {
     246              :   struct TALER_EXCHANGEDB_Refund refund;
     247            5 :   struct TALER_Amount deposit_fee = TDB_amount ("0.1");
     248              : 
     249            5 :   memset (&refund,
     250              :           0,
     251              :           sizeof (refund));
     252            5 :   refund.coin = *coin;
     253            5 :   refund.details.merchant_pub = dep->merchant_pub;
     254            5 :   TDB_fill (&refund.details.merchant_sig,
     255              :             sizeof (refund.details.merchant_sig),
     256              :             seed);
     257            5 :   refund.details.h_contract_terms = dep->h_contract_terms;
     258            5 :   refund.details.rtransaction_id = rtransaction_id;
     259            5 :   refund.details.refund_amount = TDB_amount (amount);
     260            5 :   refund.details.refund_fee = TDB_amount ("0");
     261            5 :   memset (st,
     262              :           0,
     263              :           sizeof (*st));
     264            5 :   return TALER_EXCHANGEDB_do_refund (pg,
     265              :                                      &refund,
     266              :                                      &deposit_fee,
     267              :                                      0,
     268              :                                      &st->not_found,
     269              :                                      &st->refund_ok,
     270              :                                      &st->gone,
     271              :                                      &st->conflict);
     272              : }
     273              : 
     274              : 
     275              : /**
     276              :  * Nothing is reported while the table is empty, and a refund of a deposit
     277              :  * that does not exist is refused.
     278              :  *
     279              :  * @param pg the database context
     280              :  * @return 0 on success
     281              :  */
     282              : static int
     283            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     284              : {
     285              :   struct TALER_CoinPublicInfo coin;
     286              :   struct TDB_Deposit dep;
     287            1 :   struct RefundContext ctx = { 0 };
     288            1 :   struct CoinRefundContext cctx = { 0 };
     289              :   struct RefundStatus st;
     290              : 
     291            1 :   TDB_denom (pg,
     292              :              10,
     293              :              "5",
     294              :              "0.1",
     295              :              &denom);
     296            1 :   TDB_account (pg,
     297              :                10,
     298              :                &account);
     299            1 :   TDB_coin (pg,
     300              :             &denom,
     301              :             20,
     302              :             &coin,
     303              :             NULL);
     304            1 :   memset (&dep,
     305              :           0,
     306              :           sizeof (dep));
     307            1 :   TDB_FILL (dep.merchant_pub,
     308              :             98);
     309            1 :   TDB_FILL (dep.h_contract_terms,
     310              :             98);
     311            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     312              :             run_refund (pg,
     313              :                         &coin,
     314              :                         &dep,
     315              :                         98,
     316              :                         1,
     317              :                         "1",
     318              :                         &st),
     319              :             TDB_coin_free (&coin));
     320            1 :   TDB_coin_free (&coin);
     321            1 :   FAILIF (! st.not_found);
     322            1 :   FAILIF (st.refund_ok);
     323            1 :   FAILIF (0 != TDB_count (pg,
     324              :                           "FROM refunds"));
     325              : 
     326            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     327              :           TALER_EXCHANGEDB_iterate_refunds_above_serial_id (pg,
     328              :                                                             0,
     329              :                                                             &refund_cb,
     330              :                                                             &ctx));
     331            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     332              :           TALER_EXCHANGEDB_iterate_refunds_by_coin (pg,
     333              :                                                     &coin.coin_pub,
     334              :                                                     &dep.merchant_pub,
     335              :                                                     &dep.h_contract_terms,
     336              :                                                     &coin_refund_cb,
     337              :                                                     &cctx));
     338            1 :   FAILIF (0 != ctx.total);
     339            1 :   FAILIF (0 != cctx.total);
     340            1 :   return 0;
     341              : }
     342              : 
     343              : 
     344              : /**
     345              :  * A partial refund is recorded and reported.
     346              :  *
     347              :  * @param pg the database context
     348              :  * @return 0 on success
     349              :  */
     350              : static int
     351            1 : check_refund (struct TALER_EXCHANGEDB_PostgresContext *pg)
     352              : {
     353              :   struct TALER_CoinPublicInfo coin;
     354              :   struct TDB_Deposit dep;
     355              :   struct RefundStatus st;
     356              :   struct RefundContext ctx;
     357              :   struct CoinRefundContext cctx;
     358            1 :   struct TALER_Amount expect = TDB_amount ("0.5");
     359              : 
     360            1 :   TDB_coin (pg,
     361              :             &denom,
     362              :             20,
     363              :             &coin,
     364              :             NULL);
     365            1 :   TDB_deposit (pg,
     366              :                &account,
     367              :                &coin,
     368              :                20,
     369              :                "1",
     370              :                "0.1",
     371              :                ts (1600000000),
     372              :                ts (1600000000),
     373              :                &dep);
     374            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     375              :             run_refund (pg,
     376              :                         &coin,
     377              :                         &dep,
     378              :                         20,
     379              :                         1,
     380              :                         "0.5",
     381              :                         &st),
     382              :             TDB_coin_free (&coin));
     383            1 :   FAILIF_C (st.not_found,
     384              :             TDB_coin_free (&coin));
     385            1 :   FAILIF_C (! st.refund_ok,
     386              :             TDB_coin_free (&coin));
     387            1 :   FAILIF_C (st.gone,
     388              :             TDB_coin_free (&coin));
     389            1 :   FAILIF_C (st.conflict,
     390              :             TDB_coin_free (&coin));
     391            1 :   FAILIF_C (1 != TDB_count (pg,
     392              :                             "FROM refunds"),
     393              :             TDB_coin_free (&coin));
     394              : 
     395            1 :   memset (&ctx,
     396              :           0,
     397              :           sizeof (ctx));
     398            1 :   ctx.coin_pub = &coin.coin_pub;
     399            1 :   FAILIF_C (1 !=
     400              :             TALER_EXCHANGEDB_iterate_refunds_above_serial_id (pg,
     401              :                                                               0,
     402              :                                                               &refund_cb,
     403              :                                                               &ctx),
     404              :             TDB_coin_free (&coin));
     405            1 :   FAILIF_C (1 != ctx.matched,
     406              :             TDB_coin_free (&coin));
     407            1 :   FAILIF_C (0 != TALER_amount_cmp (&ctx.amount,
     408              :                                    &expect),
     409              :             TDB_coin_free (&coin));
     410            1 :   FAILIF_C (1 != ctx.rtransaction_id,
     411              :             TDB_coin_free (&coin));
     412              :   /* only half of the deposit was refunded */
     413            1 :   FAILIF_C (ctx.full_refund,
     414              :             TDB_coin_free (&coin));
     415              : 
     416            1 :   memset (&cctx,
     417              :           0,
     418              :           sizeof (cctx));
     419            1 :   FAILIF_C (1 !=
     420              :             TALER_EXCHANGEDB_iterate_refunds_by_coin (pg,
     421              :                                                       &coin.coin_pub,
     422              :                                                       &dep.merchant_pub,
     423              :                                                       &dep.h_contract_terms,
     424              :                                                       &coin_refund_cb,
     425              :                                                       &cctx),
     426              :             TDB_coin_free (&coin));
     427            1 :   FAILIF_C (1 != cctx.total,
     428              :             TDB_coin_free (&coin));
     429              : 
     430              :   /* a contract nobody refunded has no refunds */
     431              :   {
     432              :     struct TALER_PrivateContractHashP other;
     433              : 
     434            1 :     TDB_FILL (other,
     435              :               97);
     436            1 :     memset (&cctx,
     437              :             0,
     438              :             sizeof (cctx));
     439            1 :     FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     440              :               TALER_EXCHANGEDB_iterate_refunds_by_coin (pg,
     441              :                                                         &coin.coin_pub,
     442              :                                                         &dep.merchant_pub,
     443              :                                                         &other,
     444              :                                                         &coin_refund_cb,
     445              :                                                         &cctx),
     446              :               TDB_coin_free (&coin));
     447            1 :     FAILIF_C (0 != cctx.total,
     448              :               TDB_coin_free (&coin));
     449              :   }
     450              : 
     451              :   /* reusing the refund transaction id is a conflict */
     452            1 :   FAILIF_C (0 >
     453              :             run_refund (pg,
     454              :                         &coin,
     455              :                         &dep,
     456              :                         21,
     457              :                         1,
     458              :                         "0.25",
     459              :                         &st),
     460              :             TDB_coin_free (&coin));
     461            1 :   FAILIF_C (! st.conflict,
     462              :             TDB_coin_free (&coin));
     463            1 :   FAILIF_C (1 != TDB_count (pg,
     464              :                             "FROM refunds"),
     465              :             TDB_coin_free (&coin));
     466              : 
     467              :   /* a second refund with a fresh id completes the deposit */
     468            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     469              :             run_refund (pg,
     470              :                         &coin,
     471              :                         &dep,
     472              :                         22,
     473              :                         2,
     474              :                         "0.5",
     475              :                         &st),
     476              :             TDB_coin_free (&coin));
     477            1 :   FAILIF_C (! st.refund_ok,
     478              :             TDB_coin_free (&coin));
     479            1 :   memset (&cctx,
     480              :           0,
     481              :           sizeof (cctx));
     482            1 :   FAILIF_C (2 !=
     483              :             TALER_EXCHANGEDB_iterate_refunds_by_coin (pg,
     484              :                                                       &coin.coin_pub,
     485              :                                                       &dep.merchant_pub,
     486              :                                                       &dep.h_contract_terms,
     487              :                                                       &coin_refund_cb,
     488              :                                                       &cctx),
     489              :             TDB_coin_free (&coin));
     490            1 :   FAILIF_C (2 != cctx.total,
     491              :             TDB_coin_free (&coin));
     492            1 :   TDB_coin_free (&coin);
     493            1 :   return 0;
     494              : }
     495              : 
     496              : 
     497              : /**
     498              :  * Refunding a deposit the exchange has already paid out is refused.
     499              :  *
     500              :  * @param pg the database context
     501              :  * @return 0 on success
     502              :  */
     503              : static int
     504            1 : check_gone (struct TALER_EXCHANGEDB_PostgresContext *pg)
     505              : {
     506              :   struct TALER_CoinPublicInfo coin;
     507              :   struct TDB_Deposit dep;
     508              :   struct RefundStatus st;
     509              : 
     510            1 :   TDB_coin (pg,
     511              :             &denom,
     512              :             23,
     513              :             &coin,
     514              :             NULL);
     515            1 :   TDB_deposit (pg,
     516              :                &account,
     517              :                &coin,
     518              :                23,
     519              :                "1",
     520              :                "0.1",
     521              :                ts (1600000000),
     522              :                ts (1600000000),
     523              :                &dep);
     524            1 :   FAILIF_C (GNUNET_OK !=
     525              :             TDB_exec (pg,
     526              :                       "UPDATE batch_deposits"
     527              :                       " SET done=TRUE"
     528              :                       " WHERE batch_deposit_serial_id=%llu;",
     529              :                       (unsigned long long) dep.serial),
     530              :             TDB_coin_free (&coin));
     531              :   /* The row goes in before the "already wired out" check, so this is how
     532              :      the /refund handler uses the function: inside a transaction it can
     533              :      roll back. */
     534            1 :   FAILIF_C (GNUNET_OK !=
     535              :             TALER_EXCHANGEDB_start (pg,
     536              :                                     "test-refund-gone"),
     537              :             TDB_coin_free (&coin));
     538            1 :   FAILIF_C (0 >
     539              :             run_refund (pg,
     540              :                         &coin,
     541              :                         &dep,
     542              :                         23,
     543              :                         1,
     544              :                         "0.5",
     545              :                         &st),
     546              :             TALER_EXCHANGEDB_rollback (pg); TDB_coin_free (&coin));
     547            1 :   TALER_EXCHANGEDB_rollback (pg);
     548            1 :   TDB_coin_free (&coin);
     549            1 :   FAILIF (! st.gone);
     550            1 :   FAILIF (st.refund_ok);
     551            1 :   FAILIF (2 != TDB_count (pg,
     552              :                           "FROM refunds"));
     553            1 :   return 0;
     554              : }
     555              : 
     556              : 
     557              : /**
     558              :  * insert_refund() writes the row without any of the checks do_refund()
     559              :  * makes, and finds the deposit through (coin, merchant, contract).
     560              :  *
     561              :  * @param pg the database context
     562              :  * @return 0 on success
     563              :  */
     564              : static int
     565            1 : check_insert_refund (struct TALER_EXCHANGEDB_PostgresContext *pg)
     566              : {
     567              :   struct TALER_CoinPublicInfo coin;
     568              :   struct TDB_Deposit dep;
     569              :   struct TALER_EXCHANGEDB_Refund refund;
     570              :   struct CoinRefundContext cctx;
     571              : 
     572            1 :   TDB_coin (pg,
     573              :             &denom,
     574              :             24,
     575              :             &coin,
     576              :             NULL);
     577            1 :   TDB_deposit (pg,
     578              :                &account,
     579              :                &coin,
     580              :                24,
     581              :                "2",
     582              :                "0.1",
     583              :                ts (1600000000),
     584              :                ts (1600000000),
     585              :                &dep);
     586            1 :   memset (&refund,
     587              :           0,
     588              :           sizeof (refund));
     589            1 :   refund.coin = coin;
     590            1 :   refund.details.merchant_pub = dep.merchant_pub;
     591            1 :   TDB_FILL (refund.details.merchant_sig,
     592              :             24);
     593            1 :   refund.details.h_contract_terms = dep.h_contract_terms;
     594            1 :   refund.details.rtransaction_id = 7;
     595            1 :   refund.details.refund_amount = TDB_amount ("1");
     596            1 :   refund.details.refund_fee = TDB_amount ("0");
     597            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     598              :             TALER_EXCHANGEDB_insert_refund (pg,
     599              :                                             &refund),
     600              :             TDB_coin_free (&coin));
     601            1 :   memset (&cctx,
     602              :           0,
     603              :           sizeof (cctx));
     604            1 :   FAILIF_C (1 !=
     605              :             TALER_EXCHANGEDB_iterate_refunds_by_coin (pg,
     606              :                                                       &coin.coin_pub,
     607              :                                                       &dep.merchant_pub,
     608              :                                                       &dep.h_contract_terms,
     609              :                                                       &coin_refund_cb,
     610              :                                                       &cctx),
     611              :             TDB_coin_free (&coin));
     612            1 :   FAILIF_C (1 != cctx.value_sum,
     613              :             TDB_coin_free (&coin));
     614              : 
     615              :   /* a contract that has no deposit selects no row to insert against */
     616            1 :   TDB_FILL (refund.details.h_contract_terms,
     617              :             96);
     618            1 :   refund.details.rtransaction_id = 8;
     619            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     620              :             TALER_EXCHANGEDB_insert_refund (pg,
     621              :                                             &refund),
     622              :             TDB_coin_free (&coin));
     623            1 :   TDB_coin_free (&coin);
     624            1 :   FAILIF (3 != TDB_count (pg,
     625              :                           "FROM refunds"));
     626            1 :   return 0;
     627              : }
     628              : 
     629              : 
     630              : /**
     631              :  * The serial iterator's bound and abort return behave as documented.
     632              :  *
     633              :  * @param pg the database context
     634              :  * @return 0 on success
     635              :  */
     636              : static int
     637            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     638              : {
     639              :   struct RefundContext ctx;
     640              : 
     641            1 :   memset (&ctx,
     642              :           0,
     643              :           sizeof (ctx));
     644            1 :   FAILIF (3 !=
     645              :           TALER_EXCHANGEDB_iterate_refunds_above_serial_id (pg,
     646              :                                                             0,
     647              :                                                             &refund_cb,
     648              :                                                             &ctx));
     649            1 :   memset (&ctx,
     650              :           0,
     651              :           sizeof (ctx));
     652            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     653              :           TALER_EXCHANGEDB_iterate_refunds_above_serial_id (pg,
     654              :                                                             1000,
     655              :                                                             &refund_cb,
     656              :                                                             &ctx));
     657            1 :   FAILIF (0 != ctx.total);
     658            1 :   memset (&ctx,
     659              :           0,
     660              :           sizeof (ctx));
     661            1 :   ctx.stop_after = 1;
     662            1 :   FAILIF (3 !=
     663              :           TALER_EXCHANGEDB_iterate_refunds_above_serial_id (pg,
     664              :                                                             0,
     665              :                                                             &refund_cb,
     666              :                                                             &ctx));
     667            1 :   FAILIF (1 != ctx.total);
     668            1 :   return 0;
     669              : }
     670              : 
     671              : 
     672              : /**
     673              :  * The checks to run, in order.
     674              :  */
     675              : static const struct TDB_Test tests[] = {
     676              :   { "refunds-empty",
     677              :     &check_empty },
     678              :   { "refunds-refund",
     679              :     &check_refund },
     680              :   { "refunds-gone",
     681              :     &check_gone },
     682              :   { "refunds-insert-refund",
     683              :     &check_insert_refund },
     684              :   { "refunds-iterate",
     685              :     &check_iterate },
     686              :   { NULL, NULL }
     687              : };
     688              : 
     689              : 
     690              : int
     691            1 : main (int argc,
     692              :       char *const *argv)
     693              : {
     694              :   int ret;
     695              : 
     696            1 :   ret = TDB_main (argc,
     697              :                   argv,
     698              :                   "test-refunds",
     699              :                   "Tests for the exchangedb `refunds' table",
     700              :                   tests);
     701            1 :   TDB_account_free (&account);
     702            1 :   TDB_denom_free (&denom);
     703            1 :   return ret;
     704              : }
     705              : 
     706              : 
     707              : /* end of test_refunds.c */
        

Generated by: LCOV version 2.0-1