Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2020 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_patch_product.c
21 : * @brief command to test PATCH /product
22 : * @author Christian Grothoff
23 : */
24 : #include "platform.h"
25 : struct PatchProductState;
26 : #define TALER_MERCHANT_PATCH_PRIVATE_PRODUCT_RESULT_CLOSURE struct PatchProductState
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/patch-private-products-PRODUCT_ID.h>
32 :
33 :
34 : /**
35 : * State of a "PATCH /product" CMD.
36 : */
37 : struct PatchProductState
38 : {
39 :
40 : /**
41 : * Handle for a "GET product" request.
42 : */
43 : struct TALER_MERCHANT_PatchPrivateProductHandle *iph;
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 : * description of the product
62 : */
63 : const char *description;
64 :
65 : /**
66 : * Map from IETF BCP 47 language tags to localized descriptions
67 : */
68 : json_t *description_i18n;
69 :
70 : /**
71 : * unit in which the product is measured (liters, kilograms, packages, etc.)
72 : */
73 : const char *unit;
74 :
75 : /**
76 : * the price for one @a unit of the product
77 : */
78 : struct TALER_Amount price;
79 :
80 : /**
81 : * Array of unit prices (may point to @e price for single-entry usage).
82 : */
83 : struct TALER_Amount *unit_prices;
84 :
85 : /**
86 : * Number of entries in @e unit_prices.
87 : */
88 : size_t unit_prices_len;
89 :
90 : /**
91 : * True if @e unit_prices must be freed.
92 : */
93 : bool owns_unit_prices;
94 :
95 : /**
96 : * True if we should send an explicit unit_price array.
97 : */
98 : bool use_unit_price_array;
99 :
100 : /**
101 : * base64-encoded product image
102 : */
103 : char *image;
104 :
105 : /**
106 : * list of taxes paid by the merchant
107 : */
108 : json_t *taxes;
109 :
110 : /**
111 : * in @e units, -1 to indicate "infinite" (i.e. electronic books)
112 : */
113 : int64_t total_stock;
114 :
115 : /**
116 : * Fractional stock component when fractional quantities are enabled.
117 : */
118 : uint32_t total_stock_frac;
119 :
120 : /**
121 : * Fractional precision level associated with fractional quantities.
122 : */
123 : uint32_t unit_precision_level;
124 :
125 : /**
126 : * whether fractional quantities are allowed for this product.
127 : */
128 : bool unit_allow_fraction;
129 :
130 : /**
131 : * Cached string representation of the stock level.
132 : */
133 : char unit_total_stock[64];
134 :
135 : /**
136 : * set to true if we should use the extended fractional API.
137 : */
138 : bool use_fractional;
139 :
140 : /**
141 : * in @e units.
142 : */
143 : int64_t total_lost;
144 :
145 : /**
146 : * where the product is in stock
147 : */
148 : json_t *address;
149 :
150 : /**
151 : * when the next restocking is expected to happen, 0 for unknown,
152 : */
153 : struct GNUNET_TIME_Timestamp next_restock;
154 :
155 : /**
156 : * Categories array length.
157 : */
158 : unsigned int num_cats;
159 :
160 : /**
161 : * Categories array.
162 : */
163 : uint64_t *cats;
164 :
165 : /**
166 : * Expected HTTP response code.
167 : */
168 : unsigned int http_status;
169 :
170 : };
171 :
172 : static void
173 30 : patch_product_update_unit_total_stock (struct PatchProductState *pps)
174 : {
175 : uint64_t stock;
176 : uint32_t frac;
177 :
178 30 : if (-1 == pps->total_stock)
179 : {
180 0 : stock = (uint64_t) INT64_MAX;
181 0 : frac = (uint32_t) INT32_MAX;
182 : }
183 : else
184 : {
185 30 : stock = (uint64_t) pps->total_stock;
186 30 : frac = pps->unit_allow_fraction ? pps->total_stock_frac : 0;
187 : }
188 30 : TALER_MERCHANT_format_stock_string (stock,
189 : frac,
190 30 : pps->unit_total_stock,
191 : sizeof (pps->unit_total_stock));
192 30 : }
193 :
194 :
195 : static uint32_t
196 24 : default_precision_from_unit (const char *unit)
197 : {
198 : struct PrecisionRule
199 : {
200 : const char *unit;
201 : uint32_t precision;
202 : };
203 : static const struct PrecisionRule rules[] = {
204 : { "WeightUnitMg", 0 },
205 : { "SizeUnitMm", 0 },
206 : { "WeightUnitG", 1 },
207 : { "SizeUnitCm", 1 },
208 : { "SurfaceUnitMm2", 1 },
209 : { "VolumeUnitMm3", 1 },
210 : { "WeightUnitOunce", 2 },
211 : { "SizeUnitInch", 2 },
212 : { "SurfaceUnitCm2", 2 },
213 : { "VolumeUnitOunce", 2 },
214 : { "VolumeUnitInch3", 2 },
215 : { "TimeUnitHour", 2 },
216 : { "TimeUnitMonth", 2 },
217 : { "WeightUnitTon", 3 },
218 : { "WeightUnitKg", 3 },
219 : { "WeightUnitPound", 3 },
220 : { "SizeUnitM", 3 },
221 : { "SizeUnitDm", 3 },
222 : { "SizeUnitFoot", 3 },
223 : { "SurfaceUnitDm2", 3 },
224 : { "SurfaceUnitFoot2", 3 },
225 : { "VolumeUnitCm3", 3 },
226 : { "VolumeUnitLitre", 3 },
227 : { "VolumeUnitGallon", 3 },
228 : { "TimeUnitSecond", 3 },
229 : { "TimeUnitMinute", 3 },
230 : { "TimeUnitDay", 3 },
231 : { "TimeUnitWeek", 3 },
232 : { "SurfaceUnitM2", 4 },
233 : { "SurfaceUnitInch2", 4 },
234 : { "TimeUnitYear", 4 },
235 : { "VolumeUnitDm3", 5 },
236 : { "VolumeUnitFoot3", 5 },
237 : { "VolumeUnitM3", 6 }
238 : };
239 :
240 24 : const size_t rules_len = sizeof (rules) / sizeof (rules[0]);
241 24 : if (NULL == unit)
242 0 : return 0;
243 :
244 840 : for (size_t i = 0; i<rules_len; i++)
245 816 : if (0 == strcmp (unit,
246 816 : rules[i].unit))
247 0 : return rules[i].precision;
248 24 : return 0;
249 : }
250 :
251 :
252 : /**
253 : * Callback for a PATCH /products/$ID operation.
254 : *
255 : * @param cls closure for this function
256 : * @param result response being processed
257 : */
258 : static void
259 24 : patch_product_cb (struct PatchProductState *pis,
260 : const struct TALER_MERCHANT_PatchPrivateProductResponse *
261 : result)
262 : {
263 24 : const struct TALER_MERCHANT_HttpResponse *hr = &result->hr;
264 :
265 24 : pis->iph = NULL;
266 24 : if (pis->http_status != hr->http_status)
267 : {
268 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
269 : "Unexpected response code %u (%d) to command %s\n",
270 : hr->http_status,
271 : (int) hr->ec,
272 : TALER_TESTING_interpreter_get_current_label (pis->is));
273 0 : TALER_TESTING_interpreter_fail (pis->is);
274 0 : return;
275 : }
276 24 : switch (hr->http_status)
277 : {
278 16 : case MHD_HTTP_NO_CONTENT:
279 16 : break;
280 4 : case MHD_HTTP_BAD_REQUEST:
281 4 : break;
282 0 : case MHD_HTTP_UNAUTHORIZED:
283 0 : break;
284 0 : case MHD_HTTP_FORBIDDEN:
285 0 : break;
286 4 : case MHD_HTTP_NOT_FOUND:
287 4 : break;
288 0 : case MHD_HTTP_CONFLICT:
289 0 : break;
290 0 : default:
291 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
292 : "Unhandled HTTP status %u for PATCH /products/ID.\n",
293 : hr->http_status);
294 : }
295 24 : TALER_TESTING_interpreter_next (pis->is);
296 : }
297 :
298 :
299 : /**
300 : * Run the "PATCH /products/$ID" CMD.
301 : *
302 : *
303 : * @param cls closure.
304 : * @param cmd command being run now.
305 : * @param is interpreter state.
306 : */
307 : static void
308 24 : patch_product_run (void *cls,
309 : const struct TALER_TESTING_Command *cmd,
310 : struct TALER_TESTING_Interpreter *is)
311 : {
312 24 : struct PatchProductState *pis = cls;
313 :
314 24 : pis->is = is;
315 24 : pis->iph = TALER_MERCHANT_patch_private_product_create (
316 : TALER_TESTING_interpreter_get_context (is),
317 : pis->merchant_url,
318 : pis->product_id,
319 : pis->description,
320 : pis->unit,
321 24 : pis->unit_prices_len,
322 24 : pis->unit_prices);
323 24 : GNUNET_assert (
324 : GNUNET_OK ==
325 : TALER_MERCHANT_patch_private_product_set_options (
326 : pis->iph,
327 : TALER_MERCHANT_patch_private_product_option_description_i18n (pis->description_i18n),
328 : TALER_MERCHANT_patch_private_product_option_image (pis->image),
329 : TALER_MERCHANT_patch_private_product_option_taxes (pis->taxes),
330 : TALER_MERCHANT_patch_private_product_option_total_stock_val (pis->total_stock),
331 : TALER_MERCHANT_patch_private_product_option_total_lost (pis->total_lost),
332 : TALER_MERCHANT_patch_private_product_option_address (pis->address),
333 : TALER_MERCHANT_patch_private_product_option_next_restock (pis->next_restock),
334 : TALER_MERCHANT_patch_private_product_option_categories (pis->num_cats,
335 : pis->cats)));
336 24 : GNUNET_assert (NULL != pis->iph);
337 24 : if (pis->use_fractional)
338 : {
339 8 : GNUNET_assert (
340 : GNUNET_OK ==
341 : TALER_MERCHANT_patch_private_product_set_options (
342 : pis->iph,
343 : TALER_MERCHANT_patch_private_product_option_total_stock_frac (
344 : pis->total_stock_frac),
345 : TALER_MERCHANT_patch_private_product_option_unit_allow_fraction (
346 : pis->unit_allow_fraction),
347 : TALER_MERCHANT_patch_private_product_option_unit_precision_level (
348 : pis->unit_precision_level)));
349 : }
350 : {
351 : enum TALER_ErrorCode ec;
352 :
353 24 : ec = TALER_MERCHANT_patch_private_product_start (
354 : pis->iph,
355 : &patch_product_cb,
356 : pis);
357 24 : GNUNET_assert (TALER_EC_NONE == ec);
358 : }
359 24 : }
360 :
361 :
362 : /**
363 : * Offers information from the PATCH /products CMD state to other
364 : * commands.
365 : *
366 : * @param cls closure
367 : * @param[out] ret result (could be anything)
368 : * @param trait name of the trait
369 : * @param index index number of the object to extract.
370 : * @return #GNUNET_OK on success
371 : */
372 : static enum GNUNET_GenericReturnValue
373 72 : patch_product_traits (void *cls,
374 : const void **ret,
375 : const char *trait,
376 : unsigned int index)
377 : {
378 72 : struct PatchProductState *pps = cls;
379 : struct TALER_TESTING_Trait traits[] = {
380 72 : TALER_TESTING_make_trait_product_description (pps->description),
381 72 : TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
382 72 : TALER_TESTING_make_trait_product_unit (pps->unit),
383 72 : TALER_TESTING_make_trait_amount (&pps->price),
384 72 : TALER_TESTING_make_trait_product_image (pps->image),
385 72 : TALER_TESTING_make_trait_taxes (pps->taxes),
386 72 : TALER_TESTING_make_trait_product_stock (&pps->total_stock),
387 72 : TALER_TESTING_make_trait_product_unit_total_stock (
388 72 : pps->unit_total_stock),
389 72 : TALER_TESTING_make_trait_product_unit_precision_level (
390 72 : &pps->unit_precision_level),
391 72 : TALER_TESTING_make_trait_product_unit_allow_fraction (
392 72 : &pps->unit_allow_fraction),
393 72 : TALER_TESTING_make_trait_address (pps->address),
394 72 : TALER_TESTING_make_trait_timestamp (0,
395 72 : &pps->next_restock),
396 72 : TALER_TESTING_make_trait_product_id (pps->product_id),
397 72 : TALER_TESTING_trait_end (),
398 : };
399 :
400 72 : return TALER_TESTING_get_trait (traits,
401 : ret,
402 : trait,
403 : index);
404 : }
405 :
406 :
407 : /**
408 : * Free the state of a "GET product" CMD, and possibly
409 : * cancel a pending operation thereof.
410 : *
411 : * @param cls closure.
412 : * @param cmd command being run.
413 : */
414 : static void
415 24 : patch_product_cleanup (void *cls,
416 : const struct TALER_TESTING_Command *cmd)
417 : {
418 24 : struct PatchProductState *pis = cls;
419 :
420 24 : if (NULL != pis->iph)
421 : {
422 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
423 : "PATCH /products/$ID operation did not complete\n");
424 0 : TALER_MERCHANT_patch_private_product_cancel (pis->iph);
425 : }
426 24 : if (pis->owns_unit_prices)
427 2 : GNUNET_free (pis->unit_prices);
428 24 : GNUNET_free (pis->cats);
429 24 : json_decref (pis->description_i18n);
430 24 : GNUNET_free (pis->image);
431 24 : json_decref (pis->taxes);
432 24 : json_decref (pis->address);
433 24 : GNUNET_free (pis);
434 24 : }
435 :
436 :
437 : struct TALER_TESTING_Command
438 22 : TALER_TESTING_cmd_merchant_patch_product (
439 : const char *label,
440 : const char *merchant_url,
441 : const char *product_id,
442 : const char *description,
443 : json_t *description_i18n,
444 : const char *unit,
445 : const char *price,
446 : const char *image,
447 : json_t *taxes,
448 : int64_t total_stock,
449 : uint64_t total_lost,
450 : json_t *address,
451 : struct GNUNET_TIME_Timestamp next_restock,
452 : unsigned int http_status)
453 : {
454 : struct PatchProductState *pis;
455 :
456 22 : GNUNET_assert ( (NULL == taxes) ||
457 : json_is_array (taxes));
458 22 : pis = GNUNET_new (struct PatchProductState);
459 22 : pis->merchant_url = merchant_url;
460 22 : pis->product_id = product_id;
461 22 : pis->http_status = http_status;
462 22 : pis->description = description;
463 22 : pis->description_i18n = description_i18n; /* ownership taken */
464 22 : pis->unit = unit;
465 22 : pis->unit_precision_level = default_precision_from_unit (unit);
466 22 : GNUNET_assert (GNUNET_OK ==
467 : TALER_string_to_amount (price,
468 : &pis->price));
469 22 : pis->unit_prices = &pis->price;
470 22 : pis->unit_prices_len = 1;
471 22 : pis->owns_unit_prices = false;
472 22 : pis->use_unit_price_array = false;
473 22 : pis->image = GNUNET_strdup (image);
474 22 : pis->taxes = taxes; /* ownership taken */
475 22 : pis->total_stock = total_stock;
476 22 : pis->total_stock_frac = 0;
477 22 : pis->unit_allow_fraction = false;
478 22 : pis->use_fractional = false;
479 22 : pis->total_lost = total_lost;
480 22 : pis->address = address; /* ownership taken */
481 22 : pis->next_restock = next_restock;
482 22 : patch_product_update_unit_total_stock (pis);
483 : {
484 22 : struct TALER_TESTING_Command cmd = {
485 : .cls = pis,
486 : .label = label,
487 : .run = &patch_product_run,
488 : .cleanup = &patch_product_cleanup,
489 : .traits = &patch_product_traits
490 : };
491 :
492 22 : return cmd;
493 : }
494 : }
495 :
496 :
497 : struct TALER_TESTING_Command
498 6 : TALER_TESTING_cmd_merchant_patch_product2 (
499 : const char *label,
500 : const char *merchant_url,
501 : const char *product_id,
502 : const char *description,
503 : json_t *description_i18n,
504 : const char *unit,
505 : const char *price,
506 : const char *image,
507 : json_t *taxes,
508 : int64_t total_stock,
509 : uint32_t total_stock_frac,
510 : bool unit_allow_fraction,
511 : uint64_t total_lost,
512 : json_t *address,
513 : struct GNUNET_TIME_Timestamp next_restock,
514 : unsigned int http_status)
515 : {
516 : struct TALER_TESTING_Command cmd;
517 :
518 6 : cmd = TALER_TESTING_cmd_merchant_patch_product (label,
519 : merchant_url,
520 : product_id,
521 : description,
522 : description_i18n,
523 : unit,
524 : price,
525 : image,
526 : taxes,
527 : total_stock,
528 : total_lost,
529 : address,
530 : next_restock,
531 : http_status);
532 : {
533 6 : struct PatchProductState *pps = cmd.cls;
534 :
535 6 : pps->total_stock_frac = total_stock_frac;
536 6 : pps->unit_allow_fraction = unit_allow_fraction;
537 6 : pps->use_fractional = true;
538 6 : patch_product_update_unit_total_stock (pps);
539 : }
540 6 : return cmd;
541 : }
542 :
543 :
544 : struct TALER_TESTING_Command
545 2 : TALER_TESTING_cmd_merchant_patch_product_with_unit_prices (
546 : const char *label,
547 : const char *merchant_url,
548 : const char *product_id,
549 : const char *description,
550 : const char *unit,
551 : const char *const *unit_prices,
552 : size_t unit_prices_len,
553 : unsigned int http_status)
554 : {
555 : struct PatchProductState *pis;
556 :
557 2 : GNUNET_assert (0 < unit_prices_len);
558 2 : GNUNET_assert (NULL != unit_prices);
559 2 : pis = GNUNET_new (struct PatchProductState);
560 2 : pis->merchant_url = merchant_url;
561 2 : pis->product_id = product_id;
562 2 : pis->http_status = http_status;
563 2 : pis->description = description;
564 2 : pis->description_i18n = json_pack ("{s:s}", "en", description);
565 2 : pis->unit = unit;
566 2 : pis->unit_precision_level = default_precision_from_unit (unit);
567 2 : pis->unit_prices = GNUNET_new_array (unit_prices_len,
568 : struct TALER_Amount);
569 2 : pis->unit_prices_len = unit_prices_len;
570 2 : pis->owns_unit_prices = true;
571 2 : pis->use_unit_price_array = true;
572 6 : for (size_t i = 0; i < unit_prices_len; i++)
573 4 : GNUNET_assert (GNUNET_OK ==
574 : TALER_string_to_amount (unit_prices[i],
575 : &pis->unit_prices[i]));
576 2 : pis->price = pis->unit_prices[0];
577 2 : pis->image = GNUNET_strdup ("");
578 2 : pis->taxes = json_array ();
579 2 : pis->total_stock = 5;
580 2 : pis->total_stock_frac = 0;
581 2 : pis->unit_allow_fraction = true;
582 2 : pis->unit_precision_level = 1;
583 2 : pis->use_fractional = true;
584 2 : pis->total_lost = 0;
585 2 : pis->address = json_object ();
586 2 : pis->next_restock = GNUNET_TIME_UNIT_FOREVER_TS;
587 2 : patch_product_update_unit_total_stock (pis);
588 : {
589 2 : struct TALER_TESTING_Command cmd = {
590 : .cls = pis,
591 : .label = label,
592 : .run = &patch_product_run,
593 : .cleanup = &patch_product_cleanup,
594 : .traits = &patch_product_traits
595 : };
596 :
597 2 : return cmd;
598 : }
599 : }
600 :
601 :
602 : struct TALER_TESTING_Command
603 8 : TALER_TESTING_cmd_merchant_patch_product_with_categories (
604 : const char *label,
605 : const char *merchant_url,
606 : const char *product_id,
607 : const char *description,
608 : const char *unit,
609 : const char *price,
610 : unsigned int num_cats,
611 : const uint64_t *cats,
612 : unsigned int http_status)
613 : {
614 : struct TALER_TESTING_Command cmd;
615 :
616 8 : cmd = TALER_TESTING_cmd_merchant_patch_product (
617 : label,
618 : merchant_url,
619 : product_id,
620 : description,
621 : json_pack ("{s:s}", "en", description),
622 : unit,
623 : price,
624 : "",
625 : json_array (),
626 : 4, /* total stock */
627 : 0, /* total lost */
628 : json_pack ("{s:s}", "street", "my street"),
629 8 : GNUNET_TIME_UNIT_ZERO_TS,
630 : http_status);
631 : {
632 8 : struct PatchProductState *pps = cmd.cls;
633 :
634 8 : pps->num_cats = num_cats;
635 8 : if (0 < num_cats)
636 : {
637 8 : pps->cats = GNUNET_new_array (num_cats,
638 : uint64_t);
639 18 : for (unsigned int i = 0; i < num_cats; i++)
640 10 : pps->cats[i] = cats[i];
641 : }
642 : }
643 8 : return cmd;
644 : }
645 :
646 :
647 : /* end of testing_api_cmd_patch_product.c */
|