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

Generated by: LCOV version 2.0-1