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