testing_api_cmd_abort_order.c (11775B)
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 "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_merchant_service.h" 29 #include "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 const struct TALER_Amount *denom_value; 147 148 GNUNET_assert (GNUNET_OK == 149 TALER_TESTING_get_trait_amount (coin_cmd, 150 &denom_value)); 151 icoin->amount_with_fee = *denom_value; 152 } 153 154 } 155 } 156 return GNUNET_OK; 157 } 158 159 160 /** 161 * Callback for a "pay abort" operation. Mainly, check HTTP 162 * response code was as expected and stores refund permissions 163 * in the state. 164 * 165 * @param cls closure. 166 * @param ar response 167 */ 168 static void 169 abort_cb (void *cls, 170 const struct TALER_MERCHANT_AbortResponse *ar) 171 { 172 struct AbortState *as = cls; 173 174 as->oah = NULL; 175 if (as->http_status != ar->hr.http_status) 176 { 177 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 178 "Unexpected response code %u (%d) to command `%s' (expected %u)\n", 179 ar->hr.http_status, 180 (int) ar->hr.ec, 181 TALER_TESTING_interpreter_get_current_label (as->is), 182 as->http_status); 183 TALER_TESTING_FAIL (as->is); 184 } 185 if ( (MHD_HTTP_OK == ar->hr.http_status) && 186 (TALER_EC_NONE == ar->hr.ec) ) 187 { 188 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 189 "Received %u refunds\n", 190 ar->details.ok.num_aborts); 191 as->acs_length = ar->details.ok.num_aborts; 192 as->acs = GNUNET_new_array (as->acs_length, 193 struct TALER_MERCHANT_AbortedCoin); 194 GNUNET_memcpy (as->acs, 195 ar->details.ok.aborts, 196 as->acs_length 197 * sizeof (struct TALER_MERCHANT_AbortedCoin)); 198 } 199 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 200 "Successful pay-abort (HTTP status: %u)\n", 201 ar->hr.http_status); 202 TALER_TESTING_interpreter_next (as->is); 203 } 204 205 206 /** 207 * Run an "abort" CMD. 208 * 209 * @param cls closure 210 * @param cmd command being run. 211 * @param is interpreter state 212 */ 213 static void 214 abort_run (void *cls, 215 const struct TALER_TESTING_Command *cmd, 216 struct TALER_TESTING_Interpreter *is) 217 { 218 struct AbortState *as = cls; 219 const struct TALER_TESTING_Command *pay_cmd; 220 const char *proposal_reference; 221 const char *coin_reference; 222 const struct TALER_TESTING_Command *proposal_cmd; 223 const char *order_id; 224 const struct TALER_PrivateContractHashP *h_proposal; 225 struct TALER_MerchantPublicKeyP merchant_pub; 226 struct TALER_Amount total_amount; 227 const char *error_name; 228 unsigned int error_line; 229 struct TALER_MERCHANT_AbortCoin *abort_coins; 230 unsigned int nabort_coins; 231 char *cr; 232 233 as->is = is; 234 pay_cmd = TALER_TESTING_interpreter_lookup_command (is, 235 as->pay_reference); 236 if (NULL == pay_cmd) 237 TALER_TESTING_FAIL (is); 238 if (GNUNET_OK != 239 TALER_TESTING_get_trait_proposal_reference (pay_cmd, 240 &proposal_reference)) 241 TALER_TESTING_FAIL (is); 242 if (GNUNET_OK != 243 TALER_TESTING_get_trait_coin_reference (pay_cmd, 244 0, 245 &coin_reference)) 246 TALER_TESTING_FAIL (is); 247 proposal_cmd = TALER_TESTING_interpreter_lookup_command (is, 248 proposal_reference); 249 250 if (NULL == proposal_cmd) 251 TALER_TESTING_FAIL (is); 252 253 { 254 const json_t *contract_terms; 255 256 if (GNUNET_OK != 257 TALER_TESTING_get_trait_contract_terms (proposal_cmd, 258 &contract_terms)) 259 TALER_TESTING_FAIL (is); 260 { 261 /* Get information that needs to be put verbatim in the 262 * deposit permission */ 263 struct GNUNET_JSON_Specification spec[] = { 264 GNUNET_JSON_spec_string ("order_id", 265 &order_id), 266 GNUNET_JSON_spec_fixed_auto ("merchant_pub", 267 &merchant_pub), 268 TALER_JSON_spec_amount_any ("amount", 269 &total_amount), 270 GNUNET_JSON_spec_end () 271 }; 272 273 if (GNUNET_OK != 274 GNUNET_JSON_parse (contract_terms, 275 spec, 276 &error_name, 277 &error_line)) 278 { 279 char *js; 280 281 js = json_dumps (contract_terms, 282 JSON_INDENT (1)); 283 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 284 "Parser failed on %s:%u for input `%s'\n", 285 error_name, 286 error_line, 287 js); 288 free (js); 289 TALER_TESTING_FAIL (is); 290 } 291 } 292 } 293 294 cr = GNUNET_strdup (coin_reference); 295 abort_coins = NULL; 296 nabort_coins = 0; 297 if (GNUNET_OK != 298 build_coins (&abort_coins, 299 &nabort_coins, 300 cr, 301 is)) 302 { 303 GNUNET_array_grow (abort_coins, 304 nabort_coins, 305 0); 306 GNUNET_free (cr); 307 TALER_TESTING_FAIL (is); 308 } 309 GNUNET_free (cr); 310 311 if (GNUNET_OK != 312 TALER_TESTING_get_trait_h_contract_terms (proposal_cmd, 313 &h_proposal)) 314 TALER_TESTING_FAIL (is); 315 as->oah = TALER_MERCHANT_order_abort (TALER_TESTING_interpreter_get_context ( 316 is), 317 as->merchant_url, 318 order_id, 319 &merchant_pub, 320 h_proposal, 321 nabort_coins, 322 abort_coins, 323 &abort_cb, 324 as); 325 GNUNET_array_grow (abort_coins, 326 nabort_coins, 327 0); 328 if (NULL == as->oah) 329 TALER_TESTING_FAIL (is); 330 } 331 332 333 /** 334 * Free a "pay abort" CMD, and cancel it if need be. 335 * 336 * @param cls closure. 337 * @param cmd command currently being freed. 338 */ 339 static void 340 abort_cleanup (void *cls, 341 const struct TALER_TESTING_Command *cmd) 342 { 343 struct AbortState *as = cls; 344 345 if (NULL != as->oah) 346 { 347 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 348 "Command `%s' did not complete.\n", 349 TALER_TESTING_interpreter_get_current_label ( 350 as->is)); 351 TALER_MERCHANT_order_abort_cancel (as->oah); 352 } 353 GNUNET_array_grow (as->acs, 354 as->acs_length, 355 0); 356 GNUNET_free (as); 357 } 358 359 360 /** 361 * Offer internal data useful to other commands. 362 * 363 * @param cls closure 364 * @param[out] ret result (could be anything) 365 * @param trait name of the trait 366 * @param index index number of the object to extract. 367 * @return #GNUNET_OK on success 368 */ 369 static int 370 abort_traits (void *cls, 371 const void **ret, 372 const char *trait, 373 unsigned int index) 374 { 375 struct AbortState *as = cls; 376 struct TALER_TESTING_Trait traits[] = { 377 TALER_TESTING_trait_end () 378 }; 379 380 (void) as; 381 return TALER_TESTING_get_trait (traits, 382 ret, 383 trait, 384 index); 385 } 386 387 388 struct TALER_TESTING_Command 389 TALER_TESTING_cmd_merchant_order_abort (const char *label, 390 const char *merchant_url, 391 const char *pay_reference, 392 unsigned int http_status) 393 { 394 struct AbortState *as; 395 396 as = GNUNET_new (struct AbortState); 397 as->http_status = http_status; 398 as->pay_reference = pay_reference; 399 as->merchant_url = merchant_url; 400 { 401 struct TALER_TESTING_Command cmd = { 402 .cls = as, 403 .label = label, 404 .run = &abort_run, 405 .cleanup = &abort_cleanup, 406 .traits = &abort_traits 407 }; 408 409 return cmd; 410 } 411 } 412 413 414 /* end of testing_api_cmd_abort_order.c */