testing_api_cmd_bank_history_debit.c (15927B)
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/platform.h" 25 #include "taler/taler_json_lib.h" 26 #include <gnunet/gnunet_curl_lib.h> 27 #include "taler/taler_exchange_service.h" 28 #include "taler/taler_testing_lib.h" 29 #include "taler/taler_fakebank_lib.h" 30 #include "taler/taler_bank_service.h" 31 #include "taler/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 print_expected (struct History *h, 128 unsigned int h_len, 129 unsigned int off) 130 { 131 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 132 "Transaction history (debit) mismatch at position %u/%u\n", 133 off, 134 h_len); 135 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 136 "Expected history:\n"); 137 for (unsigned int i = 0; i<h_len; i++) 138 { 139 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 } 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 command_cb (void *cls, 199 const struct TALER_TESTING_Command *cmd) 200 { 201 struct IteratorContext *ic = cls; 202 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 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 211 "Checking if command %s is relevant for debit history\n", 212 cmd->label); 213 if ( (GNUNET_OK != 214 TALER_TESTING_get_trait_bank_row (cmd, 215 &row_id)) || 216 (GNUNET_OK != 217 TALER_TESTING_get_trait_debit_payto_uri (cmd, 218 &debit_account)) || 219 (GNUNET_OK != 220 TALER_TESTING_get_trait_credit_payto_uri (cmd, 221 &credit_account)) || 222 (GNUNET_OK != 223 TALER_TESTING_get_trait_amount (cmd, 224 &amount)) || 225 (GNUNET_OK != 226 TALER_TESTING_get_trait_wtid (cmd, 227 &wtid)) || 228 (GNUNET_OK != 229 TALER_TESTING_get_trait_exchange_url (cmd, 230 &exchange_base_url)) ) 231 return; /* not an event we care about */ 232 /* Seek "/history/outgoing" starting row. */ 233 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 234 "Command %s is relevant for debit history!\n", 235 cmd->label); 236 if ( (NULL != ic->row_id_start) && 237 (*(ic->row_id_start) == *row_id) && 238 (! ic->ok) ) 239 { 240 /* Until here, nothing counted. */ 241 ic->ok = true; 242 return; 243 } 244 /* when 'start' was _not_ given, then ok == GNUNET_YES */ 245 if (! ic->ok) 246 return; /* skip until we find the marker */ 247 if (ic->total >= GNUNET_MAX (hs->num_results, 248 -hs->num_results) ) 249 { 250 TALER_LOG_DEBUG ("Hit history limit\n"); 251 return; 252 } 253 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 if (ic->pos == ic->total) 259 GNUNET_array_grow (ic->h, 260 ic->total, 261 ic->pos * 2); 262 ic->h[ic->pos].c_url = GNUNET_strdup (credit_account->full_payto); 263 ic->h[ic->pos].details.credit_account_uri.full_payto 264 = ic->h[ic->pos].c_url; 265 ic->h[ic->pos].details.amount = *amount; 266 ic->h[ic->pos].row_id = *row_id; 267 ic->h[ic->pos].details.wtid = *wtid; 268 ic->h[ic->pos].details.exchange_base_url = exchange_base_url; 269 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 build_history (struct HistoryState *hs, 286 struct History **rh) 287 { 288 struct TALER_TESTING_Interpreter *is = hs->is; 289 struct IteratorContext ic = { 290 .hs = hs 291 }; 292 293 if (NULL != hs->start_row_reference) 294 { 295 const struct TALER_TESTING_Command *add_incoming_cmd; 296 297 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 add_incoming_cmd = TALER_TESTING_interpreter_lookup_command ( 302 is, 303 hs->start_row_reference); 304 GNUNET_assert (NULL != add_incoming_cmd); 305 GNUNET_assert (GNUNET_OK == 306 TALER_TESTING_get_trait_row (add_incoming_cmd, 307 &ic.row_id_start)); 308 } 309 310 ic.ok = false; 311 if (NULL == ic.row_id_start) 312 ic.ok = true; 313 GNUNET_array_grow (ic.h, 314 ic.total, 315 4); 316 GNUNET_assert (0 != hs->num_results); 317 TALER_TESTING_iterate (is, 318 hs->num_results > 0, 319 &command_cb, 320 &ic); 321 GNUNET_assert (ic.ok); 322 GNUNET_array_grow (ic.h, 323 ic.total, 324 ic.pos); 325 if (0 == ic.pos) 326 TALER_LOG_DEBUG ("Empty credit history computed\n"); 327 *rh = ic.h; 328 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 check_result (struct History *h, 345 uint64_t total, 346 unsigned int off, 347 const struct TALER_BANK_DebitDetails *details) 348 { 349 if (off >= total) 350 { 351 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 print_expected (h, 357 total, 358 off); 359 return GNUNET_SYSERR; 360 } 361 if ( (0 != GNUNET_memcmp (&h[off].details.wtid, 362 &details->wtid)) || 363 (0 != TALER_amount_cmp (&h[off].details.amount, 364 &details->amount)) || 365 (0 != TALER_full_payto_normalize_and_cmp ( 366 h[off].details.credit_account_uri, 367 details->credit_account_uri)) ) 368 { 369 GNUNET_break (0); 370 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 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 print_expected (h, 381 total, 382 off); 383 return GNUNET_SYSERR; 384 } 385 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 history_cb (void *cls, 401 const struct TALER_BANK_DebitHistoryResponse *dhr) 402 { 403 struct HistoryState *hs = cls; 404 struct TALER_TESTING_Interpreter *is = hs->is; 405 406 hs->hh = NULL; 407 switch (dhr->http_status) 408 { 409 case 0: 410 GNUNET_break (0); 411 goto error; 412 case MHD_HTTP_OK: 413 for (unsigned int i = 0; i<dhr->details.ok.details_length; i++) 414 { 415 const struct TALER_BANK_DebitDetails *dd = 416 &dhr->details.ok.details[i]; 417 418 /* check current element */ 419 if (GNUNET_OK != 420 check_result (hs->h, 421 hs->total, 422 hs->results_obtained, 423 dd)) 424 { 425 GNUNET_break (0); 426 json_dumpf (dhr->response, 427 stderr, 428 JSON_COMPACT); 429 hs->failed = true; 430 hs->hh = NULL; 431 TALER_TESTING_interpreter_fail (is); 432 return; 433 } 434 hs->results_obtained++; 435 } 436 TALER_TESTING_interpreter_next (is); 437 return; 438 case MHD_HTTP_NO_CONTENT: 439 if (0 == hs->total) 440 { 441 /* not found is OK for empty history */ 442 TALER_TESTING_interpreter_next (is); 443 return; 444 } 445 GNUNET_break (0); 446 goto error; 447 case MHD_HTTP_NOT_FOUND: 448 if (0 == hs->total) 449 { 450 /* not found is OK for empty history */ 451 TALER_TESTING_interpreter_next (is); 452 return; 453 } 454 GNUNET_break (0); 455 goto error; 456 default: 457 hs->hh = NULL; 458 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 459 "Unwanted response code from /history/incoming: %u\n", 460 dhr->http_status); 461 TALER_TESTING_interpreter_fail (is); 462 return; 463 } 464 error: 465 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 print_expected (hs->h, 474 hs->total, 475 UINT_MAX); 476 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 history_run (void *cls, 489 const struct TALER_TESTING_Command *cmd, 490 struct TALER_TESTING_Interpreter *is) 491 { 492 struct HistoryState *hs = cls; 493 uint64_t row_id = (hs->num_results > 0) ? 0 : UINT64_MAX; 494 const uint64_t *row_ptr; 495 496 (void) cmd; 497 hs->is = is; 498 /* Get row_id from trait. */ 499 if (NULL != hs->start_row_reference) 500 { 501 const struct TALER_TESTING_Command *history_cmd; 502 503 history_cmd 504 = TALER_TESTING_interpreter_lookup_command (is, 505 hs->start_row_reference); 506 507 if (NULL == history_cmd) 508 TALER_TESTING_FAIL (is); 509 if (GNUNET_OK != 510 TALER_TESTING_get_trait_row (history_cmd, 511 &row_ptr)) 512 TALER_TESTING_FAIL (is); 513 else 514 row_id = *row_ptr; 515 TALER_LOG_DEBUG ("row id (from trait) is %llu\n", 516 (unsigned long long) row_id); 517 } 518 hs->total = build_history (hs, 519 &hs->h); 520 hs->hh = TALER_BANK_debit_history ( 521 TALER_TESTING_interpreter_get_context (is), 522 &hs->auth, 523 row_id, 524 hs->num_results, 525 GNUNET_TIME_UNIT_ZERO, 526 &history_cb, 527 hs); 528 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 history_cleanup (void *cls, 541 const struct TALER_TESTING_Command *cmd) 542 { 543 struct HistoryState *hs = cls; 544 545 (void) cmd; 546 if (NULL != hs->hh) 547 { 548 TALER_TESTING_command_incomplete (hs->is, 549 cmd->label); 550 TALER_BANK_debit_history_cancel (hs->hh); 551 } 552 for (unsigned int off = 0; off<hs->total; off++) 553 { 554 GNUNET_free (hs->h[off].c_url); 555 } 556 GNUNET_free (hs->h); 557 GNUNET_free (hs); 558 } 559 560 561 struct TALER_TESTING_Command 562 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 hs = GNUNET_new (struct HistoryState); 570 hs->account_url = auth->wire_gateway_url; 571 hs->start_row_reference = start_row_reference; 572 hs->num_results = num_results; 573 hs->auth = *auth; 574 575 { 576 struct TALER_TESTING_Command cmd = { 577 .label = label, 578 .cls = hs, 579 .run = &history_run, 580 .cleanup = &history_cleanup 581 }; 582 583 return cmd; 584 } 585 } 586 587 588 /* end of testing_api_cmd_bank_history_debit.c */