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

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file exchangedb/test_close_requests.c
      18              :  * @brief tests for the exchangedb functions whose primary table is
      19              :  *        `close_requests`
      20              :  * @author Christian Grothoff
      21              :  *
      22              :  * Covers #TALER_EXCHANGEDB_insert_close_request(),
      23              :  * #TALER_EXCHANGEDB_get_reserve_close_request_info() and
      24              :  * #TALER_EXCHANGEDB_iterate_unfinished_close_requests().
      25              :  *
      26              :  * The table references `reserves`, which TDB_reserve() creates.  The
      27              :  * iterator is destructive -- it marks everything it returns as done -- so
      28              :  * the checks that read the table run before it.
      29              :  */
      30              : #include "test_common.h"
      31              : #include "exchange-database/insert_close_request.h"
      32              : #include "exchange-database/get_reserve_close_request_info.h"
      33              : #include "exchange-database/iterate_unfinished_close_requests.h"
      34              : 
      35              : 
      36              : /**
      37              :  * Account the checks ask to be paid out to.
      38              :  */
      39              : #define PAYTO "payto://x-taler-bank/localhost/cr?receiver-name=CR"
      40              : 
      41              : 
      42              : /**
      43              :  * Build a timestamp from a number of seconds since the epoch.
      44              :  *
      45              :  * @param secs seconds since the epoch
      46              :  * @return the timestamp
      47              :  */
      48              : static struct GNUNET_TIME_Timestamp
      49            5 : ts (uint64_t secs)
      50              : {
      51            5 :   struct GNUNET_TIME_Absolute abs = {
      52            5 :     .abs_value_us = secs * 1000LLU * 1000LLU
      53              :   };
      54              : 
      55            5 :   return GNUNET_TIME_absolute_to_timestamp (abs);
      56              : }
      57              : 
      58              : 
      59              : /**
      60              :  * Closure for #expired_cb().
      61              :  */
      62              : struct ExpiredContext
      63              : {
      64              :   /**
      65              :    * How many requests did the callback see?
      66              :    */
      67              :   unsigned int total;
      68              : 
      69              :   /**
      70              :    * Reserve of the last request seen.
      71              :    */
      72              :   struct TALER_ReservePublicKeyP reserve_pub;
      73              : 
      74              :   /**
      75              :    * Balance of the last request seen.
      76              :    */
      77              :   struct TALER_Amount left;
      78              : 
      79              :   /**
      80              :    * Row of the last request seen.
      81              :    */
      82              :   uint64_t close_request_row;
      83              : 
      84              :   /**
      85              :    * Account of the last request seen, owned by this struct.
      86              :    */
      87              :   char *account;
      88              : };
      89              : 
      90              : 
      91              : /**
      92              :  * Callback for #TALER_EXCHANGEDB_iterate_unfinished_close_requests().
      93              :  *
      94              :  * @param cls a `struct ExpiredContext *`
      95              :  * @param reserve_pub the reserve to close
      96              :  * @param left balance to pay out
      97              :  * @param account_details where to pay it
      98              :  * @param expiration_date when the request was made
      99              :  * @param close_request_row row of the request
     100              :  * @return #GNUNET_OK
     101              :  */
     102              : static enum GNUNET_GenericReturnValue
     103            3 : expired_cb (void *cls,
     104              :             const struct TALER_ReservePublicKeyP *reserve_pub,
     105              :             const struct TALER_Amount *left,
     106              :             const struct TALER_FullPayto account_details,
     107              :             struct GNUNET_TIME_Timestamp expiration_date,
     108              :             uint64_t close_request_row)
     109              : {
     110            3 :   struct ExpiredContext *ctx = cls;
     111              : 
     112              :   (void) expiration_date;
     113            3 :   ctx->total++;
     114            3 :   ctx->reserve_pub = *reserve_pub;
     115            3 :   ctx->left = *left;
     116            3 :   ctx->close_request_row = close_request_row;
     117            3 :   GNUNET_free (ctx->account);
     118            6 :   ctx->account = (NULL == account_details.full_payto)
     119              :     ? NULL
     120            3 :     : GNUNET_strdup (account_details.full_payto);
     121            3 :   return GNUNET_OK;
     122              : }
     123              : 
     124              : 
     125              : /**
     126              :  * Look up the row of the close request of a reserve.
     127              :  *
     128              :  * @param pg the database context
     129              :  * @param reserve_pub reserve to look up
     130              :  * @return the row
     131              :  */
     132              : static uint64_t
     133            1 : request_row (struct TALER_EXCHANGEDB_PostgresContext *pg,
     134              :              const struct TALER_ReservePublicKeyP *reserve_pub)
     135              : {
     136            1 :   struct GNUNET_PQ_QueryParam params[] = {
     137            1 :     GNUNET_PQ_query_param_auto_from_type (reserve_pub),
     138              :     GNUNET_PQ_query_param_end
     139              :   };
     140              :   uint64_t row;
     141            1 :   struct GNUNET_PQ_ResultSpec rs[] = {
     142            1 :     GNUNET_PQ_result_spec_uint64 ("close_request_serial_id",
     143              :                                   &row),
     144              :     GNUNET_PQ_result_spec_end
     145              :   };
     146              : 
     147            1 :   GNUNET_assert (GNUNET_OK ==
     148              :                  GNUNET_PQ_prepare_anon (pg->conn,
     149              :                                          "SELECT close_request_serial_id"
     150              :                                          " FROM close_requests"
     151              :                                          " WHERE reserve_pub=$1;"));
     152            1 :   GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
     153              :                  GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
     154              :                                                            "",
     155              :                                                            params,
     156              :                                                            rs));
     157            1 :   return row;
     158              : }
     159              : 
     160              : 
     161              : /**
     162              :  * Ask for a reserve to be closed.
     163              :  *
     164              :  * @param pg the database context
     165              :  * @param reserve_pub reserve to close
     166              :  * @param seed seed for the reserve signature
     167              :  * @param when when the request was made, in seconds since the epoch
     168              :  * @param balance balance to pay out, e.g. "10"
     169              :  * @param fee closing fee, e.g. "0.5"
     170              :  * @return transaction status
     171              :  */
     172              : static enum GNUNET_DB_QueryStatus
     173            4 : add_request (struct TALER_EXCHANGEDB_PostgresContext *pg,
     174              :              const struct TALER_ReservePublicKeyP *reserve_pub,
     175              :              uint32_t seed,
     176              :              uint64_t when,
     177              :              const char *balance,
     178              :              const char *fee)
     179              : {
     180            4 :   struct TALER_FullPayto payto = {
     181              :     .full_payto = (char *) PAYTO
     182              :   };
     183              :   struct TALER_ReserveSignatureP reserve_sig;
     184            4 :   struct TALER_Amount b = TDB_amount (balance);
     185            4 :   struct TALER_Amount f = TDB_amount (fee);
     186              : 
     187            4 :   TDB_fill (&reserve_sig,
     188              :             sizeof (reserve_sig),
     189              :             seed);
     190            4 :   return TALER_EXCHANGEDB_insert_close_request (pg,
     191              :                                                 reserve_pub,
     192              :                                                 payto,
     193              :                                                 &reserve_sig,
     194              :                                                 ts (when),
     195              :                                                 &b,
     196              :                                                 &f);
     197              : }
     198              : 
     199              : 
     200              : /**
     201              :  * Nothing is known while the table is empty.
     202              :  *
     203              :  * @param pg the database context
     204              :  * @return 0 on success
     205              :  */
     206              : static int
     207            1 : check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
     208              : {
     209              :   struct TALER_ReservePublicKeyP reserve_pub;
     210              :   struct TALER_ReserveSignatureP reserve_sig;
     211              :   struct GNUNET_TIME_Timestamp request_timestamp;
     212              :   struct TALER_Amount close_balance;
     213              :   struct TALER_Amount close_fee;
     214            1 :   struct TALER_FullPayto payto = { NULL };
     215            1 :   struct ExpiredContext ctx = { 0 };
     216              : 
     217            1 :   TDB_FILL (reserve_pub,
     218              :             1);
     219            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     220              :           TALER_EXCHANGEDB_get_reserve_close_request_info (pg,
     221              :                                                            &reserve_pub,
     222              :                                                            1,
     223              :                                                            &reserve_sig,
     224              :                                                            &request_timestamp,
     225              :                                                            &close_balance,
     226              :                                                            &close_fee,
     227              :                                                            &payto));
     228            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     229              :           TALER_EXCHANGEDB_iterate_unfinished_close_requests (pg,
     230              :                                                               &expired_cb,
     231              :                                                               &ctx));
     232            1 :   FAILIF (0 != ctx.total);
     233            1 :   return 0;
     234              : }
     235              : 
     236              : 
     237              : /**
     238              :  * A close request is stored and found by (reserve, row).
     239              :  *
     240              :  * @param pg the database context
     241              :  * @return 0 on success
     242              :  */
     243              : static int
     244            1 : check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg)
     245              : {
     246              :   struct TALER_ReservePublicKeyP reserve_pub;
     247              :   struct TALER_ReserveSignatureP reserve_sig;
     248              :   struct TALER_ReserveSignatureP expect_sig;
     249              :   struct GNUNET_TIME_Timestamp request_timestamp;
     250              :   struct TALER_Amount close_balance;
     251              :   struct TALER_Amount close_fee;
     252            1 :   struct TALER_Amount expect_balance = TDB_amount ("10");
     253            1 :   struct TALER_Amount expect_fee = TDB_amount ("0.5");
     254            1 :   struct TALER_FullPayto payto = { NULL };
     255              :   uint64_t row;
     256              : 
     257            1 :   TDB_reserve (pg,
     258              :                10,
     259              :                "10",
     260              :                &reserve_pub);
     261            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     262              :           add_request (pg,
     263              :                        &reserve_pub,
     264              :                        10,
     265              :                        1600000000,
     266              :                        "10",
     267              :                        "0.5"));
     268            1 :   row = request_row (pg,
     269              :                      &reserve_pub);
     270            1 :   TDB_FILL (expect_sig,
     271              :             10);
     272            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     273              :           TALER_EXCHANGEDB_get_reserve_close_request_info (pg,
     274              :                                                            &reserve_pub,
     275              :                                                            row,
     276              :                                                            &reserve_sig,
     277              :                                                            &request_timestamp,
     278              :                                                            &close_balance,
     279              :                                                            &close_fee,
     280              :                                                            &payto));
     281            1 :   FAILIF (0 != GNUNET_memcmp (&reserve_sig,
     282              :                               &expect_sig));
     283            1 :   FAILIF (GNUNET_TIME_timestamp_cmp (request_timestamp,
     284              :                                      !=,
     285              :                                      ts (1600000000)));
     286            1 :   FAILIF (0 != TALER_amount_cmp (&close_balance,
     287              :                                  &expect_balance));
     288            1 :   FAILIF (0 != TALER_amount_cmp (&close_fee,
     289              :                                  &expect_fee));
     290            1 :   FAILIF (NULL == payto.full_payto);
     291            1 :   FAILIF_C (0 != strcmp (payto.full_payto,
     292              :                          PAYTO),
     293              :             GNUNET_free (payto.full_payto));
     294            1 :   GNUNET_free (payto.full_payto);
     295              : 
     296              :   /* the row has to belong to the reserve that is asked about */
     297            1 :   payto.full_payto = NULL;
     298            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     299              :           TALER_EXCHANGEDB_get_reserve_close_request_info (pg,
     300              :                                                            &reserve_pub,
     301              :                                                            row + 1000,
     302              :                                                            &reserve_sig,
     303              :                                                            &request_timestamp,
     304              :                                                            &close_balance,
     305              :                                                            &close_fee,
     306              :                                                            &payto));
     307              :   {
     308              :     struct TALER_ReservePublicKeyP other;
     309              : 
     310            1 :     TDB_FILL (other,
     311              :               99);
     312            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     313              :             TALER_EXCHANGEDB_get_reserve_close_request_info (pg,
     314              :                                                              &other,
     315              :                                                              row,
     316              :                                                              &reserve_sig,
     317              :                                                              &request_timestamp,
     318              :                                                              &close_balance,
     319              :                                                              &close_fee,
     320              :                                                              &payto));
     321              :   }
     322              : 
     323              :   /* a second request at the same timestamp is absorbed */
     324            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     325              :           add_request (pg,
     326              :                        &reserve_pub,
     327              :                        11,
     328              :                        1600000000,
     329              :                        "10",
     330              :                        "0.5"));
     331            1 :   FAILIF (1 != TDB_count (pg,
     332              :                           "FROM close_requests"));
     333              :   /* ...but one at another timestamp is a second request */
     334            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     335              :           add_request (pg,
     336              :                        &reserve_pub,
     337              :                        11,
     338              :                        1600003600,
     339              :                        "10",
     340              :                        "0.5"));
     341            1 :   FAILIF (2 != TDB_count (pg,
     342              :                           "FROM close_requests"));
     343            1 :   return 0;
     344              : }
     345              : 
     346              : 
     347              : /**
     348              :  * The iterator hands over every unfinished request exactly once, marking
     349              :  * it done as it goes.
     350              :  *
     351              :  * @param pg the database context
     352              :  * @return 0 on success
     353              :  */
     354              : static int
     355            1 : check_iterate_unfinished (struct TALER_EXCHANGEDB_PostgresContext *pg)
     356              : {
     357              :   struct ExpiredContext ctx;
     358              : 
     359            1 :   FAILIF (2 != TDB_count (pg,
     360              :                           "FROM close_requests WHERE NOT done"));
     361            1 :   memset (&ctx,
     362              :           0,
     363              :           sizeof (ctx));
     364            1 :   FAILIF (2 !=
     365              :           TALER_EXCHANGEDB_iterate_unfinished_close_requests (pg,
     366              :                                                               &expired_cb,
     367              :                                                               &ctx));
     368            1 :   FAILIF_C (2 != ctx.total,
     369              :             GNUNET_free (ctx.account));
     370            1 :   FAILIF_C (NULL == ctx.account,
     371              :             GNUNET_free (ctx.account));
     372            1 :   FAILIF_C (0 != strcmp (ctx.account,
     373              :                          PAYTO),
     374              :             GNUNET_free (ctx.account));
     375            1 :   FAILIF_C (0 == ctx.close_request_row,
     376              :             GNUNET_free (ctx.account));
     377            1 :   GNUNET_free (ctx.account);
     378            1 :   FAILIF (0 != TDB_count (pg,
     379              :                           "FROM close_requests WHERE NOT done"));
     380              : 
     381              :   /* everything is done now, so a second sweep finds nothing */
     382            1 :   memset (&ctx,
     383              :           0,
     384              :           sizeof (ctx));
     385            1 :   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
     386              :           TALER_EXCHANGEDB_iterate_unfinished_close_requests (pg,
     387              :                                                               &expired_cb,
     388              :                                                               &ctx));
     389            1 :   FAILIF (0 != ctx.total);
     390              : 
     391              :   /* a fresh request is picked up again */
     392              :   {
     393              :     struct TALER_ReservePublicKeyP reserve_pub;
     394              : 
     395            1 :     TDB_reserve (pg,
     396              :                  12,
     397              :                  "3",
     398              :                  &reserve_pub);
     399            1 :     FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
     400              :             add_request (pg,
     401              :                          &reserve_pub,
     402              :                          12,
     403              :                          1600007200,
     404              :                          "3",
     405              :                          "0.5"));
     406            1 :     memset (&ctx,
     407              :             0,
     408              :             sizeof (ctx));
     409            1 :     FAILIF (1 !=
     410              :             TALER_EXCHANGEDB_iterate_unfinished_close_requests (pg,
     411              :                                                                 &expired_cb,
     412              :                                                                 &ctx));
     413            1 :     FAILIF_C (0 != GNUNET_memcmp (&ctx.reserve_pub,
     414              :                                   &reserve_pub),
     415              :               GNUNET_free (ctx.account));
     416            1 :     GNUNET_free (ctx.account);
     417              :   }
     418            1 :   return 0;
     419              : }
     420              : 
     421              : 
     422              : /**
     423              :  * The checks to run, in order.
     424              :  */
     425              : static const struct TDB_Test tests[] = {
     426              :   { "close-requests-empty",
     427              :     &check_empty },
     428              :   { "close-requests-insert-and-lookup",
     429              :     &check_insert_and_lookup },
     430              :   { "close-requests-iterate-unfinished",
     431              :     &check_iterate_unfinished },
     432              :   { NULL, NULL }
     433              : };
     434              : 
     435              : 
     436              : int
     437            1 : main (int argc,
     438              :       char *const *argv)
     439              : {
     440            1 :   return TDB_main (argc,
     441              :                    argv,
     442              :                    "test-close-requests",
     443              :                    "Tests for the exchangedb `close_requests' table",
     444              :                    tests);
     445              : }
     446              : 
     447              : 
     448              : /* end of test_close_requests.c */
        

Generated by: LCOV version 2.0-1