LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_coin_history.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 122 210 58.1 %
Date: 2025-06-05 21:03:14 Functions: 7 7 100.0 %

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

Generated by: LCOV version 1.16