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 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 7 : get_orders_cb (void *cls,
78 : const struct TALER_MERCHANT_OrdersGetResponse *ogr)
79 : {
80 7 : struct GetOrdersState *gos = cls;
81 :
82 7 : gos->ogh = NULL;
83 7 : if (gos->http_status != ogr->hr.http_status)
84 : {
85 0 : 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 0 : TALER_TESTING_interpreter_fail (gos->is);
91 0 : return;
92 : }
93 7 : switch (ogr->hr.http_status)
94 : {
95 7 : case MHD_HTTP_OK:
96 7 : if (ogr->details.ok.orders_length != gos->orders_length)
97 : {
98 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
99 : "Number of orders found does not match\n");
100 0 : TALER_TESTING_interpreter_fail (gos->is);
101 0 : return;
102 : }
103 15 : for (unsigned int i = 0; i < ogr->details.ok.orders_length; ++i)
104 : {
105 8 : const struct TALER_MERCHANT_OrderEntry *order =
106 8 : &ogr->details.ok.orders[i];
107 : const struct TALER_TESTING_Command *order_cmd;
108 :
109 8 : order_cmd = TALER_TESTING_interpreter_lookup_command (
110 : gos->is,
111 8 : gos->orders[i]);
112 :
113 : {
114 : const char *order_id;
115 :
116 8 : if (GNUNET_OK !=
117 8 : TALER_TESTING_get_trait_order_id (order_cmd,
118 : &order_id))
119 : {
120 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
121 : "Could not fetch order id\n");
122 0 : TALER_TESTING_interpreter_fail (gos->is);
123 0 : return;
124 : }
125 8 : if (0 != strcmp (order->order_id,
126 : order_id))
127 : {
128 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
129 : "Order id does not match\n");
130 0 : TALER_TESTING_interpreter_fail (gos->is);
131 0 : 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 8 : GNUNET_JSON_spec_string ("summary",
140 : &summary),
141 8 : TALER_JSON_spec_amount_any ("amount",
142 : &amount),
143 8 : GNUNET_JSON_spec_end ()
144 : };
145 :
146 8 : if (GNUNET_OK !=
147 8 : TALER_TESTING_get_trait_contract_terms (order_cmd,
148 : &contract_terms))
149 : {
150 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
151 : "Could not fetch order contract terms\n");
152 0 : TALER_TESTING_interpreter_fail (gos->is);
153 0 : return;
154 : }
155 8 : if (GNUNET_OK !=
156 8 : GNUNET_JSON_parse (contract_terms,
157 : spec,
158 : NULL, NULL))
159 : {
160 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
161 : "Could not parse order contract terms\n");
162 0 : TALER_TESTING_interpreter_fail (gos->is);
163 0 : return;
164 : }
165 8 : if ((0 != strcmp (summary,
166 16 : order->summary)) ||
167 8 : (GNUNET_OK != TALER_amount_cmp_currency (&amount,
168 8 : &order->amount)) ||
169 8 : (0 != TALER_amount_cmp (&amount,
170 : &order->amount)))
171 : {
172 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
173 : "Order summary and/or amount does not match\n");
174 0 : TALER_TESTING_interpreter_fail (gos->is);
175 0 : return;
176 : }
177 : }
178 : }
179 7 : break;
180 0 : case MHD_HTTP_ACCEPTED:
181 : /* FIXME: do more checks here (new KYC logic!) */
182 0 : break;
183 0 : default:
184 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
185 : "Unhandled HTTP status.\n");
186 : }
187 7 : 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 7 : get_orders_run (void *cls,
200 : const struct TALER_TESTING_Command *cmd,
201 : struct TALER_TESTING_Interpreter *is)
202 : {
203 7 : struct GetOrdersState *gos = cls;
204 :
205 7 : gos->is = is;
206 7 : gos->ogh = TALER_MERCHANT_orders_get (TALER_TESTING_interpreter_get_context (
207 : is),
208 : gos->merchant_url,
209 : &get_orders_cb,
210 : gos);
211 7 : GNUNET_assert (NULL != gos->ogh);
212 7 : }
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 7 : get_orders_cleanup (void *cls,
224 : const struct TALER_TESTING_Command *cmd)
225 : {
226 7 : struct GetOrdersState *gos = cls;
227 :
228 7 : if (NULL != gos->ogh)
229 : {
230 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
231 : "GET /orders operation did not complete\n");
232 0 : TALER_MERCHANT_orders_get_cancel (gos->ogh);
233 : }
234 7 : GNUNET_array_grow (gos->orders,
235 : gos->orders_length,
236 : 0);
237 7 : GNUNET_free (gos);
238 7 : }
239 :
240 :
241 : struct TALER_TESTING_Command
242 7 : 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 7 : gos = GNUNET_new (struct GetOrdersState);
250 7 : gos->merchant_url = merchant_url;
251 7 : gos->http_status = http_status;
252 : {
253 : const char *clabel;
254 : va_list ap;
255 :
256 7 : va_start (ap, http_status);
257 15 : while (NULL != (clabel = va_arg (ap, const char *)))
258 : {
259 8 : GNUNET_array_append (gos->orders,
260 : gos->orders_length,
261 : clabel);
262 : }
263 7 : va_end (ap);
264 : }
265 : {
266 7 : struct TALER_TESTING_Command cmd = {
267 : .cls = gos,
268 : .label = label,
269 : .run = &get_orders_run,
270 : .cleanup = &get_orders_cleanup
271 : };
272 :
273 7 : 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 2 : conclude_task (void *cls)
350 : {
351 2 : struct MerchantPollOrdersConcludeState *poc = cls;
352 : const struct TALER_TESTING_Command *poll_cmd;
353 : struct MerchantPollOrdersStartState *pos;
354 : struct GNUNET_TIME_Absolute now;
355 :
356 2 : poc->task = NULL;
357 : poll_cmd =
358 2 : TALER_TESTING_interpreter_lookup_command (poc->is,
359 : poc->start_reference);
360 2 : if (NULL == poll_cmd)
361 0 : TALER_TESTING_FAIL (poc->is);
362 2 : pos = poll_cmd->cls;
363 2 : if (NULL != pos->ogh)
364 : {
365 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
366 : "Expected poll GET /private/orders to have completed, but it did not!\n");
367 0 : TALER_TESTING_FAIL (poc->is);
368 : }
369 2 : if (pos->http_status != poc->expected_http_status)
370 : {
371 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
372 : "Expected HTTP status %u, got %u\n",
373 : poc->expected_http_status,
374 : pos->http_status);
375 0 : TALER_TESTING_FAIL (poc->is);
376 : }
377 2 : now = GNUNET_TIME_absolute_get ();
378 2 : if (GNUNET_TIME_absolute_cmp (GNUNET_TIME_absolute_add (
379 : pos->deadline,
380 : GNUNET_TIME_UNIT_SECONDS),
381 : <,
382 : now))
383 : {
384 0 : 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 0 : TALER_TESTING_FAIL (poc->is);
389 : }
390 2 : 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 2 : merchant_poll_orders_cb (
402 : void *cls,
403 : const struct TALER_MERCHANT_OrdersGetResponse *ogr)
404 : {
405 2 : struct MerchantPollOrdersStartState *pos = cls;
406 :
407 2 : pos->ogh = NULL;
408 2 : if (MHD_HTTP_OK != ogr->hr.http_status)
409 : {
410 0 : 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 0 : TALER_TESTING_interpreter_fail (pos->is);
416 0 : return;
417 : }
418 2 : switch (ogr->hr.http_status)
419 : {
420 2 : case MHD_HTTP_OK:
421 : // FIXME: use order references to check if the data returned matches that from the POST / PATCH
422 2 : break;
423 0 : default:
424 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
425 : "Unhandled HTTP status.\n");
426 : }
427 2 : pos->http_status = ogr->hr.http_status;
428 2 : if (NULL != pos->cs)
429 : {
430 0 : GNUNET_SCHEDULER_cancel (pos->cs->task);
431 0 : pos->cs->task = GNUNET_SCHEDULER_add_now (&conclude_task,
432 0 : 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 2 : merchant_poll_orders_start_run (void *cls,
446 : const struct TALER_TESTING_Command *cmd,
447 : struct TALER_TESTING_Interpreter *is)
448 : {
449 2 : struct MerchantPollOrdersStartState *pos = cls;
450 :
451 : /* add 1s grace time to timeout */
452 : pos->deadline
453 2 : = GNUNET_TIME_relative_to_absolute (
454 : GNUNET_TIME_relative_add (pos->timeout,
455 : GNUNET_TIME_UNIT_SECONDS));
456 2 : pos->is = is;
457 2 : 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 2 : GNUNET_TIME_UNIT_ZERO_TS,
464 : 1,
465 : 2,
466 : pos->timeout,
467 : &merchant_poll_orders_cb,
468 : pos);
469 2 : GNUNET_assert (NULL != pos->ogh);
470 : /* We CONTINUE to run the interpreter while the long-polled command
471 : completes asynchronously! */
472 2 : TALER_TESTING_interpreter_next (pos->is);
473 2 : }
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 2 : merchant_poll_orders_start_cleanup (void *cls,
485 : const struct TALER_TESTING_Command *cmd)
486 : {
487 2 : struct MerchantPollOrdersStartState *pos = cls;
488 :
489 2 : if (NULL != pos->ogh)
490 : {
491 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
492 : "Command `%s' was not terminated\n",
493 : TALER_TESTING_interpreter_get_current_label (
494 : pos->is));
495 0 : TALER_MERCHANT_orders_get_cancel (pos->ogh);
496 : }
497 2 : GNUNET_free (pos);
498 2 : }
499 :
500 :
501 : struct TALER_TESTING_Command
502 2 : 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 2 : pos = GNUNET_new (struct MerchantPollOrdersStartState);
509 2 : pos->merchant_url = merchant_url;
510 2 : pos->timeout = timeout;
511 : {
512 2 : 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 2 : 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 2 : merchant_poll_orders_conclude_run (void *cls,
533 : const struct TALER_TESTING_Command *cmd,
534 : struct TALER_TESTING_Interpreter *is)
535 : {
536 2 : struct MerchantPollOrdersConcludeState *poc = cls;
537 : const struct TALER_TESTING_Command *poll_cmd;
538 : struct MerchantPollOrdersStartState *pos;
539 :
540 2 : poc->is = is;
541 : poll_cmd =
542 2 : TALER_TESTING_interpreter_lookup_command (is,
543 : poc->start_reference);
544 2 : if (NULL == poll_cmd)
545 0 : TALER_TESTING_FAIL (poc->is);
546 2 : GNUNET_assert (poll_cmd->run == &merchant_poll_orders_start_run);
547 2 : pos = poll_cmd->cls;
548 2 : pos->cs = poc;
549 2 : if (NULL == pos->ogh)
550 2 : poc->task = GNUNET_SCHEDULER_add_now (&conclude_task,
551 : poc);
552 : else
553 0 : 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 2 : merchant_poll_orders_conclude_cleanup (void *cls,
568 : const struct TALER_TESTING_Command *cmd)
569 : {
570 2 : struct MerchantPollOrdersConcludeState *poc = cls;
571 :
572 2 : if (NULL != poc->task)
573 : {
574 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
575 : "Command `%s' was not terminated\n",
576 : TALER_TESTING_interpreter_get_current_label (
577 : poc->is));
578 0 : GNUNET_SCHEDULER_cancel (poc->task);
579 0 : poc->task = NULL;
580 : }
581 2 : GNUNET_free (poc);
582 2 : }
583 :
584 :
585 : struct TALER_TESTING_Command
586 2 : 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 2 : poc = GNUNET_new (struct MerchantPollOrdersConcludeState);
593 2 : poc->start_reference = poll_start_reference;
594 2 : poc->expected_http_status = http_status;
595 : {
596 2 : 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 2 : return cmd;
604 : }
605 : }
606 :
607 :
608 : /* end of testing_api_cmd_get_orders.c */
|