testing_api_cmd_abort_order.c (11815B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 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_abort_order.c 21 * @brief command to test the abort feature. 22 * @author Marcello Stanisci 23 */ 24 #include "taler/platform.h" 25 #include <taler/taler_exchange_service.h> 26 #include <taler/taler_testing_lib.h> 27 #include <taler/taler_signatures.h> 28 #include "taler/taler_merchant_service.h" 29 #include "taler/taler_merchant_testing_lib.h" 30 31 /** 32 * State for a " abort" CMD. 33 */ 34 struct AbortState 35 { 36 37 /** 38 * Reference to the "pay" command to abort. 39 */ 40 const char *pay_reference; 41 42 /** 43 * Merchant URL. 44 */ 45 const char *merchant_url; 46 47 /** 48 * Handle to a "abort" operation. 49 */ 50 struct TALER_MERCHANT_OrderAbortHandle *oah; 51 52 /** 53 * Interpreter state. 54 */ 55 struct TALER_TESTING_Interpreter *is; 56 57 /** 58 * The actual abort/refund data. 59 */ 60 struct TALER_MERCHANT_AbortedCoin *acs; 61 62 /** 63 * Expected HTTP response code. 64 */ 65 unsigned int http_status; 66 67 /** 68 * How many refund permissions this CMD got 69 * the right for. Roughly, there is one refund 70 * permission for one coin. 71 */ 72 unsigned int acs_length; 73 74 }; 75 76 77 /** 78 * Parse the @a coins specification and grow the @a ac 79 * array with the coins found, updating @a nac. 80 * 81 * @param[in,out] ac pointer to array of coins found 82 * @param[in,out] nac length of array at @a pc 83 * @param[in] coins string specifying coins to add to @a pc, 84 * clobbered in the process 85 * @param is interpreter state 86 * @return #GNUNET_OK on success 87 */ 88 static enum GNUNET_GenericReturnValue 89 build_coins (struct TALER_MERCHANT_AbortCoin **ac, 90 unsigned int *nac, 91 char *coins, 92 struct TALER_TESTING_Interpreter *is) 93 { 94 for (char *token = strtok (coins, ";"); 95 NULL != token; 96 token = strtok (NULL, ";")) 97 { 98 char *ctok; 99 unsigned int ci; 100 struct TALER_MERCHANT_AbortCoin *icoin; 101 102 /* Token syntax is "LABEL[/NUMBER]" */ 103 ctok = strchr (token, '/'); 104 ci = 0; 105 if (NULL != ctok) 106 { 107 *ctok = '\0'; 108 ctok++; 109 if (1 != sscanf (ctok, 110 "%u", 111 &ci)) 112 { 113 GNUNET_break (0); 114 return GNUNET_SYSERR; 115 } 116 } 117 { 118 const struct TALER_TESTING_Command *coin_cmd; 119 120 coin_cmd = TALER_TESTING_interpreter_lookup_command (is, 121 token); 122 if (NULL == coin_cmd) 123 { 124 GNUNET_break (0); 125 return GNUNET_SYSERR; 126 } 127 GNUNET_array_grow (*ac, 128 *nac, 129 (*nac) + 1); 130 icoin = &((*ac)[(*nac) - 1]); 131 132 { 133 const struct TALER_CoinSpendPrivateKeyP *coin_priv; 134 135 GNUNET_assert (GNUNET_OK == 136 TALER_TESTING_get_trait_coin_priv (coin_cmd, 137 ci, 138 &coin_priv)); 139 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv, 140 &icoin->coin_pub.eddsa_pub); 141 } 142 GNUNET_assert (GNUNET_OK == 143 TALER_TESTING_get_trait_exchange_url (coin_cmd, 144 &icoin->exchange_url) 145 ); 146 { 147 const struct TALER_Amount *denom_value; 148 149 GNUNET_assert (GNUNET_OK == 150 TALER_TESTING_get_trait_amount (coin_cmd, 151 &denom_value)); 152 icoin->amount_with_fee = *denom_value; 153 } 154 155 } 156 } 157 return GNUNET_OK; 158 } 159 160 161 /** 162 * Callback for a "pay abort" operation. Mainly, check HTTP 163 * response code was as expected and stores refund permissions 164 * in the state. 165 * 166 * @param cls closure. 167 * @param ar response 168 */ 169 static void 170 abort_cb (void *cls, 171 const struct TALER_MERCHANT_AbortResponse *ar) 172 { 173 struct AbortState *as = cls; 174 175 as->oah = NULL; 176 if (as->http_status != ar->hr.http_status) 177 { 178 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 179 "Unexpected response code %u (%d) to command `%s' (expected %u)\n", 180 ar->hr.http_status, 181 (int) ar->hr.ec, 182 TALER_TESTING_interpreter_get_current_label (as->is), 183 as->http_status); 184 TALER_TESTING_FAIL (as->is); 185 } 186 if ( (MHD_HTTP_OK == ar->hr.http_status) && 187 (TALER_EC_NONE == ar->hr.ec) ) 188 { 189 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 190 "Received %u refunds\n", 191 ar->details.ok.num_aborts); 192 as->acs_length = ar->details.ok.num_aborts; 193 as->acs = GNUNET_new_array (as->acs_length, 194 struct TALER_MERCHANT_AbortedCoin); 195 GNUNET_memcpy (as->acs, 196 ar->details.ok.aborts, 197 as->acs_length 198 * sizeof (struct TALER_MERCHANT_AbortedCoin)); 199 } 200 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 201 "Successful pay-abort (HTTP status: %u)\n", 202 ar->hr.http_status); 203 TALER_TESTING_interpreter_next (as->is); 204 } 205 206 207 /** 208 * Run an "abort" CMD. 209 * 210 * @param cls closure 211 * @param cmd command being run. 212 * @param is interpreter state 213 */ 214 static void 215 abort_run (void *cls, 216 const struct TALER_TESTING_Command *cmd, 217 struct TALER_TESTING_Interpreter *is) 218 { 219 struct AbortState *as = cls; 220 const struct TALER_TESTING_Command *pay_cmd; 221 const char *proposal_reference; 222 const char *coin_reference; 223 const struct TALER_TESTING_Command *proposal_cmd; 224 const char *order_id; 225 const struct TALER_PrivateContractHashP *h_proposal; 226 struct TALER_MerchantPublicKeyP merchant_pub; 227 struct TALER_Amount total_amount; 228 const char *error_name; 229 unsigned int error_line; 230 struct TALER_MERCHANT_AbortCoin *abort_coins; 231 unsigned int nabort_coins; 232 char *cr; 233 234 as->is = is; 235 pay_cmd = TALER_TESTING_interpreter_lookup_command (is, 236 as->pay_reference); 237 if (NULL == pay_cmd) 238 TALER_TESTING_FAIL (is); 239 if (GNUNET_OK != 240 TALER_TESTING_get_trait_proposal_reference (pay_cmd, 241 &proposal_reference)) 242 TALER_TESTING_FAIL (is); 243 if (GNUNET_OK != 244 TALER_TESTING_get_trait_coin_reference (pay_cmd, 245 0, 246 &coin_reference)) 247 TALER_TESTING_FAIL (is); 248 proposal_cmd = TALER_TESTING_interpreter_lookup_command (is, 249 proposal_reference); 250 251 if (NULL == proposal_cmd) 252 TALER_TESTING_FAIL (is); 253 254 { 255 const json_t *contract_terms; 256 257 if (GNUNET_OK != 258 TALER_TESTING_get_trait_contract_terms (proposal_cmd, 259 &contract_terms)) 260 TALER_TESTING_FAIL (is); 261 { 262 /* Get information that needs to be put verbatim in the 263 * deposit permission */ 264 struct GNUNET_JSON_Specification spec[] = { 265 GNUNET_JSON_spec_string ("order_id", 266 &order_id), 267 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 268 &merchant_pub), 269 TALER_JSON_spec_amount_any ("amount", 270 &total_amount), 271 GNUNET_JSON_spec_end () 272 }; 273 274 if (GNUNET_OK != 275 GNUNET_JSON_parse (contract_terms, 276 spec, 277 &error_name, 278 &error_line)) 279 { 280 char *js; 281 282 js = json_dumps (contract_terms, 283 JSON_INDENT (1)); 284 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 285 "Parser failed on %s:%u for input `%s'\n", 286 error_name, 287 error_line, 288 js); 289 free (js); 290 TALER_TESTING_FAIL (is); 291 } 292 } 293 } 294 295 cr = GNUNET_strdup (coin_reference); 296 abort_coins = NULL; 297 nabort_coins = 0; 298 if (GNUNET_OK != 299 build_coins (&abort_coins, 300 &nabort_coins, 301 cr, 302 is)) 303 { 304 GNUNET_array_grow (abort_coins, 305 nabort_coins, 306 0); 307 GNUNET_free (cr); 308 TALER_TESTING_FAIL (is); 309 } 310 GNUNET_free (cr); 311 312 if (GNUNET_OK != 313 TALER_TESTING_get_trait_h_contract_terms (proposal_cmd, 314 &h_proposal)) 315 TALER_TESTING_FAIL (is); 316 as->oah = TALER_MERCHANT_order_abort (TALER_TESTING_interpreter_get_context ( 317 is), 318 as->merchant_url, 319 order_id, 320 &merchant_pub, 321 h_proposal, 322 nabort_coins, 323 abort_coins, 324 &abort_cb, 325 as); 326 GNUNET_array_grow (abort_coins, 327 nabort_coins, 328 0); 329 if (NULL == as->oah) 330 TALER_TESTING_FAIL (is); 331 } 332 333 334 /** 335 * Free a "pay abort" CMD, and cancel it if need be. 336 * 337 * @param cls closure. 338 * @param cmd command currently being freed. 339 */ 340 static void 341 abort_cleanup (void *cls, 342 const struct TALER_TESTING_Command *cmd) 343 { 344 struct AbortState *as = cls; 345 346 if (NULL != as->oah) 347 { 348 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 349 "Command `%s' did not complete.\n", 350 TALER_TESTING_interpreter_get_current_label ( 351 as->is)); 352 TALER_MERCHANT_order_abort_cancel (as->oah); 353 } 354 GNUNET_array_grow (as->acs, 355 as->acs_length, 356 0); 357 GNUNET_free (as); 358 } 359 360 361 /** 362 * Offer internal data useful to other commands. 363 * 364 * @param cls closure 365 * @param[out] ret result (could be anything) 366 * @param trait name of the trait 367 * @param index index number of the object to extract. 368 * @return #GNUNET_OK on success 369 */ 370 static int 371 abort_traits (void *cls, 372 const void **ret, 373 const char *trait, 374 unsigned int index) 375 { 376 struct AbortState *as = cls; 377 struct TALER_TESTING_Trait traits[] = { 378 TALER_TESTING_trait_end () 379 }; 380 381 (void) as; 382 return TALER_TESTING_get_trait (traits, 383 ret, 384 trait, 385 index); 386 } 387 388 389 struct TALER_TESTING_Command 390 TALER_TESTING_cmd_merchant_order_abort (const char *label, 391 const char *merchant_url, 392 const char *pay_reference, 393 unsigned int http_status) 394 { 395 struct AbortState *as; 396 397 as = GNUNET_new (struct AbortState); 398 as->http_status = http_status; 399 as->pay_reference = pay_reference; 400 as->merchant_url = merchant_url; 401 { 402 struct TALER_TESTING_Command cmd = { 403 .cls = as, 404 .label = label, 405 .run = &abort_run, 406 .cleanup = &abort_cleanup, 407 .traits = &abort_traits 408 }; 409 410 return cmd; 411 } 412 } 413 414 415 /* end of testing_api_cmd_abort_order.c */