exchange

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

test_profit_drains.c (12435B)


      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_profit_drains.c
     18  * @brief tests for the exchangedb functions whose primary table is
     19  *        `profit_drains`
     20  * @author Christian Grothoff
     21  *
     22  * Covers #TALER_EXCHANGEDB_insert_profit_drain(),
     23  * #TALER_EXCHANGEDB_get_profit_drain(),
     24  * #TALER_EXCHANGEDB_get_pending_profit_drain() and
     25  * #TALER_EXCHANGEDB_update_to_profit_drain_finished().
     26  *
     27  * `profit_drains` has no foreign keys.  The pending lookup picks the
     28  * oldest not-yet-executed request, so the checks insert two out of order
     29  * and drain them one by one.
     30  */
     31 #include "test_common.h"
     32 #include "exchange-database/insert_profit_drain.h"
     33 #include "exchange-database/get_profit_drain.h"
     34 #include "exchange-database/get_pending_profit_drain.h"
     35 #include "exchange-database/update_to_profit_drain_finished.h"
     36 
     37 
     38 /**
     39  * Build a timestamp from a number of seconds since the epoch.
     40  *
     41  * @param secs seconds since the epoch
     42  * @return the timestamp
     43  */
     44 static struct GNUNET_TIME_Timestamp
     45 ts (uint64_t secs)
     46 {
     47   struct GNUNET_TIME_Absolute abs = {
     48     .abs_value_us = secs * 1000LLU * 1000LLU
     49   };
     50 
     51   return GNUNET_TIME_absolute_to_timestamp (abs);
     52 }
     53 
     54 
     55 /**
     56  * Insert a drain request.
     57  *
     58  * @param pg the database context
     59  * @param seed seed for the wire transfer identifier and signature
     60  * @param when when was the request made
     61  * @param amount amount to drain, e.g. "10"
     62  * @param[out] wtid set to the wire transfer identifier used
     63  * @return transaction status
     64  */
     65 static enum GNUNET_DB_QueryStatus
     66 add_drain (struct TALER_EXCHANGEDB_PostgresContext *pg,
     67            uint32_t seed,
     68            uint64_t when,
     69            const char *amount,
     70            struct TALER_WireTransferIdentifierRawP *wtid)
     71 {
     72   struct TALER_FullPayto payto = {
     73     .full_payto = (char *) "payto://x-taler-bank/localhost/profits"
     74   };
     75   struct TALER_Amount a = TDB_amount (amount);
     76   struct TALER_MasterSignatureP master_sig;
     77 
     78   TDB_fill (wtid,
     79             sizeof (*wtid),
     80             seed);
     81   TDB_FILL (master_sig,
     82             seed);
     83   return TALER_EXCHANGEDB_insert_profit_drain (pg,
     84                                                wtid,
     85                                                "exchange-account-1",
     86                                                payto,
     87                                                ts (when),
     88                                                &a,
     89                                                &master_sig);
     90 }
     91 
     92 
     93 /**
     94  * Nothing is pending and nothing is found while the table is empty.
     95  *
     96  * @param pg the database context
     97  * @return 0 on success
     98  */
     99 static int
    100 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg)
    101 {
    102   struct TALER_WireTransferIdentifierRawP wtid;
    103   struct TALER_FullPayto payto_uri;
    104   struct GNUNET_TIME_Timestamp request_timestamp;
    105   struct TALER_Amount amount;
    106   struct TALER_MasterSignatureP master_sig;
    107   char *account_section = NULL;
    108   uint64_t serial;
    109 
    110   TDB_FILL (wtid,
    111             1);
    112   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    113           TALER_EXCHANGEDB_get_profit_drain (pg,
    114                                              &wtid,
    115                                              &serial,
    116                                              &account_section,
    117                                              &payto_uri,
    118                                              &request_timestamp,
    119                                              &amount,
    120                                              &master_sig));
    121   FAILIF (NULL != account_section);
    122   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    123           TALER_EXCHANGEDB_get_pending_profit_drain (pg,
    124                                                      &serial,
    125                                                      &wtid,
    126                                                      &account_section,
    127                                                      &payto_uri,
    128                                                      &request_timestamp,
    129                                                      &amount,
    130                                                      &master_sig));
    131   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    132           TALER_EXCHANGEDB_update_to_profit_drain_finished (pg,
    133                                                             1));
    134   return 0;
    135 }
    136 
    137 
    138 /**
    139  * A drain request is stored, found by its wire transfer identifier and
    140  * reported as pending.
    141  *
    142  * @param pg the database context
    143  * @return 0 on success
    144  */
    145 static int
    146 check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg)
    147 {
    148   struct TALER_WireTransferIdentifierRawP wtid;
    149   struct TALER_WireTransferIdentifierRawP unknown;
    150   struct TALER_WireTransferIdentifierRawP got_wtid;
    151   struct TALER_FullPayto payto_uri = { NULL };
    152   struct GNUNET_TIME_Timestamp request_timestamp;
    153   struct TALER_Amount amount;
    154   struct TALER_Amount expect = TDB_amount ("10");
    155   struct TALER_MasterSignatureP master_sig;
    156   struct TALER_MasterSignatureP expect_sig;
    157   char *account_section = NULL;
    158   uint64_t serial = 0;
    159 
    160   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    161           add_drain (pg,
    162                      10,
    163                      1600000000,
    164                      "10",
    165                      &wtid));
    166   TDB_FILL (expect_sig,
    167             10);
    168   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    169           TALER_EXCHANGEDB_get_profit_drain (pg,
    170                                              &wtid,
    171                                              &serial,
    172                                              &account_section,
    173                                              &payto_uri,
    174                                              &request_timestamp,
    175                                              &amount,
    176                                              &master_sig));
    177   FAILIF (0 == serial);
    178   FAILIF (0 != strcmp (account_section,
    179                        "exchange-account-1"));
    180   GNUNET_free (account_section);
    181   FAILIF (0 != strcmp (payto_uri.full_payto,
    182                        "payto://x-taler-bank/localhost/profits"));
    183   GNUNET_free (payto_uri.full_payto);
    184   FAILIF (0 != TALER_amount_cmp (&amount,
    185                                  &expect));
    186   FAILIF (GNUNET_TIME_timestamp_cmp (request_timestamp,
    187                                      !=,
    188                                      ts (1600000000)));
    189   FAILIF (0 != GNUNET_memcmp (&master_sig,
    190                               &expect_sig));
    191 
    192   /* a wire transfer identifier we never used is not found */
    193   TDB_FILL (unknown,
    194             99);
    195   account_section = NULL;
    196   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    197           TALER_EXCHANGEDB_get_profit_drain (pg,
    198                                              &unknown,
    199                                              &serial,
    200                                              &account_section,
    201                                              &payto_uri,
    202                                              &request_timestamp,
    203                                              &amount,
    204                                              &master_sig));
    205 
    206   account_section = NULL;
    207   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    208           TALER_EXCHANGEDB_get_pending_profit_drain (pg,
    209                                                      &serial,
    210                                                      &got_wtid,
    211                                                      &account_section,
    212                                                      &payto_uri,
    213                                                      &request_timestamp,
    214                                                      &amount,
    215                                                      &master_sig));
    216   GNUNET_free (account_section);
    217   GNUNET_free (payto_uri.full_payto);
    218   FAILIF (0 != GNUNET_memcmp (&got_wtid,
    219                               &wtid));
    220   return 0;
    221 }
    222 
    223 
    224 /**
    225  * The oldest unexecuted request is the pending one, and marking it
    226  * executed hands the next one over.
    227  *
    228  * @param pg the database context
    229  * @return 0 on success
    230  */
    231 static int
    232 check_pending_order (struct TALER_EXCHANGEDB_PostgresContext *pg)
    233 {
    234   struct TALER_WireTransferIdentifierRawP older;
    235   struct TALER_WireTransferIdentifierRawP got_wtid;
    236   struct TALER_FullPayto payto_uri = { NULL };
    237   struct GNUNET_TIME_Timestamp request_timestamp;
    238   struct TALER_Amount amount;
    239   struct TALER_MasterSignatureP master_sig;
    240   char *account_section = NULL;
    241   uint64_t serial = 0;
    242 
    243   /* inserted second, but with an earlier trigger date */
    244   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    245           add_drain (pg,
    246                      11,
    247                      1500000000,
    248                      "3",
    249                      &older));
    250   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    251           TALER_EXCHANGEDB_get_pending_profit_drain (pg,
    252                                                      &serial,
    253                                                      &got_wtid,
    254                                                      &account_section,
    255                                                      &payto_uri,
    256                                                      &request_timestamp,
    257                                                      &amount,
    258                                                      &master_sig));
    259   GNUNET_free (account_section);
    260   GNUNET_free (payto_uri.full_payto);
    261   FAILIF (0 != GNUNET_memcmp (&got_wtid,
    262                               &older));
    263 
    264   /* draining it hands over the remaining one */
    265   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    266           TALER_EXCHANGEDB_update_to_profit_drain_finished (pg,
    267                                                             serial));
    268   account_section = NULL;
    269   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    270           TALER_EXCHANGEDB_get_pending_profit_drain (pg,
    271                                                      &serial,
    272                                                      &got_wtid,
    273                                                      &account_section,
    274                                                      &payto_uri,
    275                                                      &request_timestamp,
    276                                                      &amount,
    277                                                      &master_sig));
    278   GNUNET_free (account_section);
    279   GNUNET_free (payto_uri.full_payto);
    280   FAILIF (0 == GNUNET_memcmp (&got_wtid,
    281                               &older));
    282 
    283   /* and once both are executed, nothing is pending */
    284   FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
    285           TALER_EXCHANGEDB_update_to_profit_drain_finished (pg,
    286                                                             serial));
    287   account_section = NULL;
    288   FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
    289           TALER_EXCHANGEDB_get_pending_profit_drain (pg,
    290                                                      &serial,
    291                                                      &got_wtid,
    292                                                      &account_section,
    293                                                      &payto_uri,
    294                                                      &request_timestamp,
    295                                                      &amount,
    296                                                      &master_sig));
    297   /* the executed rows are still on file */
    298   FAILIF (2 != TDB_count (pg,
    299                           "FROM profit_drains WHERE executed"));
    300   return 0;
    301 }
    302 
    303 
    304 /**
    305  * The checks to run, in order.
    306  */
    307 static const struct TDB_Test tests[] = {
    308   { "profit-drains-empty",
    309     &check_empty },
    310   { "profit-drains-insert-and-lookup",
    311     &check_insert_and_lookup },
    312   { "profit-drains-pending-order",
    313     &check_pending_order },
    314   { NULL, NULL }
    315 };
    316 
    317 
    318 int
    319 main (int argc,
    320       char *const *argv)
    321 {
    322   return TDB_main (argc,
    323                    argv,
    324                    "test-profit-drains",
    325                    "Tests for the exchangedb `profit_drains' table",
    326                    tests);
    327 }
    328 
    329 
    330 /* end of test_profit_drains.c */