Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU General Public License as
7 : published by the Free Software Foundation; either version 3, or
8 : (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not, see
17 : <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_bank_history_credit.c
21 : * @brief command to check the /history/incoming API from the bank.
22 : * @author Marcello Stanisci
23 : */
24 : #include "taler/taler_json_lib.h"
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler/taler_testing_lib.h"
27 : #include "taler/taler_fakebank_lib.h"
28 : #include "taler/taler_bank_service.h"
29 : #include "taler/taler_fakebank_lib.h"
30 :
31 :
32 : /**
33 : * Item in the transaction history, as reconstructed from the
34 : * command history.
35 : */
36 : struct History
37 : {
38 :
39 : /**
40 : * Wire details.
41 : */
42 : struct TALER_BANK_CreditDetails credit_details;
43 :
44 : /**
45 : * Serial ID of the wire transfer.
46 : */
47 : uint64_t row_id;
48 :
49 : /**
50 : * URL to free.
51 : */
52 : char *url;
53 : };
54 :
55 :
56 : /**
57 : * State for a "history" CMD.
58 : */
59 : struct HistoryState
60 : {
61 : /**
62 : * Base URL of the account offering the "history" operation.
63 : */
64 : char *account_url;
65 :
66 : /**
67 : * Reference to command defining the
68 : * first row number we want in the result.
69 : */
70 : const char *start_row_reference;
71 :
72 : /**
73 : * How many rows we want in the result, _at most_,
74 : * and ascending/descending.
75 : */
76 : long long num_results;
77 :
78 : /**
79 : * Handle to a pending "history" operation.
80 : */
81 : struct TALER_BANK_CreditHistoryHandle *hh;
82 :
83 : /**
84 : * The interpreter.
85 : */
86 : struct TALER_TESTING_Interpreter *is;
87 :
88 : /**
89 : * Authentication data for the operation.
90 : */
91 : struct TALER_BANK_AuthenticationData auth;
92 :
93 : /**
94 : * Expected number of results (= rows).
95 : */
96 : uint64_t results_obtained;
97 :
98 : /**
99 : * Set to true if the callback detects something
100 : * unexpected.
101 : */
102 : bool failed;
103 :
104 : /**
105 : * Expected history.
106 : */
107 : struct History *h;
108 :
109 : /**
110 : * Length of @e h
111 : */
112 : unsigned int total;
113 :
114 : };
115 :
116 :
117 : /**
118 : * Log which history we expected. Called when an error occurs.
119 : *
120 : * @param h what we expected.
121 : * @param h_len number of entries in @a h.
122 : * @param off position of the mismatch.
123 : */
124 : static void
125 0 : print_expected (struct History *h,
126 : unsigned int h_len,
127 : unsigned int off)
128 : {
129 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
130 : "Transaction history (credit) mismatch at position %u/%u\n",
131 : off,
132 : h_len);
133 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
134 : "Expected history:\n");
135 0 : for (unsigned int i = 0; i<h_len; i++)
136 : {
137 0 : const struct TALER_BANK_CreditDetails *cd
138 0 : = &h[i].credit_details;
139 :
140 0 : switch (cd->type)
141 : {
142 0 : case TALER_BANK_CT_RESERVE:
143 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
144 : "H(%u): %s (serial: %llu, RES: %s,"
145 : " counterpart: %s)\n",
146 : i,
147 : TALER_amount2s (&cd->amount),
148 : (unsigned long long) h[i].row_id,
149 : TALER_B2S (&cd->details.reserve.reserve_pub),
150 : cd->debit_account_uri.full_payto);
151 0 : break;
152 0 : case TALER_BANK_CT_KYCAUTH:
153 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
154 : "H(%u): %s (serial: %llu, KYC: %s,"
155 : " counterpart: %s)\n",
156 : i,
157 : TALER_amount2s (&cd->amount),
158 : (unsigned long long) h[i].row_id,
159 : TALER_B2S (&cd->details.kycauth.account_pub),
160 : cd->debit_account_uri.full_payto);
161 0 : break;
162 0 : case TALER_BANK_CT_WAD:
163 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
164 : "H(%u): %s (serial: %llu, WAD: %s-%s,"
165 : " counterpart: %s)\n",
166 : i,
167 : TALER_amount2s (&cd->amount),
168 : (unsigned long long) h[i].row_id,
169 : TALER_B2S (&cd->details.wad.wad_id),
170 : cd->details.wad.origin_exchange_url,
171 : cd->debit_account_uri.full_payto);
172 0 : break;
173 : }
174 : }
175 0 : }
176 :
177 :
178 : /**
179 : * Closure for command_cb().
180 : */
181 : struct IteratorContext
182 : {
183 : /**
184 : * Array of history items to return.
185 : */
186 : struct History *h;
187 :
188 : /**
189 : * Set to the row ID from where on we should actually process history items,
190 : * or NULL if we should process all of them.
191 : */
192 : const uint64_t *row_id_start;
193 :
194 : /**
195 : * History state we are working on.
196 : */
197 : struct HistoryState *hs;
198 :
199 : /**
200 : * Current length of the @e h array.
201 : */
202 : unsigned int total;
203 :
204 : /**
205 : * Current write position in @e h array.
206 : */
207 : unsigned int pos;
208 :
209 : /**
210 : * Ok equals True whenever a starting row_id was provided AND was found
211 : * among the CMDs, OR no starting row was given in the first place.
212 : */
213 : bool ok;
214 :
215 : };
216 :
217 :
218 : /**
219 : * Helper function of build_history() that expands
220 : * the history for each relevant command encountered.
221 : *
222 : * @param[in,out] cls our `struct IteratorContext`
223 : * @param cmd a command to process
224 : */
225 : static void
226 14 : command_cb (void *cls,
227 : const struct TALER_TESTING_Command *cmd)
228 : {
229 14 : struct IteratorContext *ic = cls;
230 14 : struct HistoryState *hs = ic->hs;
231 : const uint64_t *row_id;
232 : const struct TALER_FullPayto *credit_account;
233 : const struct TALER_FullPayto *debit_account;
234 : const struct TALER_Amount *amount;
235 : const struct TALER_ReservePublicKeyP *reserve_pub;
236 : const char *exchange_credit_url;
237 :
238 : /**
239 : * The following command allows us to skip over those CMDs
240 : * that do not offer a "row_id" trait. Such skipped CMDs are
241 : * not interesting for building a history.
242 : */
243 14 : if ( (GNUNET_OK !=
244 14 : TALER_TESTING_get_trait_bank_row (cmd,
245 2 : &row_id)) ||
246 : (GNUNET_OK !=
247 2 : TALER_TESTING_get_trait_credit_payto_uri (cmd,
248 2 : &credit_account)) ||
249 : (GNUNET_OK !=
250 2 : TALER_TESTING_get_trait_debit_payto_uri (cmd,
251 2 : &debit_account)) ||
252 : (GNUNET_OK !=
253 2 : TALER_TESTING_get_trait_amount (cmd,
254 2 : &amount)) ||
255 : (GNUNET_OK !=
256 2 : TALER_TESTING_get_trait_reserve_pub (cmd,
257 2 : &reserve_pub)) ||
258 : (GNUNET_OK !=
259 2 : TALER_TESTING_get_trait_exchange_bank_account_url (
260 : cmd,
261 : &exchange_credit_url)) )
262 12 : return; // Not an interesting event
263 : // FIXME: support KYCAUTH transfer events!
264 : // FIXME-#7271: support WAD transfer events!
265 :
266 : /**
267 : * Is the interesting event a match with regard to
268 : * the row_id value? If yes, store this condition
269 : * to the state and analyze the next CMDs.
270 : */
271 2 : if ( (NULL != ic->row_id_start) &&
272 0 : (*(ic->row_id_start) == *row_id) &&
273 0 : (! ic->ok) )
274 : {
275 0 : ic->ok = true;
276 0 : return;
277 : }
278 : /**
279 : * The interesting event didn't match the wanted
280 : * row_id value, analyze the next CMDs. Note: this
281 : * branch is relevant only when row_id WAS given.
282 : */
283 2 : if (! ic->ok)
284 0 : return;
285 2 : if (0 != strcasecmp (hs->account_url,
286 : exchange_credit_url))
287 0 : return; // Account mismatch
288 2 : if (ic->total >= GNUNET_MAX (hs->num_results,
289 : -hs->num_results) )
290 : {
291 0 : TALER_LOG_DEBUG ("Hit history limit\n");
292 0 : return;
293 : }
294 2 : TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
295 : debit_account->full_payto,
296 : credit_account->full_payto,
297 : hs->account_url);
298 : /* found matching record, make sure we have room */
299 2 : if (ic->pos == ic->total)
300 0 : GNUNET_array_grow (ic->h,
301 : ic->total,
302 : ic->pos * 2);
303 2 : ic->h[ic->pos].url
304 2 : = GNUNET_strdup (debit_account->full_payto);
305 2 : ic->h[ic->pos].row_id
306 2 : = *row_id;
307 2 : ic->h[ic->pos].credit_details.type
308 2 : = TALER_BANK_CT_RESERVE;
309 2 : ic->h[ic->pos].credit_details.debit_account_uri.full_payto
310 2 : = ic->h[ic->pos].url;
311 2 : ic->h[ic->pos].credit_details.amount
312 2 : = *amount;
313 2 : ic->h[ic->pos].credit_details.details.reserve.reserve_pub
314 2 : = *reserve_pub;
315 2 : ic->pos++;
316 : }
317 :
318 :
319 : /**
320 : * This function constructs the list of history elements that
321 : * interest the account number of the caller. It has two main
322 : * loops: the first to figure out how many history elements have
323 : * to be allocated, and the second to actually populate every
324 : * element.
325 : *
326 : * @param hs history state
327 : * @param[out] rh history array to initialize.
328 : * @return number of entries in @a rh.
329 : */
330 : static unsigned int
331 4 : build_history (struct HistoryState *hs,
332 : struct History **rh)
333 : {
334 4 : struct TALER_TESTING_Interpreter *is = hs->is;
335 4 : struct IteratorContext ic = {
336 : .hs = hs
337 : };
338 :
339 4 : if (NULL != hs->start_row_reference)
340 : {
341 : const struct TALER_TESTING_Command *add_incoming_cmd;
342 :
343 0 : TALER_LOG_INFO ("`%s': start row given via reference `%s'\n",
344 : TALER_TESTING_interpreter_get_current_label (is),
345 : hs->start_row_reference);
346 : add_incoming_cmd
347 0 : = TALER_TESTING_interpreter_lookup_command (is,
348 : hs->start_row_reference);
349 0 : GNUNET_assert (NULL != add_incoming_cmd);
350 0 : GNUNET_assert (GNUNET_OK ==
351 : TALER_TESTING_get_trait_row (add_incoming_cmd,
352 : &ic.row_id_start));
353 : }
354 :
355 4 : ic.ok = false;
356 4 : if (NULL == ic.row_id_start)
357 4 : ic.ok = true;
358 4 : GNUNET_array_grow (ic.h,
359 : ic.total,
360 : 4);
361 4 : GNUNET_assert (0 != hs->num_results);
362 4 : TALER_TESTING_iterate (is,
363 4 : hs->num_results > 0,
364 : &command_cb,
365 : &ic);
366 4 : GNUNET_assert (ic.ok);
367 4 : GNUNET_array_grow (ic.h,
368 : ic.total,
369 : ic.pos);
370 4 : if (0 == ic.pos)
371 2 : TALER_LOG_DEBUG ("Empty credit history computed\n");
372 4 : *rh = ic.h;
373 4 : return ic.pos;
374 : }
375 :
376 :
377 : /**
378 : * Check that the "/history/incoming" response matches the
379 : * CMD whose offset in the list of CMDs is @a off.
380 : *
381 : * @param h expected history (array)
382 : * @param total length of @a h
383 : * @param off the offset (of the CMD list) where the command
384 : * to check is.
385 : * @param credit_details the expected transaction details.
386 : * @return #GNUNET_OK if the transaction is what we expect.
387 : */
388 : static enum GNUNET_GenericReturnValue
389 2 : check_result (struct History *h,
390 : unsigned int total,
391 : unsigned int off,
392 : const struct TALER_BANK_CreditDetails *credit_details)
393 : {
394 2 : if (off >= total)
395 : {
396 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
397 : "Test says history has at most %u"
398 : " results, but got result #%u to check\n",
399 : total,
400 : off);
401 0 : print_expected (h,
402 : total,
403 : off);
404 0 : return GNUNET_SYSERR;
405 : }
406 2 : if ( (h[off].credit_details.type !=
407 4 : credit_details->type) ||
408 2 : (0 != TALER_amount_cmp (&h[off].credit_details.amount,
409 2 : &credit_details->amount)) ||
410 2 : (0 != TALER_full_payto_normalize_and_cmp (
411 2 : h[off].credit_details.debit_account_uri,
412 : credit_details->debit_account_uri)) )
413 : {
414 0 : GNUNET_break (0);
415 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
416 : "expected debit_account_uri: %s with %s\n",
417 : h[off].credit_details.debit_account_uri.full_payto,
418 : TALER_amount2s (&h[off].credit_details.amount));
419 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
420 : "actual debit_account_uri: %s with %s\n",
421 : credit_details->debit_account_uri.full_payto,
422 : TALER_amount2s (&credit_details->amount));
423 0 : print_expected (h,
424 : total,
425 : off);
426 0 : return GNUNET_SYSERR;
427 : }
428 2 : switch (credit_details->type)
429 : {
430 2 : case TALER_BANK_CT_RESERVE:
431 2 : if (0 !=
432 2 : GNUNET_memcmp (&h[off].credit_details.details.reserve.reserve_pub,
433 : &credit_details->details.reserve.reserve_pub))
434 : {
435 0 : GNUNET_break (0);
436 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
437 : "expected debit_account_uri: %s with %s for %s\n",
438 : h[off].credit_details.debit_account_uri.full_payto,
439 : TALER_amount2s (&h[off].credit_details.amount),
440 : TALER_B2S (&h[off].credit_details.details.reserve.reserve_pub)
441 : );
442 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
443 : "actual debit_account_uri: %s with %s for %s\n",
444 : credit_details->debit_account_uri.full_payto,
445 : TALER_amount2s (&credit_details->amount),
446 : TALER_B2S (&credit_details->details.reserve.reserve_pub));
447 0 : print_expected (h,
448 : total,
449 : off);
450 0 : return GNUNET_SYSERR;
451 : }
452 2 : break;
453 0 : case TALER_BANK_CT_KYCAUTH:
454 0 : if (0 != GNUNET_memcmp (&h[off].credit_details.details.kycauth.account_pub,
455 : &credit_details->details.kycauth.account_pub))
456 : {
457 0 : GNUNET_break (0);
458 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
459 : "expected debit_account_uri: %s with %s for %s\n",
460 : h[off].credit_details.debit_account_uri.full_payto,
461 : TALER_amount2s (&h[off].credit_details.amount),
462 : TALER_B2S (&h[off].credit_details.details.kycauth.account_pub)
463 : );
464 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
465 : "actual debit_account_uri: %s with %s for %s\n",
466 : credit_details->debit_account_uri.full_payto,
467 : TALER_amount2s (&credit_details->amount),
468 : TALER_B2S (&credit_details->details.kycauth.account_pub));
469 0 : print_expected (h,
470 : total,
471 : off);
472 0 : return GNUNET_SYSERR;
473 : }
474 0 : break;
475 0 : case TALER_BANK_CT_WAD:
476 0 : if ( (0 != GNUNET_memcmp (&h[off].credit_details.details.wad.wad_id,
477 0 : &credit_details->details.wad.wad_id)) ||
478 0 : (0 != strcmp (h[off].credit_details.details.wad.origin_exchange_url,
479 0 : credit_details->details.wad.origin_exchange_url)) )
480 : {
481 0 : GNUNET_break (0);
482 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
483 : "expected debit_account_uri: %s with %s for %s-%s\n",
484 : h[off].credit_details.debit_account_uri.full_payto,
485 : TALER_amount2s (&h[off].credit_details.amount),
486 : h[off].credit_details.details.wad.origin_exchange_url,
487 : TALER_B2S (&h[off].credit_details.details.wad.wad_id));
488 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
489 : "actual debit_account_uri: %s with %s for %s-%s\n",
490 : credit_details->debit_account_uri.full_payto,
491 : TALER_amount2s (&credit_details->amount),
492 : credit_details->details.wad.origin_exchange_url,
493 : TALER_B2S (&credit_details->details.wad.wad_id));
494 0 : print_expected (h,
495 : total,
496 : off);
497 0 : return GNUNET_SYSERR;
498 : }
499 0 : break;
500 : }
501 2 : return GNUNET_OK;
502 : }
503 :
504 :
505 : /**
506 : * This callback will (1) check that the HTTP response code
507 : * is acceptable and (2) that the history is consistent. The
508 : * consistency is checked by going through all the past CMDs,
509 : * reconstructing then the expected history as of those, and
510 : * finally check it against what the bank returned.
511 : *
512 : * @param cls closure.
513 : * @param chr http response details
514 : */
515 : static void
516 4 : history_cb (void *cls,
517 : const struct TALER_BANK_CreditHistoryResponse *chr)
518 : {
519 4 : struct HistoryState *hs = cls;
520 4 : struct TALER_TESTING_Interpreter *is = hs->is;
521 :
522 4 : hs->hh = NULL;
523 4 : switch (chr->http_status)
524 : {
525 0 : case 0:
526 0 : GNUNET_break (0);
527 0 : goto error;
528 2 : case MHD_HTTP_OK:
529 4 : for (unsigned int i = 0; i<chr->details.ok.details_length; i++)
530 : {
531 2 : const struct TALER_BANK_CreditDetails *cd =
532 2 : &chr->details.ok.details[i];
533 :
534 : /* check current element */
535 2 : if (GNUNET_OK !=
536 2 : check_result (hs->h,
537 : hs->total,
538 2 : hs->results_obtained,
539 : cd))
540 : {
541 0 : GNUNET_break (0);
542 0 : json_dumpf (chr->response,
543 : stderr,
544 : JSON_COMPACT);
545 0 : hs->failed = true;
546 0 : hs->hh = NULL;
547 0 : TALER_TESTING_interpreter_fail (is);
548 0 : return;
549 : }
550 2 : hs->results_obtained++;
551 : }
552 2 : TALER_TESTING_interpreter_next (is);
553 2 : return;
554 1 : case MHD_HTTP_NO_CONTENT:
555 1 : if (0 == hs->total)
556 : {
557 : /* not found is OK for empty history */
558 1 : TALER_TESTING_interpreter_next (is);
559 1 : return;
560 : }
561 0 : GNUNET_break (0);
562 0 : goto error;
563 1 : case MHD_HTTP_NOT_FOUND:
564 1 : if (0 == hs->total)
565 : {
566 : /* not found is OK for empty history */
567 1 : TALER_TESTING_interpreter_next (is);
568 1 : return;
569 : }
570 0 : GNUNET_break (0);
571 0 : goto error;
572 0 : default:
573 0 : hs->hh = NULL;
574 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
575 : "Unwanted response code from /history/incoming: %u\n",
576 : chr->http_status);
577 0 : TALER_TESTING_interpreter_fail (is);
578 0 : return;
579 : }
580 0 : error:
581 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
582 : "Expected history of length %u, got %llu;"
583 : " HTTP status code: %u/%d, failed: %d\n",
584 : hs->total,
585 : (unsigned long long) hs->results_obtained,
586 : chr->http_status,
587 : (int) chr->ec,
588 : hs->failed ? 1 : 0);
589 0 : print_expected (hs->h,
590 : hs->total,
591 : UINT_MAX);
592 0 : TALER_TESTING_interpreter_fail (is);
593 : }
594 :
595 :
596 : /**
597 : * Run the command.
598 : *
599 : * @param cls closure.
600 : * @param cmd the command to execute.
601 : * @param is the interpreter state.
602 : */
603 : static void
604 4 : history_run (void *cls,
605 : const struct TALER_TESTING_Command *cmd,
606 : struct TALER_TESTING_Interpreter *is)
607 : {
608 4 : struct HistoryState *hs = cls;
609 4 : uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX;
610 : const uint64_t *row_ptr;
611 :
612 : (void) cmd;
613 4 : hs->is = is;
614 : /* Get row_id from trait. */
615 4 : if (NULL != hs->start_row_reference)
616 : {
617 : const struct TALER_TESTING_Command *history_cmd;
618 :
619 0 : history_cmd = TALER_TESTING_interpreter_lookup_command (
620 : is,
621 : hs->start_row_reference);
622 0 : if (NULL == history_cmd)
623 0 : TALER_TESTING_FAIL (is);
624 :
625 0 : if (GNUNET_OK !=
626 0 : TALER_TESTING_get_trait_row (history_cmd,
627 : &row_ptr))
628 0 : TALER_TESTING_FAIL (is);
629 : else
630 0 : row_id = *row_ptr;
631 0 : TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
632 : (unsigned long long) row_id);
633 : }
634 4 : hs->total = build_history (hs,
635 : &hs->h);
636 4 : hs->hh = TALER_BANK_credit_history (
637 : TALER_TESTING_interpreter_get_context (is),
638 4 : &hs->auth,
639 : row_id,
640 4 : hs->num_results,
641 4 : GNUNET_TIME_UNIT_ZERO,
642 : &history_cb,
643 : hs);
644 4 : GNUNET_assert (NULL != hs->hh);
645 : }
646 :
647 :
648 : /**
649 : * Free the state from a "history" CMD, and possibly cancel
650 : * a pending operation thereof.
651 : *
652 : * @param cls closure.
653 : * @param cmd the command which is being cleaned up.
654 : */
655 : static void
656 4 : history_cleanup (void *cls,
657 : const struct TALER_TESTING_Command *cmd)
658 : {
659 4 : struct HistoryState *hs = cls;
660 :
661 : (void) cmd;
662 4 : if (NULL != hs->hh)
663 : {
664 0 : TALER_TESTING_command_incomplete (hs->is,
665 : cmd->label);
666 0 : TALER_BANK_credit_history_cancel (hs->hh);
667 : }
668 4 : GNUNET_free (hs->account_url);
669 6 : for (unsigned int off = 0; off<hs->total; off++)
670 2 : GNUNET_free (hs->h[off].url);
671 4 : GNUNET_free (hs->h);
672 4 : GNUNET_free (hs);
673 4 : }
674 :
675 :
676 : struct TALER_TESTING_Command
677 4 : TALER_TESTING_cmd_bank_credits (
678 : const char *label,
679 : const struct TALER_BANK_AuthenticationData *auth,
680 : const char *start_row_reference,
681 : long long num_results)
682 : {
683 : struct HistoryState *hs;
684 :
685 4 : hs = GNUNET_new (struct HistoryState);
686 4 : hs->account_url = GNUNET_strdup (auth->wire_gateway_url);
687 4 : hs->start_row_reference = start_row_reference;
688 4 : hs->num_results = num_results;
689 4 : hs->auth = *auth;
690 : {
691 4 : struct TALER_TESTING_Command cmd = {
692 : .label = label,
693 : .cls = hs,
694 : .run = &history_run,
695 : .cleanup = &history_cleanup
696 : };
697 :
698 4 : return cmd;
699 : }
700 : }
701 :
702 :
703 : /* end of testing_api_cmd_credit_history.c */
|