Line data Source code
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 7 : get_orders_cb (void *cls,
85 : const struct TALER_MERCHANT_GetPrivateOrdersResponse *ogr)
86 : {
87 7 : struct GetOrdersState *gos = cls;
88 :
89 7 : gos->ogh = NULL;
90 7 : if (gos->http_status != ogr->hr.http_status)
91 : {
92 0 : 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 0 : TALER_TESTING_interpreter_fail (gos->is);
98 0 : return;
99 : }
100 7 : switch (ogr->hr.http_status)
101 : {
102 7 : case MHD_HTTP_OK:
103 7 : if (ogr->details.ok.orders_length != gos->orders_length)
104 : {
105 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
106 : "Number of orders found does not match\n");
107 0 : TALER_TESTING_interpreter_fail (gos->is);
108 0 : return;
109 : }
110 15 : for (unsigned int i = 0; i < ogr->details.ok.orders_length; ++i)
111 : {
112 8 : const struct TALER_MERCHANT_GetPrivateOrdersOrderEntry *order =
113 8 : &ogr->details.ok.orders[i];
114 : const struct TALER_TESTING_Command *order_cmd;
115 :
116 8 : order_cmd = TALER_TESTING_interpreter_lookup_command (
117 : gos->is,
118 8 : gos->orders[i]);
119 :
120 : {
121 : const char *order_id;
122 :
123 8 : if (GNUNET_OK !=
124 8 : TALER_TESTING_get_trait_order_id (order_cmd,
125 : &order_id))
126 : {
127 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
128 : "Could not fetch order id\n");
129 0 : TALER_TESTING_interpreter_fail (gos->is);
130 0 : return;
131 : }
132 8 : if (0 != strcmp (order->order_id,
133 : order_id))
134 : {
135 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
136 : "Order id does not match\n");
137 0 : TALER_TESTING_interpreter_fail (gos->is);
138 0 : 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 8 : GNUNET_JSON_spec_string ("summary",
147 : &summary),
148 8 : TALER_JSON_spec_amount_any ("amount",
149 : &amount),
150 8 : GNUNET_JSON_spec_end ()
151 : };
152 :
153 8 : if (GNUNET_OK !=
154 8 : TALER_TESTING_get_trait_contract_terms (order_cmd,
155 : &contract_terms))
156 : {
157 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
158 : "Could not fetch order contract terms\n");
159 0 : TALER_TESTING_interpreter_fail (gos->is);
160 0 : return;
161 : }
162 8 : if (GNUNET_OK !=
163 8 : GNUNET_JSON_parse (contract_terms,
164 : spec,
165 : NULL, NULL))
166 : {
167 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
168 : "Could not parse order contract terms\n");
169 0 : TALER_TESTING_interpreter_fail (gos->is);
170 0 : return;
171 : }
172 8 : if ((0 != strcmp (summary,
173 16 : order->summary)) ||
174 8 : (GNUNET_OK != TALER_amount_cmp_currency (&amount,
175 8 : &order->amount)) ||
176 8 : (0 != TALER_amount_cmp (&amount,
177 : &order->amount)))
178 : {
179 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
180 : "Order summary and/or amount does not match\n");
181 0 : TALER_TESTING_interpreter_fail (gos->is);
182 0 : return;
183 : }
184 : }
185 : }
186 7 : break;
187 0 : case MHD_HTTP_ACCEPTED:
188 : /* FIXME: do more checks here (new KYC logic!) */
189 0 : break;
190 0 : default:
191 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
192 : "Unhandled HTTP status.\n");
193 : }
194 7 : 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 7 : get_orders_run (void *cls,
207 : const struct TALER_TESTING_Command *cmd,
208 : struct TALER_TESTING_Interpreter *is)
209 : {
210 7 : struct GetOrdersState *gos = cls;
211 :
212 7 : gos->is = is;
213 7 : gos->ogh = TALER_MERCHANT_get_private_orders_create (
214 : TALER_TESTING_interpreter_get_context (is),
215 : gos->merchant_url);
216 7 : GNUNET_assert (NULL != gos->ogh);
217 : {
218 : enum TALER_ErrorCode ec;
219 :
220 7 : ec = TALER_MERCHANT_get_private_orders_start (gos->ogh,
221 : &get_orders_cb,
222 : gos);
223 7 : GNUNET_assert (TALER_EC_NONE == ec);
224 : }
225 7 : }
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 7 : get_orders_cleanup (void *cls,
237 : const struct TALER_TESTING_Command *cmd)
238 : {
239 7 : struct GetOrdersState *gos = cls;
240 :
241 7 : if (NULL != gos->ogh)
242 : {
243 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
244 : "GET /orders operation did not complete\n");
245 0 : TALER_MERCHANT_get_private_orders_cancel (gos->ogh);
246 : }
247 7 : GNUNET_array_grow (gos->orders,
248 : gos->orders_length,
249 : 0);
250 7 : GNUNET_free (gos);
251 7 : }
252 :
253 :
254 : struct TALER_TESTING_Command
255 7 : 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 7 : gos = GNUNET_new (struct GetOrdersState);
263 7 : gos->merchant_url = merchant_url;
264 7 : gos->http_status = http_status;
265 : {
266 : const char *clabel;
267 : va_list ap;
268 :
269 7 : va_start (ap, http_status);
270 15 : while (NULL != (clabel = va_arg (ap, const char *)))
271 : {
272 8 : GNUNET_array_append (gos->orders,
273 : gos->orders_length,
274 : clabel);
275 : }
276 7 : va_end (ap);
277 : }
278 : {
279 7 : struct TALER_TESTING_Command cmd = {
280 : .cls = gos,
281 : .label = label,
282 : .run = &get_orders_run,
283 : .cleanup = &get_orders_cleanup
284 : };
285 :
286 7 : 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 2 : conclude_task (void *cls)
363 : {
364 2 : struct MerchantPollOrdersConcludeState *poc = cls;
365 : const struct TALER_TESTING_Command *poll_cmd;
366 : struct MerchantPollOrdersStartState *pos;
367 : struct GNUNET_TIME_Absolute now;
368 :
369 2 : poc->task = NULL;
370 : poll_cmd =
371 2 : TALER_TESTING_interpreter_lookup_command (poc->is,
372 : poc->start_reference);
373 2 : if (NULL == poll_cmd)
374 0 : TALER_TESTING_FAIL (poc->is);
375 2 : pos = poll_cmd->cls;
376 2 : if (NULL != pos->ogh)
377 : {
378 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
379 : "Expected poll GET /private/orders to have completed, but it did not!\n");
380 0 : TALER_TESTING_FAIL (poc->is);
381 : }
382 2 : if (pos->http_status != poc->expected_http_status)
383 : {
384 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
385 : "Expected HTTP status %u, got %u\n",
386 : poc->expected_http_status,
387 : pos->http_status);
388 0 : TALER_TESTING_FAIL (poc->is);
389 : }
390 2 : now = GNUNET_TIME_absolute_get ();
391 2 : if (GNUNET_TIME_absolute_cmp (GNUNET_TIME_absolute_add (
392 : pos->deadline,
393 : GNUNET_TIME_UNIT_SECONDS),
394 : <,
395 : now))
396 : {
397 0 : 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 0 : TALER_TESTING_FAIL (poc->is);
402 : }
403 2 : 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 2 : merchant_poll_orders_cb (
418 : void *cls,
419 : const struct TALER_MERCHANT_GetPrivateOrdersResponse *ogr)
420 : {
421 2 : struct MerchantPollOrdersStartState *pos = cls;
422 :
423 2 : pos->ogh = NULL;
424 2 : if (MHD_HTTP_OK != ogr->hr.http_status)
425 : {
426 0 : 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 0 : TALER_TESTING_interpreter_fail (pos->is);
432 0 : return;
433 : }
434 2 : switch (ogr->hr.http_status)
435 : {
436 2 : case MHD_HTTP_OK:
437 : // FIXME: use order references to check if the data returned matches that from the POST / PATCH
438 2 : break;
439 0 : default:
440 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
441 : "Unhandled HTTP status.\n");
442 : }
443 2 : pos->http_status = ogr->hr.http_status;
444 2 : if (NULL != pos->cs)
445 : {
446 0 : GNUNET_SCHEDULER_cancel (pos->cs->task);
447 0 : pos->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
448 0 : 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 2 : merchant_poll_orders_start_run (void *cls,
462 : const struct TALER_TESTING_Command *cmd,
463 : struct TALER_TESTING_Interpreter *is)
464 : {
465 2 : struct MerchantPollOrdersStartState *pos = cls;
466 :
467 : /* add 1s grace time to timeout */
468 : pos->deadline
469 2 : = GNUNET_TIME_relative_to_absolute (
470 : GNUNET_TIME_relative_add (pos->timeout,
471 : GNUNET_TIME_UNIT_SECONDS));
472 2 : pos->is = is;
473 2 : pos->ogh = TALER_MERCHANT_get_private_orders_create (
474 : TALER_TESTING_interpreter_get_context (is),
475 : pos->merchant_url);
476 2 : GNUNET_assert (NULL != pos->ogh);
477 2 : 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 2 : ec = TALER_MERCHANT_get_private_orders_start (pos->ogh,
495 : &merchant_poll_orders_cb,
496 : pos);
497 2 : GNUNET_assert (TALER_EC_NONE == ec);
498 : }
499 : /* We CONTINUE to run the interpreter while the long-polled command
500 : completes asynchronously! */
501 2 : TALER_TESTING_interpreter_next (pos->is);
502 2 : }
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 2 : merchant_poll_orders_start_cleanup (void *cls,
514 : const struct TALER_TESTING_Command *cmd)
515 : {
516 2 : struct MerchantPollOrdersStartState *pos = cls;
517 :
518 2 : if (NULL != pos->ogh)
519 : {
520 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
521 : "Command `%s' was not terminated\n",
522 : TALER_TESTING_interpreter_get_current_label (
523 : pos->is));
524 0 : TALER_MERCHANT_get_private_orders_cancel (pos->ogh);
525 : }
526 2 : GNUNET_free (pos);
527 2 : }
528 :
529 :
530 : struct TALER_TESTING_Command
531 2 : 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 2 : pos = GNUNET_new (struct MerchantPollOrdersStartState);
538 2 : pos->merchant_url = merchant_url;
539 2 : pos->timeout = timeout;
540 : {
541 2 : 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 2 : 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 2 : merchant_poll_orders_conclude_run (void *cls,
562 : const struct TALER_TESTING_Command *cmd,
563 : struct TALER_TESTING_Interpreter *is)
564 : {
565 2 : struct MerchantPollOrdersConcludeState *poc = cls;
566 : const struct TALER_TESTING_Command *poll_cmd;
567 : struct MerchantPollOrdersStartState *pos;
568 :
569 2 : poc->is = is;
570 : poll_cmd =
571 2 : TALER_TESTING_interpreter_lookup_command (is,
572 : poc->start_reference);
573 2 : if (NULL == poll_cmd)
574 0 : TALER_TESTING_FAIL (poc->is);
575 2 : GNUNET_assert (poll_cmd->run == &merchant_poll_orders_start_run);
576 2 : pos = poll_cmd->cls;
577 2 : pos->cs = poc;
578 2 : if (NULL == pos->ogh)
579 2 : poc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
580 : poc);
581 : else
582 0 : 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 2 : merchant_poll_orders_conclude_cleanup (void *cls,
597 : const struct TALER_TESTING_Command *cmd)
598 : {
599 2 : struct MerchantPollOrdersConcludeState *poc = cls;
600 :
601 2 : if (NULL != poc->task)
602 : {
603 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
604 : "Command `%s' was not terminated\n",
605 : TALER_TESTING_interpreter_get_current_label (
606 : poc->is));
607 0 : GNUNET_SCHEDULER_cancel (poc->task);
608 0 : poc->task = NULL;
609 : }
610 2 : GNUNET_free (poc);
611 2 : }
612 :
613 :
614 : struct TALER_TESTING_Command
615 2 : 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 2 : poc = GNUNET_new (struct MerchantPollOrdersConcludeState);
622 2 : poc->start_reference = poll_start_reference;
623 2 : poc->expected_http_status = http_status;
624 : {
625 2 : 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 2 : return cmd;
633 : }
634 : }
635 :
636 :
637 : /* end of testing_api_cmd_get_orders.c */
|