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