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

Generated by: LCOV version 2.0-1