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