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

Generated by: LCOV version 2.0-1