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_patch_product.c
21 : * @brief command to test PATCH /product
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 "PATCH /product" CMD.
34 : */
35 : struct PatchProductState
36 : {
37 :
38 : /**
39 : * Handle for a "GET product" request.
40 : */
41 : struct TALER_MERCHANT_ProductPatchHandle *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 GET 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 : * in @e units.
120 : */
121 : int64_t total_lost;
122 :
123 : /**
124 : * where the product is in stock
125 : */
126 : json_t *address;
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 18 : patch_product_update_unit_total_stock (struct PatchProductState *pps)
142 : {
143 : uint64_t stock;
144 : uint32_t frac;
145 :
146 18 : 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 18 : stock = (uint64_t) pps->total_stock;
154 18 : frac = pps->unit_allow_fraction ? pps->total_stock_frac : 0;
155 : }
156 18 : TALER_MERCHANT_format_stock_string (stock,
157 : frac,
158 18 : pps->unit_total_stock,
159 : sizeof (pps->unit_total_stock));
160 18 : }
161 :
162 :
163 : static uint32_t
164 12 : 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 12 : const size_t rules_len = sizeof (rules) / sizeof (rules[0]);
209 12 : if (NULL == unit)
210 0 : return 0;
211 :
212 420 : for (size_t i = 0; i<rules_len; i++)
213 408 : if (0 == strcmp (unit,
214 408 : rules[i].unit))
215 0 : return rules[i].precision;
216 12 : return 0;
217 : }
218 :
219 :
220 : /**
221 : * Callback for a PATCH /products/$ID operation.
222 : *
223 : * @param cls closure for this function
224 : * @param hr response being processed
225 : */
226 : static void
227 12 : patch_product_cb (void *cls,
228 : const struct TALER_MERCHANT_HttpResponse *hr)
229 : {
230 12 : struct PatchProductState *pis = cls;
231 :
232 12 : pis->iph = NULL;
233 12 : 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 12 : switch (hr->http_status)
244 : {
245 10 : case MHD_HTTP_NO_CONTENT:
246 10 : break;
247 0 : case MHD_HTTP_UNAUTHORIZED:
248 0 : break;
249 0 : case MHD_HTTP_FORBIDDEN:
250 0 : break;
251 2 : case MHD_HTTP_NOT_FOUND:
252 2 : break;
253 0 : case MHD_HTTP_CONFLICT:
254 0 : break;
255 0 : default:
256 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
257 : "Unhandled HTTP status %u for PATCH /products/ID.\n",
258 : hr->http_status);
259 : }
260 12 : TALER_TESTING_interpreter_next (pis->is);
261 : }
262 :
263 :
264 : /**
265 : * Run the "PATCH /products/$ID" CMD.
266 : *
267 : *
268 : * @param cls closure.
269 : * @param cmd command being run now.
270 : * @param is interpreter state.
271 : */
272 : static void
273 12 : patch_product_run (void *cls,
274 : const struct TALER_TESTING_Command *cmd,
275 : struct TALER_TESTING_Interpreter *is)
276 : {
277 12 : struct PatchProductState *pis = cls;
278 :
279 12 : pis->is = is;
280 12 : if (pis->use_fractional)
281 : {
282 12 : pis->iph = TALER_MERCHANT_product_patch2 (
283 : TALER_TESTING_interpreter_get_context (is),
284 : pis->merchant_url,
285 : pis->product_id,
286 : pis->description,
287 6 : pis->description_i18n,
288 : pis->unit,
289 6 : &pis->price,
290 : 1,
291 6 : pis->image,
292 6 : pis->taxes,
293 : pis->total_stock,
294 : pis->total_stock_frac,
295 6 : pis->unit_allow_fraction,
296 6 : pis->use_fractional
297 : ? &pis->unit_precision_level
298 : : NULL,
299 6 : pis->total_lost,
300 6 : pis->address,
301 : pis->next_restock,
302 : &patch_product_cb,
303 : pis);
304 : }
305 : else
306 : {
307 6 : pis->iph = TALER_MERCHANT_product_patch (
308 : TALER_TESTING_interpreter_get_context (is),
309 : pis->merchant_url,
310 : pis->product_id,
311 : pis->description,
312 6 : pis->description_i18n,
313 : pis->unit,
314 6 : &pis->price,
315 6 : pis->image,
316 6 : pis->taxes,
317 : pis->total_stock,
318 6 : pis->total_lost,
319 6 : pis->address,
320 : pis->next_restock,
321 : &patch_product_cb,
322 : pis);
323 : }
324 12 : GNUNET_assert (NULL != pis->iph);
325 12 : }
326 :
327 :
328 : /**
329 : * Offers information from the PATCH /products CMD state to other
330 : * commands.
331 : *
332 : * @param cls closure
333 : * @param[out] ret result (could be anything)
334 : * @param trait name of the trait
335 : * @param index index number of the object to extract.
336 : * @return #GNUNET_OK on success
337 : */
338 : static enum GNUNET_GenericReturnValue
339 48 : patch_product_traits (void *cls,
340 : const void **ret,
341 : const char *trait,
342 : unsigned int index)
343 : {
344 48 : struct PatchProductState *pps = cls;
345 : struct TALER_TESTING_Trait traits[] = {
346 48 : TALER_TESTING_make_trait_product_description (pps->description),
347 48 : TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
348 48 : TALER_TESTING_make_trait_product_unit (pps->unit),
349 48 : TALER_TESTING_make_trait_amount (&pps->price),
350 48 : TALER_TESTING_make_trait_product_image (pps->image),
351 48 : TALER_TESTING_make_trait_taxes (pps->taxes),
352 48 : TALER_TESTING_make_trait_product_stock (&pps->total_stock),
353 48 : TALER_TESTING_make_trait_product_unit_total_stock (
354 48 : pps->unit_total_stock),
355 48 : TALER_TESTING_make_trait_product_unit_precision_level (
356 48 : &pps->unit_precision_level),
357 48 : TALER_TESTING_make_trait_product_unit_allow_fraction (
358 48 : &pps->unit_allow_fraction),
359 48 : TALER_TESTING_make_trait_address (pps->address),
360 48 : TALER_TESTING_make_trait_timestamp (0,
361 48 : &pps->next_restock),
362 48 : TALER_TESTING_make_trait_product_id (pps->product_id),
363 48 : TALER_TESTING_trait_end (),
364 : };
365 :
366 48 : return TALER_TESTING_get_trait (traits,
367 : ret,
368 : trait,
369 : index);
370 : }
371 :
372 :
373 : /**
374 : * Free the state of a "GET product" CMD, and possibly
375 : * cancel a pending operation thereof.
376 : *
377 : * @param cls closure.
378 : * @param cmd command being run.
379 : */
380 : static void
381 12 : patch_product_cleanup (void *cls,
382 : const struct TALER_TESTING_Command *cmd)
383 : {
384 12 : struct PatchProductState *pis = cls;
385 :
386 12 : if (NULL != pis->iph)
387 : {
388 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
389 : "PATCH /products/$ID operation did not complete\n");
390 0 : TALER_MERCHANT_product_patch_cancel (pis->iph);
391 : }
392 12 : json_decref (pis->description_i18n);
393 12 : GNUNET_free (pis->image);
394 12 : json_decref (pis->taxes);
395 12 : json_decref (pis->address);
396 12 : GNUNET_free (pis);
397 12 : }
398 :
399 :
400 : struct TALER_TESTING_Command
401 12 : TALER_TESTING_cmd_merchant_patch_product (
402 : const char *label,
403 : const char *merchant_url,
404 : const char *product_id,
405 : const char *description,
406 : json_t *description_i18n,
407 : const char *unit,
408 : const char *price,
409 : const char *image,
410 : json_t *taxes,
411 : int64_t total_stock,
412 : uint64_t total_lost,
413 : json_t *address,
414 : struct GNUNET_TIME_Timestamp next_restock,
415 : unsigned int http_status)
416 : {
417 : struct PatchProductState *pis;
418 :
419 12 : GNUNET_assert ( (NULL == taxes) ||
420 : json_is_array (taxes));
421 12 : pis = GNUNET_new (struct PatchProductState);
422 12 : pis->merchant_url = merchant_url;
423 12 : pis->product_id = product_id;
424 12 : pis->http_status = http_status;
425 12 : pis->description = description;
426 12 : pis->description_i18n = description_i18n; /* ownership taken */
427 12 : pis->unit = unit;
428 12 : pis->unit_precision_level = default_precision_from_unit (unit);
429 12 : GNUNET_assert (GNUNET_OK ==
430 : TALER_string_to_amount (price,
431 : &pis->price));
432 12 : pis->image = GNUNET_strdup (image);
433 12 : pis->taxes = taxes; /* ownership taken */
434 12 : pis->total_stock = total_stock;
435 12 : pis->total_stock_frac = 0;
436 12 : pis->unit_allow_fraction = false;
437 12 : pis->use_fractional = false;
438 12 : pis->total_lost = total_lost;
439 12 : pis->address = address; /* ownership taken */
440 12 : pis->next_restock = next_restock;
441 12 : patch_product_update_unit_total_stock (pis);
442 : {
443 12 : struct TALER_TESTING_Command cmd = {
444 : .cls = pis,
445 : .label = label,
446 : .run = &patch_product_run,
447 : .cleanup = &patch_product_cleanup,
448 : .traits = &patch_product_traits
449 : };
450 :
451 12 : return cmd;
452 : }
453 : }
454 :
455 :
456 : struct TALER_TESTING_Command
457 6 : TALER_TESTING_cmd_merchant_patch_product2 (
458 : const char *label,
459 : const char *merchant_url,
460 : const char *product_id,
461 : const char *description,
462 : json_t *description_i18n,
463 : const char *unit,
464 : const char *price,
465 : const char *image,
466 : json_t *taxes,
467 : int64_t total_stock,
468 : uint32_t total_stock_frac,
469 : bool unit_allow_fraction,
470 : uint64_t total_lost,
471 : json_t *address,
472 : struct GNUNET_TIME_Timestamp next_restock,
473 : unsigned int http_status)
474 : {
475 : struct TALER_TESTING_Command cmd;
476 :
477 6 : cmd = TALER_TESTING_cmd_merchant_patch_product (label,
478 : merchant_url,
479 : product_id,
480 : description,
481 : description_i18n,
482 : unit,
483 : price,
484 : image,
485 : taxes,
486 : total_stock,
487 : total_lost,
488 : address,
489 : next_restock,
490 : http_status);
491 : {
492 6 : struct PatchProductState *pps = cmd.cls;
493 :
494 6 : pps->total_stock_frac = total_stock_frac;
495 6 : pps->unit_allow_fraction = unit_allow_fraction;
496 6 : pps->use_fractional = true;
497 6 : patch_product_update_unit_total_stock (pps);
498 : }
499 6 : return cmd;
500 : }
501 :
502 :
503 : /* end of testing_api_cmd_patch_product.c */
|