testing_api_cmd_wallet_get_template.c (16472B)
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 product_id_matches (const json_t *product, 124 const char *expected_id) 125 { 126 const json_t *id_val; 127 128 if (NULL == expected_id) 129 return false; 130 id_val = json_object_get (product, 131 "product_id"); 132 if (! json_is_string (id_val)) 133 return false; 134 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 wallet_get_template_cb (struct WalletGetTemplateState *wgs, 147 const struct 148 TALER_MERCHANT_GetTemplatesResponse *tgr) 149 { 150 151 wgs->igh = NULL; 152 if (wgs->http_status != tgr->hr.http_status) 153 { 154 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 TALER_TESTING_interpreter_fail (wgs->is); 160 return; 161 } 162 if (MHD_HTTP_OK == tgr->hr.http_status) 163 { 164 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 if ( (NULL == tgr->details.ok.merchant) || 170 (NULL == tgr->details.ok.merchant_pub) ) 171 { 172 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 173 "Missing merchant identity in wallet template\n"); 174 TALER_TESTING_interpreter_fail (wgs->is); 175 return; 176 } 177 if (NULL != wgs->expected_exchange_url) 178 { 179 bool found_candidate = false; 180 181 if (! tgr->details.ok.exchange_candidates_provided) 182 { 183 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 184 "Missing exchange_candidates in wallet template\n"); 185 TALER_TESTING_interpreter_fail (wgs->is); 186 return; 187 } 188 for (unsigned int i = 0; 189 i<tgr->details.ok.num_exchange_candidates; 190 i++) 191 { 192 const struct TALER_MERCHANT_TemplateExchangeCandidate *candidate 193 = &tgr->details.ok.exchange_candidates[i]; 194 bool found_method = (NULL == wgs->expected_wire_method); 195 196 if (0 != strcmp (candidate->base_url, 197 wgs->expected_exchange_url)) 198 continue; 199 if (NULL != wgs->expected_wire_method) 200 for (unsigned int j = 0; j<candidate->num_wire_methods; j++) 201 if (0 == strcasecmp (candidate->wire_methods[j], 202 wgs->expected_wire_method)) 203 found_method = true; 204 if (found_method) 205 { 206 found_candidate = true; 207 break; 208 } 209 } 210 if (! found_candidate) 211 { 212 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 213 "Expected compatible exchange candidate missing\n"); 214 TALER_TESTING_interpreter_fail (wgs->is); 215 return; 216 } 217 } 218 219 inventory_payload = json_object_get (template_contract, 220 "inventory_payload"); 221 expect_inventory = ( (0 < wgs->expected_products_len) || 222 (NULL != wgs->expected_product_id) || 223 (NULL != wgs->expected_product_id2) || 224 (NULL != wgs->expected_product_id3) || 225 (NULL != wgs->expected_unit_name_short_i18n) || 226 (0 != wgs->expected_category_id1) || 227 (0 != wgs->expected_category_id2) ); 228 if (! json_is_object (inventory_payload)) 229 { 230 if (! expect_inventory) 231 goto done; 232 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 233 "Missing inventory_payload in wallet template\n"); 234 TALER_TESTING_interpreter_fail (wgs->is); 235 return; 236 } 237 products = json_object_get (inventory_payload, 238 "products"); 239 if (! json_is_array (products)) 240 { 241 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 242 "Missing products in wallet template\n"); 243 TALER_TESTING_interpreter_fail (wgs->is); 244 return; 245 } 246 if ( (0 < wgs->expected_products_len) && 247 (json_array_size (products) != wgs->expected_products_len) ) 248 { 249 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 250 "Unexpected products length\n"); 251 TALER_TESTING_interpreter_fail (wgs->is); 252 return; 253 } 254 255 { 256 bool found_unit = (NULL == wgs->expected_product_id); 257 bool found_extra = (NULL == wgs->expected_product_id2); 258 bool found_extra2 = (NULL == wgs->expected_product_id3); 259 260 for (size_t i = 0; i < json_array_size (products); i++) 261 { 262 const json_t *product = json_array_get (products, 263 i); 264 265 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 unit_val = json_object_get (product, 273 "unit"); 274 allow_val = json_object_get (product, 275 "unit_allow_fraction"); 276 prec_val = json_object_get (product, 277 "unit_precision_level"); 278 if ( (NULL == unit_val) || 279 (! json_is_string (unit_val)) || 280 (0 != strcmp (json_string_value (unit_val), 281 wgs->expected_unit)) ) 282 { 283 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 284 "Unexpected unit in wallet template\n"); 285 TALER_TESTING_interpreter_fail (wgs->is); 286 return; 287 } 288 if ( (! json_is_boolean (allow_val)) || 289 (json_boolean_value (allow_val) != 290 wgs->expected_unit_allow_fraction) ) 291 { 292 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 293 "Unexpected unit_allow_fraction in wallet template\n"); 294 TALER_TESTING_interpreter_fail (wgs->is); 295 return; 296 } 297 if ( (! json_is_integer (prec_val)) || 298 ((uint32_t) json_integer_value (prec_val) != 299 wgs->expected_unit_precision_level) ) 300 { 301 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 302 "Unexpected unit_precision_level in wallet template\n"); 303 TALER_TESTING_interpreter_fail (wgs->is); 304 return; 305 } 306 found_unit = true; 307 } 308 if (product_id_matches (product, 309 wgs->expected_product_id2)) 310 found_extra = true; 311 if (product_id_matches (product, 312 wgs->expected_product_id3)) 313 found_extra2 = true; 314 } 315 if (! found_unit || ! found_extra || ! found_extra2) 316 { 317 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 318 "Expected product ids missing in wallet template\n"); 319 TALER_TESTING_interpreter_fail (wgs->is); 320 return; 321 } 322 } 323 324 if (NULL != wgs->expected_unit_name_short_i18n) 325 { 326 const json_t *units; 327 bool found_unit_i18n = false; 328 329 units = json_object_get (inventory_payload, 330 "units"); 331 if (! json_is_array (units)) 332 { 333 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 334 "Missing units in wallet template\n"); 335 TALER_TESTING_interpreter_fail (wgs->is); 336 return; 337 } 338 for (size_t i = 0; i < json_array_size (units); i++) 339 { 340 const json_t *unit = json_array_get (units, 341 i); 342 const json_t *unit_id; 343 const json_t *unit_i18n; 344 345 unit_id = json_object_get (unit, 346 "unit"); 347 if (! json_is_string (unit_id)) 348 continue; 349 if (0 != strcmp (json_string_value (unit_id), 350 wgs->expected_unit)) 351 continue; 352 unit_i18n = json_object_get (unit, 353 "unit_name_short_i18n"); 354 if ( (NULL == unit_i18n) || 355 (! json_is_object (unit_i18n)) || 356 (1 != json_equal (unit_i18n, 357 wgs->expected_unit_name_short_i18n)) ) 358 { 359 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 360 "Unexpected unit_name_short_i18n in wallet template\n"); 361 TALER_TESTING_interpreter_fail (wgs->is); 362 return; 363 } 364 found_unit_i18n = true; 365 break; 366 } 367 if (! found_unit_i18n) 368 { 369 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 370 "Expected unit entry missing in wallet template\n"); 371 TALER_TESTING_interpreter_fail (wgs->is); 372 return; 373 } 374 } 375 376 if ( (0 != wgs->expected_category_id1) || 377 (0 != wgs->expected_category_id2) ) 378 { 379 const json_t *categories; 380 bool found_cat1 = (0 == wgs->expected_category_id1); 381 bool found_cat2 = (0 == wgs->expected_category_id2); 382 383 categories = json_object_get (inventory_payload, 384 "categories"); 385 if (! json_is_array (categories)) 386 { 387 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 388 "Missing categories in wallet template\n"); 389 TALER_TESTING_interpreter_fail (wgs->is); 390 return; 391 } 392 for (size_t i = 0; i < json_array_size (categories); i++) 393 { 394 const json_t *category = json_array_get (categories, 395 i); 396 const json_t *cid; 397 398 cid = json_object_get (category, 399 "category_id"); 400 if (! json_is_integer (cid)) 401 continue; 402 if ( (0 != wgs->expected_category_id1) && 403 ((uint64_t) json_integer_value (cid) 404 == wgs->expected_category_id1) ) 405 found_cat1 = true; 406 if ( (0 != wgs->expected_category_id2) && 407 ((uint64_t) json_integer_value (cid) 408 == wgs->expected_category_id2) ) 409 found_cat2 = true; 410 } 411 if (! found_cat1 || ! found_cat2) 412 { 413 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 414 "Expected category ids missing in wallet template\n"); 415 TALER_TESTING_interpreter_fail (wgs->is); 416 return; 417 } 418 } 419 } 420 done: 421 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 wallet_get_template_run (void *cls, 434 const struct TALER_TESTING_Command *cmd, 435 struct TALER_TESTING_Interpreter *is) 436 { 437 struct WalletGetTemplateState *wgs = cls; 438 439 wgs->is = is; 440 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 ec = TALER_MERCHANT_get_templates_start ( 448 wgs->igh, 449 &wallet_get_template_cb, 450 wgs); 451 GNUNET_assert (TALER_EC_NONE == ec); 452 } 453 } 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 wallet_get_template_cleanup (void *cls, 465 const struct TALER_TESTING_Command *cmd) 466 { 467 struct WalletGetTemplateState *wgs = cls; 468 469 if (NULL != wgs->igh) 470 { 471 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 472 "GET /templates/$ID operation did not complete\n"); 473 TALER_MERCHANT_get_templates_cancel (wgs->igh); 474 } 475 json_decref (wgs->expected_unit_name_short_i18n); 476 GNUNET_free (wgs); 477 } 478 479 480 struct TALER_TESTING_Command 481 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 wgs = GNUNET_new (struct WalletGetTemplateState); 502 wgs->merchant_url = merchant_url; 503 wgs->template_id = template_id; 504 wgs->expected_products_len = expected_products_len; 505 wgs->expected_product_id = expected_product_id; 506 wgs->expected_unit = expected_unit; 507 wgs->expected_unit_allow_fraction = expected_unit_allow_fraction; 508 wgs->expected_unit_precision_level = expected_unit_precision_level; 509 if (NULL != expected_unit_name_short_i18n) 510 wgs->expected_unit_name_short_i18n = 511 json_incref (expected_unit_name_short_i18n); 512 wgs->expected_product_id2 = expected_product_id2; 513 wgs->expected_product_id3 = expected_product_id3; 514 wgs->expected_category_id1 = expected_category_id1; 515 wgs->expected_category_id2 = expected_category_id2; 516 wgs->expected_exchange_url = expected_exchange_url; 517 wgs->expected_wire_method = expected_wire_method; 518 wgs->http_status = http_status; 519 { 520 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 return cmd; 528 } 529 } 530 531 532 /* end of testing_api_cmd_wallet_get_template.c */