Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2018-2021 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_debit.c
21 : * @brief command to check the /history/outgoing 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 : * Item in the transaction history, as reconstructed from the
33 : * command history.
34 : */
35 : struct History
36 : {
37 :
38 : /**
39 : * Wire details.
40 : */
41 : struct TALER_BANK_DebitDetails details;
42 :
43 : /**
44 : * Serial ID of the wire transfer.
45 : */
46 : uint64_t row_id;
47 :
48 : /**
49 : * URL to free.
50 : */
51 : char *c_url;
52 :
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 : const 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 : * Login data to use to authenticate.
80 : */
81 : struct TALER_BANK_AuthenticationData auth;
82 :
83 : /**
84 : * Handle to a pending "history" operation.
85 : */
86 : struct TALER_BANK_DebitHistoryHandle *hh;
87 :
88 : /**
89 : * Our interpreter.
90 : */
91 : struct TALER_TESTING_Interpreter *is;
92 :
93 : /**
94 : * Expected number of results (= rows).
95 : */
96 : uint64_t results_obtained;
97 :
98 : /**
99 : * Set to #GNUNET_YES if the callback detects something
100 : * unexpected.
101 : */
102 : int 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 (debit) 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 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
138 : "H(%u): %s (serial: %llu, subject: %s, counterpart: %s)\n",
139 : i,
140 : TALER_amount2s (&h[i].details.amount),
141 : (unsigned long long) h[i].row_id,
142 : TALER_B2S (&h[i].details.wtid),
143 : h[i].details.credit_account_uri.full_payto);
144 : }
145 0 : }
146 :
147 :
148 : /**
149 : * Closure for command_cb().
150 : */
151 : struct IteratorContext
152 : {
153 : /**
154 : * Array of history items to return.
155 : */
156 : struct History *h;
157 :
158 : /**
159 : * Set to the row ID from where on we should actually process history items,
160 : * or NULL if we should process all of them.
161 : */
162 : const uint64_t *row_id_start;
163 :
164 : /**
165 : * History state we are working on.
166 : */
167 : struct HistoryState *hs;
168 :
169 : /**
170 : * Current length of the @e h array.
171 : */
172 : unsigned int total;
173 :
174 : /**
175 : * Current write position in @e h array.
176 : */
177 : unsigned int pos;
178 :
179 : /**
180 : * Ok equals True whenever a starting row_id was provided AND was found
181 : * among the CMDs, OR no starting row was given in the first place.
182 : */
183 : bool ok;
184 :
185 : };
186 :
187 :
188 : /**
189 : * Helper function of build_history() that expands
190 : * the history for each relevant command encountered.
191 : *
192 : * @param[in,out] cls our `struct IteratorContext`
193 : * @param cmd a command to process
194 : */
195 : static void
196 30 : command_cb (void *cls,
197 : const struct TALER_TESTING_Command *cmd)
198 : {
199 30 : struct IteratorContext *ic = cls;
200 30 : struct HistoryState *hs = ic->hs;
201 : const uint64_t *row_id;
202 : const struct TALER_FullPayto *debit_account;
203 : const struct TALER_FullPayto *credit_account;
204 : const struct TALER_Amount *amount;
205 : const struct TALER_WireTransferIdentifierRawP *wtid;
206 : const char *exchange_base_url;
207 :
208 30 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
209 : "Checking if command %s is relevant for debit history\n",
210 : cmd->label);
211 30 : if ( (GNUNET_OK !=
212 30 : TALER_TESTING_get_trait_bank_row (cmd,
213 8 : &row_id)) ||
214 : (GNUNET_OK !=
215 8 : TALER_TESTING_get_trait_debit_payto_uri (cmd,
216 8 : &debit_account)) ||
217 : (GNUNET_OK !=
218 8 : TALER_TESTING_get_trait_credit_payto_uri (cmd,
219 8 : &credit_account)) ||
220 : (GNUNET_OK !=
221 8 : TALER_TESTING_get_trait_amount (cmd,
222 8 : &amount)) ||
223 : (GNUNET_OK !=
224 8 : TALER_TESTING_get_trait_wtid (cmd,
225 2 : &wtid)) ||
226 : (GNUNET_OK !=
227 2 : TALER_TESTING_get_trait_exchange_url (cmd,
228 : &exchange_base_url)) )
229 28 : return; /* not an event we care about */
230 : /* Seek "/history/outgoing" starting row. */
231 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
232 : "Command %s is relevant for debit history!\n",
233 : cmd->label);
234 2 : if ( (NULL != ic->row_id_start) &&
235 0 : (*(ic->row_id_start) == *row_id) &&
236 0 : (! ic->ok) )
237 : {
238 : /* Until here, nothing counted. */
239 0 : ic->ok = true;
240 0 : return;
241 : }
242 : /* when 'start' was _not_ given, then ok == GNUNET_YES */
243 2 : if (! ic->ok)
244 0 : return; /* skip until we find the marker */
245 2 : if (ic->total >= GNUNET_MAX (hs->num_results,
246 : -hs->num_results) )
247 : {
248 0 : TALER_LOG_DEBUG ("Hit history limit\n");
249 0 : return;
250 : }
251 2 : TALER_LOG_INFO ("Found history: %s->%s for account %s\n",
252 : debit_account->full_payto,
253 : credit_account->full_payto,
254 : hs->account_url);
255 : /* found matching record, make sure we have room */
256 2 : if (ic->pos == ic->total)
257 0 : GNUNET_array_grow (ic->h,
258 : ic->total,
259 : ic->pos * 2);
260 2 : ic->h[ic->pos].c_url = GNUNET_strdup (credit_account->full_payto);
261 2 : ic->h[ic->pos].details.credit_account_uri.full_payto
262 2 : = ic->h[ic->pos].c_url;
263 2 : ic->h[ic->pos].details.amount = *amount;
264 2 : ic->h[ic->pos].row_id = *row_id;
265 2 : ic->h[ic->pos].details.wtid = *wtid;
266 2 : ic->h[ic->pos].details.exchange_base_url = exchange_base_url;
267 2 : ic->pos++;
268 : }
269 :
270 :
271 : /**
272 : * This function constructs the list of history elements that
273 : * interest the account number of the caller. It has two main
274 : * loops: the first to figure out how many history elements have
275 : * to be allocated, and the second to actually populate every
276 : * element.
277 : *
278 : * @param hs history state command context
279 : * @param[out] rh history array to initialize.
280 : * @return number of entries in @a rh.
281 : */
282 : static unsigned int
283 4 : build_history (struct HistoryState *hs,
284 : struct History **rh)
285 : {
286 4 : struct TALER_TESTING_Interpreter *is = hs->is;
287 4 : struct IteratorContext ic = {
288 : .hs = hs
289 : };
290 :
291 4 : if (NULL != hs->start_row_reference)
292 : {
293 : const struct TALER_TESTING_Command *add_incoming_cmd;
294 :
295 0 : TALER_LOG_INFO (
296 : "`%s': start row given via reference `%s'\n",
297 : TALER_TESTING_interpreter_get_current_label (is),
298 : hs->start_row_reference);
299 0 : add_incoming_cmd = TALER_TESTING_interpreter_lookup_command (
300 : is,
301 : hs->start_row_reference);
302 0 : GNUNET_assert (NULL != add_incoming_cmd);
303 0 : GNUNET_assert (GNUNET_OK ==
304 : TALER_TESTING_get_trait_row (add_incoming_cmd,
305 : &ic.row_id_start));
306 : }
307 :
308 4 : ic.ok = false;
309 4 : if (NULL == ic.row_id_start)
310 4 : ic.ok = true;
311 4 : GNUNET_array_grow (ic.h,
312 : ic.total,
313 : 4);
314 4 : GNUNET_assert (0 != hs->num_results);
315 4 : TALER_TESTING_iterate (is,
316 4 : hs->num_results > 0,
317 : &command_cb,
318 : &ic);
319 4 : GNUNET_assert (ic.ok);
320 4 : GNUNET_array_grow (ic.h,
321 : ic.total,
322 : ic.pos);
323 4 : if (0 == ic.pos)
324 2 : TALER_LOG_DEBUG ("Empty credit history computed\n");
325 4 : *rh = ic.h;
326 4 : return ic.pos;
327 : }
328 :
329 :
330 : /**
331 : * Check that the "/history/outgoing" response matches the
332 : * CMD whose offset in the list of CMDs is @a off.
333 : *
334 : * @param h expected history
335 : * @param total number of entries in @a h
336 : * @param off the offset (of the CMD list) where the command
337 : * to check is.
338 : * @param details the expected transaction details.
339 : * @return #GNUNET_OK if the transaction is what we expect.
340 : */
341 : static enum GNUNET_GenericReturnValue
342 2 : check_result (struct History *h,
343 : uint64_t total,
344 : unsigned int off,
345 : const struct TALER_BANK_DebitDetails *details)
346 : {
347 2 : if (off >= total)
348 : {
349 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
350 : "Test says history has at most %u"
351 : " results, but got result #%u to check\n",
352 : (unsigned int) total,
353 : off);
354 0 : print_expected (h,
355 : total,
356 : off);
357 0 : return GNUNET_SYSERR;
358 : }
359 2 : if ( (0 != GNUNET_memcmp (&h[off].details.wtid,
360 2 : &details->wtid)) ||
361 2 : (0 != TALER_amount_cmp (&h[off].details.amount,
362 2 : &details->amount)) ||
363 2 : (0 != TALER_full_payto_normalize_and_cmp (
364 2 : h[off].details.credit_account_uri,
365 : details->credit_account_uri)) )
366 : {
367 0 : GNUNET_break (0);
368 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
369 : "expected debit_account_uri: %s with %s for %s\n",
370 : h[off].details.credit_account_uri.full_payto,
371 : TALER_amount2s (&h[off].details.amount),
372 : TALER_B2S (&h[off].details.wtid));
373 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
374 : "actual debit_account_uri: %s with %s for %s\n",
375 : details->credit_account_uri.full_payto,
376 : TALER_amount2s (&details->amount),
377 : TALER_B2S (&details->wtid));
378 0 : print_expected (h,
379 : total,
380 : off);
381 0 : return GNUNET_SYSERR;
382 : }
383 2 : return GNUNET_OK;
384 : }
385 :
386 :
387 : /**
388 : * This callback will (1) check that the HTTP response code
389 : * is acceptable and (2) that the history is consistent. The
390 : * consistency is checked by going through all the past CMDs,
391 : * reconstructing then the expected history as of those, and
392 : * finally check it against what the bank returned.
393 : *
394 : * @param cls closure.
395 : * @param dhr http response details
396 : */
397 : static void
398 4 : history_cb (void *cls,
399 : const struct TALER_BANK_DebitHistoryResponse *dhr)
400 : {
401 4 : struct HistoryState *hs = cls;
402 4 : struct TALER_TESTING_Interpreter *is = hs->is;
403 :
404 4 : hs->hh = NULL;
405 4 : switch (dhr->http_status)
406 : {
407 0 : case 0:
408 0 : GNUNET_break (0);
409 0 : goto error;
410 2 : case MHD_HTTP_OK:
411 4 : for (unsigned int i = 0; i<dhr->details.ok.details_length; i++)
412 : {
413 2 : const struct TALER_BANK_DebitDetails *dd =
414 2 : &dhr->details.ok.details[i];
415 :
416 : /* check current element */
417 2 : if (GNUNET_OK !=
418 2 : check_result (hs->h,
419 2 : hs->total,
420 2 : hs->results_obtained,
421 : dd))
422 : {
423 0 : GNUNET_break (0);
424 0 : json_dumpf (dhr->response,
425 : stderr,
426 : JSON_COMPACT);
427 0 : hs->failed = true;
428 0 : hs->hh = NULL;
429 0 : TALER_TESTING_interpreter_fail (is);
430 0 : return;
431 : }
432 2 : hs->results_obtained++;
433 : }
434 2 : TALER_TESTING_interpreter_next (is);
435 2 : return;
436 2 : case MHD_HTTP_NO_CONTENT:
437 2 : if (0 == hs->total)
438 : {
439 : /* not found is OK for empty history */
440 2 : TALER_TESTING_interpreter_next (is);
441 2 : return;
442 : }
443 0 : GNUNET_break (0);
444 0 : goto error;
445 0 : case MHD_HTTP_NOT_FOUND:
446 0 : if (0 == hs->total)
447 : {
448 : /* not found is OK for empty history */
449 0 : TALER_TESTING_interpreter_next (is);
450 0 : return;
451 : }
452 0 : GNUNET_break (0);
453 0 : goto error;
454 0 : default:
455 0 : hs->hh = NULL;
456 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
457 : "Unwanted response code from /history/incoming: %u\n",
458 : dhr->http_status);
459 0 : TALER_TESTING_interpreter_fail (is);
460 0 : return;
461 : }
462 0 : error:
463 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
464 : "Expected history of length %u, got %llu;"
465 : " HTTP status code: %u/%d, failed: %d\n",
466 : hs->total,
467 : (unsigned long long) hs->results_obtained,
468 : dhr->http_status,
469 : (int) dhr->ec,
470 : hs->failed ? 1 : 0);
471 0 : print_expected (hs->h,
472 : hs->total,
473 : UINT_MAX);
474 0 : TALER_TESTING_interpreter_fail (is);
475 : }
476 :
477 :
478 : /**
479 : * Run the command.
480 : *
481 : * @param cls closure.
482 : * @param cmd the command to execute.
483 : * @param is the interpreter state.
484 : */
485 : static void
486 4 : history_run (void *cls,
487 : const struct TALER_TESTING_Command *cmd,
488 : struct TALER_TESTING_Interpreter *is)
489 : {
490 4 : struct HistoryState *hs = cls;
491 4 : uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX;
492 : const uint64_t *row_ptr;
493 :
494 : (void) cmd;
495 4 : hs->is = is;
496 : /* Get row_id from trait. */
497 4 : if (NULL != hs->start_row_reference)
498 : {
499 : const struct TALER_TESTING_Command *history_cmd;
500 :
501 : history_cmd
502 0 : = TALER_TESTING_interpreter_lookup_command (is,
503 : hs->start_row_reference);
504 :
505 0 : if (NULL == history_cmd)
506 0 : TALER_TESTING_FAIL (is);
507 0 : if (GNUNET_OK !=
508 0 : TALER_TESTING_get_trait_row (history_cmd,
509 : &row_ptr))
510 0 : TALER_TESTING_FAIL (is);
511 : else
512 0 : row_id = *row_ptr;
513 0 : TALER_LOG_DEBUG ("row id (from trait) is %llu\n",
514 : (unsigned long long) row_id);
515 : }
516 4 : hs->total = build_history (hs,
517 : &hs->h);
518 4 : hs->hh = TALER_BANK_debit_history (
519 : TALER_TESTING_interpreter_get_context (is),
520 4 : &hs->auth,
521 : row_id,
522 4 : hs->num_results,
523 4 : GNUNET_TIME_UNIT_ZERO,
524 : &history_cb,
525 : hs);
526 4 : GNUNET_assert (NULL != hs->hh);
527 : }
528 :
529 :
530 : /**
531 : * Free the state from a "history" CMD, and possibly cancel
532 : * a pending operation thereof.
533 : *
534 : * @param cls closure.
535 : * @param cmd the command which is being cleaned up.
536 : */
537 : static void
538 4 : history_cleanup (void *cls,
539 : const struct TALER_TESTING_Command *cmd)
540 : {
541 4 : struct HistoryState *hs = cls;
542 :
543 : (void) cmd;
544 4 : if (NULL != hs->hh)
545 : {
546 0 : TALER_TESTING_command_incomplete (hs->is,
547 : cmd->label);
548 0 : TALER_BANK_debit_history_cancel (hs->hh);
549 : }
550 6 : for (unsigned int off = 0; off<hs->total; off++)
551 : {
552 2 : GNUNET_free (hs->h[off].c_url);
553 : }
554 4 : GNUNET_free (hs->h);
555 4 : GNUNET_free (hs);
556 4 : }
557 :
558 :
559 : struct TALER_TESTING_Command
560 4 : TALER_TESTING_cmd_bank_debits (const char *label,
561 : const struct TALER_BANK_AuthenticationData *auth,
562 : const char *start_row_reference,
563 : long long num_results)
564 : {
565 : struct HistoryState *hs;
566 :
567 4 : hs = GNUNET_new (struct HistoryState);
568 4 : hs->account_url = auth->wire_gateway_url;
569 4 : hs->start_row_reference = start_row_reference;
570 4 : hs->num_results = num_results;
571 4 : hs->auth = *auth;
572 :
573 : {
574 4 : struct TALER_TESTING_Command cmd = {
575 : .label = label,
576 : .cls = hs,
577 : .run = &history_run,
578 : .cleanup = &history_cleanup
579 : };
580 :
581 4 : return cmd;
582 : }
583 : }
584 :
585 :
586 : /* end of testing_api_cmd_bank_history_debit.c */
|