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