testing_api_cmd_get_orders.c (18082B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2020-2023 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 src/testing/testing_api_cmd_get_orders.c 21 * @brief command to test GET /orders 22 * @author Jonathan Buchanan 23 */ 24 #include "platform.h" 25 #include <taler/taler_exchange_service.h> 26 #include <taler/taler_testing_lib.h> 27 #include "taler/taler_merchant_service.h" 28 #include "taler/taler_merchant_testing_lib.h" 29 #include <taler/merchant/get-private-orders.h> 30 31 32 /** 33 * State of a "GET orders" CMD. 34 */ 35 struct GetOrdersState 36 { 37 38 /** 39 * Handle for a "GET orders" request. 40 */ 41 struct TALER_MERCHANT_GetPrivateOrdersHandle *ogh; 42 43 /** 44 * The interpreter state. 45 */ 46 struct TALER_TESTING_Interpreter *is; 47 48 /** 49 * Base URL of the merchant serving the request. 50 */ 51 const char *merchant_url; 52 53 /** 54 * Expected HTTP response code. 55 */ 56 unsigned int http_status; 57 58 /** 59 * A NULL-terminated array of CMD labels that created orders. 60 */ 61 const char **orders; 62 63 /** 64 * The length of @e orders. 65 */ 66 unsigned int orders_length; 67 68 }; 69 70 71 /** 72 * Callback for a GET /orders operation. 73 * 74 * @param cls closure for this function 75 * @param ogr response 76 */ 77 /* FIXME_POLYMORPHISM: TALER_MERCHANT_GetPrivateOrdersCallback is used in this 78 compilation unit with two different closure types (struct GetOrdersState here, 79 struct MerchantPollOrdersStartState in merchant_poll_orders_cb below), so a single 80 TALER_MERCHANT_GET_PRIVATE_ORDERS_RESULT_CLOSURE override cannot type both. Left as 81 void *cls; adopting the RESULT_CLOSURE pattern needs a manual refactor (e.g. split 82 the two commands into separate translation units). */ 83 static void 84 get_orders_cb (void *cls, 85 const struct TALER_MERCHANT_GetPrivateOrdersResponse *ogr) 86 { 87 struct GetOrdersState *gos = cls; 88 89 gos->ogh = NULL; 90 if (gos->http_status != ogr->hr.http_status) 91 { 92 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 93 "Unexpected response code %u (%d) to command %s\n", 94 ogr->hr.http_status, 95 (int) ogr->hr.ec, 96 TALER_TESTING_interpreter_get_current_label (gos->is)); 97 TALER_TESTING_interpreter_fail (gos->is); 98 return; 99 } 100 switch (ogr->hr.http_status) 101 { 102 case MHD_HTTP_OK: 103 if (ogr->details.ok.orders_length != gos->orders_length) 104 { 105 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 106 "Number of orders found does not match\n"); 107 TALER_TESTING_interpreter_fail (gos->is); 108 return; 109 } 110 for (unsigned int i = 0; i < ogr->details.ok.orders_length; ++i) 111 { 112 const struct TALER_MERCHANT_GetPrivateOrdersOrderEntry *order = 113 &ogr->details.ok.orders[i]; 114 const struct TALER_TESTING_Command *order_cmd; 115 116 order_cmd = TALER_TESTING_interpreter_lookup_command ( 117 gos->is, 118 gos->orders[i]); 119 120 { 121 const char *order_id; 122 123 if (GNUNET_OK != 124 TALER_TESTING_get_trait_order_id (order_cmd, 125 &order_id)) 126 { 127 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 128 "Could not fetch order id\n"); 129 TALER_TESTING_interpreter_fail (gos->is); 130 return; 131 } 132 if (0 != strcmp (order->order_id, 133 order_id)) 134 { 135 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 136 "Order id does not match\n"); 137 TALER_TESTING_interpreter_fail (gos->is); 138 return; 139 } 140 } 141 { 142 const json_t *contract_terms; 143 struct TALER_Amount amount; 144 const char *summary; 145 struct GNUNET_JSON_Specification spec[] = { 146 GNUNET_JSON_spec_string ("summary", 147 &summary), 148 TALER_JSON_spec_amount_any ("amount", 149 &amount), 150 GNUNET_JSON_spec_end () 151 }; 152 153 if (GNUNET_OK != 154 TALER_TESTING_get_trait_contract_terms (order_cmd, 155 &contract_terms)) 156 { 157 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 158 "Could not fetch order contract terms\n"); 159 TALER_TESTING_interpreter_fail (gos->is); 160 return; 161 } 162 if (GNUNET_OK != 163 GNUNET_JSON_parse (contract_terms, 164 spec, 165 NULL, NULL)) 166 { 167 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 168 "Could not parse order contract terms\n"); 169 TALER_TESTING_interpreter_fail (gos->is); 170 return; 171 } 172 if ((0 != strcmp (summary, 173 order->summary)) || 174 (GNUNET_OK != TALER_amount_cmp_currency (&amount, 175 &order->amount)) || 176 (0 != TALER_amount_cmp (&amount, 177 &order->amount))) 178 { 179 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 180 "Order summary and/or amount does not match\n"); 181 TALER_TESTING_interpreter_fail (gos->is); 182 return; 183 } 184 } 185 } 186 break; 187 case MHD_HTTP_ACCEPTED: 188 /* FIXME: do more checks here (new KYC logic!) */ 189 break; 190 default: 191 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 192 "Unhandled HTTP status.\n"); 193 } 194 TALER_TESTING_interpreter_next (gos->is); 195 } 196 197 198 /** 199 * Run the "GET /orders" CMD. 200 * 201 * @param cls closure. 202 * @param cmd command being run now. 203 * @param is interpreter state. 204 */ 205 static void 206 get_orders_run (void *cls, 207 const struct TALER_TESTING_Command *cmd, 208 struct TALER_TESTING_Interpreter *is) 209 { 210 struct GetOrdersState *gos = cls; 211 212 gos->is = is; 213 gos->ogh = TALER_MERCHANT_get_private_orders_create ( 214 TALER_TESTING_interpreter_get_context (is), 215 gos->merchant_url); 216 GNUNET_assert (NULL != gos->ogh); 217 { 218 enum TALER_ErrorCode ec; 219 220 ec = TALER_MERCHANT_get_private_orders_start (gos->ogh, 221 &get_orders_cb, 222 gos); 223 GNUNET_assert (TALER_EC_NONE == ec); 224 } 225 } 226 227 228 /** 229 * Free the state of a "GET orders" CMD, and possibly 230 * cancel a pending operation thereof. 231 * 232 * @param cls closure. 233 * @param cmd command being run. 234 */ 235 static void 236 get_orders_cleanup (void *cls, 237 const struct TALER_TESTING_Command *cmd) 238 { 239 struct GetOrdersState *gos = cls; 240 241 if (NULL != gos->ogh) 242 { 243 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 244 "GET /orders operation did not complete\n"); 245 TALER_MERCHANT_get_private_orders_cancel (gos->ogh); 246 } 247 GNUNET_array_grow (gos->orders, 248 gos->orders_length, 249 0); 250 GNUNET_free (gos); 251 } 252 253 254 struct TALER_TESTING_Command 255 TALER_TESTING_cmd_merchant_get_orders (const char *label, 256 const char *merchant_url, 257 unsigned int http_status, 258 ...) 259 { 260 struct GetOrdersState *gos; 261 262 gos = GNUNET_new (struct GetOrdersState); 263 gos->merchant_url = merchant_url; 264 gos->http_status = http_status; 265 { 266 const char *clabel; 267 va_list ap; 268 269 va_start (ap, http_status); 270 while (NULL != (clabel = va_arg (ap, const char *))) 271 { 272 GNUNET_array_append (gos->orders, 273 gos->orders_length, 274 clabel); 275 } 276 va_end (ap); 277 } 278 { 279 struct TALER_TESTING_Command cmd = { 280 .cls = gos, 281 .label = label, 282 .run = &get_orders_run, 283 .cleanup = &get_orders_cleanup 284 }; 285 286 return cmd; 287 } 288 } 289 290 291 struct MerchantPollOrdersConcludeState 292 { 293 /** 294 * The interpreter state. 295 */ 296 struct TALER_TESTING_Interpreter *is; 297 298 /** 299 * Reference to a command that can provide a poll orders start command. 300 */ 301 const char *start_reference; 302 303 /** 304 * Task to wait for the deadline. 305 */ 306 struct GNUNET_SCHEDULER_Task *task; 307 308 /** 309 * Expected HTTP response status code. 310 */ 311 unsigned int expected_http_status; 312 }; 313 314 315 struct MerchantPollOrdersStartState 316 { 317 /** 318 * The merchant base URL. 319 */ 320 const char *merchant_url; 321 322 /** 323 * The handle to the current GET /private/orders request. 324 */ 325 struct TALER_MERCHANT_GetPrivateOrdersHandle *ogh; 326 327 /** 328 * The interpreter state. 329 */ 330 struct TALER_TESTING_Interpreter *is; 331 332 /** 333 * How long to wait for server to return a response. 334 */ 335 struct GNUNET_TIME_Relative timeout; 336 337 /** 338 * Conclude state waiting for completion (if any). 339 */ 340 struct MerchantPollOrdersConcludeState *cs; 341 342 /** 343 * The HTTP status code returned by the backend. 344 */ 345 unsigned int http_status; 346 347 /** 348 * When the request should be completed by. 349 */ 350 struct GNUNET_TIME_Absolute deadline; 351 }; 352 353 354 /** 355 * Task called when either the timeout for the get orders 356 * command expired or we got a response. Checks if the 357 * result is what we expected. 358 * 359 * @param cls a `struct MerchantPollOrdersConcludeState` 360 */ 361 static void 362 conclude_task (void *cls) 363 { 364 struct MerchantPollOrdersConcludeState *poc = cls; 365 const struct TALER_TESTING_Command *poll_cmd; 366 struct MerchantPollOrdersStartState *pos; 367 struct GNUNET_TIME_Absolute now; 368 369 poc->task = NULL; 370 poll_cmd = 371 TALER_TESTING_interpreter_lookup_command (poc->is, 372 poc->start_reference); 373 if (NULL == poll_cmd) 374 TALER_TESTING_FAIL (poc->is); 375 pos = poll_cmd->cls; 376 if (NULL != pos->ogh) 377 { 378 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 379 "Expected poll GET /private/orders to have completed, but it did not!\n"); 380 TALER_TESTING_FAIL (poc->is); 381 } 382 if (pos->http_status != poc->expected_http_status) 383 { 384 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 385 "Expected HTTP status %u, got %u\n", 386 poc->expected_http_status, 387 pos->http_status); 388 TALER_TESTING_FAIL (poc->is); 389 } 390 now = GNUNET_TIME_absolute_get (); 391 if (GNUNET_TIME_absolute_cmp (GNUNET_TIME_absolute_add ( 392 pos->deadline, 393 GNUNET_TIME_UNIT_SECONDS), 394 <, 395 now)) 396 { 397 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 398 "Expected answer to be delayed until %llu, but got response at %llu\n", 399 (unsigned long long) pos->deadline.abs_value_us, 400 (unsigned long long) now.abs_value_us); 401 TALER_TESTING_FAIL (poc->is); 402 } 403 TALER_TESTING_interpreter_next (poc->is); 404 } 405 406 407 /** 408 * Callback to process a GET /orders request 409 * 410 * @param cls closure 411 * @param ogr response details 412 */ 413 /* FIXME_POLYMORPHISM: see get_orders_cb above — same GetPrivateOrdersCallback typedef 414 is reused here with a different closure type, so this compilation unit cannot adopt 415 a single TALER_MERCHANT_GET_PRIVATE_ORDERS_RESULT_CLOSURE override. Left as void *cls. */ 416 static void 417 merchant_poll_orders_cb ( 418 void *cls, 419 const struct TALER_MERCHANT_GetPrivateOrdersResponse *ogr) 420 { 421 struct MerchantPollOrdersStartState *pos = cls; 422 423 pos->ogh = NULL; 424 if (MHD_HTTP_OK != ogr->hr.http_status) 425 { 426 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 427 "Unexpected response code %u (%d) to command %s\n", 428 ogr->hr.http_status, 429 (int) ogr->hr.ec, 430 TALER_TESTING_interpreter_get_current_label (pos->is)); 431 TALER_TESTING_interpreter_fail (pos->is); 432 return; 433 } 434 switch (ogr->hr.http_status) 435 { 436 case MHD_HTTP_OK: 437 // FIXME: use order references to check if the data returned matches that from the POST / PATCH 438 break; 439 default: 440 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 441 "Unhandled HTTP status.\n"); 442 } 443 pos->http_status = ogr->hr.http_status; 444 if (NULL != pos->cs) 445 { 446 GNUNET_SCHEDULER_cancel (pos->cs->task); 447 pos->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task, 448 pos->cs); 449 } 450 } 451 452 453 /** 454 * Run the "GET orders" CMD. 455 * 456 * @param cls closure. 457 * @param cmd command being run now. 458 * @param is interpreter state. 459 */ 460 static void 461 merchant_poll_orders_start_run (void *cls, 462 const struct TALER_TESTING_Command *cmd, 463 struct TALER_TESTING_Interpreter *is) 464 { 465 struct MerchantPollOrdersStartState *pos = cls; 466 467 /* add 1s grace time to timeout */ 468 pos->deadline 469 = GNUNET_TIME_relative_to_absolute ( 470 GNUNET_TIME_relative_add (pos->timeout, 471 GNUNET_TIME_UNIT_SECONDS)); 472 pos->is = is; 473 pos->ogh = TALER_MERCHANT_get_private_orders_create ( 474 TALER_TESTING_interpreter_get_context (is), 475 pos->merchant_url); 476 GNUNET_assert (NULL != pos->ogh); 477 TALER_MERCHANT_get_private_orders_set_options ( 478 pos->ogh, 479 TALER_MERCHANT_get_private_orders_option_paid ( 480 TALER_EXCHANGE_YNA_ALL), 481 TALER_MERCHANT_get_private_orders_option_refunded ( 482 TALER_EXCHANGE_YNA_ALL), 483 TALER_MERCHANT_get_private_orders_option_wired ( 484 TALER_EXCHANGE_YNA_ALL), 485 TALER_MERCHANT_get_private_orders_option_date ( 486 GNUNET_TIME_UNIT_ZERO_TS), 487 TALER_MERCHANT_get_private_orders_option_offset (1), 488 TALER_MERCHANT_get_private_orders_option_limit (2), 489 TALER_MERCHANT_get_private_orders_option_timeout ( 490 pos->timeout)); 491 { 492 enum TALER_ErrorCode ec; 493 494 ec = TALER_MERCHANT_get_private_orders_start (pos->ogh, 495 &merchant_poll_orders_cb, 496 pos); 497 GNUNET_assert (TALER_EC_NONE == ec); 498 } 499 /* We CONTINUE to run the interpreter while the long-polled command 500 completes asynchronously! */ 501 TALER_TESTING_interpreter_next (pos->is); 502 } 503 504 505 /** 506 * Free the state of a "GET orders" CMD, and possibly 507 * cancel a pending operation thereof. 508 * 509 * @param cls closure. 510 * @param cmd command being run. 511 */ 512 static void 513 merchant_poll_orders_start_cleanup (void *cls, 514 const struct TALER_TESTING_Command *cmd) 515 { 516 struct MerchantPollOrdersStartState *pos = cls; 517 518 if (NULL != pos->ogh) 519 { 520 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 521 "Command `%s' was not terminated\n", 522 TALER_TESTING_interpreter_get_current_label ( 523 pos->is)); 524 TALER_MERCHANT_get_private_orders_cancel (pos->ogh); 525 } 526 GNUNET_free (pos); 527 } 528 529 530 struct TALER_TESTING_Command 531 TALER_TESTING_cmd_poll_orders_start (const char *label, 532 const char *merchant_url, 533 struct GNUNET_TIME_Relative timeout) 534 { 535 struct MerchantPollOrdersStartState *pos; 536 537 pos = GNUNET_new (struct MerchantPollOrdersStartState); 538 pos->merchant_url = merchant_url; 539 pos->timeout = timeout; 540 { 541 struct TALER_TESTING_Command cmd = { 542 .cls = pos, 543 .label = label, 544 .run = &merchant_poll_orders_start_run, 545 .cleanup = &merchant_poll_orders_start_cleanup 546 }; 547 548 return cmd; 549 } 550 } 551 552 553 /** 554 * Wait for the "GET orders" CMD to complete. 555 * 556 * @param cls closure. 557 * @param cmd command being run now. 558 * @param is interpreter state. 559 */ 560 static void 561 merchant_poll_orders_conclude_run (void *cls, 562 const struct TALER_TESTING_Command *cmd, 563 struct TALER_TESTING_Interpreter *is) 564 { 565 struct MerchantPollOrdersConcludeState *poc = cls; 566 const struct TALER_TESTING_Command *poll_cmd; 567 struct MerchantPollOrdersStartState *pos; 568 569 poc->is = is; 570 poll_cmd = 571 TALER_TESTING_interpreter_lookup_command (is, 572 poc->start_reference); 573 if (NULL == poll_cmd) 574 TALER_TESTING_FAIL (poc->is); 575 GNUNET_assert (poll_cmd->run == &merchant_poll_orders_start_run); 576 pos = poll_cmd->cls; 577 pos->cs = poc; 578 if (NULL == pos->ogh) 579 poc->task = GNUNET_SCHEDULER_add_now (&conclude_task, 580 poc); 581 else 582 poc->task = GNUNET_SCHEDULER_add_at (pos->deadline, 583 &conclude_task, 584 poc); 585 } 586 587 588 /** 589 * Free the state of a "GET orders" CMD, and possibly 590 * cancel a pending operation thereof. 591 * 592 * @param cls closure. 593 * @param cmd command being run. 594 */ 595 static void 596 merchant_poll_orders_conclude_cleanup (void *cls, 597 const struct TALER_TESTING_Command *cmd) 598 { 599 struct MerchantPollOrdersConcludeState *poc = cls; 600 601 if (NULL != poc->task) 602 { 603 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 604 "Command `%s' was not terminated\n", 605 TALER_TESTING_interpreter_get_current_label ( 606 poc->is)); 607 GNUNET_SCHEDULER_cancel (poc->task); 608 poc->task = NULL; 609 } 610 GNUNET_free (poc); 611 } 612 613 614 struct TALER_TESTING_Command 615 TALER_TESTING_cmd_poll_orders_conclude (const char *label, 616 unsigned int http_status, 617 const char *poll_start_reference) 618 { 619 struct MerchantPollOrdersConcludeState *poc; 620 621 poc = GNUNET_new (struct MerchantPollOrdersConcludeState); 622 poc->start_reference = poll_start_reference; 623 poc->expected_http_status = http_status; 624 { 625 struct TALER_TESTING_Command cmd = { 626 .cls = poc, 627 .label = label, 628 .run = &merchant_poll_orders_conclude_run, 629 .cleanup = &merchant_poll_orders_conclude_cleanup 630 }; 631 632 return cmd; 633 } 634 } 635 636 637 /* end of testing_api_cmd_get_orders.c */