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.1 % 210 122
Test Date: 2025-12-26 23:00:34 Functions: 100.0 % 7 7

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 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/platform.h"
      25              : #include "taler/taler_json_lib.h"
      26              : #include <gnunet/gnunet_curl_lib.h>
      27              : #include "taler/taler_testing_lib.h"
      28              : 
      29              : 
      30              : /**
      31              :  * State for a "history" CMD.
      32              :  */
      33              : struct HistoryState
      34              : {
      35              : 
      36              :   /**
      37              :    * Public key of the 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          147 :   {
     274              :     const struct TALER_CoinSpendPublicKeyP *rp;
     275              :     const struct TALER_EXCHANGE_CoinHistoryEntry *he;
     276          574 :     bool matched = false;
     277              : 
     278          574 :     if (GNUNET_OK !=
     279          574 :         TALER_TESTING_get_trait_coin_pub (cmd,
     280              :                                           j,
     281              :                                           &rp))
     282              :     {
     283          418 :       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     284              :                   "Command `%s#%u' has no public key for a coin\n",
     285              :                   cmd->label,
     286              :                   j);
     287          418 :       break; /* command does nothing for coins */
     288              :     }
     289          298 :     if (0 !=
     290          156 :         GNUNET_memcmp (rp,
     291              :                        coin_pub))
     292              :     {
     293          142 :       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          142 :       continue; /* command affects some _other_ coin */
     299              :     }
     300           14 :     if (GNUNET_OK !=
     301           14 :         TALER_TESTING_get_trait_coin_history (cmd,
     302              :                                               j,
     303              :                                               &he))
     304              :     {
     305              :       /* NOTE: only for debugging... */
     306            9 :       if (0 == j)
     307            9 :         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     308              :                     "Command `%s' has the coin_pub, but lacks coin history trait\n",
     309              :                     cmd->label);
     310            9 :       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 2.0-1