LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_reserve_history.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 62.6 % 219 137
Test Date: 2025-12-26 23:00:34 Functions: 100.0 % 7 7

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2014-2024 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify
       6              :   it under the terms of the GNU General Public License as
       7              :   published by the Free Software Foundation; either version 3, or
       8              :   (at your option) any later version.
       9              : 
      10              :   TALER is distributed in the hope that it will be useful, but
      11              :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13              :   GNU General Public License for more details.
      14              : 
      15              :   You should have received a copy of the GNU General Public
      16              :   License along with TALER; see the file COPYING.  If not, see
      17              :   <http://www.gnu.org/licenses/>
      18              : */
      19              : /**
      20              :  * @file testing/testing_api_cmd_reserve_history.c
      21              :  * @brief Implement the /reserve/history test command.
      22              :  * @author Marcello Stanisci
      23              :  */
      24              : #include "taler/platform.h"
      25              : #include "taler/taler_json_lib.h"
      26              : #include <gnunet/gnunet_curl_lib.h>
      27              : #include "taler/taler_testing_lib.h"
      28              : 
      29              : 
      30              : /**
      31              :  * State for a "history" CMD.
      32              :  */
      33              : struct HistoryState
      34              : {
      35              : 
      36              :   /**
      37              :    * Public key of the reserve being analyzed.
      38              :    */
      39              :   struct TALER_ReservePublicKeyP reserve_pub;
      40              : 
      41              :   /**
      42              :    * Label to the command which created the reserve to check,
      43              :    * needed to resort the reserve key.
      44              :    */
      45              :   const char *reserve_reference;
      46              : 
      47              :   /**
      48              :    * Handle to the "reserve history" operation.
      49              :    */
      50              :   struct TALER_EXCHANGE_ReservesHistoryHandle *rsh;
      51              : 
      52              :   /**
      53              :    * Expected reserve balance.
      54              :    */
      55              :   const char *expected_balance;
      56              : 
      57              :   /**
      58              :    * Private key of the reserve being analyzed.
      59              :    */
      60              :   const struct TALER_ReservePrivateKeyP *reserve_priv;
      61              : 
      62              :   /**
      63              :    * Interpreter state.
      64              :    */
      65              :   struct TALER_TESTING_Interpreter *is;
      66              : 
      67              :   /**
      68              :    * Expected HTTP response code.
      69              :    */
      70              :   unsigned int expected_response_code;
      71              : 
      72              : };
      73              : 
      74              : 
      75              : /**
      76              :  * Closure for analysis_cb().
      77              :  */
      78              : struct AnalysisContext
      79              : {
      80              :   /**
      81              :    * Reserve public key we are looking at.
      82              :    */
      83              :   const struct TALER_ReservePublicKeyP *reserve_pub;
      84              : 
      85              :   /**
      86              :    * Length of the @e history array.
      87              :    */
      88              :   unsigned int history_length;
      89              : 
      90              :   /**
      91              :    * Array of history items to match.
      92              :    */
      93              :   const struct TALER_EXCHANGE_ReserveHistoryEntry *history;
      94              : 
      95              :   /**
      96              :    * Array of @e history_length of matched entries.
      97              :    */
      98              :   bool *found;
      99              : 
     100              :   /**
     101              :    * Set to true if an entry could not be found.
     102              :    */
     103              :   bool failure;
     104              : };
     105              : 
     106              : 
     107              : /**
     108              :  * Compare @a h1 and @a h2.
     109              :  *
     110              :  * @param h1 a history entry
     111              :  * @param h2 a history entry
     112              :  * @return 0 if @a h1 and @a h2 are equal
     113              :  */
     114              : static int
     115           23 : history_entry_cmp (
     116              :   const struct TALER_EXCHANGE_ReserveHistoryEntry *h1,
     117              :   const struct TALER_EXCHANGE_ReserveHistoryEntry *h2)
     118              : {
     119           23 :   if (h1->type != h2->type)
     120            0 :     return 1;
     121           23 :   switch (h1->type)
     122              :   {
     123            8 :   case TALER_EXCHANGE_RTT_CREDIT:
     124            8 :     if ( (0 ==
     125            8 :           TALER_amount_cmp (&h1->amount,
     126            8 :                             &h2->amount)) &&
     127              :          (0 ==
     128            8 :           TALER_full_payto_cmp (h1->details.in_details.sender_url,
     129            8 :                                 h2->details.in_details.sender_url)) &&
     130            8 :          (h1->details.in_details.wire_reference ==
     131            8 :           h2->details.in_details.wire_reference) &&
     132            8 :          (GNUNET_TIME_timestamp_cmp (h1->details.in_details.timestamp,
     133              :                                      ==,
     134              :                                      h2->details.in_details.timestamp)) )
     135            8 :       return 0;
     136            0 :     return 1;
     137            7 :   case TALER_EXCHANGE_RTT_WITHDRAWAL:
     138            7 :     if ( (0 ==
     139            7 :           TALER_amount_cmp (&h1->amount,
     140            7 :                             &h2->amount)) &&
     141              :          (0 ==
     142            7 :           TALER_amount_cmp (&h1->details.withdraw.fee,
     143            7 :                             &h2->details.withdraw.fee)) &&
     144            7 :          (h1->details.withdraw.age_restricted ==
     145            7 :           h2->details.withdraw.age_restricted) &&
     146            7 :          ((! h1->details.withdraw.age_restricted) ||
     147            0 :           (h1->details.withdraw.max_age == h2->details.withdraw.max_age) ))
     148            7 :       return 0;
     149            0 :     return 1;
     150            0 :   case TALER_EXCHANGE_RTT_RECOUP:
     151              :     /* exchange_sig, exchange_pub and timestamp are NOT available
     152              :        from the original recoup response, hence here NOT check(able/ed) */
     153            0 :     if ( (0 ==
     154            0 :           TALER_amount_cmp (&h1->amount,
     155            0 :                             &h2->amount)) &&
     156              :          (0 ==
     157            0 :           GNUNET_memcmp (&h1->details.recoup_details.coin_pub,
     158              :                          &h2->details.recoup_details.coin_pub)) )
     159            0 :       return 0;
     160            0 :     return 1;
     161            0 :   case TALER_EXCHANGE_RTT_CLOSING:
     162              :     /* testing_api_cmd_exec_closer doesn't set the
     163              :        receiver_account_details, exchange_sig, exchange_pub or wtid or timestamp
     164              :        so we cannot test for it here. but if the amount matches,
     165              :        that should be good enough. */
     166            0 :     if ( (0 ==
     167            0 :           TALER_amount_cmp (&h1->amount,
     168            0 :                             &h2->amount)) &&
     169              :          (0 ==
     170            0 :           TALER_amount_cmp (&h1->details.close_details.fee,
     171              :                             &h2->details.close_details.fee)) )
     172            0 :       return 0;
     173            0 :     return 1;
     174            8 :   case TALER_EXCHANGE_RTT_MERGE:
     175            8 :     if ( (0 ==
     176            8 :           TALER_amount_cmp (&h1->amount,
     177            8 :                             &h2->amount)) &&
     178              :          (0 ==
     179            8 :           TALER_amount_cmp (&h1->details.merge_details.purse_fee,
     180            8 :                             &h2->details.merge_details.purse_fee)) &&
     181            8 :          (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.merge_timestamp,
     182              :                                      ==,
     183              :                                      h2->details.merge_details.merge_timestamp))
     184            8 :          &&
     185            8 :          (GNUNET_TIME_timestamp_cmp (h1->details.merge_details.purse_expiration,
     186              :                                      ==,
     187              :                                      h2->details.merge_details.purse_expiration)
     188              :          )
     189            8 :          &&
     190              :          (0 ==
     191            8 :           GNUNET_memcmp (&h1->details.merge_details.merge_pub,
     192            8 :                          &h2->details.merge_details.merge_pub)) &&
     193              :          (0 ==
     194            8 :           GNUNET_memcmp (&h1->details.merge_details.h_contract_terms,
     195            8 :                          &h2->details.merge_details.h_contract_terms)) &&
     196              :          (0 ==
     197            8 :           GNUNET_memcmp (&h1->details.merge_details.purse_pub,
     198            8 :                          &h2->details.merge_details.purse_pub)) &&
     199              :          (0 ==
     200            8 :           GNUNET_memcmp (&h1->details.merge_details.reserve_sig,
     201            8 :                          &h2->details.merge_details.reserve_sig)) &&
     202            8 :          (h1->details.merge_details.min_age ==
     203            8 :           h2->details.merge_details.min_age) &&
     204            8 :          (h1->details.merge_details.flags ==
     205            8 :           h2->details.merge_details.flags) )
     206            8 :       return 0;
     207            0 :     return 1;
     208            0 :   case TALER_EXCHANGE_RTT_OPEN:
     209            0 :     if ( (0 ==
     210            0 :           TALER_amount_cmp (&h1->amount,
     211            0 :                             &h2->amount)) &&
     212            0 :          (GNUNET_TIME_timestamp_cmp (
     213              :             h1->details.open_request.request_timestamp,
     214              :             ==,
     215            0 :             h2->details.open_request.request_timestamp)) &&
     216            0 :          (GNUNET_TIME_timestamp_cmp (
     217              :             h1->details.open_request.reserve_expiration,
     218              :             ==,
     219            0 :             h2->details.open_request.reserve_expiration)) &&
     220            0 :          (h1->details.open_request.purse_limit ==
     221            0 :           h2->details.open_request.purse_limit) &&
     222              :          (0 ==
     223            0 :           TALER_amount_cmp (&h1->details.open_request.reserve_payment,
     224            0 :                             &h2->details.open_request.reserve_payment)) &&
     225              :          (0 ==
     226            0 :           GNUNET_memcmp (&h1->details.open_request.reserve_sig,
     227              :                          &h2->details.open_request.reserve_sig)) )
     228            0 :       return 0;
     229            0 :     return 1;
     230            0 :   case TALER_EXCHANGE_RTT_CLOSE:
     231            0 :     if ( (0 ==
     232            0 :           TALER_amount_cmp (&h1->amount,
     233            0 :                             &h2->amount)) &&
     234            0 :          (GNUNET_TIME_timestamp_cmp (
     235              :             h1->details.close_request.request_timestamp,
     236              :             ==,
     237            0 :             h2->details.close_request.request_timestamp)) &&
     238              :          (0 ==
     239            0 :           GNUNET_memcmp (&h1->details.close_request.target_account_h_payto,
     240            0 :                          &h2->details.close_request.target_account_h_payto)) &&
     241              :          (0 ==
     242            0 :           GNUNET_memcmp (&h1->details.close_request.reserve_sig,
     243              :                          &h2->details.close_request.reserve_sig)) )
     244            0 :       return 0;
     245            0 :     return 1;
     246              :   }
     247            0 :   GNUNET_assert (0);
     248              :   return 1;
     249              : }
     250              : 
     251              : 
     252              : /**
     253              :  * Check if @a cmd changed the reserve, if so, find the
     254              :  * entry in our history and set the respective index in found
     255              :  * to true. If the entry is not found, set failure.
     256              :  *
     257              :  * @param cls our `struct AnalysisContext *`
     258              :  * @param cmd command to analyze for impact on history
     259              :  */
     260              : static void
     261          584 : analyze_command (void *cls,
     262              :                  const struct TALER_TESTING_Command *cmd)
     263              : {
     264          584 :   struct AnalysisContext *ac = cls;
     265          584 :   const struct TALER_ReservePublicKeyP *reserve_pub = ac->reserve_pub;
     266          584 :   const struct TALER_EXCHANGE_ReserveHistoryEntry *history = ac->history;
     267          584 :   unsigned int history_length = ac->history_length;
     268          584 :   bool *found = ac->found;
     269              : 
     270          584 :   if (TALER_TESTING_cmd_is_batch (cmd))
     271              :   {
     272              :     struct TALER_TESTING_Command *cur;
     273              :     struct TALER_TESTING_Command *bcmd;
     274              : 
     275           47 :     cur = TALER_TESTING_cmd_batch_get_current (cmd);
     276           47 :     if (GNUNET_OK !=
     277           47 :         TALER_TESTING_get_trait_batch_cmds (cmd,
     278              :                                             &bcmd))
     279              :     {
     280            0 :       GNUNET_break (0);
     281            0 :       ac->failure = true;
     282            0 :       return;
     283              :     }
     284          552 :     for (unsigned int i = 0; NULL != bcmd[i].label; i++)
     285              :     {
     286          513 :       struct TALER_TESTING_Command *step = &bcmd[i];
     287              : 
     288          513 :       analyze_command (ac,
     289              :                        step);
     290          513 :       if (ac->failure)
     291              :       {
     292            0 :         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     293              :                     "Entry for batch step `%s' missing in reserve history\n",
     294              :                     step->label);
     295            0 :         return;
     296              :       }
     297          513 :       if (step == cur)
     298            8 :         break; /* if *we* are in a batch, make sure not to analyze commands past 'now' */
     299              :     }
     300           47 :     return;
     301              :   }
     302              : 
     303              :   {
     304              :     const struct TALER_ReservePublicKeyP *rp;
     305          537 :     bool matched = false;
     306              : 
     307          537 :     if (GNUNET_OK !=
     308          537 :         TALER_TESTING_get_trait_reserve_pub (cmd,
     309              :                                              &rp))
     310          514 :       return; /* command does nothing for reserves */
     311          104 :     if (0 !=
     312          104 :         GNUNET_memcmp (rp,
     313              :                        reserve_pub))
     314           66 :       return; /* command affects some _other_ reserve */
     315           38 :     for (unsigned int j = 0; true; j++)
     316            0 :     {
     317              :       const struct TALER_EXCHANGE_ReserveHistoryEntry *he;
     318              : 
     319           38 :       if (GNUNET_OK !=
     320           38 :           TALER_TESTING_get_trait_reserve_history (cmd,
     321              :                                                    j,
     322              :                                                    &he))
     323              :       {
     324              :         /* NOTE: only for debugging... */
     325           15 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     326              :                     "Command `%s' has the reserve_pub, but lacks reserve history trait for index #%u\n",
     327              :                     cmd->label,
     328              :                     j);
     329           15 :         return; /* command does nothing for reserves */
     330              :       }
     331           47 :       for (unsigned int i = 0; i<history_length; i++)
     332              :       {
     333           47 :         if (found[i])
     334           24 :           continue; /* already found, skip */
     335           23 :         if (0 ==
     336           23 :             history_entry_cmp (he,
     337           23 :                                &history[i]))
     338              :         {
     339           23 :           found[i] = true;
     340           23 :           matched = true;
     341           23 :           ac->failure = false;
     342           23 :           break;
     343              :         }
     344              :       }
     345           23 :       if (matched)
     346           23 :         break;
     347              :     }
     348           23 :     if (! matched)
     349              :     {
     350            0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     351              :                   "Command `%s' no relevant reserve history entry not found\n",
     352              :                   cmd->label);
     353            0 :       ac->failure = true;
     354              :       ;
     355              :     }
     356              :   }
     357              : }
     358              : 
     359              : 
     360              : /**
     361              :  * Check that the reserve balance and HTTP response code are
     362              :  * both acceptable.
     363              :  *
     364              :  * @param cls closure.
     365              :  * @param rs HTTP response details
     366              :  */
     367              : static void
     368            8 : reserve_history_cb (void *cls,
     369              :                     const struct TALER_EXCHANGE_ReserveHistory *rs)
     370              : {
     371            8 :   struct HistoryState *ss = cls;
     372            8 :   struct TALER_TESTING_Interpreter *is = ss->is;
     373              :   struct TALER_Amount eb;
     374              : 
     375            8 :   ss->rsh = NULL;
     376            8 :   if (ss->expected_response_code != rs->hr.http_status)
     377              :   {
     378            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     379              :                 "Unexpected HTTP response code: %d in %s:%u\n",
     380              :                 rs->hr.http_status,
     381              :                 __FILE__,
     382              :                 __LINE__);
     383            0 :     json_dumpf (rs->hr.reply,
     384              :                 stderr,
     385              :                 0);
     386            0 :     TALER_TESTING_interpreter_fail (ss->is);
     387            0 :     return;
     388              :   }
     389            8 :   if (MHD_HTTP_OK != rs->hr.http_status)
     390              :   {
     391            0 :     TALER_TESTING_interpreter_next (is);
     392            0 :     return;
     393              :   }
     394            8 :   GNUNET_assert (GNUNET_OK ==
     395              :                  TALER_string_to_amount (ss->expected_balance,
     396              :                                          &eb));
     397              : 
     398            8 :   if (0 != TALER_amount_cmp (&eb,
     399              :                              &rs->details.ok.balance))
     400              :   {
     401            0 :     GNUNET_break (0);
     402            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     403              :                 "Unexpected amount in reserve: %s\n",
     404              :                 TALER_amount_to_string (&rs->details.ok.balance));
     405            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     406              :                 "Expected balance of: %s\n",
     407              :                 TALER_amount_to_string (&eb));
     408            0 :     TALER_TESTING_interpreter_fail (ss->is);
     409            0 :     return;
     410              :   }
     411            8 :   {
     412            8 :     bool found[rs->details.ok.history_len];
     413            8 :     struct AnalysisContext ac = {
     414            8 :       .reserve_pub = &ss->reserve_pub,
     415            8 :       .history = rs->details.ok.history,
     416            8 :       .history_length = rs->details.ok.history_len,
     417              :       .found = found
     418              :     };
     419              : 
     420            8 :     memset (found,
     421              :             0,
     422            8 :             sizeof (found));
     423            8 :     TALER_TESTING_iterate (is,
     424              :                            true,
     425              :                            &analyze_command,
     426              :                            &ac);
     427            8 :     if (ac.failure)
     428              :     {
     429            0 :       json_dumpf (rs->hr.reply,
     430              :                   stderr,
     431              :                   JSON_INDENT (2));
     432            0 :       TALER_TESTING_interpreter_fail (ss->is);
     433            0 :       return;
     434              :     }
     435           31 :     for (unsigned int i = 0; i<rs->details.ok.history_len; i++)
     436              :     {
     437           23 :       if (found[i])
     438           23 :         continue;
     439            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     440              :                   "History entry at index %u of type %d not justified by command history\n",
     441              :                   i,
     442              :                   rs->details.ok.history[i].type);
     443            0 :       json_dumpf (rs->hr.reply,
     444              :                   stderr,
     445              :                   JSON_INDENT (2));
     446            0 :       TALER_TESTING_interpreter_fail (ss->is);
     447            0 :       return;
     448              :     }
     449              :   }
     450            8 :   TALER_TESTING_interpreter_next (is);
     451              : }
     452              : 
     453              : 
     454              : /**
     455              :  * Run the command.
     456              :  *
     457              :  * @param cls closure.
     458              :  * @param cmd the command being executed.
     459              :  * @param is the interpreter state.
     460              :  */
     461              : static void
     462            8 : history_run (void *cls,
     463              :              const struct TALER_TESTING_Command *cmd,
     464              :              struct TALER_TESTING_Interpreter *is)
     465              : {
     466            8 :   struct HistoryState *ss = cls;
     467              :   const struct TALER_TESTING_Command *create_reserve;
     468              : 
     469            8 :   ss->is = is;
     470              :   create_reserve
     471            8 :     = TALER_TESTING_interpreter_lookup_command (is,
     472              :                                                 ss->reserve_reference);
     473            8 :   if (NULL == create_reserve)
     474              :   {
     475            0 :     GNUNET_break (0);
     476            0 :     TALER_TESTING_interpreter_fail (is);
     477            0 :     return;
     478              :   }
     479            8 :   if (GNUNET_OK !=
     480            8 :       TALER_TESTING_get_trait_reserve_priv (create_reserve,
     481              :                                             &ss->reserve_priv))
     482              :   {
     483            0 :     GNUNET_break (0);
     484            0 :     TALER_LOG_ERROR ("Failed to find reserve_priv for history query\n");
     485            0 :     TALER_TESTING_interpreter_fail (is);
     486            0 :     return;
     487              :   }
     488            8 :   GNUNET_CRYPTO_eddsa_key_get_public (&ss->reserve_priv->eddsa_priv,
     489              :                                       &ss->reserve_pub.eddsa_pub);
     490            8 :   ss->rsh = TALER_EXCHANGE_reserves_history (
     491              :     TALER_TESTING_interpreter_get_context (is),
     492              :     TALER_TESTING_get_exchange_url (is),
     493              :     TALER_TESTING_get_keys (is),
     494              :     ss->reserve_priv,
     495              :     0,
     496              :     &reserve_history_cb,
     497              :     ss);
     498              : }
     499              : 
     500              : 
     501              : /**
     502              :  * Offer internal data from a "history" CMD, to other commands.
     503              :  *
     504              :  * @param cls closure.
     505              :  * @param[out] ret result.
     506              :  * @param trait name of the trait.
     507              :  * @param index index number of the object to offer.
     508              :  * @return #GNUNET_OK on success.
     509              :  */
     510              : static enum GNUNET_GenericReturnValue
     511           24 : history_traits (void *cls,
     512              :                 const void **ret,
     513              :                 const char *trait,
     514              :                 unsigned int index)
     515              : {
     516           24 :   struct HistoryState *hs = cls;
     517              :   struct TALER_TESTING_Trait traits[] = {
     518           24 :     TALER_TESTING_make_trait_reserve_pub (&hs->reserve_pub),
     519           24 :     TALER_TESTING_trait_end ()
     520              :   };
     521              : 
     522           24 :   return TALER_TESTING_get_trait (traits,
     523              :                                   ret,
     524              :                                   trait,
     525              :                                   index);
     526              : }
     527              : 
     528              : 
     529              : /**
     530              :  * Cleanup the state from a "reserve history" CMD, and possibly
     531              :  * cancel a pending operation thereof.
     532              :  *
     533              :  * @param cls closure.
     534              :  * @param cmd the command which is being cleaned up.
     535              :  */
     536              : static void
     537            8 : history_cleanup (void *cls,
     538              :                  const struct TALER_TESTING_Command *cmd)
     539              : {
     540            8 :   struct HistoryState *ss = cls;
     541              : 
     542            8 :   if (NULL != ss->rsh)
     543              :   {
     544            0 :     TALER_TESTING_command_incomplete (ss->is,
     545              :                                       cmd->label);
     546            0 :     TALER_EXCHANGE_reserves_history_cancel (ss->rsh);
     547            0 :     ss->rsh = NULL;
     548              :   }
     549            8 :   GNUNET_free (ss);
     550            8 : }
     551              : 
     552              : 
     553              : struct TALER_TESTING_Command
     554            8 : TALER_TESTING_cmd_reserve_history (const char *label,
     555              :                                    const char *reserve_reference,
     556              :                                    const char *expected_balance,
     557              :                                    unsigned int expected_response_code)
     558              : {
     559              :   struct HistoryState *ss;
     560              : 
     561            8 :   GNUNET_assert (NULL != reserve_reference);
     562            8 :   ss = GNUNET_new (struct HistoryState);
     563            8 :   ss->reserve_reference = reserve_reference;
     564            8 :   ss->expected_balance = expected_balance;
     565            8 :   ss->expected_response_code = expected_response_code;
     566              :   {
     567            8 :     struct TALER_TESTING_Command cmd = {
     568              :       .cls = ss,
     569              :       .label = label,
     570              :       .run = &history_run,
     571              :       .cleanup = &history_cleanup,
     572              :       .traits = &history_traits
     573              :     };
     574              : 
     575            8 :     return cmd;
     576              :   }
     577              : }
        

Generated by: LCOV version 2.0-1