Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2025 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file src/testing/testing_api_cmd_wallet_get_template.c
18 : * @brief command to test GET /templates/$ID (wallet)
19 : * @author Bohdan Potuzhnyi
20 : */
21 : #include "platform.h"
22 : struct WalletGetTemplateState;
23 : #define TALER_MERCHANT_GET_TEMPLATES_RESULT_CLOSURE struct WalletGetTemplateState
24 : #include <microhttpd.h>
25 : #include <jansson.h>
26 : #include <taler/taler_testing_lib.h>
27 : #include "taler/taler_merchant_service.h"
28 : #include "taler/taler_merchant_testing_lib.h"
29 : #include <taler/merchant/get-templates-TEMPLATE_ID.h>
30 :
31 : /**
32 : * State of a "GET template" wallet CMD.
33 : */
34 : struct WalletGetTemplateState
35 : {
36 : /**
37 : * Handle for a "GET template" request.
38 : */
39 : struct TALER_MERCHANT_GetTemplatesHandle *igh;
40 :
41 : /**
42 : * The interpreter state.
43 : */
44 : struct TALER_TESTING_Interpreter *is;
45 :
46 : /**
47 : * Base URL of the merchant serving the request.
48 : */
49 : const char *merchant_url;
50 :
51 : /**
52 : * ID of the template to run GET for.
53 : */
54 : const char *template_id;
55 :
56 : /**
57 : * Expected product count (0 to ignore).
58 : */
59 : size_t expected_products_len;
60 :
61 : /**
62 : * Product id to verify unit info for (optional).
63 : */
64 : const char *expected_product_id;
65 :
66 : /**
67 : * Expected unit for @e expected_product_id.
68 : */
69 : const char *expected_unit;
70 :
71 : /**
72 : * Expected allow_fraction for @e expected_product_id.
73 : */
74 : bool expected_unit_allow_fraction;
75 :
76 : /**
77 : * Expected precision for @e expected_product_id.
78 : */
79 : uint32_t expected_unit_precision_level;
80 :
81 : /**
82 : * Expected unit name short i18n for @e expected_unit.
83 : */
84 : json_t *expected_unit_name_short_i18n;
85 :
86 : /**
87 : * Optional second product id expected to be present.
88 : */
89 : const char *expected_product_id2;
90 :
91 : /**
92 : * Optional third product id expected to be present.
93 : */
94 : const char *expected_product_id3;
95 :
96 : /**
97 : * Expected category id 1 (0 to ignore).
98 : */
99 : uint64_t expected_category_id1;
100 :
101 : /**
102 : * Expected category id 2 (0 to ignore).
103 : */
104 : uint64_t expected_category_id2;
105 :
106 : /**
107 : * Exchange candidate URL expected in the response (optional).
108 : */
109 : const char *expected_exchange_url;
110 :
111 : /**
112 : * Wire method expected for @e expected_exchange_url.
113 : */
114 : const char *expected_wire_method;
115 :
116 : /**
117 : * Expected HTTP response code.
118 : */
119 : unsigned int http_status;
120 : };
121 :
122 : static bool
123 30 : product_id_matches (const json_t *product,
124 : const char *expected_id)
125 : {
126 : const json_t *id_val;
127 :
128 30 : if (NULL == expected_id)
129 12 : return false;
130 18 : id_val = json_object_get (product,
131 : "product_id");
132 18 : if (! json_is_string (id_val))
133 0 : return false;
134 18 : return (0 == strcmp (json_string_value (id_val),
135 : expected_id));
136 : }
137 :
138 :
139 : /**
140 : * Callback for a wallet /get/templates/$ID operation.
141 : *
142 : * @param cls closure for this function
143 : * @param tgr HTTP response details
144 : */
145 : static void
146 6 : wallet_get_template_cb (struct WalletGetTemplateState *wgs,
147 : const struct
148 : TALER_MERCHANT_GetTemplatesResponse *tgr)
149 : {
150 :
151 6 : wgs->igh = NULL;
152 6 : if (wgs->http_status != tgr->hr.http_status)
153 : {
154 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
155 : "Unexpected response code %u (%d) to command %s\n",
156 : tgr->hr.http_status,
157 : (int) tgr->hr.ec,
158 : TALER_TESTING_interpreter_get_current_label (wgs->is));
159 0 : TALER_TESTING_interpreter_fail (wgs->is);
160 0 : return;
161 : }
162 6 : if (MHD_HTTP_OK == tgr->hr.http_status)
163 : {
164 6 : const json_t *template_contract = tgr->details.ok.template_contract;
165 : const json_t *inventory_payload;
166 : const json_t *products;
167 : bool expect_inventory;
168 :
169 6 : if ( (NULL == tgr->details.ok.merchant) ||
170 6 : (NULL == tgr->details.ok.merchant_pub) )
171 : {
172 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
173 : "Missing merchant identity in wallet template\n");
174 0 : TALER_TESTING_interpreter_fail (wgs->is);
175 0 : return;
176 : }
177 6 : if (NULL != wgs->expected_exchange_url)
178 : {
179 6 : bool found_candidate = false;
180 :
181 6 : if (! tgr->details.ok.exchange_candidates_provided)
182 : {
183 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
184 : "Missing exchange_candidates in wallet template\n");
185 0 : TALER_TESTING_interpreter_fail (wgs->is);
186 0 : return;
187 : }
188 6 : for (unsigned int i = 0;
189 6 : i<tgr->details.ok.num_exchange_candidates;
190 0 : i++)
191 : {
192 6 : const struct TALER_MERCHANT_TemplateExchangeCandidate *candidate
193 6 : = &tgr->details.ok.exchange_candidates[i];
194 6 : bool found_method = (NULL == wgs->expected_wire_method);
195 :
196 6 : if (0 != strcmp (candidate->base_url,
197 : wgs->expected_exchange_url))
198 0 : continue;
199 6 : if (NULL != wgs->expected_wire_method)
200 12 : for (unsigned int j = 0; j<candidate->num_wire_methods; j++)
201 6 : if (0 == strcasecmp (candidate->wire_methods[j],
202 : wgs->expected_wire_method))
203 6 : found_method = true;
204 6 : if (found_method)
205 : {
206 6 : found_candidate = true;
207 6 : break;
208 : }
209 : }
210 6 : if (! found_candidate)
211 : {
212 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
213 : "Expected compatible exchange candidate missing\n");
214 0 : TALER_TESTING_interpreter_fail (wgs->is);
215 0 : return;
216 : }
217 : }
218 :
219 6 : inventory_payload = json_object_get (template_contract,
220 : "inventory_payload");
221 16 : expect_inventory = ( (0 < wgs->expected_products_len) ||
222 4 : (NULL != wgs->expected_product_id) ||
223 4 : (NULL != wgs->expected_product_id2) ||
224 4 : (NULL != wgs->expected_product_id3) ||
225 4 : (NULL != wgs->expected_unit_name_short_i18n) ||
226 14 : (0 != wgs->expected_category_id1) ||
227 4 : (0 != wgs->expected_category_id2) );
228 6 : if (! json_is_object (inventory_payload))
229 : {
230 2 : if (! expect_inventory)
231 2 : goto done;
232 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
233 : "Missing inventory_payload in wallet template\n");
234 0 : TALER_TESTING_interpreter_fail (wgs->is);
235 0 : return;
236 : }
237 4 : products = json_object_get (inventory_payload,
238 : "products");
239 4 : if (! json_is_array (products))
240 : {
241 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
242 : "Missing products in wallet template\n");
243 0 : TALER_TESTING_interpreter_fail (wgs->is);
244 0 : return;
245 : }
246 4 : if ( (0 < wgs->expected_products_len) &&
247 2 : (json_array_size (products) != wgs->expected_products_len) )
248 : {
249 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
250 : "Unexpected products length\n");
251 0 : TALER_TESTING_interpreter_fail (wgs->is);
252 0 : return;
253 : }
254 :
255 : {
256 4 : bool found_unit = (NULL == wgs->expected_product_id);
257 4 : bool found_extra = (NULL == wgs->expected_product_id2);
258 4 : bool found_extra2 = (NULL == wgs->expected_product_id3);
259 :
260 14 : for (size_t i = 0; i < json_array_size (products); i++)
261 : {
262 10 : const json_t *product = json_array_get (products,
263 : i);
264 :
265 10 : if (product_id_matches (product,
266 : wgs->expected_product_id))
267 : {
268 : const json_t *unit_val;
269 : const json_t *allow_val;
270 : const json_t *prec_val;
271 :
272 2 : unit_val = json_object_get (product,
273 : "unit");
274 2 : allow_val = json_object_get (product,
275 : "unit_allow_fraction");
276 2 : prec_val = json_object_get (product,
277 : "unit_precision_level");
278 2 : if ( (NULL == unit_val) ||
279 2 : (! json_is_string (unit_val)) ||
280 2 : (0 != strcmp (json_string_value (unit_val),
281 : wgs->expected_unit)) )
282 : {
283 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
284 : "Unexpected unit in wallet template\n");
285 0 : TALER_TESTING_interpreter_fail (wgs->is);
286 0 : return;
287 : }
288 2 : if ( (! json_is_boolean (allow_val)) ||
289 2 : (json_boolean_value (allow_val) !=
290 2 : wgs->expected_unit_allow_fraction) )
291 : {
292 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
293 : "Unexpected unit_allow_fraction in wallet template\n");
294 0 : TALER_TESTING_interpreter_fail (wgs->is);
295 0 : return;
296 : }
297 2 : if ( (! json_is_integer (prec_val)) ||
298 2 : ((uint32_t) json_integer_value (prec_val) !=
299 2 : wgs->expected_unit_precision_level) )
300 : {
301 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
302 : "Unexpected unit_precision_level in wallet template\n");
303 0 : TALER_TESTING_interpreter_fail (wgs->is);
304 0 : return;
305 : }
306 2 : found_unit = true;
307 : }
308 10 : if (product_id_matches (product,
309 : wgs->expected_product_id2))
310 2 : found_extra = true;
311 10 : if (product_id_matches (product,
312 : wgs->expected_product_id3))
313 2 : found_extra2 = true;
314 : }
315 4 : if (! found_unit || ! found_extra || ! found_extra2)
316 : {
317 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
318 : "Expected product ids missing in wallet template\n");
319 0 : TALER_TESTING_interpreter_fail (wgs->is);
320 0 : return;
321 : }
322 : }
323 :
324 4 : if (NULL != wgs->expected_unit_name_short_i18n)
325 : {
326 : const json_t *units;
327 2 : bool found_unit_i18n = false;
328 :
329 2 : units = json_object_get (inventory_payload,
330 : "units");
331 2 : if (! json_is_array (units))
332 : {
333 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
334 : "Missing units in wallet template\n");
335 0 : TALER_TESTING_interpreter_fail (wgs->is);
336 0 : return;
337 : }
338 2 : for (size_t i = 0; i < json_array_size (units); i++)
339 : {
340 2 : const json_t *unit = json_array_get (units,
341 : i);
342 : const json_t *unit_id;
343 : const json_t *unit_i18n;
344 :
345 2 : unit_id = json_object_get (unit,
346 : "unit");
347 2 : if (! json_is_string (unit_id))
348 0 : continue;
349 2 : if (0 != strcmp (json_string_value (unit_id),
350 : wgs->expected_unit))
351 0 : continue;
352 2 : unit_i18n = json_object_get (unit,
353 : "unit_name_short_i18n");
354 2 : if ( (NULL == unit_i18n) ||
355 4 : (! json_is_object (unit_i18n)) ||
356 2 : (1 != json_equal (unit_i18n,
357 2 : wgs->expected_unit_name_short_i18n)) )
358 : {
359 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
360 : "Unexpected unit_name_short_i18n in wallet template\n");
361 0 : TALER_TESTING_interpreter_fail (wgs->is);
362 0 : return;
363 : }
364 2 : found_unit_i18n = true;
365 2 : break;
366 : }
367 2 : if (! found_unit_i18n)
368 : {
369 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
370 : "Expected unit entry missing in wallet template\n");
371 0 : TALER_TESTING_interpreter_fail (wgs->is);
372 0 : return;
373 : }
374 : }
375 :
376 4 : if ( (0 != wgs->expected_category_id1) ||
377 2 : (0 != wgs->expected_category_id2) )
378 : {
379 : const json_t *categories;
380 2 : bool found_cat1 = (0 == wgs->expected_category_id1);
381 2 : bool found_cat2 = (0 == wgs->expected_category_id2);
382 :
383 2 : categories = json_object_get (inventory_payload,
384 : "categories");
385 2 : if (! json_is_array (categories))
386 : {
387 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
388 : "Missing categories in wallet template\n");
389 0 : TALER_TESTING_interpreter_fail (wgs->is);
390 0 : return;
391 : }
392 6 : for (size_t i = 0; i < json_array_size (categories); i++)
393 : {
394 4 : const json_t *category = json_array_get (categories,
395 : i);
396 : const json_t *cid;
397 :
398 4 : cid = json_object_get (category,
399 : "category_id");
400 4 : if (! json_is_integer (cid))
401 0 : continue;
402 4 : if ( (0 != wgs->expected_category_id1) &&
403 4 : ((uint64_t) json_integer_value (cid)
404 4 : == wgs->expected_category_id1) )
405 2 : found_cat1 = true;
406 4 : if ( (0 != wgs->expected_category_id2) &&
407 4 : ((uint64_t) json_integer_value (cid)
408 4 : == wgs->expected_category_id2) )
409 2 : found_cat2 = true;
410 : }
411 2 : if (! found_cat1 || ! found_cat2)
412 : {
413 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
414 : "Expected category ids missing in wallet template\n");
415 0 : TALER_TESTING_interpreter_fail (wgs->is);
416 0 : return;
417 : }
418 : }
419 : }
420 4 : done:
421 6 : TALER_TESTING_interpreter_next (wgs->is);
422 : }
423 :
424 :
425 : /**
426 : * Run the "GET /templates/$ID" wallet CMD.
427 : *
428 : * @param cls closure.
429 : * @param cmd command being run now.
430 : * @param is interpreter state.
431 : */
432 : static void
433 6 : wallet_get_template_run (void *cls,
434 : const struct TALER_TESTING_Command *cmd,
435 : struct TALER_TESTING_Interpreter *is)
436 : {
437 6 : struct WalletGetTemplateState *wgs = cls;
438 :
439 6 : wgs->is = is;
440 6 : wgs->igh = TALER_MERCHANT_get_templates_create (
441 : TALER_TESTING_interpreter_get_context (is),
442 : wgs->merchant_url,
443 : wgs->template_id);
444 : {
445 : enum TALER_ErrorCode ec;
446 :
447 6 : ec = TALER_MERCHANT_get_templates_start (
448 : wgs->igh,
449 : &wallet_get_template_cb,
450 : wgs);
451 6 : GNUNET_assert (TALER_EC_NONE == ec);
452 : }
453 6 : }
454 :
455 :
456 : /**
457 : * Free the state of a "GET template" CMD, and possibly
458 : * cancel a pending operation thereof.
459 : *
460 : * @param cls closure.
461 : * @param cmd command being run.
462 : */
463 : static void
464 6 : wallet_get_template_cleanup (void *cls,
465 : const struct TALER_TESTING_Command *cmd)
466 : {
467 6 : struct WalletGetTemplateState *wgs = cls;
468 :
469 6 : if (NULL != wgs->igh)
470 : {
471 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
472 : "GET /templates/$ID operation did not complete\n");
473 0 : TALER_MERCHANT_get_templates_cancel (wgs->igh);
474 : }
475 6 : json_decref (wgs->expected_unit_name_short_i18n);
476 6 : GNUNET_free (wgs);
477 6 : }
478 :
479 :
480 : struct TALER_TESTING_Command
481 6 : TALER_TESTING_cmd_merchant_wallet_get_template (
482 : const char *label,
483 : const char *merchant_url,
484 : const char *template_id,
485 : size_t expected_products_len,
486 : const char *expected_product_id,
487 : const char *expected_unit,
488 : bool expected_unit_allow_fraction,
489 : uint32_t expected_unit_precision_level,
490 : json_t *expected_unit_name_short_i18n,
491 : const char *expected_product_id2,
492 : const char *expected_product_id3,
493 : uint64_t expected_category_id1,
494 : uint64_t expected_category_id2,
495 : const char *expected_exchange_url,
496 : const char *expected_wire_method,
497 : unsigned int http_status)
498 : {
499 : struct WalletGetTemplateState *wgs;
500 :
501 6 : wgs = GNUNET_new (struct WalletGetTemplateState);
502 6 : wgs->merchant_url = merchant_url;
503 6 : wgs->template_id = template_id;
504 6 : wgs->expected_products_len = expected_products_len;
505 6 : wgs->expected_product_id = expected_product_id;
506 6 : wgs->expected_unit = expected_unit;
507 6 : wgs->expected_unit_allow_fraction = expected_unit_allow_fraction;
508 6 : wgs->expected_unit_precision_level = expected_unit_precision_level;
509 6 : if (NULL != expected_unit_name_short_i18n)
510 2 : wgs->expected_unit_name_short_i18n =
511 2 : json_incref (expected_unit_name_short_i18n);
512 6 : wgs->expected_product_id2 = expected_product_id2;
513 6 : wgs->expected_product_id3 = expected_product_id3;
514 6 : wgs->expected_category_id1 = expected_category_id1;
515 6 : wgs->expected_category_id2 = expected_category_id2;
516 6 : wgs->expected_exchange_url = expected_exchange_url;
517 6 : wgs->expected_wire_method = expected_wire_method;
518 6 : wgs->http_status = http_status;
519 : {
520 6 : struct TALER_TESTING_Command cmd = {
521 : .cls = wgs,
522 : .label = label,
523 : .run = &wallet_get_template_run,
524 : .cleanup = &wallet_get_template_cleanup
525 : };
526 :
527 6 : return cmd;
528 : }
529 : }
530 :
531 :
532 : /* end of testing_api_cmd_wallet_get_template.c */
|