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_product.c
21 : * @brief command to test GET /product/$ID
22 : * @author Christian Grothoff
23 : */
24 : #include "platform.h"
25 : struct GetProductState;
26 : #define TALER_MERCHANT_GET_PRIVATE_PRODUCT_RESULT_CLOSURE struct GetProductState
27 : #include <taler/taler_exchange_service.h>
28 : #include <taler/taler_testing_lib.h>
29 : #include "taler/taler_merchant_service.h"
30 : #include "taler/taler_merchant_testing_lib.h"
31 : #include <taler/merchant/get-private-products-PRODUCT_ID.h>
32 :
33 :
34 : /**
35 : * State of a "GET product" CMD.
36 : */
37 : struct GetProductState
38 : {
39 :
40 : /**
41 : * Handle for a "GET product" request.
42 : */
43 : struct TALER_MERCHANT_GetPrivateProductHandle *igh;
44 :
45 : /**
46 : * The interpreter state.
47 : */
48 : struct TALER_TESTING_Interpreter *is;
49 :
50 : /**
51 : * Base URL of the merchant serving the request.
52 : */
53 : const char *merchant_url;
54 :
55 : /**
56 : * ID of the product to run GET for.
57 : */
58 : const char *product_id;
59 :
60 : /**
61 : * Reference for a POST or PATCH /products CMD (optional).
62 : */
63 : const char *product_reference;
64 :
65 : /**
66 : * Expected HTTP response code.
67 : */
68 : unsigned int http_status;
69 :
70 : /**
71 : * Optional overrides for fractional fields.
72 : */
73 : const struct TALER_TESTING_ProductUnitExpectations *unit_expectations;
74 :
75 : };
76 :
77 :
78 : /**
79 : * Callback for a /get/product/$ID operation.
80 : *
81 : * @param cls closure for this function
82 : * @param pgr response details
83 : */
84 : static void
85 32 : get_product_cb (struct GetProductState *gis,
86 : const struct TALER_MERCHANT_GetPrivateProductResponse *pgr)
87 : {
88 : const struct TALER_TESTING_Command *product_cmd;
89 32 : const struct TALER_TESTING_ProductUnitExpectations *ue =
90 : gis->unit_expectations;
91 :
92 32 : gis->igh = NULL;
93 32 : if (gis->http_status != pgr->hr.http_status)
94 : {
95 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
96 : "Unexpected response code %u (%d) to command %s\n",
97 : pgr->hr.http_status,
98 : (int) pgr->hr.ec,
99 : TALER_TESTING_interpreter_get_current_label (gis->is));
100 0 : TALER_TESTING_interpreter_fail (gis->is);
101 0 : return;
102 : }
103 32 : switch (pgr->hr.http_status)
104 : {
105 18 : case MHD_HTTP_OK:
106 : {
107 : const char *expected_description;
108 :
109 18 : product_cmd = TALER_TESTING_interpreter_lookup_command (
110 : gis->is,
111 : gis->product_reference);
112 18 : if (GNUNET_OK !=
113 18 : TALER_TESTING_get_trait_product_description (product_cmd,
114 : &expected_description))
115 : {
116 0 : TALER_TESTING_interpreter_fail (gis->is);
117 0 : return;
118 : }
119 18 : if (0 != strcmp (pgr->details.ok.description,
120 : expected_description))
121 : {
122 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
123 : "Product description does not match\n");
124 0 : TALER_TESTING_interpreter_fail (gis->is);
125 0 : return;
126 : }
127 : }
128 : {
129 : const json_t *expected_description_i18n;
130 :
131 18 : if (GNUNET_OK !=
132 18 : TALER_TESTING_get_trait_i18n_description (product_cmd,
133 : &expected_description_i18n))
134 : {
135 0 : TALER_TESTING_interpreter_fail (gis->is);
136 0 : return;
137 : }
138 18 : if (1 != json_equal (pgr->details.ok.description_i18n,
139 : expected_description_i18n))
140 : {
141 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
142 : "Product description i18n does not match\n");
143 0 : TALER_TESTING_interpreter_fail (gis->is);
144 0 : return;
145 : }
146 : }
147 : {
148 : const struct TALER_Amount *expected_price;
149 :
150 18 : if (GNUNET_OK !=
151 18 : TALER_TESTING_get_trait_amount (product_cmd,
152 : &expected_price))
153 : {
154 0 : TALER_TESTING_interpreter_fail (gis->is);
155 0 : return;
156 : }
157 18 : if ((GNUNET_OK !=
158 18 : TALER_amount_cmp_currency (&pgr->details.ok.price,
159 18 : expected_price)) ||
160 18 : (0 != TALER_amount_cmp (&pgr->details.ok.price,
161 : expected_price)))
162 : {
163 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
164 : "Product price does not match\n");
165 0 : TALER_TESTING_interpreter_fail (gis->is);
166 0 : return;
167 : }
168 : }
169 : {
170 : const bool *expected_allow;
171 18 : bool have_allow = GNUNET_OK ==
172 18 : TALER_TESTING_get_trait_product_unit_allow_fraction (
173 : product_cmd,
174 : &expected_allow);
175 22 : bool override_allow = (NULL != ue) &&
176 4 : ue->have_unit_allow_fraction;
177 :
178 18 : if (override_allow)
179 : {
180 4 : if (pgr->details.ok.unit_allow_fraction !=
181 4 : ue->unit_allow_fraction)
182 : {
183 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
184 : "Product fractional flag does not match expectation\n");
185 0 : TALER_TESTING_interpreter_fail (gis->is);
186 0 : return;
187 : }
188 : }
189 14 : else if (! have_allow)
190 : {
191 0 : if (pgr->details.ok.unit_allow_fraction)
192 : {
193 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
194 : "Product fractional flag unexpected\n");
195 0 : TALER_TESTING_interpreter_fail (gis->is);
196 0 : return;
197 : }
198 : }
199 : else
200 : {
201 14 : if (pgr->details.ok.unit_allow_fraction != *expected_allow)
202 : {
203 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
204 : "Product fractional flag does not match\n");
205 0 : TALER_TESTING_interpreter_fail (gis->is);
206 0 : return;
207 : }
208 : {
209 : const char *expected_unit_total_stock;
210 :
211 14 : if (GNUNET_OK !=
212 14 : TALER_TESTING_get_trait_product_unit_total_stock (
213 : product_cmd,
214 : &expected_unit_total_stock))
215 0 : TALER_TESTING_interpreter_fail (gis->is);
216 14 : else if (0 != strcmp (pgr->details.ok.unit_total_stock,
217 : expected_unit_total_stock))
218 : {
219 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
220 : "Product stock string does not match\n");
221 0 : TALER_TESTING_interpreter_fail (gis->is);
222 0 : return;
223 : }
224 : }
225 : }
226 : }
227 : {
228 : const uint32_t *expected_precision;
229 18 : bool have_precision = GNUNET_OK ==
230 18 : TALER_TESTING_get_trait_product_unit_precision_level
231 : (
232 : product_cmd,
233 : &expected_precision);
234 22 : bool override_precision = (NULL != ue) &&
235 4 : ue->have_unit_precision_level;
236 :
237 18 : if (override_precision)
238 : {
239 4 : if (pgr->details.ok.unit_precision_level !=
240 4 : ue->unit_precision_level)
241 : {
242 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
243 : "Product fractional precision does not match expectation\n");
244 0 : TALER_TESTING_interpreter_fail (gis->is);
245 0 : return;
246 : }
247 : }
248 14 : else if (have_precision)
249 : {
250 14 : if (pgr->details.ok.unit_precision_level != *expected_precision)
251 : {
252 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
253 : "Product fractional precision does not match\n");
254 0 : TALER_TESTING_interpreter_fail (gis->is);
255 0 : return;
256 : }
257 : }
258 0 : else if (! pgr->details.ok.unit_allow_fraction)
259 : {
260 0 : if (0 != pgr->details.ok.unit_precision_level)
261 : {
262 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
263 : "Product fractional precision should be zero when disallowed\n");
264 0 : TALER_TESTING_interpreter_fail (gis->is);
265 0 : return;
266 : }
267 : }
268 0 : else if (pgr->details.ok.unit_precision_level > 8)
269 : {
270 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
271 : "Product fractional precision exceeds supported range\n");
272 0 : TALER_TESTING_interpreter_fail (gis->is);
273 0 : return;
274 : }
275 : }
276 : {
277 : const char *expected_image;
278 :
279 18 : if (GNUNET_OK !=
280 18 : TALER_TESTING_get_trait_product_image (product_cmd,
281 : &expected_image))
282 : {
283 0 : TALER_TESTING_interpreter_fail (gis->is);
284 0 : return;
285 : }
286 18 : if (0 != strcmp (pgr->details.ok.image,
287 : expected_image))
288 : {
289 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
290 : "Product image does not match\n");
291 0 : TALER_TESTING_interpreter_fail (gis->is);
292 0 : return;
293 : }
294 : }
295 : {
296 : const json_t *expected_taxes;
297 :
298 18 : if (GNUNET_OK !=
299 18 : TALER_TESTING_get_trait_taxes (product_cmd,
300 : &expected_taxes))
301 : {
302 0 : TALER_TESTING_interpreter_fail (gis->is);
303 0 : return;
304 : }
305 18 : if (1 != json_equal (pgr->details.ok.taxes,
306 : expected_taxes))
307 : {
308 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
309 : "Product taxes do not match\n");
310 0 : TALER_TESTING_interpreter_fail (gis->is);
311 0 : return;
312 : }
313 : }
314 : {
315 : const char *expected_unit;
316 :
317 18 : if (GNUNET_OK !=
318 18 : TALER_TESTING_get_trait_product_unit (product_cmd,
319 : &expected_unit))
320 : {
321 0 : TALER_TESTING_interpreter_fail (gis->is);
322 0 : return;
323 : }
324 18 : if (0 != strcmp (pgr->details.ok.unit,
325 : expected_unit))
326 : {
327 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
328 : "Product unit does not match\n");
329 0 : TALER_TESTING_interpreter_fail (gis->is);
330 0 : return;
331 : }
332 : }
333 : {
334 : const json_t *expected_location;
335 :
336 18 : if (GNUNET_OK !=
337 18 : TALER_TESTING_get_trait_address (product_cmd,
338 : &expected_location))
339 : {
340 0 : TALER_TESTING_interpreter_fail (gis->is);
341 0 : return;
342 : }
343 18 : if (NULL != expected_location)
344 : {
345 16 : if (1 != json_equal (pgr->details.ok.location,
346 : expected_location))
347 : {
348 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
349 : "Product location does not match\n");
350 0 : TALER_TESTING_interpreter_fail (gis->is);
351 0 : return;
352 : }
353 : }
354 : }
355 : {
356 : const int64_t *expected_total_stock;
357 :
358 18 : if (GNUNET_OK !=
359 18 : TALER_TESTING_get_trait_product_stock (product_cmd,
360 : &expected_total_stock))
361 : {
362 0 : TALER_TESTING_interpreter_fail (gis->is);
363 0 : return;
364 : }
365 18 : if (pgr->details.ok.total_stock != *expected_total_stock)
366 : {
367 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
368 : "Product total stock does not match\n");
369 0 : TALER_TESTING_interpreter_fail (gis->is);
370 0 : return;
371 : }
372 : }
373 : {
374 : const struct GNUNET_TIME_Timestamp *expected_next_restock;
375 :
376 18 : if (GNUNET_OK !=
377 18 : TALER_TESTING_get_trait_timestamp (product_cmd,
378 : 0,
379 : &expected_next_restock))
380 : {
381 0 : TALER_TESTING_interpreter_fail (gis->is);
382 0 : return;
383 : }
384 18 : if (GNUNET_TIME_timestamp_cmp (pgr->details.ok.next_restock,
385 : !=,
386 : *expected_next_restock))
387 : {
388 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
389 : "Product next restock does not match\n");
390 0 : TALER_TESTING_interpreter_fail (gis->is);
391 0 : return;
392 : }
393 : }
394 18 : break;
395 4 : case MHD_HTTP_UNAUTHORIZED:
396 4 : break;
397 10 : case MHD_HTTP_NOT_FOUND:
398 10 : break;
399 0 : default:
400 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
401 : "Unhandled HTTP status.\n");
402 : }
403 32 : TALER_TESTING_interpreter_next (gis->is);
404 : }
405 :
406 :
407 : /**
408 : * Run the "GET product" CMD.
409 : *
410 : *
411 : * @param cls closure.
412 : * @param cmd command being run now.
413 : * @param is interpreter state.
414 : */
415 : static void
416 32 : get_product_run (void *cls,
417 : const struct TALER_TESTING_Command *cmd,
418 : struct TALER_TESTING_Interpreter *is)
419 : {
420 32 : struct GetProductState *gis = cls;
421 :
422 32 : gis->is = is;
423 32 : gis->igh = TALER_MERCHANT_get_private_product_create (
424 : TALER_TESTING_interpreter_get_context (is),
425 : gis->merchant_url,
426 : gis->product_id);
427 : {
428 : enum TALER_ErrorCode ec;
429 :
430 32 : ec = TALER_MERCHANT_get_private_product_start (
431 : gis->igh,
432 : &get_product_cb,
433 : gis);
434 32 : GNUNET_assert (TALER_EC_NONE == ec);
435 : }
436 32 : }
437 :
438 :
439 : /**
440 : * Free the state of a "GET product" CMD, and possibly
441 : * cancel a pending operation thereof.
442 : *
443 : * @param cls closure.
444 : * @param cmd command being run.
445 : */
446 : static void
447 32 : get_product_cleanup (void *cls,
448 : const struct TALER_TESTING_Command *cmd)
449 : {
450 32 : struct GetProductState *gis = cls;
451 :
452 32 : if (NULL != gis->igh)
453 : {
454 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
455 : "GET /products/$ID operation did not complete\n");
456 0 : TALER_MERCHANT_get_private_product_cancel (gis->igh);
457 : }
458 32 : GNUNET_free (gis);
459 32 : }
460 :
461 :
462 : struct TALER_TESTING_Command
463 28 : TALER_TESTING_cmd_merchant_get_product (const char *label,
464 : const char *merchant_url,
465 : const char *product_id,
466 : unsigned int http_status,
467 : const char *product_reference)
468 : {
469 28 : return TALER_TESTING_cmd_merchant_get_product2 (label,
470 : merchant_url,
471 : product_id,
472 : http_status,
473 : product_reference,
474 : NULL);
475 : }
476 :
477 :
478 : struct TALER_TESTING_Command
479 32 : TALER_TESTING_cmd_merchant_get_product2 (
480 : const char *label,
481 : const char *merchant_url,
482 : const char *product_id,
483 : unsigned int http_status,
484 : const char *product_reference,
485 : const struct TALER_TESTING_ProductUnitExpectations *unit_expectations)
486 : {
487 : struct GetProductState *gis;
488 :
489 32 : gis = GNUNET_new (struct GetProductState);
490 32 : gis->merchant_url = merchant_url;
491 32 : gis->product_id = product_id;
492 32 : gis->http_status = http_status;
493 32 : gis->product_reference = product_reference;
494 32 : gis->unit_expectations = unit_expectations;
495 : {
496 32 : struct TALER_TESTING_Command cmd = {
497 : .cls = gis,
498 : .label = label,
499 : .run = &get_product_run,
500 : .cleanup = &get_product_cleanup
501 : };
502 :
503 32 : return cmd;
504 : }
505 : }
506 :
507 :
508 : /* end of testing_api_cmd_get_product.c */
|