exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

test_aggregation_tracking.c (23077B)


      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_aggregation_tracking.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `aggregation_tracking`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_do_aggregate(),
     23  * #TALER_EXCHANGEDB_get_pending_aggregation(),
     24  * #TALER_EXCHANGEDB_get_transfer_by_deposit(),
     25  * #TALER_EXCHANGEDB_iterate_aggregation_wtids_above_serial_id(),
     26  * #TALER_EXCHANGEDB_iterate_aggregations_above_serial_id() and
     27  * #TALER_EXCHANGEDB_iterate_wire_transfers().
     28  *
     29  * `aggregation_tracking` records which deposit went into which wire
     30  * transfer.  do_aggregate() writes it, netting deposits against refunds and
     31  * charging the deposit fee only for coins that were not refunded in full --
     32  * the checks below pin that arithmetic down.
     33  */
     34 #include "test_common.h"
     35 #include "exchange-database/do_aggregate.h"
     36 #include "exchange-database/do_refund.h"
     37 #include "exchange-database/get_pending_aggregation.h"
     38 #include "exchange-database/get_transfer_by_deposit.h"
     39 #include "exchange-database/insert_wire_out.h"
     40 #include "exchange-database/iterate_aggregation_wtids_above_serial_id.h"
     41 #include "exchange-database/iterate_aggregations_above_serial_id.h"
     42 #include "exchange-database/iterate_wire_transfers.h"
     43 
     44 
     45 /**
     46  * Account the merchants of the checks are paid at.
     47  */
     48 static struct TDB_Account account;
     49 
     50 
     51 /**
     52  * Denomination the checks deposit.
     53  */
     54 static struct TDB_Denom denom;
     55 
     56 
     57 /**
     58  * Build a timestamp from a number of seconds since the epoch.
     59  *
     60  * @param secs seconds since the epoch
     61  * @return the timestamp
     62  */
     63 static struct GNUNET_TIME_Timestamp
     64 ts (uint64_t secs)
     65 {
     66   struct GNUNET_TIME_Absolute abs = {
     67     .abs_value_us = secs * 1000LLU * 1000LLU
     68   };
     69 
     70   return GNUNET_TIME_absolute_to_timestamp (abs);
     71 }
     72 
     73 
     74 /**
     75  * Closure for #wtid_cb().
     76  */
     77 struct WtidContext
     78 {
     79   /**
     80    * How many rows did the callback see?
     81    */
     82   unsigned int total;
     83 
     84   /**
     85    * Wire transfer we are looking for, NULL to match nothing.
     86    */
     87   const struct TALER_WireTransferIdentifierRawP *wtid;
     88 
     89   /**
     90    * How many times did we see it?
     91    */
     92   unsigned int matched;
     93 
     94   /**
     95    * Was the last matching row reported as pending?
     96    */
     97   bool pending;
     98 };
     99 
    100 
    101 /**
    102  * Callback for
    103  * #TALER_EXCHANGEDB_iterate_aggregation_wtids_above_serial_id().
    104  *
    105  * @param cls a `struct WtidContext *`
    106  * @param rowid row of the tracking entry
    107  * @param wtid wire transfer the deposit was aggregated into
    108  * @param wire_target_h_payto where the transfer goes
    109  * @param pending whether the transfer has not been made yet
    110  * @return #GNUNET_OK
    111  */
    112 static enum GNUNET_GenericReturnValue
    113 wtid_cb (void *cls,
    114          uint64_t rowid,
    115          const struct TALER_WireTransferIdentifierRawP *wtid,
    116          const struct TALER_FullPaytoHashP *wire_target_h_payto,
    117          bool pending)
    118 {
    119   struct WtidContext *ctx = cls;
    120 
    121   (void) rowid;
    122   (void) wire_target_h_payto;
    123   ctx->total++;
    124   if ( (NULL != ctx->wtid) &&
    125        (0 == GNUNET_memcmp (wtid,
    126                             ctx->wtid)) )
    127   {
    128     ctx->matched++;
    129     ctx->pending = pending;
    130   }
    131   return GNUNET_OK;
    132 }
    133 
    134 
    135 /**
    136  * Closure for #aggregation_cb() and #transfer_cb().
    137  */
    138 struct SumContext
    139 {
    140   /**
    141    * How many rows did the callback see?
    142    */
    143   unsigned int total;
    144 
    145   /**
    146    * Sum of the whole-unit parts of the amounts seen.
    147    */
    148   uint64_t value_sum;
    149 
    150   /**
    151    * Row of the first entry seen.
    152    */
    153   uint64_t first_row;
    154 };
    155 
    156 
    157 /**
    158  * Callback for #TALER_EXCHANGEDB_iterate_aggregations_above_serial_id().
    159  *
    160  * @param cls a `struct SumContext *`
    161  * @param amount how much was aggregated
    162  * @param tracking_serial_id row of the tracking entry
    163  * @param batch_deposit_serial_id deposit that was aggregated
    164  */
    165 static void
    166 aggregation_cb (void *cls,
    167                 const struct TALER_Amount *amount,
    168                 uint64_t tracking_serial_id,
    169                 uint64_t batch_deposit_serial_id)
    170 {
    171   struct SumContext *ctx = cls;
    172 
    173   (void) batch_deposit_serial_id;
    174   if (0 == ctx->total++)
    175     ctx->first_row = tracking_serial_id;
    176   ctx->value_sum += amount->value;
    177 }
    178 
    179 
    180 /**
    181  * Callback for #TALER_EXCHANGEDB_iterate_wire_transfers().
    182  *
    183  * @param cls a `struct SumContext *`
    184  * @param rowid row of the tracking entry
    185  * @param merchant_pub merchant that was paid
    186  * @param account_payto_uri account of the merchant
    187  * @param h_payto hash of that account
    188  * @param exchange_payto_uri account of the exchange
    189  * @param exec_time when the transfer was made
    190  * @param h_contract_terms contract that was paid
    191  * @param denom_pub denomination of the coin
    192  * @param coin_pub the coin that was deposited
    193  * @param coin_value how much the coin contributed
    194  * @param coin_fee how much the exchange charged for it
    195  */
    196 static void
    197 transfer_cb (void *cls,
    198              uint64_t rowid,
    199              const struct TALER_MerchantPublicKeyP *merchant_pub,
    200              const struct TALER_FullPayto account_payto_uri,
    201              const struct TALER_FullPaytoHashP *h_payto,
    202              const struct TALER_FullPayto exchange_payto_uri,
    203              struct GNUNET_TIME_Timestamp exec_time,
    204              const struct TALER_PrivateContractHashP *h_contract_terms,
    205              const struct TALER_DenominationPublicKey *denom_pub,
    206              const struct TALER_CoinSpendPublicKeyP *coin_pub,
    207              const struct TALER_Amount *coin_value,
    208              const struct TALER_Amount *coin_fee)
    209 {
    210   struct SumContext *ctx = cls;
    211 
    212   (void) merchant_pub;
    213   (void) account_payto_uri;
    214   (void) h_payto;
    215   (void) exchange_payto_uri;
    216   (void) exec_time;
    217   (void) h_contract_terms;
    218   (void) denom_pub;
    219   (void) coin_pub;
    220   (void) coin_fee;
    221   if (0 == ctx->total++)
    222     ctx->first_row = rowid;
    223   ctx->value_sum += coin_value->value;
    224 }
    225 
    226 
    227 /**
    228  * Nothing is reported while the table is empty, and aggregating an
    229  * account with no deposits comes out at zero.
    230  *
    231  * @param pg the database context
    232  * @return 0 on success
    233  */
    234 static int
    235 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    236 {
    237   struct TALER_MerchantPublicKeyP merchant_pub;
    238   struct TALER_WireTransferIdentifierRawP wtid;
    239   struct TALER_FullPaytoHashP h_payto;
    240   struct TALER_FullPayto payto_uri = { NULL };
    241   struct TALER_Amount total;
    242   struct TALER_Amount zero = TDB_amount ("0");
    243   struct TALER_Amount deposited;
    244   struct TALER_Amount refunded;
    245   struct TALER_Amount fee;
    246   struct WtidContext ctx = { 0 };
    247   struct SumContext sctx = { 0 };
    248 
    249   TDB_denom (pg,
    250              10,
    251              "5",
    252              "0.1",
    253              &denom);
    254   TDB_account (pg,
    255                10,
    256                &account);
    257   TDB_FILL (merchant_pub,
    258             1);
    259   TDB_FILL (wtid,
    260             1);
    261   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    262           TALER_EXCHANGEDB_do_aggregate (pg,
    263                                          &account.h_full,
    264                                          &merchant_pub,
    265                                          &wtid,
    266                                          &total));
    267   FAILIF (0 != TALER_amount_cmp (&total,
    268                                  &zero));
    269   FAILIF (0 != TDB_count (pg,
    270                           "FROM aggregation_tracking"));
    271   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    272           TALER_EXCHANGEDB_get_pending_aggregation (pg,
    273                                                     &wtid,
    274                                                     &h_payto,
    275                                                     &payto_uri,
    276                                                     &deposited,
    277                                                     &refunded,
    278                                                     &fee));
    279   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    280           TALER_EXCHANGEDB_iterate_aggregation_wtids_above_serial_id (
    281             pg,
    282             0,
    283             &wtid_cb,
    284             &ctx));
    285   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    286           TALER_EXCHANGEDB_iterate_aggregations_above_serial_id (
    287             pg,
    288             0,
    289             &aggregation_cb,
    290             &sctx));
    291   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    292           TALER_EXCHANGEDB_iterate_wire_transfers (pg,
    293                                                    &wtid,
    294                                                    &transfer_cb,
    295                                                    &sctx));
    296   FAILIF (0 != ctx.total);
    297   FAILIF (0 != sctx.total);
    298   return 0;
    299 }
    300 
    301 
    302 /**
    303  * Aggregating a deposit records it and nets out the deposit fee.
    304  *
    305  * @param pg the database context
    306  * @return 0 on success
    307  */
    308 static int
    309 check_aggregate (struct TALER_EXCHANGEDB_PostgresContext *pg)
    310 {
    311   struct TALER_CoinPublicInfo coin;
    312   struct TDB_Deposit dep;
    313   struct TALER_WireTransferIdentifierRawP wtid;
    314   struct TALER_FullPaytoHashP h_payto;
    315   struct TALER_FullPayto payto_uri = { NULL };
    316   struct TALER_Amount total;
    317   struct TALER_Amount deposited;
    318   struct TALER_Amount refunded;
    319   struct TALER_Amount fee;
    320   struct TALER_Amount expect_total = TDB_amount ("0.9");
    321   struct TALER_Amount expect_deposited = TDB_amount ("1");
    322   struct TALER_Amount expect_refunded = TDB_amount ("0");
    323   struct TALER_Amount expect_fee = TDB_amount ("0.1");
    324   struct WtidContext ctx;
    325 
    326   TDB_coin (pg,
    327             &denom,
    328             20,
    329             &coin,
    330             NULL);
    331   TDB_deposit (pg,
    332                &account,
    333                &coin,
    334                20,
    335                "1",
    336                "0.1",
    337                ts (1600000000),
    338                ts (1600000000),
    339                &dep);
    340   TDB_FILL (wtid,
    341             20);
    342   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    343             TALER_EXCHANGEDB_do_aggregate (pg,
    344                                            &account.h_full,
    345                                            &dep.merchant_pub,
    346                                            &wtid,
    347                                            &total),
    348             TDB_coin_free (&coin));
    349   FAILIF_C (0 != TALER_amount_cmp (&total,
    350                                    &expect_total),
    351             TDB_coin_free (&coin));
    352   FAILIF_C (1 != TDB_count (pg,
    353                             "FROM aggregation_tracking"),
    354             TDB_coin_free (&coin));
    355   /* the deposit is now marked as done */
    356   FAILIF_C (1 != TDB_count (pg,
    357                             "FROM batch_deposits WHERE done"),
    358             TDB_coin_free (&coin));
    359 
    360   /* the transfer has not been made yet, so it is pending */
    361   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    362             TALER_EXCHANGEDB_get_pending_aggregation (pg,
    363                                                       &wtid,
    364                                                       &h_payto,
    365                                                       &payto_uri,
    366                                                       &deposited,
    367                                                       &refunded,
    368                                                       &fee),
    369             TDB_coin_free (&coin));
    370   FAILIF_C (0 != GNUNET_memcmp (&h_payto,
    371                                 &account.h_full),
    372             GNUNET_free (payto_uri.full_payto); TDB_coin_free (&coin));
    373   FAILIF_C (0 != strcmp (payto_uri.full_payto,
    374                          account.payto.full_payto),
    375             GNUNET_free (payto_uri.full_payto); TDB_coin_free (&coin));
    376   GNUNET_free (payto_uri.full_payto);
    377   FAILIF_C (0 != TALER_amount_cmp (&deposited,
    378                                    &expect_deposited),
    379             TDB_coin_free (&coin));
    380   FAILIF_C (0 != TALER_amount_cmp (&refunded,
    381                                    &expect_refunded),
    382             TDB_coin_free (&coin));
    383   FAILIF_C (0 != TALER_amount_cmp (&fee,
    384                                    &expect_fee),
    385             TDB_coin_free (&coin));
    386 
    387   memset (&ctx,
    388           0,
    389           sizeof (ctx));
    390   ctx.wtid = &wtid;
    391   FAILIF_C (0 >=
    392             TALER_EXCHANGEDB_iterate_aggregation_wtids_above_serial_id (
    393               pg,
    394               0,
    395               &wtid_cb,
    396               &ctx),
    397             TDB_coin_free (&coin));
    398   FAILIF_C (1 != ctx.matched,
    399             TDB_coin_free (&coin));
    400   FAILIF_C (! ctx.pending,
    401             TDB_coin_free (&coin));
    402   TDB_coin_free (&coin);
    403   return 0;
    404 }
    405 
    406 
    407 /**
    408  * A deposit that was refunded in full is aggregated at zero and is not
    409  * charged a deposit fee.
    410  *
    411  * @param pg the database context
    412  * @return 0 on success
    413  */
    414 static int
    415 check_full_refund (struct TALER_EXCHANGEDB_PostgresContext *pg)
    416 {
    417   struct TALER_CoinPublicInfo coin;
    418   struct TDB_Deposit dep;
    419   struct TALER_EXCHANGEDB_Refund refund;
    420   struct TALER_Amount deposit_fee = TDB_amount ("0.1");
    421   struct TALER_WireTransferIdentifierRawP wtid;
    422   struct TALER_Amount total;
    423   struct TALER_Amount zero = TDB_amount ("0");
    424   bool not_found;
    425   bool refund_ok;
    426   bool gone;
    427   bool conflict;
    428 
    429   TDB_coin (pg,
    430             &denom,
    431             21,
    432             &coin,
    433             NULL);
    434   TDB_deposit (pg,
    435                &account,
    436                &coin,
    437                21,
    438                "1",
    439                "0.1",
    440                ts (1600000000),
    441                ts (1600000000),
    442                &dep);
    443   memset (&refund,
    444           0,
    445           sizeof (refund));
    446   refund.coin = coin;
    447   refund.details.merchant_pub = dep.merchant_pub;
    448   TDB_FILL (refund.details.merchant_sig,
    449             21);
    450   refund.details.h_contract_terms = dep.h_contract_terms;
    451   refund.details.rtransaction_id = 1;
    452   refund.details.refund_amount = TDB_amount ("1");
    453   refund.details.refund_fee = TDB_amount ("0");
    454   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    455             TALER_EXCHANGEDB_do_refund (pg,
    456                                         &refund,
    457                                         &deposit_fee,
    458                                         0,
    459                                         &not_found,
    460                                         &refund_ok,
    461                                         &gone,
    462                                         &conflict),
    463             TDB_coin_free (&coin));
    464   FAILIF_C (! refund_ok,
    465             TDB_coin_free (&coin));
    466 
    467   TDB_FILL (wtid,
    468             21);
    469   FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    470             TALER_EXCHANGEDB_do_aggregate (pg,
    471                                            &account.h_full,
    472                                            &dep.merchant_pub,
    473                                            &wtid,
    474                                            &total),
    475             TDB_coin_free (&coin));
    476   /* deposit EUR:1 minus refund EUR:1, and no deposit fee on a coin that
    477      was refunded in full */
    478   FAILIF_C (0 != TALER_amount_cmp (&total,
    479                                    &zero),
    480             TDB_coin_free (&coin));
    481   TDB_coin_free (&coin);
    482   return 0;
    483 }
    484 
    485 
    486 /**
    487  * Executing the wire transfer takes the aggregation out of "pending".
    488  *
    489  * @param pg the database context
    490  * @return 0 on success
    491  */
    492 static int
    493 check_executed (struct TALER_EXCHANGEDB_PostgresContext *pg)
    494 {
    495   struct TALER_WireTransferIdentifierRawP wtid;
    496   struct TALER_FullPaytoHashP h_payto;
    497   struct TALER_FullPayto payto_uri = { NULL };
    498   struct TALER_FullPayto exchange_payto = {
    499     .full_payto = (char *) "payto://x-taler-bank/localhost/exchange"
    500   };
    501   struct TALER_Amount amount = TDB_amount ("0.9");
    502   struct TALER_Amount deposited;
    503   struct TALER_Amount refunded;
    504   struct TALER_Amount fee;
    505   struct WtidContext ctx;
    506 
    507   TDB_FILL (wtid,
    508             20);
    509   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    510           TALER_EXCHANGEDB_insert_wire_out (pg,
    511                                             ts (1600003600),
    512                                             &wtid,
    513                                             &account.h_full,
    514                                             "exchange-account-1",
    515                                             exchange_payto,
    516                                             &amount,
    517                                             NULL));
    518   /* the aggregation is no longer pending... */
    519   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    520           TALER_EXCHANGEDB_get_pending_aggregation (pg,
    521                                                     &wtid,
    522                                                     &h_payto,
    523                                                     &payto_uri,
    524                                                     &deposited,
    525                                                     &refunded,
    526                                                     &fee));
    527   /* ...and the wtid iterator says so too */
    528   memset (&ctx,
    529           0,
    530           sizeof (ctx));
    531   ctx.wtid = &wtid;
    532   FAILIF (0 >=
    533           TALER_EXCHANGEDB_iterate_aggregation_wtids_above_serial_id (
    534             pg,
    535             0,
    536             &wtid_cb,
    537             &ctx));
    538   FAILIF (1 != ctx.matched);
    539   FAILIF (ctx.pending);
    540   return 0;
    541 }
    542 
    543 
    544 /**
    545  * The deposit can be traced to the wire transfer it went into, and the
    546  * transfer back to the deposits it paid.
    547  *
    548  * @param pg the database context
    549  * @return 0 on success
    550  */
    551 static int
    552 check_transfer_by_deposit (struct TALER_EXCHANGEDB_PostgresContext *pg)
    553 {
    554   struct TALER_CoinSpendPublicKeyP coin_pub;
    555   struct TDB_Deposit dep;
    556   struct TALER_WireTransferIdentifierRawP wtid;
    557   struct TALER_WireTransferIdentifierRawP got_wtid;
    558   struct GNUNET_TIME_Timestamp exec_time;
    559   struct TALER_Amount amount_with_fee;
    560   struct TALER_Amount deposit_fee;
    561   struct TALER_Amount expect_amount = TDB_amount ("1");
    562   struct TALER_EXCHANGEDB_KycStatus kyc;
    563   union TALER_AccountPublicKeyP account_pub;
    564   struct SumContext sctx;
    565   bool pending = true;
    566 
    567   /* the deposit of check_aggregate() */
    568   TDB_FILL (coin_pub,
    569             20);
    570   TDB_FILL (wtid,
    571             20);
    572   memset (&dep,
    573           0,
    574           sizeof (dep));
    575   TDB_FILL (dep.merchant_pub,
    576             20);
    577   TDB_FILL (dep.h_contract_terms,
    578             20);
    579   TDB_FILL (dep.wire_salt,
    580             20);
    581   TALER_merchant_wire_signature_hash (account.payto,
    582                                       &dep.wire_salt,
    583                                       &dep.h_wire);
    584   memset (&kyc,
    585           0,
    586           sizeof (kyc));
    587   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    588           TALER_EXCHANGEDB_get_transfer_by_deposit (pg,
    589                                                     &dep.h_contract_terms,
    590                                                     &dep.h_wire,
    591                                                     &coin_pub,
    592                                                     &dep.merchant_pub,
    593                                                     &pending,
    594                                                     &got_wtid,
    595                                                     &exec_time,
    596                                                     &amount_with_fee,
    597                                                     &deposit_fee,
    598                                                     &kyc,
    599                                                     &account_pub));
    600   FAILIF (pending);
    601   FAILIF (0 != GNUNET_memcmp (&got_wtid,
    602                               &wtid));
    603   FAILIF (0 != TALER_amount_cmp (&amount_with_fee,
    604                                  &expect_amount));
    605 
    606   /* a contract nobody deposited is not found */
    607   {
    608     struct TALER_PrivateContractHashP other;
    609 
    610     TDB_FILL (other,
    611               97);
    612     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    613             TALER_EXCHANGEDB_get_transfer_by_deposit (pg,
    614                                                       &other,
    615                                                       &dep.h_wire,
    616                                                       &coin_pub,
    617                                                       &dep.merchant_pub,
    618                                                       &pending,
    619                                                       &got_wtid,
    620                                                       &exec_time,
    621                                                       &amount_with_fee,
    622                                                       &deposit_fee,
    623                                                       &kyc,
    624                                                       &account_pub));
    625   }
    626 
    627   /* and the transfer lists the coins it paid for */
    628   memset (&sctx,
    629           0,
    630           sizeof (sctx));
    631   FAILIF (0 >=
    632           TALER_EXCHANGEDB_iterate_wire_transfers (pg,
    633                                                    &wtid,
    634                                                    &transfer_cb,
    635                                                    &sctx));
    636   FAILIF (1 != sctx.total);
    637   FAILIF (1 != sctx.value_sum);
    638   {
    639     struct TALER_WireTransferIdentifierRawP other;
    640 
    641     TDB_FILL (other,
    642               96);
    643     memset (&sctx,
    644             0,
    645             sizeof (sctx));
    646     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    647             TALER_EXCHANGEDB_iterate_wire_transfers (pg,
    648                                                      &other,
    649                                                      &transfer_cb,
    650                                                      &sctx));
    651     FAILIF (0 != sctx.total);
    652   }
    653   return 0;
    654 }
    655 
    656 
    657 /**
    658  * The aggregation iterator walks the table by serial.
    659  *
    660  * @param pg the database context
    661  * @return 0 on success
    662  */
    663 static int
    664 check_iterate_aggregations (struct TALER_EXCHANGEDB_PostgresContext *pg)
    665 {
    666   struct SumContext ctx;
    667 
    668   memset (&ctx,
    669           0,
    670           sizeof (ctx));
    671   FAILIF (2 !=
    672           TALER_EXCHANGEDB_iterate_aggregations_above_serial_id (
    673             pg,
    674             0,
    675             &aggregation_cb,
    676             &ctx));
    677   FAILIF (2 != ctx.total);
    678   memset (&ctx,
    679           0,
    680           sizeof (ctx));
    681   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    682           TALER_EXCHANGEDB_iterate_aggregations_above_serial_id (
    683             pg,
    684             1000,
    685             &aggregation_cb,
    686             &ctx));
    687   FAILIF (0 != ctx.total);
    688   return 0;
    689 }
    690 
    691 
    692 /**
    693  * The checks to run, in order.
    694  */
    695 static const struct TDB_Test tests[] = {
    696   { "aggregation-tracking-empty",
    697     &check_empty },
    698   { "aggregation-tracking-aggregate",
    699     &check_aggregate },
    700   { "aggregation-tracking-full-refund",
    701     &check_full_refund },
    702   { "aggregation-tracking-executed",
    703     &check_executed },
    704   { "aggregation-tracking-transfer-by-deposit",
    705     &check_transfer_by_deposit },
    706   { "aggregation-tracking-iterate-aggregations",
    707     &check_iterate_aggregations },
    708   { NULL, NULL }
    709 };
    710 
    711 
    712 int
    713 main (int argc,
    714       char *const *argv)
    715 {
    716   int ret;
    717 
    718   ret = TDB_main (argc,
    719                   argv,
    720                   "test-aggregation-tracking",
    721                   "Tests for the exchangedb `aggregation_tracking' table",
    722                   tests);
    723   TDB_account_free (&account);
    724   TDB_denom_free (&denom);
    725   return ret;
    726 }
    727 
    728 
    729 /* end of test_aggregation_tracking.c */