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