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

            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_account_merges.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `account_merges`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_do_reserve_purse(),
      23              :  * #TALER_EXCHANGEDB_iterate_account_merges_above_serial_id() and
      24              :  * #TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check().
      25              :  *
      26              :  * `account_merges` is the account holder's side of a merge: the request
      27              :  * the wallet signs to have a purse land in its reserve.  do_reserve_purse()
      28              :  * writes it (together with the `purse_merges` row) and declines when the
      29              :  * reserve is unknown, when its purse quota is used up, and when the purse
      30              :  * was already merged into a different reserve.
      31              :  */
      32              : #include "test_common.h"
      33              : #include "exchange-database/do_reserve_purse.h"
      34              : #include "exchange-database/insert_purse_request.h"
      35              : #include "exchange-database/iterate_account_merges_above_serial_id.h"
      36              : #include "exchange-database/iterate_merge_amounts_for_kyc_check.h"
      37              : #include "exchange-database/rollback.h"
      38              : #include "exchange-database/start.h"
      39              : 
      40              : 
      41              : /**
      42              :  * Wallet account the reserves of the checks belong to.
      43              :  */
      44              : static struct TDB_Account account;
      45              : 
      46              : 
      47              : /**
      48              :  * Build a timestamp from a number of seconds since the epoch.
      49              :  *
      50              :  * @param secs seconds since the epoch
      51              :  * @return the timestamp
      52              :  */
      53              : static struct GNUNET_TIME_Timestamp
      54            8 : ts (uint64_t secs)
      55              : {
      56            8 :   struct GNUNET_TIME_Absolute abs = {
      57            8 :     .abs_value_us = secs * 1000LLU * 1000LLU
      58              :   };
      59              : 
      60            8 :   return GNUNET_TIME_absolute_to_timestamp (abs);
      61              : }
      62              : 
      63              : 
      64              : /**
      65              :  * Closure for #merge_cb().
      66              :  */
      67              : struct MergeContext
      68              : {
      69              :   /**
      70              :    * How many rows did the callback see?
      71              :    */
      72              :   unsigned int total;
      73              : 
      74              :   /**
      75              :    * Stop after this many rows; 0 for no limit.
      76              :    */
      77              :   unsigned int stop_after;
      78              : 
      79              :   /**
      80              :    * Purse we are looking for, NULL to match nothing.
      81              :    */
      82              :   const struct TALER_PurseContractPublicKeyP *purse_pub;
      83              : 
      84              :   /**
      85              :    * How many times did we see it?
      86              :    */
      87              :   unsigned int matched;
      88              : 
      89              :   /**
      90              :    * Reserve reported for it.
      91              :    */
      92              :   struct TALER_ReservePublicKeyP reserve_pub;
      93              : 
      94              :   /**
      95              :    * Amount reported for it.
      96              :    */
      97              :   struct TALER_Amount amount;
      98              : 
      99              :   /**
     100              :    * Purse fee reported for it.
     101              :    */
     102              :   struct TALER_Amount purse_fee;
     103              : };
     104              : 
     105              : 
     106              : /**
     107              :  * Callback for
     108              :  * #TALER_EXCHANGEDB_iterate_account_merges_above_serial_id().
     109              :  *
     110              :  * @param cls a `struct MergeContext *`
     111              :  * @param rowid row of the request
     112              :  * @param reserve_pub reserve the purse is to land in
     113              :  * @param purse_pub the purse
     114              :  * @param h_contract_terms contract of the purse
     115              :  * @param purse_expiration when the purse expires
     116              :  * @param amount target amount of the purse
     117              :  * @param min_age age limit of the purse
     118              :  * @param flags flags of the purse
     119              :  * @param purse_fee fee charged to the reserve for the purse
     120              :  * @param merge_timestamp when the request was made
     121              :  * @param reserve_sig signature of the account holder
     122              :  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
     123              :  */
     124              : static enum GNUNET_GenericReturnValue
     125            2 : merge_cb (void *cls,
     126              :           uint64_t rowid,
     127              :           const struct TALER_ReservePublicKeyP *reserve_pub,
     128              :           const struct TALER_PurseContractPublicKeyP *purse_pub,
     129              :           const struct TALER_PrivateContractHashP *h_contract_terms,
     130              :           struct GNUNET_TIME_Timestamp purse_expiration,
     131              :           const struct TALER_Amount *amount,
     132              :           uint32_t min_age,
     133              :           enum TALER_WalletAccountMergeFlags flags,
     134              :           const struct TALER_Amount *purse_fee,
     135              :           struct GNUNET_TIME_Timestamp merge_timestamp,
     136              :           const struct TALER_ReserveSignatureP *reserve_sig)
     137              : {
     138            2 :   struct MergeContext *ctx = cls;
     139              : 
     140              :   (void) rowid;
     141              :   (void) h_contract_terms;
     142              :   (void) purse_expiration;
     143              :   (void) min_age;
     144              :   (void) flags;
     145              :   (void) merge_timestamp;
     146              :   (void) reserve_sig;
     147            2 :   ctx->total++;
     148            2 :   if ( (NULL != ctx->purse_pub) &&
     149            1 :        (0 == GNUNET_memcmp (purse_pub,
     150              :                             ctx->purse_pub)) )
     151              :   {
     152            1 :     ctx->matched++;
     153            1 :     ctx->reserve_pub = *reserve_pub;
     154            1 :     ctx->amount = *amount;
     155            1 :     ctx->purse_fee = *purse_fee;
     156              :   }
     157            2 :   if ( (0 != ctx->stop_after) &&
     158            1 :        (ctx->total >= ctx->stop_after) )
     159            1 :     return GNUNET_SYSERR;
     160            1 :   return GNUNET_OK;
     161              : }
     162              : 
     163              : 
     164              : /**
     165              :  * Closure for #amount_cb().
     166              :  */
     167              : struct AmountContext
     168              : {
     169              :   /**
     170              :    * How many amounts did the callback see?
     171              :    */
     172              :   unsigned int total;
     173              : 
     174              :   /**
     175              :    * Sum of the whole-unit parts of the amounts seen.
     176              :    */
     177              :   uint64_t value_sum;
     178              : 
     179              :   /**
     180              :    * Return this from the callback.
     181              :    */
     182              :   enum GNUNET_GenericReturnValue ret;
     183              : };
     184              : 
     185              : 
     186              : /**
     187              :  * Callback for
     188              :  * #TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check().
     189              :  *
     190              :  * @param cls a `struct AmountContext *`
     191              :  * @param amount how much was merged
     192              :  * @param date when it was merged
     193              :  * @return what @e ret of the closure says
     194              :  */
     195              : static enum GNUNET_GenericReturnValue
     196            2 : amount_cb (void *cls,
     197              :            const struct TALER_Amount *amount,
     198              :            struct GNUNET_TIME_Absolute date)
     199              : {
     200            2 :   struct AmountContext *ctx = cls;
     201              : 
     202              :   (void) date;
     203            2 :   ctx->total++;
     204            2 :   ctx->value_sum += amount->value;
     205            2 :   return ctx->ret;
     206              : }
     207              : 
     208              : 
     209              : /**
     210              :  * Outcome of a reserve-purse request.
     211              :  */
     212              : struct PurseStatus
     213              : {
     214              :   /**
     215              :    * Was the purse already merged into another reserve?
     216              :    */
     217              :   bool in_conflict;
     218              : 
     219              :   /**
     220              :    * Is the reserve unknown?
     221              :    */
     222              :   bool no_reserve;
     223              : 
     224              :   /**
     225              :    * Has the reserve run out of purses?
     226              :    */
     227              :   bool insufficient_funds;
     228              : };
     229              : 
     230              : 
     231              : /**
     232              :  * Ask for a purse to be merged into a reserve.
     233              :  *
     234              :  * @param pg the database context
     235              :  * @param purse purse to merge
     236              :  * @param reserve_pub reserve to merge it into
     237              :  * @param seed seed for the signatures
     238              :  * @param purse_fee fee to charge the reserve, NULL to use the quota
     239              :  * @param[out] st set to the outcome
     240              :  * @return transaction status
     241              :  */
     242              : static enum GNUNET_DB_QueryStatus
     243            4 : run_reserve_purse (struct TALER_EXCHANGEDB_PostgresContext *pg,
     244              :                    const struct TDB_Purse *purse,
     245              :                    const struct TALER_ReservePublicKeyP *reserve_pub,
     246              :                    uint32_t seed,
     247              :                    const struct TALER_Amount *purse_fee,
     248              :                    struct PurseStatus *st)
     249              : {
     250              :   struct TALER_PurseMergeSignatureP merge_sig;
     251              :   struct TALER_ReserveSignatureP reserve_sig;
     252              : 
     253            4 :   TDB_fill (&merge_sig,
     254              :             sizeof (merge_sig),
     255              :             seed);
     256            4 :   TDB_fill (&reserve_sig,
     257              :             sizeof (reserve_sig),
     258              :             seed);
     259            4 :   memset (st,
     260              :           0,
     261              :           sizeof (*st));
     262            4 :   return TALER_EXCHANGEDB_do_reserve_purse (pg,
     263              :                                             &purse->purse_pub,
     264              :                                             &merge_sig,
     265              :                                             ts (1600000000),
     266              :                                             &reserve_sig,
     267              :                                             purse_fee,
     268              :                                             reserve_pub,
     269              :                                             &st->in_conflict,
     270              :                                             &st->no_reserve,
     271              :                                             &st->insufficient_funds);
     272              : }
     273              : 
     274              : 
     275              : /**
     276              :  * Create a purse that records a purse fee, the way the /reserves/$RP/purse
     277              :  * endpoint does when the account holder offers to pay for the purse.
     278              :  * TDB_purse() always records a zero fee, and the auditor's view reports
     279              :  * the fee the *purse* recorded, not the one do_reserve_purse() charged.
     280              :  *
     281              :  * @param pg the database context
     282              :  * @param seed seed for the purse's keys and signature
     283              :  * @param amount target amount of the purse, e.g. "5"
     284              :  * @param fee purse fee the account holder offers, e.g. "1"
     285              :  * @param[out] purse set to the purse
     286              :  */
     287              : static void
     288            1 : make_purse (struct TALER_EXCHANGEDB_PostgresContext *pg,
     289              :             uint32_t seed,
     290              :             const char *amount,
     291              :             const char *fee,
     292              :             struct TDB_Purse *purse)
     293              : {
     294            1 :   struct TALER_Amount purse_fee = TDB_amount (fee);
     295              :   enum GNUNET_DB_QueryStatus qs;
     296            1 :   bool in_conflict = false;
     297              : 
     298            1 :   memset (purse,
     299              :           0,
     300              :           sizeof (*purse));
     301            1 :   TDB_fill (&purse->purse_pub,
     302              :             sizeof (purse->purse_pub),
     303              :             seed);
     304            1 :   TDB_fill (&purse->merge_pub,
     305              :             sizeof (purse->merge_pub),
     306              :             seed);
     307            1 :   TDB_fill (&purse->h_contract_terms,
     308              :             sizeof (purse->h_contract_terms),
     309              :             seed);
     310            1 :   TDB_fill (&purse->purse_sig,
     311              :             sizeof (purse->purse_sig),
     312              :             seed);
     313            1 :   purse->purse_expiration = ts (1700000000);
     314            1 :   purse->amount = TDB_amount (amount);
     315            1 :   qs = TALER_EXCHANGEDB_insert_purse_request (
     316              :     pg,
     317            1 :     &purse->purse_pub,
     318            1 :     &purse->merge_pub,
     319              :     purse->purse_expiration,
     320            1 :     &purse->h_contract_terms,
     321              :     0,
     322              :     TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE,
     323              :     &purse_fee,
     324            1 :     &purse->amount,
     325            1 :     &purse->purse_sig,
     326              :     &in_conflict);
     327            1 :   GNUNET_assert (0 <= qs);
     328            1 :   GNUNET_assert (! in_conflict);
     329            1 : }
     330              : 
     331              : 
     332              : /**
     333              :  * Nothing is reported while the table is empty.
     334              :  *
     335              :  * @param pg the database context
     336              :  * @return 0 on success
     337              :  */
     338              : static int
     339            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     340              : {
     341            1 :   struct MergeContext ctx = { 0 };
     342            1 :   struct AmountContext actx = { 0 };
     343              : 
     344            1 :   TDB_account (pg,
     345              :                10,
     346              :                &account);
     347            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     348              :           TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
     349              :             pg,
     350              :             0,
     351              :             &merge_cb,
     352              :             &ctx));
     353            1 :   actx.ret = GNUNET_OK;
     354            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     355              :           TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
     356              :             pg,
     357              :             &account.h_normalized,
     358              :             GNUNET_TIME_UNIT_ZERO_ABS,
     359              :             &amount_cb,
     360              :             &actx));
     361            1 :   FAILIF (0 != ctx.total);
     362            1 :   FAILIF (0 != actx.total);
     363            1 :   return 0;
     364              : }
     365              : 
     366              : 
     367              : /**
     368              :  * A reserve that does not exist cannot take a purse.
     369              :  *
     370              :  * @param pg the database context
     371              :  * @return 0 on success
     372              :  */
     373              : static int
     374            1 : check_no_reserve (struct TALER_EXCHANGEDB_PostgresContext *pg)
     375              : {
     376              :   struct TDB_Purse purse;
     377              :   struct TALER_ReservePublicKeyP reserve_pub;
     378            1 :   struct TALER_Amount purse_fee = TDB_amount ("1");
     379              :   struct PurseStatus st;
     380              : 
     381            1 :   TDB_purse (pg,
     382              :              9,
     383              :              "5",
     384              :              ts (1700000000),
     385              :              &purse);
     386            1 :   TDB_FILL (reserve_pub,
     387              :             99);
     388              :   /* A non-zero purse fee is what makes an unknown reserve fatal: with a
     389              :      zero fee the function creates the reserve instead.  The `purse_merges`
     390              :      row goes in before the reserve is looked at, so the caller has to roll
     391              :      back -- which is what the /purses/.../merge handler does. */
     392            1 :   FAILIF (GNUNET_OK !=
     393              :           TALER_EXCHANGEDB_start (pg,
     394              :                                   "test-account-merges-no-reserve"));
     395            1 :   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     396              :             run_reserve_purse (pg,
     397              :                                &purse,
     398              :                                &reserve_pub,
     399              :                                9,
     400              :                                &purse_fee,
     401              :                                &st),
     402              :             TALER_EXCHANGEDB_rollback (pg));
     403            1 :   TALER_EXCHANGEDB_rollback (pg);
     404            1 :   FAILIF (! st.no_reserve);
     405            1 :   FAILIF (! st.insufficient_funds);
     406            1 :   FAILIF (0 != TDB_count (pg,
     407              :                           "FROM account_merges"));
     408            1 :   FAILIF (0 != TDB_count (pg,
     409              :                           "FROM purse_merges"));
     410            1 :   return 0;
     411              : }
     412              : 
     413              : 
     414              : /**
     415              :  * Paying the purse fee from the reserve creates the merge request.
     416              :  *
     417              :  * @param pg the database context
     418              :  * @return 0 on success
     419              :  */
     420              : static int
     421            1 : check_reserve_purse (struct TALER_EXCHANGEDB_PostgresContext *pg)
     422              : {
     423              :   struct TDB_Purse purse;
     424              :   struct TALER_ReservePublicKeyP reserve_pub;
     425            1 :   struct TALER_Amount purse_fee = TDB_amount ("1");
     426            1 :   struct TALER_Amount expect_balance = TDB_amount ("9");
     427              :   struct MergeContext ctx;
     428              :   struct PurseStatus st;
     429              :   char *hex;
     430              : 
     431            1 :   make_purse (pg,
     432              :               10,
     433              :               "5",
     434              :               "1",
     435              :               &purse);
     436            1 :   TDB_reserve (pg,
     437              :                10,
     438              :                "10",
     439              :                &reserve_pub);
     440            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     441              :           run_reserve_purse (pg,
     442              :                              &purse,
     443              :                              &reserve_pub,
     444              :                              10,
     445              :                              &purse_fee,
     446              :                              &st));
     447            1 :   FAILIF (st.no_reserve);
     448            1 :   FAILIF (st.in_conflict);
     449            1 :   FAILIF (st.insufficient_funds);
     450            1 :   FAILIF (1 != TDB_count (pg,
     451              :                           "FROM account_merges"));
     452              :   /* the merge is also recorded from the purse's side */
     453            1 :   FAILIF (1 != TDB_count (pg,
     454              :                           "FROM purse_merges"));
     455              :   /* the reserve paid the purse fee */
     456            1 :   hex = TDB_hex (&reserve_pub,
     457              :                  sizeof (reserve_pub));
     458            1 :   FAILIF_C (1 != TDB_count (pg,
     459              :                             "FROM reserves"
     460              :                             " WHERE reserve_pub=decode('%s','hex')"
     461              :                             "   AND current_balance=ROW(9,0)::taler_amount",
     462              :                             hex),
     463              :             GNUNET_free (hex));
     464            1 :   GNUNET_free (hex);
     465              :   (void) expect_balance;
     466              : 
     467            1 :   memset (&ctx,
     468              :           0,
     469              :           sizeof (ctx));
     470            1 :   ctx.purse_pub = &purse.purse_pub;
     471            1 :   FAILIF (0 >=
     472              :           TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
     473              :             pg,
     474              :             0,
     475              :             &merge_cb,
     476              :             &ctx));
     477            1 :   FAILIF (1 != ctx.matched);
     478            1 :   FAILIF (0 != GNUNET_memcmp (&ctx.reserve_pub,
     479              :                               &reserve_pub));
     480            1 :   FAILIF (0 != TALER_amount_cmp (&ctx.amount,
     481              :                                  &purse.amount));
     482            1 :   FAILIF (0 != TALER_amount_cmp (&ctx.purse_fee,
     483              :                                  &purse_fee));
     484              : 
     485              :   /* the same purse into a different reserve is a conflict */
     486              :   {
     487              :     struct TALER_ReservePublicKeyP other;
     488              : 
     489            1 :     TDB_reserve (pg,
     490              :                  11,
     491              :                  "10",
     492              :                  &other);
     493            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     494              :             run_reserve_purse (pg,
     495              :                                &purse,
     496              :                                &other,
     497              :                                11,
     498              :                                &purse_fee,
     499              :                                &st));
     500            1 :     FAILIF (! st.in_conflict);
     501            1 :     FAILIF (1 != TDB_count (pg,
     502              :                             "FROM account_merges"));
     503              :   }
     504            1 :   return 0;
     505              : }
     506              : 
     507              : 
     508              : /**
     509              :  * A reserve that cannot pay the purse fee is refused.
     510              :  *
     511              :  * @param pg the database context
     512              :  * @return 0 on success
     513              :  */
     514              : static int
     515            1 : check_insufficient_funds (struct TALER_EXCHANGEDB_PostgresContext *pg)
     516              : {
     517              :   struct TDB_Purse purse;
     518              :   struct TALER_ReservePublicKeyP reserve_pub;
     519            1 :   struct TALER_Amount purse_fee = TDB_amount ("5");
     520              :   struct PurseStatus st;
     521              : 
     522            1 :   TDB_purse (pg,
     523              :              12,
     524              :              "5",
     525              :              ts (1700000000),
     526              :              &purse);
     527            1 :   TDB_reserve (pg,
     528              :                12,
     529              :                "1",
     530              :                &reserve_pub);
     531            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     532              :           run_reserve_purse (pg,
     533              :                              &purse,
     534              :                              &reserve_pub,
     535              :                              12,
     536              :                              &purse_fee,
     537              :                              &st));
     538            1 :   FAILIF (! st.insufficient_funds);
     539            1 :   FAILIF (1 != TDB_count (pg,
     540              :                           "FROM account_merges"));
     541            1 :   return 0;
     542              : }
     543              : 
     544              : 
     545              : /**
     546              :  * The wallet's KYC view sums the merges that landed in its account.
     547              :  *
     548              :  * The account in question is the reserve's own `taler-reserve://` account,
     549              :  * which is what do_reserve_purse() records in `wallet_h_payto` -- not a
     550              :  * bank account.
     551              :  *
     552              :  * @param pg the database context
     553              :  * @return 0 on success
     554              :  */
     555              : static int
     556            1 : check_kyc_amounts (struct TALER_EXCHANGEDB_PostgresContext *pg)
     557              : {
     558              :   struct AmountContext ctx;
     559              :   struct TALER_ReservePublicKeyP reserve_pub;
     560              :   struct TALER_NormalizedPayto payto;
     561              :   struct TALER_NormalizedPaytoHashP h_payto;
     562              :   struct TALER_NormalizedPaytoHashP other;
     563              : 
     564            1 :   TDB_fill (&reserve_pub,
     565              :             sizeof (reserve_pub),
     566              :             10);
     567            1 :   payto = TALER_reserve_make_payto (pg->exchange_url,
     568              :                                     &reserve_pub);
     569            1 :   TALER_normalized_payto_hash (payto,
     570              :                                &h_payto);
     571            1 :   GNUNET_free (payto.normalized_payto);
     572              : 
     573              :   /* the merge of check_reserve_purse() is charged to our account, but the
     574              :      purse has not been decided yet, so the KYC view has nothing */
     575            1 :   memset (&ctx,
     576              :           0,
     577              :           sizeof (ctx));
     578            1 :   ctx.ret = GNUNET_OK;
     579            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     580              :           TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
     581              :             pg,
     582              :             &h_payto,
     583              :             GNUNET_TIME_UNIT_ZERO_ABS,
     584              :             &amount_cb,
     585              :             &ctx));
     586            1 :   FAILIF (0 != ctx.total);
     587              : 
     588              :   /* once the purse is decided in favour of the reserve, it counts */
     589            1 :   FAILIF (GNUNET_OK !=
     590              :           TDB_exec (pg,
     591              :                     "INSERT INTO purse_decision"
     592              :                     " (purse_pub,action_timestamp,refunded)"
     593              :                     " SELECT purse_pub,1000000,FALSE"
     594              :                     "   FROM account_merges;"));
     595            1 :   memset (&ctx,
     596              :           0,
     597              :           sizeof (ctx));
     598            1 :   ctx.ret = GNUNET_OK;
     599            1 :   FAILIF (0 >=
     600              :           TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
     601              :             pg,
     602              :             &h_payto,
     603              :             GNUNET_TIME_UNIT_ZERO_ABS,
     604              :             &amount_cb,
     605              :             &ctx));
     606            1 :   FAILIF (0 == ctx.total);
     607            1 :   FAILIF (0 == ctx.value_sum);
     608              : 
     609              :   /* an account nobody merged into has nothing */
     610            1 :   TDB_FILL (other,
     611              :             97);
     612            1 :   memset (&ctx,
     613              :           0,
     614              :           sizeof (ctx));
     615            1 :   ctx.ret = GNUNET_OK;
     616            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     617              :           TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
     618              :             pg,
     619              :             &other,
     620              :             GNUNET_TIME_UNIT_ZERO_ABS,
     621              :             &amount_cb,
     622              :             &ctx));
     623            1 :   FAILIF (0 != ctx.total);
     624              : 
     625              :   /* a time limit past the merges hides them */
     626            1 :   memset (&ctx,
     627              :           0,
     628              :           sizeof (ctx));
     629            1 :   ctx.ret = GNUNET_OK;
     630            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     631              :           TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
     632              :             pg,
     633              :             &h_payto,
     634              :             ts (1700000000).abs_time,
     635              :             &amount_cb,
     636              :             &ctx));
     637            1 :   FAILIF (0 != ctx.total);
     638              : 
     639              :   /* and an aborting callback is not an error */
     640            1 :   memset (&ctx,
     641              :           0,
     642              :           sizeof (ctx));
     643            1 :   ctx.ret = GNUNET_NO;
     644            1 :   FAILIF (0 >
     645              :           TALER_EXCHANGEDB_iterate_merge_amounts_for_kyc_check (
     646              :             pg,
     647              :             &h_payto,
     648              :             GNUNET_TIME_UNIT_ZERO_ABS,
     649              :             &amount_cb,
     650              :             &ctx));
     651            1 :   FAILIF (1 != ctx.total);
     652            1 :   return 0;
     653              : }
     654              : 
     655              : 
     656              : /**
     657              :  * The iterator's bound and abort return behave as documented.
     658              :  *
     659              :  * @param pg the database context
     660              :  * @return 0 on success
     661              :  */
     662              : static int
     663            1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
     664              : {
     665              :   struct MergeContext ctx;
     666              : 
     667            1 :   memset (&ctx,
     668              :           0,
     669              :           sizeof (ctx));
     670            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     671              :           TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
     672              :             pg,
     673              :             1000,
     674              :             &merge_cb,
     675              :             &ctx));
     676            1 :   FAILIF (0 != ctx.total);
     677            1 :   memset (&ctx,
     678              :           0,
     679              :           sizeof (ctx));
     680            1 :   ctx.stop_after = 1;
     681            1 :   FAILIF (0 >=
     682              :           TALER_EXCHANGEDB_iterate_account_merges_above_serial_id (
     683              :             pg,
     684              :             0,
     685              :             &merge_cb,
     686              :             &ctx));
     687            1 :   FAILIF (1 != ctx.total);
     688            1 :   return 0;
     689              : }
     690              : 
     691              : 
     692              : /**
     693              :  * The checks to run, in order.
     694              :  */
     695              : static const struct TDB_Test tests[] = {
     696              :   { "account-merges-empty",
     697              :     &check_empty },
     698              :   { "account-merges-no-reserve",
     699              :     &check_no_reserve },
     700              :   { "account-merges-reserve-purse",
     701              :     &check_reserve_purse },
     702              :   { "account-merges-insufficient-funds",
     703              :     &check_insufficient_funds },
     704              :   { "account-merges-kyc-amounts",
     705              :     &check_kyc_amounts },
     706              :   { "account-merges-iterate",
     707              :     &check_iterate },
     708              :   { NULL, NULL }
     709              : };
     710              : 
     711              : 
     712              : int
     713            1 : main (int argc,
     714              :       char *const *argv)
     715              : {
     716              :   int ret;
     717              : 
     718            1 :   ret = TDB_main (argc,
     719              :                   argv,
     720              :                   "test-account-merges",
     721              :                   "Tests for the exchangedb `account_merges' table",
     722              :                   tests);
     723            1 :   TDB_account_free (&account);
     724            1 :   return ret;
     725              : }
     726              : 
     727              : 
     728              : /* end of test_account_merges.c */
        

Generated by: LCOV version 2.0-1