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 testing_api_cmd_post_products.c
21 : * @brief command to test POST /products
22 : * @author Christian Grothoff
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 : #include "merchant_api_common.h"
30 :
31 :
32 : /**
33 : * State of a "POST /products" CMD.
34 : */
35 : struct PostProductsState
36 : {
37 :
38 : /**
39 : * Handle for a "POST /products" request.
40 : */
41 : struct TALER_MERCHANT_ProductsPostHandle *iph;
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 : * ID of the product to run POST for.
55 : */
56 : const char *product_id;
57 :
58 : /**
59 : * description of the product
60 : */
61 : const char *description;
62 :
63 : /**
64 : * Map from IETF BCP 47 language tags to localized descriptions
65 : */
66 : json_t *description_i18n;
67 :
68 : /**
69 : * unit in which the product is measured (liters, kilograms, packages, etc.)
70 : */
71 : const char *unit;
72 :
73 : /**
74 : * the price for one @a unit of the product
75 : */
76 : struct TALER_Amount price;
77 :
78 : /**
79 : * base64-encoded product image
80 : */
81 : char *image;
82 :
83 : /**
84 : * list of taxes paid by the merchant
85 : */
86 : json_t *taxes;
87 :
88 : /**
89 : * in @e units, -1 to indicate "infinite" (i.e. electronic books)
90 : */
91 : int64_t total_stock;
92 :
93 : /**
94 : * Fractional stock component when fractional quantities are enabled.
95 : */
96 : uint32_t total_stock_frac;
97 :
98 : /**
99 : * Fractional precision level associated with fractional quantities.
100 : */
101 : uint32_t unit_precision_level;
102 :
103 : /**
104 : * whether fractional quantities are allowed for this product.
105 : */
106 : bool unit_allow_fraction;
107 :
108 : /**
109 : * Cached string representation of the stock level.
110 : */
111 : char unit_total_stock[64];
112 :
113 : /**
114 : * set to true if we should use the extended fractional API.
115 : */
116 : bool use_fractional;
117 :
118 : /**
119 : * where the product is in stock
120 : */
121 : json_t *address;
122 :
123 : /**
124 : * Minimum age requirement to use for the product.
125 : */
126 : unsigned int minimum_age;
127 :
128 : /**
129 : * when the next restocking is expected to happen, 0 for unknown,
130 : */
131 : struct GNUNET_TIME_Timestamp next_restock;
132 :
133 : /**
134 : * Expected HTTP response code.
135 : */
136 : unsigned int http_status;
137 :
138 : };
139 :
140 : static void
141 20 : post_products_update_unit_total_stock (struct PostProductsState *pps)
142 : {
143 : uint64_t stock;
144 : uint32_t frac;
145 :
146 20 : if (-1 == pps->total_stock)
147 : {
148 0 : stock = (uint64_t) INT64_MAX;
149 0 : frac = (uint32_t) INT32_MAX;
150 : }
151 : else
152 : {
153 20 : stock = (uint64_t) pps->total_stock;
154 20 : frac = pps->unit_allow_fraction ? pps->total_stock_frac : 0;
155 : }
156 20 : TALER_MERCHANT_format_stock_string (stock,
157 : frac,
158 20 : pps->unit_total_stock,
159 : sizeof (pps->unit_total_stock));
160 20 : }
161 :
162 :
163 : static uint32_t
164 18 : default_precision_from_unit (const char *unit)
165 : {
166 : struct PrecisionRule
167 : {
168 : const char *unit;
169 : uint32_t precision;
170 : };
171 : static const struct PrecisionRule rules[] = {
172 : { "WeightUnitMg", 0 },
173 : { "SizeUnitMm", 0 },
174 : { "WeightUnitG", 1 },
175 : { "SizeUnitCm", 1 },
176 : { "SurfaceUnitMm2", 1 },
177 : { "VolumeUnitMm3", 1 },
178 : { "WeightUnitOunce", 2 },
179 : { "SizeUnitInch", 2 },
180 : { "SurfaceUnitCm2", 2 },
181 : { "VolumeUnitOunce", 2 },
182 : { "VolumeUnitInch3", 2 },
183 : { "TimeUnitHour", 2 },
184 : { "TimeUnitMonth", 2 },
185 : { "WeightUnitTon", 3 },
186 : { "WeightUnitKg", 3 },
187 : { "WeightUnitPound", 3 },
188 : { "SizeUnitM", 3 },
189 : { "SizeUnitDm", 3 },
190 : { "SizeUnitFoot", 3 },
191 : { "SurfaceUnitDm2", 3 },
192 : { "SurfaceUnitFoot2", 3 },
193 : { "VolumeUnitCm3", 3 },
194 : { "VolumeUnitLitre", 3 },
195 : { "VolumeUnitGallon", 3 },
196 : { "TimeUnitSecond", 3 },
197 : { "TimeUnitMinute", 3 },
198 : { "TimeUnitDay", 3 },
199 : { "TimeUnitWeek", 3 },
200 : { "SurfaceUnitM2", 4 },
201 : { "SurfaceUnitInch2", 4 },
202 : { "TimeUnitYear", 4 },
203 : { "VolumeUnitDm3", 5 },
204 : { "VolumeUnitFoot3", 5 },
205 : { "VolumeUnitM3", 6 }
206 : };
207 :
208 18 : const size_t rules_len = sizeof (rules) / sizeof (rules[0]);
209 18 : if (NULL == unit)
210 0 : return 0;
211 :
212 630 : for (size_t i = 0; i<rules_len; i++)
213 612 : if (0 == strcmp (unit,
214 612 : rules[i].unit))
215 0 : return rules[i].precision;
216 18 : return 0;
217 : }
218 :
219 :
220 : /**
221 : * Callback for a POST /products operation.
222 : *
223 : * @param cls closure for this function
224 : * @param hr response being processed
225 : */
226 : static void
227 18 : post_products_cb (void *cls,
228 : const struct TALER_MERCHANT_HttpResponse *hr)
229 : {
230 18 : struct PostProductsState *pis = cls;
231 :
232 18 : pis->iph = NULL;
233 18 : if (pis->http_status != hr->http_status)
234 : {
235 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
236 : "Unexpected response code %u (%d) to command %s\n",
237 : hr->http_status,
238 : (int) hr->ec,
239 : TALER_TESTING_interpreter_get_current_label (pis->is));
240 0 : TALER_TESTING_interpreter_fail (pis->is);
241 0 : return;
242 : }
243 18 : switch (hr->http_status)
244 : {
245 16 : case MHD_HTTP_NO_CONTENT:
246 16 : break;
247 0 : case MHD_HTTP_UNAUTHORIZED:
248 0 : break;
249 0 : case MHD_HTTP_FORBIDDEN:
250 0 : break;
251 0 : case MHD_HTTP_NOT_FOUND:
252 0 : break;
253 2 : case MHD_HTTP_CONFLICT:
254 2 : break;
255 0 : default:
256 0 : GNUNET_break (0);
257 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
258 : "Unhandled HTTP status %u for POST /products.\n",
259 : hr->http_status);
260 : }
261 18 : TALER_TESTING_interpreter_next (pis->is);
262 : }
263 :
264 :
265 : /**
266 : * Run the "POST /products" CMD.
267 : *
268 : *
269 : * @param cls closure.
270 : * @param cmd command being run now.
271 : * @param is interpreter state.
272 : */
273 : static void
274 18 : post_products_run (void *cls,
275 : const struct TALER_TESTING_Command *cmd,
276 : struct TALER_TESTING_Interpreter *is)
277 : {
278 18 : struct PostProductsState *pis = cls;
279 :
280 18 : pis->is = is;
281 18 : if (pis->use_fractional)
282 : {
283 4 : pis->iph = TALER_MERCHANT_products_post4 (
284 : TALER_TESTING_interpreter_get_context (is),
285 : pis->merchant_url,
286 : pis->product_id,
287 : pis->description,
288 2 : pis->description_i18n,
289 : pis->unit,
290 2 : &pis->price,
291 : 1,
292 2 : pis->image,
293 2 : pis->taxes,
294 : pis->total_stock,
295 : pis->total_stock_frac,
296 2 : pis->unit_allow_fraction,
297 2 : pis->use_fractional
298 : ? &pis->unit_precision_level
299 : : NULL,
300 2 : pis->address,
301 : pis->next_restock,
302 : pis->minimum_age,
303 : 0,
304 : NULL,
305 : &post_products_cb,
306 : pis);
307 : }
308 : else
309 : {
310 16 : pis->iph = TALER_MERCHANT_products_post2 (
311 : TALER_TESTING_interpreter_get_context (is),
312 : pis->merchant_url,
313 : pis->product_id,
314 : pis->description,
315 16 : pis->description_i18n,
316 : pis->unit,
317 16 : &pis->price,
318 16 : pis->image,
319 16 : pis->taxes,
320 : pis->total_stock,
321 16 : pis->address,
322 : pis->next_restock,
323 : pis->minimum_age,
324 : &post_products_cb,
325 : pis);
326 : }
327 18 : GNUNET_assert (NULL != pis->iph);
328 18 : }
329 :
330 :
331 : /**
332 : * Offers information from the POST /products CMD state to other
333 : * commands.
334 : *
335 : * @param cls closure
336 : * @param[out] ret result (could be anything)
337 : * @param trait name of the trait
338 : * @param index index number of the object to extract.
339 : * @return #GNUNET_OK on success
340 : */
341 : static enum GNUNET_GenericReturnValue
342 96 : post_products_traits (void *cls,
343 : const void **ret,
344 : const char *trait,
345 : unsigned int index)
346 : {
347 96 : struct PostProductsState *pps = cls;
348 : struct TALER_TESTING_Trait traits[] = {
349 96 : TALER_TESTING_make_trait_product_description (pps->description),
350 96 : TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
351 96 : TALER_TESTING_make_trait_product_unit (pps->unit),
352 96 : TALER_TESTING_make_trait_amount (&pps->price),
353 96 : TALER_TESTING_make_trait_product_image (pps->image),
354 96 : TALER_TESTING_make_trait_taxes (pps->taxes),
355 96 : TALER_TESTING_make_trait_product_stock (&pps->total_stock),
356 96 : TALER_TESTING_make_trait_product_unit_total_stock (
357 96 : pps->unit_total_stock),
358 96 : TALER_TESTING_make_trait_product_unit_precision_level (
359 96 : &pps->unit_precision_level),
360 96 : TALER_TESTING_make_trait_product_unit_allow_fraction (
361 96 : &pps->unit_allow_fraction),
362 96 : TALER_TESTING_make_trait_address (pps->address),
363 96 : TALER_TESTING_make_trait_timestamp (0,
364 96 : &pps->next_restock),
365 96 : TALER_TESTING_make_trait_product_id (pps->product_id),
366 96 : TALER_TESTING_trait_end (),
367 : };
368 :
369 96 : return TALER_TESTING_get_trait (traits,
370 : ret,
371 : trait,
372 : index);
373 : }
374 :
375 :
376 : /**
377 : * Free the state of a "POST product" CMD, and possibly
378 : * cancel a pending operation thereof.
379 : *
380 : * @param cls closure.
381 : * @param cmd command being run.
382 : */
383 : static void
384 18 : post_products_cleanup (void *cls,
385 : const struct TALER_TESTING_Command *cmd)
386 : {
387 18 : struct PostProductsState *pis = cls;
388 :
389 18 : if (NULL != pis->iph)
390 : {
391 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
392 : "POST /products operation did not complete\n");
393 0 : TALER_MERCHANT_products_post_cancel (pis->iph);
394 : }
395 18 : json_decref (pis->description_i18n);
396 18 : GNUNET_free (pis->image);
397 18 : json_decref (pis->taxes);
398 18 : json_decref (pis->address);
399 18 : GNUNET_free (pis);
400 18 : }
401 :
402 :
403 : struct TALER_TESTING_Command
404 18 : TALER_TESTING_cmd_merchant_post_products2 (
405 : const char *label,
406 : const char *merchant_url,
407 : const char *product_id,
408 : const char *description,
409 : json_t *description_i18n,
410 : const char *unit,
411 : const char *price,
412 : const char *image,
413 : json_t *taxes,
414 : int64_t total_stock,
415 : uint32_t minimum_age,
416 : json_t *address,
417 : struct GNUNET_TIME_Timestamp next_restock,
418 : unsigned int http_status)
419 : {
420 : struct PostProductsState *pis;
421 :
422 18 : GNUNET_assert ((NULL == taxes) ||
423 : json_is_array (taxes));
424 18 : GNUNET_assert ((NULL == description_i18n) ||
425 : json_is_object (description_i18n));
426 18 : pis = GNUNET_new (struct PostProductsState);
427 18 : pis->merchant_url = merchant_url;
428 18 : pis->product_id = product_id;
429 18 : pis->http_status = http_status;
430 18 : pis->description = description;
431 18 : pis->description_i18n = description_i18n; /* ownership taken */
432 18 : pis->unit = unit;
433 18 : pis->unit_precision_level = default_precision_from_unit (unit);
434 18 : GNUNET_assert (GNUNET_OK ==
435 : TALER_string_to_amount (price,
436 : &pis->price));
437 18 : pis->image = GNUNET_strdup (image);
438 18 : pis->taxes = taxes; /* ownership taken */
439 18 : pis->total_stock = total_stock;
440 18 : pis->total_stock_frac = 0;
441 18 : pis->unit_allow_fraction = false;
442 18 : pis->use_fractional = false;
443 18 : pis->minimum_age = minimum_age;
444 18 : pis->address = address; /* ownership taken */
445 18 : pis->next_restock = next_restock;
446 18 : post_products_update_unit_total_stock (pis);
447 : {
448 18 : struct TALER_TESTING_Command cmd = {
449 : .cls = pis,
450 : .label = label,
451 : .run = &post_products_run,
452 : .cleanup = &post_products_cleanup,
453 : .traits = &post_products_traits
454 : };
455 :
456 18 : return cmd;
457 : }
458 : }
459 :
460 :
461 : struct TALER_TESTING_Command
462 2 : TALER_TESTING_cmd_merchant_post_products3 (
463 : const char *label,
464 : const char *merchant_url,
465 : const char *product_id,
466 : const char *description,
467 : json_t *description_i18n,
468 : const char *unit,
469 : const char *price,
470 : const char *image,
471 : json_t *taxes,
472 : int64_t total_stock,
473 : uint32_t total_stock_frac,
474 : bool unit_allow_fraction,
475 : uint32_t minimum_age,
476 : json_t *address,
477 : struct GNUNET_TIME_Timestamp next_restock,
478 : unsigned int http_status)
479 : {
480 : struct TALER_TESTING_Command cmd;
481 :
482 2 : cmd = TALER_TESTING_cmd_merchant_post_products2 (label,
483 : merchant_url,
484 : product_id,
485 : description,
486 : description_i18n,
487 : unit,
488 : price,
489 : image,
490 : taxes,
491 : total_stock,
492 : minimum_age,
493 : address,
494 : next_restock,
495 : http_status);
496 : {
497 2 : struct PostProductsState *pis = cmd.cls;
498 :
499 2 : pis->total_stock_frac = total_stock_frac;
500 2 : pis->unit_allow_fraction = unit_allow_fraction;
501 2 : pis->use_fractional = true;
502 2 : post_products_update_unit_total_stock (pis);
503 : }
504 2 : return cmd;
505 : }
506 :
507 :
508 : struct TALER_TESTING_Command
509 10 : TALER_TESTING_cmd_merchant_post_products (
510 : const char *label,
511 : const char *merchant_url,
512 : const char *product_id,
513 : const char *description,
514 : const char *price,
515 : unsigned int http_status)
516 : {
517 10 : return TALER_TESTING_cmd_merchant_post_products2 (
518 : label,
519 : merchant_url,
520 : product_id,
521 : description,
522 : json_pack ("{s:s}", "en", description),
523 : "test-unit",
524 : price,
525 : "",
526 : json_array (),
527 : 4, /* total stock */
528 : 0, /* minimum age */
529 : json_pack ("{s:s}", "street", "my street"),
530 10 : GNUNET_TIME_UNIT_ZERO_TS,
531 : http_status);
532 : }
533 :
534 :
535 : /* end of testing_api_cmd_post_products.c */
|