test_merchantdb.c (432287B)
1 /* 2 This file is part of TALER 3 (C) 2014-2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Lesser 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/backenddb/test_merchantdb.c 18 * @brief testcase for merchant's postgres db plugin 19 * @author Marcello Stanisci 20 * @author Christian Grothoff 21 * @author Pricilla Huang 22 */ 23 #include "platform.h" 24 #include "microhttpd.h" 25 #include <taler/taler_util.h> 26 #include <taler/taler_json_lib.h> 27 #include <taler/taler_pq_lib.h> 28 #include <taler/taler_dbevents.h> 29 #include <taler/taler_signatures.h> 30 #include "taler/taler_merchant_util.h" 31 #include "merchantdb_lib.h" 32 #include "helper.h" 33 #include "merchant-database/gc.h" 34 #include "merchant-database/start.h" 35 #include "merchant-database/iterate_statistic_bucket_amounts.h" 36 #include "merchant-database/iterate_statistic_bucket_counters.h" 37 #include "merchant-database/iterate_kyc_statuses.h" 38 #include "merchant-database/insert_kyc_failure.h" 39 #include "merchant-database/insert_kyc_status.h" 40 #include "merchant-database/delete_contract_terms.h" 41 #include "merchant-database/update_to_instance_private_key_deleted.h" 42 #include "merchant-database/delete_order.h" 43 #include "merchant-database/delete_pending_webhook.h" 44 #include "merchant-database/delete_product.h" 45 #include "merchant-database/delete_template.h" 46 #include "merchant-database/delete_webhook.h" 47 #include "merchant-database/update_to_account_inactive.h" 48 #include "merchant-database/do_increase_refund.h" 49 #include "merchant-database/insert_account.h" 50 #include "merchant-database/insert_category.h" 51 #include "merchant-database/update_category.h" 52 #include "merchant-database/insert_contract_terms.h" 53 #include "merchant-database/insert_deposit.h" 54 #include "merchant-database/insert_deposit_confirmation.h" 55 #include "merchant-database/insert_deposit_to_transfer.h" 56 #include "merchant-database/insert_exchange_signing_key.h" 57 #include "merchant-database/insert_instance.h" 58 #include "merchant-database/insert_mfa_challenge.h" 59 #include "merchant-database/do_solve_mfa_challenge.h" 60 #include "merchant-database/insert_order.h" 61 #include "merchant-database/insert_order_lock.h" 62 #include "merchant-database/insert_otp_device.h" 63 #include "merchant-database/get_otp_device.h" 64 #include "merchant-database/update_otp_device.h" 65 #include "merchant-database/delete_otp_device.h" 66 #include "merchant-database/get_contract_terms_pos.h" 67 #include "merchant-database/insert_pending_webhook.h" 68 #include "merchant-database/insert_product.h" 69 #include "merchant-database/insert_refund_proof.h" 70 #include "merchant-database/insert_report.h" 71 #include "merchant-database/insert_template.h" 72 #include "merchant-database/insert_transfer.h" 73 #include "merchant-database/insert_transfer_details.h" 74 #include "merchant-database/insert_webhook.h" 75 #include "merchant-database/insert_inventory_lock.h" 76 #include "merchant-database/insert_issued_token.h" 77 #include "merchant-database/insert_used_token.h" 78 #include "merchant-database/insert_token_family.h" 79 #include "merchant-database/insert_token_family_key.h" 80 #include "merchant-database/get_token_family.h" 81 #include "merchant-database/get_token_family_key.h" 82 #include "merchant-database/delete_token_family.h" 83 #include "merchant-database/get_account_serial.h" 84 #include "merchant-database/get_contract_terms.h" 85 #include "merchant-database/get_contract_terms_status.h" 86 #include "merchant-database/iterate_deposits.h" 87 #include "merchant-database/iterate_deposits_by_contract_and_coin.h" 88 #include "merchant-database/iterate_deposits_by_order.h" 89 #include "merchant-database/iterate_instances.h" 90 #include "merchant-database/get_order.h" 91 #include "merchant-database/get_order_by_fulfillment.h" 92 #include "merchant-database/get_order_status.h" 93 #include "merchant-database/iterate_orders.h" 94 #include "merchant-database/iterate_pending_deposits.h" 95 #include "merchant-database/iterate_pending_webhooks_above_serial_id.h" 96 #include "merchant-database/iterate_pending_webhooks.h" 97 #include "merchant-database/get_product.h" 98 #include "merchant-database/iterate_products.h" 99 #include "merchant-database/get_refund_proof.h" 100 #include "merchant-database/iterate_refunds.h" 101 #include "merchant-database/iterate_refunds_detailed.h" 102 #include "merchant-database/get_template.h" 103 #include "merchant-database/iterate_templates.h" 104 #include "merchant-database/iterate_transfer_details.h" 105 #include "merchant-database/iterate_transfer_details_by_order.h" 106 #include "merchant-database/iterate_transfer_summaries.h" 107 #include "merchant-database/iterate_transfers.h" 108 #include "merchant-database/get_webhook.h" 109 #include "merchant-database/iterate_webhooks_by_event.h" 110 #include "merchant-database/iterate_webhooks.h" 111 #include "merchant-database/get_exchange_wire_fee.h" 112 #include "merchant-database/get_instance_auth.h" 113 #include "merchant-database/update_to_contract_terms_paid.h" 114 #include "merchant-database/update_to_contract_terms_wired.h" 115 #include "merchant-database/insert_refund_by_coin.h" 116 #include "merchant-database/set_instance.h" 117 #include "merchant-database/insert_exchange_wire_fee.h" 118 #include "merchant-database/delete_inventory_lock.h" 119 #include "merchant-database/update_contract_terms.h" 120 #include "merchant-database/update_instance.h" 121 #include "merchant-database/update_pending_webhook.h" 122 #include "merchant-database/update_product.h" 123 #include "merchant-database/update_template.h" 124 #include "merchant-database/update_webhook.h" 125 #include "merchant-database/create_tables.h" 126 #include "merchant-database/drop_tables.h" 127 #include "merchant-database/event_listen.h" 128 #include "merchant-database/preflight.h" 129 #include "merchant-database/delete_instance.h" 130 131 132 /** 133 * Global return value for the test. Initially -1, set to 0 upon 134 * completion. Other values indicate some kind of error. 135 */ 136 static int result; 137 138 /** 139 * Handle to the database we are testing. 140 */ 141 static struct TALER_MERCHANTDB_PostgresContext *pg; 142 143 /** 144 * Configuration of the database we are testing. Used by tests that 145 * need a second, concurrent connection. 146 */ 147 static const struct GNUNET_CONFIGURATION_Handle *test_cfg; 148 149 /** 150 * @param test 0 on success, non-zero on failure 151 */ 152 #define TEST_WITH_FAIL_CLAUSE(test, on_fail) \ 153 if ((test)) \ 154 { \ 155 GNUNET_break (0); \ 156 on_fail \ 157 } 158 159 #define TEST_COND_RET_ON_FAIL(cond, msg) \ 160 if (! (cond)) \ 161 { \ 162 GNUNET_break (0); \ 163 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \ 164 msg); \ 165 return 1; \ 166 } 167 168 /** 169 * @param __test 0 on success, non-zero on failure 170 */ 171 #define TEST_RET_ON_FAIL(__test) \ 172 TEST_WITH_FAIL_CLAUSE (__test, \ 173 return 1; \ 174 ) 175 176 /** 177 * Activate the per-instance schema for @a iid before invoking any 178 * per-instance MERCHANTDB function. Returns 1 from the enclosing 179 * test helper if routing fails for an unexpected reason. 180 * 181 * If @a iid does not resolve to an existing instance, set_instance 182 * returns NO_RESULTS. In that case the underlying per-instance call 183 * could never have run, so we treat it as the test outcome: when the 184 * caller expected NO_RESULTS, return 0 (pass); otherwise return 1. 185 * 186 * @param iid C-string instance identifier 187 * @param expected expected GNUNET_DB_QueryStatus from the wrapped call 188 */ 189 #define TEST_SET_INSTANCE(iid, expected) \ 190 do { \ 191 enum GNUNET_DB_QueryStatus _si_qs; \ 192 _si_qs = TALER_MERCHANTDB_set_instance (pg, (iid)); \ 193 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == _si_qs) \ 194 { \ 195 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == (expected)) \ 196 return 0; \ 197 GNUNET_break (0); \ 198 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \ 199 "set_instance(%s): instance missing, " \ 200 "expected qs=%d\n", \ 201 (iid), \ 202 (int) (expected)); \ 203 return 1; \ 204 } \ 205 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != _si_qs) \ 206 { \ 207 GNUNET_break (0); \ 208 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \ 209 "set_instance(%s) failed (qs=%d)\n", \ 210 (iid), \ 211 (int) _si_qs); \ 212 return 1; \ 213 } \ 214 } while (0) 215 216 217 /** 218 * Runs @a sql directly on the database connection of the test. Used to 219 * set up or inspect state that has no dedicated MERCHANTDB entry point, 220 * such as the notification settings of an instance or the rows queued by 221 * a trigger. 222 * 223 * @param sql the SQL statement(s) to execute 224 * @return 0 on success, 1 otherwise. 225 */ 226 static int 227 exec_sql (const char *sql) 228 { 229 struct GNUNET_PQ_ExecuteStatement es[] = { 230 GNUNET_PQ_make_execute (sql), 231 GNUNET_PQ_EXECUTE_STATEMENT_END 232 }; 233 234 TEST_COND_RET_ON_FAIL (GNUNET_OK == 235 GNUNET_PQ_exec_statements (pg->conn, 236 es), 237 "Direct SQL execution failed\n"); 238 return 0; 239 } 240 241 242 /** 243 * Evaluates @a sql, which must be a parameter-less query returning 244 * exactly one row with a single INT8 column named "num". 245 * 246 * @param sql the query to run 247 * @param[out] num set to the value returned 248 * @return 0 on success, 1 otherwise. 249 */ 250 static int 251 query_sql_num (const char *sql, 252 uint64_t *num) 253 { 254 struct GNUNET_PQ_QueryParam params[] = { 255 GNUNET_PQ_query_param_end 256 }; 257 struct GNUNET_PQ_ResultSpec rs[] = { 258 GNUNET_PQ_result_spec_uint64 ("num", 259 num), 260 GNUNET_PQ_result_spec_end 261 }; 262 263 TEST_COND_RET_ON_FAIL (GNUNET_OK == 264 GNUNET_PQ_prepare_anon (pg->conn, 265 sql), 266 "Preparing direct query failed\n"); 267 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 268 GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 269 "", 270 params, 271 rs), 272 "Direct query failed\n"); 273 return 0; 274 } 275 276 277 /** 278 * Evaluates @a sql, which must be a parameter-less query returning 279 * exactly one row with a single TEXT column named "txt". 280 * 281 * @param sql the query to run 282 * @param[out] txt set to the value returned, to be freed by the caller 283 * @return 0 on success, 1 otherwise. 284 */ 285 static int 286 query_sql_text (const char *sql, 287 char **txt) 288 { 289 struct GNUNET_PQ_QueryParam params[] = { 290 GNUNET_PQ_query_param_end 291 }; 292 struct GNUNET_PQ_ResultSpec rs[] = { 293 GNUNET_PQ_result_spec_string ("txt", 294 txt), 295 GNUNET_PQ_result_spec_end 296 }; 297 298 TEST_COND_RET_ON_FAIL (GNUNET_OK == 299 GNUNET_PQ_prepare_anon (pg->conn, 300 sql), 301 "Preparing direct query failed\n"); 302 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 303 GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 304 "", 305 params, 306 rs), 307 "Direct query failed\n"); 308 return 0; 309 } 310 311 312 /* ********** Instances ********** */ 313 314 315 /** 316 * Container for instance settings along with keys. 317 */ 318 struct InstanceData 319 { 320 /** 321 * The instance settings. 322 */ 323 struct TALER_MERCHANTDB_InstanceSettings instance; 324 325 /** 326 * The public key for the instance. 327 */ 328 struct TALER_MerchantPublicKeyP merchant_pub; 329 330 /** 331 * The private key for the instance. 332 */ 333 struct TALER_MerchantPrivateKeyP merchant_priv; 334 }; 335 336 337 /** 338 * Creates data for an instance to use with testing. 339 * 340 * @param instance_id the identifier for this instance. 341 * @param instance the instance data to be filled. 342 */ 343 static void 344 make_instance (const char *instance_id, 345 struct InstanceData *instance) 346 { 347 memset (instance, 348 0, 349 sizeof (*instance)); 350 GNUNET_CRYPTO_eddsa_key_create (&instance->merchant_priv.eddsa_priv); 351 GNUNET_CRYPTO_eddsa_key_get_public (&instance->merchant_priv.eddsa_priv, 352 &instance->merchant_pub.eddsa_pub); 353 instance->instance.id = (char *) instance_id; 354 instance->instance.name = (char *) "Test"; 355 instance->instance.address = json_array (); 356 GNUNET_assert (NULL != instance->instance.address); 357 GNUNET_assert (0 == json_array_append_new (instance->instance.address, 358 json_string ("123 Example St"))); 359 instance->instance.jurisdiction = json_array (); 360 GNUNET_assert (NULL != instance->instance.jurisdiction); 361 GNUNET_assert (0 == json_array_append_new (instance->instance.jurisdiction, 362 json_string ("Ohio"))); 363 instance->instance.use_stefan = true; 364 instance->instance.default_wire_transfer_delay = 365 GNUNET_TIME_relative_get_minute_ (); 366 instance->instance.default_pay_delay = GNUNET_TIME_UNIT_SECONDS; 367 instance->instance.default_refund_delay = GNUNET_TIME_UNIT_MINUTES; 368 } 369 370 371 /** 372 * Frees memory allocated when creating an instance for testing. 373 * 374 * @param instance the instance containing the memory to be freed. 375 */ 376 static void 377 free_instance_data (struct InstanceData *instance) 378 { 379 json_decref (instance->instance.address); 380 json_decref (instance->instance.jurisdiction); 381 } 382 383 384 /** 385 * Creates an account with test data for an instance. 386 * 387 * @param account the account to initialize. 388 */ 389 static void 390 make_account (struct TALER_MERCHANTDB_AccountDetails *account) 391 { 392 memset (account, 393 0, 394 sizeof (*account)); 395 GNUNET_CRYPTO_random_block (&account->h_wire.hash, 396 sizeof account->h_wire.hash); 397 GNUNET_CRYPTO_random_block (&account->salt, 398 sizeof (account->salt)); 399 account->payto_uri.full_payto 400 = (char *) "payto://x-taler-bank/bank.demo.taler.net/4"; 401 account->active = true; 402 } 403 404 405 /** 406 * Instance settings along with corresponding accounts. 407 */ 408 struct InstanceWithAccounts 409 { 410 /** 411 * Pointer to the instance settings. 412 */ 413 const struct TALER_MERCHANTDB_InstanceSettings *instance; 414 415 /** 416 * Length of the array of accounts. 417 */ 418 unsigned int accounts_length; 419 420 /** 421 * Pointer to the array of accounts. 422 */ 423 const struct TALER_MERCHANTDB_AccountDetails *accounts; 424 425 }; 426 427 428 /** 429 * Closure for testing instance lookup. 430 */ 431 struct TestLookupInstances_Closure 432 { 433 /** 434 * Number of instances to compare to. 435 */ 436 unsigned int instances_to_cmp_length; 437 438 /** 439 * Pointer to array of instances. 440 */ 441 const struct InstanceWithAccounts *instances_to_cmp; 442 443 /** 444 * Pointer to array of number of matches for each instance. 445 */ 446 unsigned int *results_matching; 447 448 /** 449 * Total number of results returned. 450 */ 451 unsigned int results_length; 452 }; 453 454 455 /** 456 * Compares two instances for equality. 457 * 458 * @param a the first instance. 459 * @param b the second instance. 460 * @return 0 on equality, 1 otherwise. 461 */ 462 static int 463 check_instances_equal (const struct TALER_MERCHANTDB_InstanceSettings *a, 464 const struct TALER_MERCHANTDB_InstanceSettings *b) 465 { 466 if ((0 != strcmp (a->id, 467 b->id)) || 468 (0 != strcmp (a->name, 469 b->name)) || 470 (1 != json_equal (a->address, 471 b->address)) || 472 (1 != json_equal (a->jurisdiction, 473 b->jurisdiction)) || 474 (a->use_stefan != b->use_stefan) || 475 (a->default_wire_transfer_delay.rel_value_us != 476 b->default_wire_transfer_delay.rel_value_us) || 477 (a->default_pay_delay.rel_value_us != b->default_pay_delay.rel_value_us)) 478 return 1; 479 return 0; 480 } 481 482 483 #if 0 484 /** 485 * Compares two accounts for equality. 486 * 487 * @param a the first account. 488 * @param b the second account. 489 * @return 0 on equality, 1 otherwise. 490 */ 491 static int 492 check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a, 493 const struct TALER_MERCHANTDB_AccountDetails *b) 494 { 495 if ((0 != GNUNET_memcmp (&a->h_wire, 496 &b->h_wire)) || 497 (0 != GNUNET_memcmp (&a->salt, 498 &b->salt)) || 499 (0 != TALER_full_payto_cmp (a->payto_uri, 500 b->payto_uri)) || 501 (a->active != b->active)) 502 return 1; 503 return 0; 504 } 505 506 507 #endif 508 509 510 /** 511 * Called after testing 'iterate_instances'. 512 * 513 * @param cls pointer to 'struct TestLookupInstances_Closure'. 514 * @param merchant_pub public key of the instance 515 * @param merchant_priv private key of the instance, NULL if not available 516 * @param is general instance settings 517 * @param ias instance authentication settings 518 */ 519 static void 520 lookup_instances_cb (void *cls, 521 const struct TALER_MerchantPublicKeyP *merchant_pub, 522 const struct TALER_MerchantPrivateKeyP *merchant_priv, 523 const struct TALER_MERCHANTDB_InstanceSettings *is, 524 const struct TALER_MERCHANTDB_InstanceAuthSettings *ias) 525 { 526 struct TestLookupInstances_Closure *cmp = cls; 527 528 if (NULL == cmp) 529 return; 530 cmp->results_length += 1; 531 /* Look through the closure and test each instance for equality */ 532 for (unsigned int i = 0; cmp->instances_to_cmp_length > i; ++i) 533 { 534 if (0 != check_instances_equal (cmp->instances_to_cmp[i].instance, 535 is)) 536 continue; 537 cmp->results_matching[i] += 1; 538 } 539 } 540 541 542 /** 543 * Tests @e insert_instance. 544 * 545 * @param instance the instance data to insert. 546 * @param expected_result the result that should be returned from the DB. 547 * @return 0 on success, 1 on failure. 548 */ 549 static int 550 test_insert_instance (const struct InstanceData *instance, 551 enum GNUNET_DB_QueryStatus expected_result) 552 { 553 struct TALER_MERCHANTDB_InstanceAuthSettings ias = { 0 }; 554 555 TEST_COND_RET_ON_FAIL (expected_result == 556 TALER_MERCHANTDB_insert_instance (pg, 557 &instance->merchant_pub, 558 &instance->merchant_priv, 559 &instance->instance, 560 &ias), 561 "Insert instance failed\n"); 562 return 0; 563 } 564 565 566 /** 567 * Tests @e update_instance. 568 * 569 * @param updated_data the instance data to update the row in the database to. 570 * @param expected_result the result that should be returned from the DB. 571 * @return 0 on success, 1 on failure. 572 */ 573 static int 574 test_update_instance (const struct InstanceData *updated_data, 575 enum GNUNET_DB_QueryStatus expected_result) 576 { 577 TEST_COND_RET_ON_FAIL (expected_result == 578 TALER_MERCHANTDB_update_instance (pg, 579 &updated_data->instance), 580 "Update instance failed\n"); 581 return 0; 582 } 583 584 585 /** 586 * Tests @e iterate_instances. 587 * 588 * @param active_only whether to lookup all instance, or only active ones. 589 * @param instances_length number of instances to compare to in @e instances. 590 * @param instances a list of instances that will be compared with the results 591 * found. 592 * @return 0 on success, 1 otherwise. 593 */ 594 static int 595 test_lookup_instances (bool active_only, 596 unsigned int instances_length, 597 struct InstanceWithAccounts instances[]) 598 { 599 unsigned int results_matching[GNUNET_NZL (instances_length)]; 600 struct TestLookupInstances_Closure cmp = { 601 .instances_to_cmp_length = instances_length, 602 .instances_to_cmp = instances, 603 .results_matching = results_matching, 604 .results_length = 0 605 }; 606 memset (results_matching, 0, sizeof (unsigned int) * instances_length); 607 if (0 > TALER_MERCHANTDB_iterate_instances (pg, 608 active_only, 609 &lookup_instances_cb, 610 &cmp)) 611 { 612 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 613 "Lookup instances failed\n"); 614 return 1; 615 } 616 if (instances_length != cmp.results_length) 617 { 618 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 619 "Lookup instances failed: incorrect number of results\n"); 620 return 1; 621 } 622 for (unsigned int i = 0; instances_length > i; ++i) 623 { 624 if (1 != cmp.results_matching[i]) 625 { 626 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 627 "Lookup instances failed: mismatched data\n"); 628 return 1; 629 } 630 } 631 return 0; 632 } 633 634 635 /** 636 * Tests removing the private key of the given instance from the database. 637 * 638 * @param instance the instance whose private key to delete. 639 * @param expected_result the result we expect the db to return. 640 * @return 0 on success, 1 otherwise. 641 */ 642 static int 643 test_delete_instance_private_key (const struct InstanceData *instance, 644 enum GNUNET_DB_QueryStatus expected_result) 645 { 646 TEST_SET_INSTANCE (instance->instance.id, expected_result); 647 TEST_COND_RET_ON_FAIL (expected_result == 648 TALER_MERCHANTDB_update_to_instance_private_key_deleted ( 649 pg, 650 instance->instance.id), 651 "Delete instance private key failed\n"); 652 return 0; 653 } 654 655 656 /** 657 * Tests purging all data for an instance from the database. 658 * 659 * @param instance the instance to purge. 660 * @param expected_result the result we expect the db to return. 661 * @return 0 on success, 1 otherwise. 662 */ 663 static int 664 test_purge_instance (const struct InstanceData *instance, 665 enum GNUNET_DB_QueryStatus expected_result) 666 { 667 TEST_SET_INSTANCE (instance->instance.id, expected_result); 668 TEST_COND_RET_ON_FAIL (expected_result == 669 TALER_MERCHANTDB_delete_instance (pg, 670 instance->instance.id), 671 "Purge instance failed\n"); 672 return 0; 673 } 674 675 676 /** 677 * Tests inserting an account for a merchant instance. 678 * 679 * @param instance the instance to associate the account with. 680 * @param account the account to insert. 681 * @param expected_result the result expected from the db. 682 * @return 0 on success, 1 otherwise. 683 */ 684 static int 685 test_insert_account (const struct InstanceData *instance, 686 const struct TALER_MERCHANTDB_AccountDetails *account, 687 enum GNUNET_DB_QueryStatus expected_result) 688 { 689 TEST_SET_INSTANCE (instance->instance.id, expected_result); 690 TEST_COND_RET_ON_FAIL (expected_result == 691 TALER_MERCHANTDB_insert_account (pg, 692 account), 693 "Insert account failed\n"); 694 return 0; 695 } 696 697 698 /** 699 * Tests deactivating an account. 700 * 701 * @param account the account to deactivate. 702 * @param expected_result the result expected from the DB. 703 * @return 0 on success, 1 otherwise. 704 */ 705 static int 706 test_inactivate_account (const struct InstanceData *instance, 707 const struct TALER_MERCHANTDB_AccountDetails *account, 708 enum GNUNET_DB_QueryStatus expected_result) 709 { 710 TEST_SET_INSTANCE (instance->instance.id, expected_result); 711 TEST_COND_RET_ON_FAIL (expected_result == 712 TALER_MERCHANTDB_update_to_account_inactive (pg, 713 instance->instance.id, 714 &account->h_wire), 715 "Deactivate account failed\n"); 716 return 0; 717 } 718 719 720 /** 721 * Closure for instance tests 722 */ 723 struct TestInstances_Closure 724 { 725 /** 726 * The list of instances that we use for testing instances. 727 */ 728 struct InstanceData instances[2]; 729 730 /** 731 * The list of accounts to use with the instances. 732 */ 733 struct TALER_MERCHANTDB_AccountDetails accounts[2]; 734 735 }; 736 737 738 /** 739 * Sets up the data structures used in the instance tests 740 * 741 * @cls the closure to initialize with test data. 742 */ 743 static void 744 pre_test_instances (struct TestInstances_Closure *cls) 745 { 746 /* Instance */ 747 make_instance ("test_instances_inst0", 748 &cls->instances[0]); 749 make_instance ("test_instances_inst1", 750 &cls->instances[1]); 751 752 /* Accounts */ 753 make_account (&cls->accounts[0]); 754 cls->accounts[0].instance_id 755 = cls->instances[0].instance.id; 756 make_account (&cls->accounts[1]); 757 cls->accounts[1].instance_id 758 = cls->instances[1].instance.id; 759 } 760 761 762 /** 763 * Handles all teardown after testing 764 * 765 * @cls the closure to free data from 766 */ 767 static void 768 post_test_instances (struct TestInstances_Closure *cls) 769 { 770 free_instance_data (&cls->instances[0]); 771 free_instance_data (&cls->instances[1]); 772 } 773 774 775 /** 776 * Tests that a per-instance operation still resolves against the 777 * per-instance schema after the database connection was dropped and 778 * re-established. 779 * 780 * Regression test: reconnect_cb() re-issues 'SET search_path TO 781 * merchant' but used to leave @e current_merchant_id set, so the 782 * TALER_MERCHANTDB_set_instance() the dispatcher does for the next 783 * request found the instance already selected and returned without 784 * re-issuing the per-instance search_path. Every unqualified 785 * statement of that instance then hit schema 'merchant' and failed 786 * with 42P01 (HTTP 500) until a different instance was touched. 787 * 788 * @param instance the instance to run the test against. 789 * @return 0 when successful, 1 otherwise. 790 */ 791 static int 792 test_reconnect_search_path (const struct InstanceData *instance) 793 { 794 json_t *i18n = json_object (); 795 struct TALER_MERCHANTDB_PostgresContext *cpg; 796 uint64_t backend_pid; 797 uint64_t gen; 798 uint64_t cat; 799 char sql[128]; 800 int ret = 1; 801 802 GNUNET_assert (NULL != i18n); 803 TEST_SET_INSTANCE (instance->instance.id, 804 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 805 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 806 TALER_MERCHANTDB_insert_category (pg, 807 instance->instance.id, 808 "reconnect-before", 809 i18n, 810 &cat)) 811 { 812 GNUNET_break (0); 813 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 814 "Insert category before reconnect failed\n"); 815 goto cleanup; 816 } 817 /* Find out which backend serves our connection. */ 818 { 819 struct GNUNET_PQ_QueryParam params[] = { 820 GNUNET_PQ_query_param_end 821 }; 822 struct GNUNET_PQ_ResultSpec rs[] = { 823 GNUNET_PQ_result_spec_uint64 ("pid", 824 &backend_pid), 825 GNUNET_PQ_result_spec_end 826 }; 827 828 if ( (GNUNET_OK != 829 GNUNET_PQ_prepare_anon (pg->conn, 830 "SELECT pg_backend_pid()::INT8 AS pid")) || 831 (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 832 GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 833 "", 834 params, 835 rs)) ) 836 { 837 GNUNET_break (0); 838 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 839 "Failed to determine backend PID\n"); 840 goto cleanup; 841 } 842 } 843 /* Kill our own backend from a second connection, the way a 844 PostgreSQL restart or a pgbouncer recycle would. */ 845 GNUNET_snprintf (sql, 846 sizeof (sql), 847 "DO $$ BEGIN" 848 " PERFORM pg_terminate_backend(%llu);" 849 " END $$", 850 (unsigned long long) backend_pid); 851 cpg = TALER_MERCHANTDB_connect (test_cfg); 852 if (NULL == cpg) 853 { 854 GNUNET_break (0); 855 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 856 "Failed to open second database connection\n"); 857 goto cleanup; 858 } 859 { 860 struct GNUNET_PQ_ExecuteStatement es[] = { 861 GNUNET_PQ_make_execute (sql), 862 GNUNET_PQ_EXECUTE_STATEMENT_END 863 }; 864 enum GNUNET_GenericReturnValue res; 865 866 res = GNUNET_PQ_exec_statements (cpg->conn, 867 es); 868 TALER_MERCHANTDB_disconnect (cpg); 869 if (GNUNET_OK != res) 870 { 871 GNUNET_break (0); 872 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 873 "Failed to terminate our own backend\n"); 874 goto cleanup; 875 } 876 } 877 gen = TMH_PG_prep_gen_; 878 for (unsigned int i = 0; i<10; i++) 879 { 880 GNUNET_PQ_reconnect_if_down (pg->conn); 881 if (gen != TMH_PG_prep_gen_) 882 break; 883 sleep (1); 884 } 885 if (gen == TMH_PG_prep_gen_) 886 { 887 GNUNET_break (0); 888 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 889 "Database did not reconnect\n"); 890 goto cleanup; 891 } 892 /* This is what the dispatcher does for the next request; it must 893 restore the per-instance search_path. */ 894 TEST_SET_INSTANCE (instance->instance.id, 895 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 896 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 897 TALER_MERCHANTDB_insert_category (pg, 898 instance->instance.id, 899 "reconnect-after", 900 i18n, 901 &cat)) 902 { 903 GNUNET_break (0); 904 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 905 "Insert category after reconnect failed\n"); 906 goto cleanup; 907 } 908 ret = 0; 909 cleanup: 910 json_decref (i18n); 911 return ret; 912 } 913 914 915 /** 916 * Tests that an instance whose 'auth_hash'/'auth_salt' are NULL is 917 * read back as "no password configured" instead of failing. 918 * 919 * Both columns are nullable, but the result spec did not allow NULL, 920 * so such a row made TALER_MERCHANTDB_get_instance_auth() return a 921 * hard error (HTTP 500 on every authenticated request of that 922 * instance). No code path in the tree writes NULL today -- the 923 * "no password" state is an all-zero hash -- so the NULL is set here 924 * directly via SQL. 925 * 926 * @param instance the instance to run the test against. 927 * @return 0 on success, 1 otherwise. 928 */ 929 static int 930 test_get_instance_auth_null (const struct InstanceData *instance) 931 { 932 struct TALER_MERCHANTDB_InstanceAuthSettings ias; 933 char sql[256]; 934 935 GNUNET_snprintf (sql, 936 sizeof (sql), 937 "UPDATE merchant.merchant_instances" 938 " SET auth_hash=NULL" 939 " ,auth_salt=NULL" 940 " WHERE merchant_id='%s'", 941 instance->instance.id); 942 { 943 struct GNUNET_PQ_ExecuteStatement es[] = { 944 GNUNET_PQ_make_execute (sql), 945 GNUNET_PQ_EXECUTE_STATEMENT_END 946 }; 947 948 TEST_COND_RET_ON_FAIL (GNUNET_OK == 949 GNUNET_PQ_exec_statements (pg->conn, 950 es), 951 "Failed to NULL out the instance authentication\n"); 952 } 953 memset (&ias, 954 42, 955 sizeof (ias)); 956 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 957 TALER_MERCHANTDB_get_instance_auth ( 958 pg, 959 instance->instance.id, 960 &ias), 961 "Lookup of NULL instance authentication failed\n"); 962 TEST_COND_RET_ON_FAIL (GNUNET_is_zero (&ias.auth_hash) && 963 GNUNET_is_zero (&ias.auth_salt), 964 "NULL instance authentication was not zeroed out\n"); 965 return 0; 966 } 967 968 969 /** 970 * Tests that storing a report always yields a fresh serial. 971 * 972 * 'merchant_reports' has no unique key other than the identity 973 * column 'report_serial' that the INSERT never supplies, so the 974 * 'ON CONFLICT DO NOTHING' the INSERT used to carry could never 975 * fire. Should a deduplicating key ever be introduced, the INSERT 976 * must report it (as 'no results') rather than silently return no 977 * serial: POST /private/reports would otherwise answer 200 OK with 978 * an uninitialised 'report_serial_id'. 979 * 980 * @param instance the instance to store the reports for. 981 * @return 0 on success, 1 otherwise. 982 */ 983 static int 984 test_insert_report_serial (const struct InstanceData *instance) 985 { 986 uint64_t report_ids[3]; 987 988 TEST_SET_INSTANCE (instance->instance.id, 989 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 990 for (unsigned int i = 0; i<3; i++) 991 { 992 report_ids[i] = 0; 993 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 994 TALER_MERCHANTDB_insert_report ( 995 pg, 996 instance->instance.id, 997 "report-generator-test", 998 "a test report", 999 "text/plain", 1000 "/private/orders", 1001 "test@example.com", 1002 GNUNET_TIME_UNIT_DAYS, 1003 GNUNET_TIME_UNIT_ZERO, 1004 &report_ids[i]), 1005 "Insert report failed\n"); 1006 TEST_COND_RET_ON_FAIL (0 != report_ids[i], 1007 "Insert report did not return a serial\n"); 1008 for (unsigned int j = 0; j<i; j++) 1009 TEST_COND_RET_ON_FAIL (report_ids[i] != report_ids[j], 1010 "Insert report returned a duplicate serial\n"); 1011 } 1012 return 0; 1013 } 1014 1015 1016 /** 1017 * Function that tests instances. 1018 * 1019 * @param cls closure with config 1020 * @return 0 on success, 1 if failure. 1021 */ 1022 static int 1023 run_test_instances (struct TestInstances_Closure *cls) 1024 { 1025 struct InstanceWithAccounts instances[2] = { 1026 { 1027 .accounts_length = 0, 1028 .accounts = cls->accounts, 1029 .instance = &cls->instances[0].instance 1030 }, 1031 { 1032 .accounts_length = 0, 1033 .accounts = cls->accounts, 1034 .instance = &cls->instances[1].instance 1035 } 1036 }; 1037 uint64_t account_serial; 1038 1039 /* Test inserting an instance */ 1040 TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0], 1041 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1042 /* Test double insertion fails */ 1043 TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0], 1044 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 1045 /* Test lookup instances- is our new instance there? */ 1046 TEST_RET_ON_FAIL (test_lookup_instances (false, 1047 1, 1048 instances)); 1049 /* Test update instance */ 1050 cls->instances[0].instance.name = "Test - updated"; 1051 json_array_append_new (cls->instances[0].instance.address, 1052 json_pack ("{s:s, s:I}", 1053 "this", "is", 1054 "more data", 47)); 1055 json_array_append_new (cls->instances[0].instance.jurisdiction, 1056 json_pack ("{s:s}", 1057 "vegetables", "bad")); 1058 cls->instances[0].instance.use_stefan = false; 1059 cls->instances[0].instance.default_wire_transfer_delay = 1060 GNUNET_TIME_UNIT_HOURS; 1061 cls->instances[0].instance.default_pay_delay = GNUNET_TIME_UNIT_MINUTES; 1062 1063 TEST_RET_ON_FAIL (test_update_instance (&cls->instances[0], 1064 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1065 TEST_RET_ON_FAIL (test_lookup_instances (false, 1066 1, 1067 instances)); 1068 TEST_RET_ON_FAIL (test_update_instance (&cls->instances[1], 1069 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 1070 /* Test account creation */ 1071 TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0], 1072 &cls->accounts[0], 1073 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1074 /* Test double account insertion fails */ 1075 TEST_RET_ON_FAIL (test_insert_account (&cls->instances[1], 1076 &cls->accounts[1], 1077 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 1078 TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0], 1079 &cls->accounts[0], 1080 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 1081 instances[0].accounts_length = 1; 1082 TEST_RET_ON_FAIL (test_lookup_instances (false, 1083 1, 1084 instances)); 1085 /* Test deactivate account */ 1086 TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[0], 1087 &cls->accounts[0], 1088 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1089 TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[1], 1090 &cls->accounts[1], 1091 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 1092 cls->accounts[0].active = false; 1093 TEST_RET_ON_FAIL (test_lookup_instances (false, 1094 1, 1095 instances)); 1096 /* Test lookup account */ 1097 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1098 TALER_MERCHANTDB_get_account_serial (pg, 1099 cls->instances[0].instance.id, 1100 cls->accounts[0].payto_uri, 1101 &account_serial)) 1102 { 1103 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1104 "Lookup account failed\n"); 1105 return 1; 1106 } 1107 if (1 != account_serial) 1108 { 1109 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1110 "Lookup account failed: incorrect serial number found\n"); 1111 return 1; 1112 } 1113 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 1114 TALER_MERCHANTDB_get_account_serial (pg, 1115 cls->instances[0].instance.id, 1116 (struct TALER_FullPayto) { 1117 (char *) "payto://other-uri" 1118 }, 1119 &account_serial)) 1120 { 1121 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1122 "Lookup account failed: account found where there is none\n"); 1123 return 1; 1124 } 1125 /* Test that a reconnect does not lose the per-instance search_path */ 1126 TEST_RET_ON_FAIL (test_reconnect_search_path (&cls->instances[0])); 1127 /* Test that every stored report gets its own serial */ 1128 TEST_RET_ON_FAIL (test_insert_report_serial (&cls->instances[0])); 1129 /* Test that a NULL authentication hash/salt is not an error */ 1130 TEST_RET_ON_FAIL (test_get_instance_auth_null (&cls->instances[0])); 1131 /* Test instance private key deletion */ 1132 TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[0], 1133 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1134 TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[1], 1135 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 1136 TEST_RET_ON_FAIL (test_lookup_instances (true, 1137 0, 1138 NULL)); 1139 TEST_RET_ON_FAIL (test_lookup_instances (false, 1140 1, 1141 instances)); 1142 TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[1], 1143 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1144 TEST_RET_ON_FAIL (test_lookup_instances (false, 1145 2, 1146 instances)); 1147 TEST_RET_ON_FAIL (test_lookup_instances (true, 1148 1, 1149 &instances[1])); 1150 TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1], 1151 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1152 TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1], 1153 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 1154 /* Test that the instance is gone. */ 1155 TEST_RET_ON_FAIL (test_lookup_instances (false, 1156 1, 1157 instances)); 1158 return 0; 1159 } 1160 1161 1162 /** 1163 * Function that tests instances. 1164 * 1165 * @return 0 on success, 1 otherwise. 1166 */ 1167 static int 1168 test_instances (void) 1169 { 1170 struct TestInstances_Closure test_cls; 1171 int test_result; 1172 1173 pre_test_instances (&test_cls); 1174 test_result = run_test_instances (&test_cls); 1175 post_test_instances (&test_cls); 1176 return test_result; 1177 } 1178 1179 1180 /* *********** Products ********** */ 1181 1182 1183 /** 1184 * A container for data relevant to a product. 1185 */ 1186 struct ProductData 1187 { 1188 /** 1189 * The identifier of the product. 1190 */ 1191 const char *id; 1192 1193 /** 1194 * The details of the product. 1195 */ 1196 struct TALER_MERCHANTDB_ProductDetails product; 1197 }; 1198 1199 1200 /** 1201 * Creates a product for testing with. 1202 * 1203 * @param id the id of the product. 1204 * @param product the product data to fill. 1205 */ 1206 static void 1207 make_product (const char *id, 1208 struct ProductData *product) 1209 { 1210 static struct TALER_Amount htwenty40; 1211 1212 GNUNET_assert (GNUNET_OK == 1213 TALER_string_to_amount ("EUR:120.40", 1214 &htwenty40)); 1215 1216 memset (product, 1217 0, 1218 sizeof (*product)); 1219 product->id = id; 1220 product->product.product_name = "Test product"; 1221 product->product.description = "This is a test product"; 1222 product->product.description_i18n = json_array (); 1223 GNUNET_assert (NULL != product->product.description_i18n); 1224 product->product.unit = "boxes"; 1225 product->product.minimum_age = 0; 1226 product->product.price_array = &htwenty40; 1227 product->product.price_array_length = 1; 1228 product->product.taxes = json_array (); 1229 GNUNET_assert (NULL != product->product.taxes); 1230 product->product.total_stock = 55; 1231 product->product.total_sold = 0; 1232 product->product.total_lost = 0; 1233 product->product.image = GNUNET_strdup (""); 1234 GNUNET_assert (NULL != product->product.image); 1235 product->product.address = json_array (); 1236 GNUNET_assert (NULL != product->product.address); 1237 product->product.next_restock = GNUNET_TIME_UNIT_ZERO_TS; 1238 } 1239 1240 1241 /** 1242 * Frees memory associated with @e ProductData. 1243 * 1244 * @param product the container to free. 1245 */ 1246 static void 1247 free_product_data (struct ProductData *product) 1248 { 1249 json_decref (product->product.description_i18n); 1250 json_decref (product->product.taxes); 1251 GNUNET_free (product->product.image); 1252 json_decref (product->product.address); 1253 } 1254 1255 1256 /** 1257 * Compare two products for equality. 1258 * 1259 * @param a the first product. 1260 * @param b the second product. 1261 * @return 0 on equality, 1 otherwise. 1262 */ 1263 static int 1264 check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a, 1265 const struct TALER_MERCHANTDB_ProductDetails *b) 1266 { 1267 if ((0 != strcmp (a->description, 1268 b->description)) || 1269 (1 != json_equal (a->description_i18n, 1270 b->description_i18n)) || 1271 (0 != strcmp (a->unit, 1272 b->unit)) || 1273 (a->price_array_length != b->price_array_length) || 1274 (1 != json_equal (a->taxes, 1275 b->taxes)) || 1276 (a->total_stock != b->total_stock) || 1277 (a->total_sold != b->total_sold) || 1278 (a->total_lost != b->total_lost) || 1279 (0 != strcmp (a->image, 1280 b->image)) || 1281 (1 != json_equal (a->address, 1282 b->address)) || 1283 (GNUNET_TIME_timestamp_cmp (a->next_restock, 1284 !=, 1285 b->next_restock))) 1286 1287 return 1; 1288 for (size_t i = 0; i<a->price_array_length; i++) 1289 if ( (GNUNET_OK != 1290 TALER_amount_cmp_currency (&a->price_array[i], 1291 &b->price_array[i])) || 1292 (0 != TALER_amount_cmp (&a->price_array[i], 1293 &b->price_array[i])) ) 1294 return 1; 1295 return 0; 1296 } 1297 1298 1299 /** 1300 * Tests inserting product data into the database. 1301 * 1302 * @param instance the instance to insert the product for. 1303 * @param product the product data to insert. 1304 * @param num_cats length of the @a cats array 1305 * @param cats array of categories for the product 1306 * @param expected_result the result we expect the db to return. 1307 * @param expect_conflict expected conflict status 1308 * @param expect_no_instance expected instance missing status 1309 * @param expected_no_cat expected category missing index 1310 * @return 0 when successful, 1 otherwise. 1311 */ 1312 static int 1313 test_insert_product (const struct InstanceData *instance, 1314 const struct ProductData *product, 1315 unsigned int num_cats, 1316 const uint64_t *cats, 1317 enum GNUNET_DB_QueryStatus expected_result, 1318 bool expect_conflict, 1319 bool expect_no_instance, 1320 ssize_t expected_no_cat) 1321 { 1322 bool conflict; 1323 ssize_t no_cat; 1324 bool no_group; 1325 bool no_pot; 1326 1327 if (expect_no_instance) 1328 { 1329 TEST_COND_RET_ON_FAIL ( 1330 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 1331 TALER_MERCHANTDB_set_instance (pg, instance->instance.id), 1332 "Expected missing instance, but set_instance succeeded\n"); 1333 return 0; 1334 } 1335 TEST_SET_INSTANCE (instance->instance.id, expected_result); 1336 TEST_COND_RET_ON_FAIL (expected_result == 1337 TALER_MERCHANTDB_insert_product (pg, 1338 instance->instance.id, 1339 product->id, 1340 &product->product, 1341 num_cats, 1342 cats, 1343 &conflict, 1344 &no_cat, 1345 &no_group, 1346 &no_pot), 1347 "Insert product failed\n"); 1348 if (expected_result > 0) 1349 { 1350 TEST_COND_RET_ON_FAIL (conflict == expect_conflict, 1351 "Conflict wrong"); 1352 TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat, 1353 "Wrong category missing returned"); 1354 } 1355 return 0; 1356 } 1357 1358 1359 /** 1360 * Tests updating product data in the database. 1361 * 1362 * @param instance the instance to update the product for. 1363 * @param product the product data to update. 1364 * @param expected_result the result we expect the db to return. 1365 * @return 0 when successful, 1 otherwise. 1366 */ 1367 static int 1368 test_update_product (const struct InstanceData *instance, 1369 const struct ProductData *product, 1370 unsigned int num_cats, 1371 const uint64_t *cats, 1372 enum GNUNET_DB_QueryStatus expected_result, 1373 bool expect_no_instance, 1374 bool expect_no_product, 1375 bool expect_lost_reduced, 1376 bool expect_sold_reduced, 1377 bool expect_stocked_reduced, 1378 ssize_t expected_no_cat) 1379 1380 { 1381 ssize_t no_cat; 1382 bool no_product; 1383 bool lost_reduced; 1384 bool sold_reduced; 1385 bool stocked_reduced; 1386 bool no_group; 1387 bool no_pot; 1388 1389 if (expect_no_instance) 1390 { 1391 TEST_COND_RET_ON_FAIL ( 1392 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 1393 TALER_MERCHANTDB_set_instance (pg, instance->instance.id), 1394 "Expected missing instance, but set_instance succeeded\n"); 1395 return 0; 1396 } 1397 TEST_SET_INSTANCE (instance->instance.id, expected_result); 1398 TEST_COND_RET_ON_FAIL ( 1399 expected_result == 1400 TALER_MERCHANTDB_update_product (pg, 1401 instance->instance.id, 1402 product->id, 1403 &product->product, 1404 num_cats, 1405 cats, 1406 &no_cat, 1407 &no_product, 1408 &lost_reduced, 1409 &sold_reduced, 1410 &stocked_reduced, 1411 &no_group, 1412 &no_pot), 1413 "Update product failed\n"); 1414 if (expected_result > 0) 1415 { 1416 TEST_COND_RET_ON_FAIL (no_product == expect_no_product, 1417 "No product wrong"); 1418 TEST_COND_RET_ON_FAIL (lost_reduced == expect_lost_reduced, 1419 "No product wrong"); 1420 TEST_COND_RET_ON_FAIL (stocked_reduced == expect_stocked_reduced, 1421 "Stocked reduced wrong"); 1422 TEST_COND_RET_ON_FAIL (sold_reduced == expect_sold_reduced, 1423 "Sold reduced wrong"); 1424 TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat, 1425 "Wrong category missing returned"); 1426 } 1427 return 0; 1428 } 1429 1430 1431 /** 1432 * Tests looking up a product from the db. 1433 * 1434 * @param instance the instance to query from. 1435 * @param product the product to query and compare to. 1436 * @return 0 when successful, 1 otherwise. 1437 */ 1438 static int 1439 test_lookup_product (const struct InstanceData *instance, 1440 const struct ProductData *product) 1441 { 1442 struct TALER_MERCHANTDB_ProductDetails lookup_result; 1443 size_t num_categories = 0; 1444 uint64_t *categories = NULL; 1445 1446 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 1447 if (0 > TALER_MERCHANTDB_get_product (pg, 1448 instance->instance.id, 1449 product->id, 1450 &lookup_result, 1451 &num_categories, 1452 &categories)) 1453 { 1454 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1455 "Lookup product failed\n"); 1456 TALER_MERCHANTDB_product_details_free (&lookup_result); 1457 return 1; 1458 } 1459 GNUNET_free (categories); 1460 { 1461 const struct TALER_MERCHANTDB_ProductDetails *to_cmp = &product->product; 1462 1463 if (0 != check_products_equal (&lookup_result, 1464 to_cmp)) 1465 { 1466 GNUNET_break (0); 1467 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1468 "Lookup product failed: incorrect product returned\n"); 1469 TALER_MERCHANTDB_product_details_free (&lookup_result); 1470 return 1; 1471 } 1472 } 1473 TALER_MERCHANTDB_product_details_free (&lookup_result); 1474 return 0; 1475 } 1476 1477 1478 /** 1479 * Closure for testing product lookup 1480 */ 1481 struct TestLookupProducts_Closure 1482 { 1483 /** 1484 * Number of product ids to compare to 1485 */ 1486 unsigned int products_to_cmp_length; 1487 1488 /** 1489 * Pointer to array of product ids 1490 */ 1491 const struct ProductData *products_to_cmp; 1492 1493 /** 1494 * Pointer to array of number of matches for each product 1495 */ 1496 unsigned int *results_matching; 1497 1498 /** 1499 * Total number of results returned 1500 */ 1501 unsigned int results_length; 1502 }; 1503 1504 1505 /** 1506 * Function called after calling @e test_lookup_products 1507 * 1508 * @param cls a pointer to the lookup closure. 1509 * @param product_serial DB row ID 1510 * @param product_id the identifier of the product found. 1511 */ 1512 static void 1513 lookup_products_cb (void *cls, 1514 uint64_t product_serial, 1515 const char *product_id) 1516 { 1517 struct TestLookupProducts_Closure *cmp = cls; 1518 1519 GNUNET_assert (product_serial > 0); 1520 if (NULL == cmp) 1521 return; 1522 cmp->results_length += 1; 1523 for (unsigned int i = 0; cmp->products_to_cmp_length > i; ++i) 1524 { 1525 if (0 == strcmp (cmp->products_to_cmp[i].id, 1526 product_id)) 1527 cmp->results_matching[i] += 1; 1528 } 1529 } 1530 1531 1532 /** 1533 * Tests looking up all products for an instance. 1534 * 1535 * @param instance the instance to query from. 1536 * @param products_length the number of products we are expecting. 1537 * @param products the list of products that we expect to be found. 1538 * @return 0 when successful, 1 otherwise. 1539 */ 1540 static int 1541 test_lookup_products (const struct InstanceData *instance, 1542 unsigned int products_length, 1543 const struct ProductData *products) 1544 { 1545 unsigned int results_matching[GNUNET_NZL (products_length)]; 1546 struct TestLookupProducts_Closure cls = { 1547 .products_to_cmp_length = products_length, 1548 .products_to_cmp = products, 1549 .results_matching = results_matching, 1550 .results_length = 0 1551 }; 1552 memset (results_matching, 0, sizeof (unsigned int) * products_length); 1553 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 1554 if (0 > TALER_MERCHANTDB_iterate_products (pg, 1555 instance->instance.id, 1556 0, 1557 20, 1558 NULL, 1559 NULL, 1560 NULL, 1561 0, 1562 &lookup_products_cb, 1563 &cls)) 1564 { 1565 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1566 "Lookup products failed\n"); 1567 return 1; 1568 } 1569 if (products_length != cls.results_length) 1570 { 1571 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1572 "Lookup products failed: incorrect number of results\n"); 1573 return 1; 1574 } 1575 for (unsigned int i = 0; products_length > i; ++i) 1576 { 1577 if (1 != cls.results_matching[i]) 1578 { 1579 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1580 "Lookup products failed: mismatched data\n"); 1581 return 1; 1582 } 1583 } 1584 return 0; 1585 } 1586 1587 1588 /** 1589 * Tests deleting a product. 1590 * 1591 * @param instance the instance to delete the product from. 1592 * @param product the product that should be deleted. 1593 * @param force whether to force deletion of a locked product. 1594 * @param expect_not_found whether we expect the product to be reported as 1595 * unknown. 1596 * @param expect_locked whether we expect deletion to be refused because the 1597 * product is locked. 1598 * @return 0 when successful, 1 otherwise. 1599 */ 1600 static int 1601 test_delete_product (const struct InstanceData *instance, 1602 const struct ProductData *product, 1603 bool force, 1604 bool expect_not_found, 1605 bool expect_locked) 1606 { 1607 bool not_found = false; 1608 bool locked = false; 1609 enum GNUNET_DB_QueryStatus qs; 1610 1611 TEST_SET_INSTANCE (instance->instance.id, 1612 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 1613 qs = TALER_MERCHANTDB_delete_product (pg, 1614 instance->instance.id, 1615 product->id, 1616 force, 1617 ¬_found, 1618 &locked); 1619 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs, 1620 "Delete product failed\n"); 1621 TEST_COND_RET_ON_FAIL (expect_not_found == not_found, 1622 "Delete product 'not_found' mismatch\n"); 1623 TEST_COND_RET_ON_FAIL (expect_locked == locked, 1624 "Delete product 'locked' mismatch\n"); 1625 return 0; 1626 } 1627 1628 1629 /** 1630 * Closure for product tests. 1631 */ 1632 struct TestProducts_Closure 1633 { 1634 /** 1635 * The instance to use for this test. 1636 */ 1637 struct InstanceData instance; 1638 1639 /** 1640 * The array of products. 1641 */ 1642 struct ProductData products[2]; 1643 }; 1644 1645 1646 /** 1647 * Sets up the data structures used in the product tests. 1648 * 1649 * @param cls the closure to fill with test data. 1650 */ 1651 static void 1652 pre_test_products (struct TestProducts_Closure *cls) 1653 { 1654 static struct TALER_Amount four95; 1655 1656 /* Instance */ 1657 make_instance ("test_inst_products", 1658 &cls->instance); 1659 1660 /* Products */ 1661 make_product ("test_products_pd_0", 1662 &cls->products[0]); 1663 1664 make_product ("test_products_pd_1", 1665 &cls->products[1]); 1666 cls->products[1].product.description = "This is a another test product"; 1667 cls->products[1].product.unit = "cans"; 1668 cls->products[1].product.minimum_age = 0; 1669 1670 GNUNET_assert (GNUNET_OK == 1671 TALER_string_to_amount ("EUR:4.95", 1672 &four95)); 1673 cls->products[1].product.price_array = &four95; 1674 cls->products[1].product.price_array_length = 1; 1675 cls->products[1].product.total_stock = 5001; 1676 } 1677 1678 1679 /** 1680 * Handles all teardown after testing. 1681 * 1682 * @param cls the closure containing memory to be freed. 1683 */ 1684 static void 1685 post_test_products (struct TestProducts_Closure *cls) 1686 { 1687 free_instance_data (&cls->instance); 1688 free_product_data (&cls->products[0]); 1689 free_product_data (&cls->products[1]); 1690 } 1691 1692 1693 /** 1694 * Ensure a device update cannot use validation of a replaced device or 1695 * an obsolete algorithm. The writes between lookup and update model 1696 * the relevant interleaving of concurrent requests deterministically. 1697 */ 1698 static int 1699 test_otp_device_conditional_update (const struct InstanceData *instance) 1700 { 1701 const char *id = "conditional-update"; 1702 struct TALER_MERCHANTDB_OtpDeviceDetails td = { 1703 .otp_description = (char *) "original", 1704 .otp_key = (char *) "original-private", 1705 .otp_device_pub = (char *) "original-public", 1706 .otp_algorithm = TALER_MCA_ECDSA_CHALLENGE 1707 }; 1708 struct TALER_MERCHANTDB_OtpDeviceDetails original; 1709 struct TALER_MERCHANTDB_OtpDeviceDetails current; 1710 uint64_t original_serial; 1711 uint64_t replacement_serial; 1712 1713 TEST_SET_INSTANCE (instance->instance.id, 1714 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 1715 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1716 TALER_MERCHANTDB_insert_otp_device (pg, 1717 instance->instance.id, 1718 id, 1719 &td)); 1720 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1721 TALER_MERCHANTDB_get_otp_device (pg, 1722 instance->instance.id, 1723 id, 1724 &original)); 1725 original_serial = original.otp_serial; 1726 GNUNET_free (original.otp_description); 1727 GNUNET_free (original.otp_key); 1728 GNUNET_free (original.otp_device_pub); 1729 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1730 TALER_MERCHANTDB_delete_otp_device (pg, 1731 instance->instance.id, 1732 id)); 1733 td.otp_description = (char *) "replacement"; 1734 td.otp_key = (char *) "replacement-private"; 1735 td.otp_device_pub = (char *) "replacement-public"; 1736 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1737 TALER_MERCHANTDB_insert_otp_device (pg, 1738 instance->instance.id, 1739 id, 1740 &td)); 1741 /* Same ID and algorithm, but a different database identity. */ 1742 td.otp_description = (char *) "stale update"; 1743 td.otp_key = NULL; 1744 td.otp_device_pub = NULL; 1745 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 1746 TALER_MERCHANTDB_update_otp_device ( 1747 pg, instance->instance.id, id, 1748 original_serial, TALER_MCA_ECDSA_CHALLENGE, &td)); 1749 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1750 TALER_MERCHANTDB_get_otp_device (pg, 1751 instance->instance.id, 1752 id, 1753 ¤t)); 1754 replacement_serial = current.otp_serial; 1755 GNUNET_assert (original_serial != replacement_serial); 1756 GNUNET_assert (0 == strcmp ("replacement", current.otp_description)); 1757 GNUNET_assert (0 == strcmp ("replacement-private", current.otp_key)); 1758 GNUNET_assert (0 == strcmp ("replacement-public", current.otp_device_pub)); 1759 GNUNET_free (current.otp_description); 1760 GNUNET_free (current.otp_key); 1761 GNUNET_free (current.otp_device_pub); 1762 /* Model another writer changing the algorithm of this same row. */ 1763 td.otp_algorithm = TALER_MCA_EDDSA_CHALLENGE; 1764 td.otp_description = (char *) "concurrent update"; 1765 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1766 TALER_MERCHANTDB_update_otp_device ( 1767 pg, instance->instance.id, id, 1768 replacement_serial, TALER_MCA_ECDSA_CHALLENGE, &td)); 1769 td.otp_algorithm = TALER_MCA_ECDSA_CHALLENGE; 1770 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 1771 TALER_MERCHANTDB_update_otp_device ( 1772 pg, instance->instance.id, id, 1773 replacement_serial, TALER_MCA_ECDSA_CHALLENGE, &td)); 1774 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1775 TALER_MERCHANTDB_get_otp_device (pg, 1776 instance->instance.id, 1777 id, 1778 ¤t)); 1779 GNUNET_assert (TALER_MCA_EDDSA_CHALLENGE == current.otp_algorithm); 1780 GNUNET_assert (0 == strcmp ("concurrent update", current.otp_description)); 1781 GNUNET_free (current.otp_description); 1782 GNUNET_free (current.otp_key); 1783 GNUNET_free (current.otp_device_pub); 1784 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1785 TALER_MERCHANTDB_delete_otp_device (pg, 1786 instance->instance.id, 1787 id)); 1788 /* Unknown stored algorithms must fail before exposing an enum or keys. */ 1789 for (unsigned int invalid = 0; invalid < 2; invalid++) 1790 { 1791 td.otp_algorithm = invalid ? -1 : TALER_MCA_EDDSA_CHALLENGE + 1; 1792 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1793 TALER_MERCHANTDB_insert_otp_device ( 1794 pg, instance->instance.id, id, &td)); 1795 GNUNET_assert (GNUNET_DB_STATUS_HARD_ERROR == 1796 TALER_MERCHANTDB_get_otp_device ( 1797 pg, instance->instance.id, id, ¤t)); 1798 GNUNET_assert (NULL == current.otp_description); 1799 GNUNET_assert (NULL == current.otp_key); 1800 GNUNET_assert (NULL == current.otp_device_pub); 1801 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 1802 TALER_MERCHANTDB_delete_otp_device ( 1803 pg, instance->instance.id, id)); 1804 } 1805 return 0; 1806 } 1807 1808 1809 /** 1810 * Tests that inserting a category, an OTP device or a webhook that 1811 * already exists is reported as #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS 1812 * and, crucially, does *not* abort the enclosing transaction. 1813 * 1814 * Regression test: these three used to be plain INSERTs without an 1815 * ON CONFLICT clause. A duplicate key raises 23505, which GNUnet 1816 * maps to SUCCESS_NO_RESULTS (0); the POST handlers have no branch 1817 * for 0 and fell through to 'GNUNET_assert (SOFT_ERROR == qs)', 1818 * aborting taler-merchant-httpd. On top of that the failed INSERT 1819 * left the transaction in the aborted state, so every following 1820 * statement failed with 25P02 -- which is what this test checks for, 1821 * as the query status alone cannot tell the two variants apart. 1822 * 1823 * @param instance the instance to run the test against. 1824 * @return 0 when successful, 1 otherwise. 1825 */ 1826 static int 1827 test_insert_duplicate_conflicts (const struct InstanceData *instance) 1828 { 1829 json_t *i18n = json_object (); 1830 struct TALER_MERCHANTDB_OtpDeviceDetails td = { 1831 .otp_description = (char *) "conflict test device", 1832 .otp_key = (char *) "my key", 1833 .otp_algorithm = 1, 1834 .otp_ctr = 42 1835 }; 1836 struct TALER_MERCHANTDB_WebhookDetails wb = { 1837 .event_type = (char *) "pay", 1838 .url = (char *) "http://example.com/", 1839 .http_method = (char *) "POST", 1840 .header_template = (char *) "", 1841 .body_template = (char *) "" 1842 }; 1843 uint64_t cat; 1844 int ret = 1; 1845 1846 GNUNET_assert (NULL != i18n); 1847 TEST_SET_INSTANCE (instance->instance.id, 1848 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 1849 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1850 TALER_MERCHANTDB_insert_category (pg, 1851 instance->instance.id, 1852 "duplicate-conflict", 1853 i18n, 1854 &cat)) || 1855 (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1856 TALER_MERCHANTDB_insert_otp_device (pg, 1857 instance->instance.id, 1858 "duplicate-conflict", 1859 &td)) || 1860 (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1861 TALER_MERCHANTDB_insert_webhook (pg, 1862 instance->instance.id, 1863 "duplicate-conflict", 1864 &wb)) ) 1865 { 1866 GNUNET_break (0); 1867 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1868 "Initial insert for the conflict test failed\n"); 1869 goto cleanup; 1870 } 1871 if (GNUNET_OK != 1872 TALER_MERCHANTDB_start (pg, 1873 "insert duplicate conflicts")) 1874 { 1875 GNUNET_break (0); 1876 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1877 "Failed to start transaction\n"); 1878 goto cleanup; 1879 } 1880 /* Each of these is a duplicate: expected to be 'no results', and the 1881 transaction must survive, otherwise the next one fails with a hard 1882 error (25P02) instead. */ 1883 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 1884 TALER_MERCHANTDB_insert_category (pg, 1885 instance->instance.id, 1886 "duplicate-conflict", 1887 i18n, 1888 &cat)) 1889 { 1890 GNUNET_break (0); 1891 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1892 "Duplicate category insert not reported as 'no results'\n"); 1893 goto rollback; 1894 } 1895 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 1896 TALER_MERCHANTDB_insert_otp_device (pg, 1897 instance->instance.id, 1898 "duplicate-conflict", 1899 &td)) 1900 { 1901 GNUNET_break (0); 1902 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1903 "Duplicate OTP device insert not reported as 'no results'\n"); 1904 goto rollback; 1905 } 1906 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 1907 TALER_MERCHANTDB_insert_webhook (pg, 1908 instance->instance.id, 1909 "duplicate-conflict", 1910 &wb)) 1911 { 1912 GNUNET_break (0); 1913 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1914 "Duplicate webhook insert not reported as 'no results'\n"); 1915 goto rollback; 1916 } 1917 /* ... and the transaction must still be able to do useful work. */ 1918 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1919 TALER_MERCHANTDB_insert_category (pg, 1920 instance->instance.id, 1921 "duplicate-conflict-other", 1922 i18n, 1923 &cat)) 1924 { 1925 GNUNET_break (0); 1926 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1927 "Insert after duplicate conflict failed\n"); 1928 goto rollback; 1929 } 1930 if (0 > 1931 TALER_MERCHANTDB_commit (pg)) 1932 { 1933 GNUNET_break (0); 1934 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1935 "Commit after duplicate conflict failed\n"); 1936 goto cleanup; 1937 } 1938 ret = 0; 1939 goto cleanup; 1940 rollback: 1941 TALER_MERCHANTDB_rollback (pg); 1942 cleanup: 1943 json_decref (i18n); 1944 return ret; 1945 } 1946 1947 1948 /** 1949 * Tests that renaming a category onto the name of an existing 1950 * category is reported as a conflict (and not as 1951 * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, which the HTTP layer would 1952 * turn into a bogus 404 "category unknown"), and that the failed 1953 * rename does not poison the enclosing transaction. 1954 * 1955 * @param instance the instance to run the test against. 1956 * @return 0 when successful, 1 otherwise. 1957 */ 1958 static int 1959 test_update_category_conflict (const struct InstanceData *instance) 1960 { 1961 json_t *i18n = json_object (); 1962 uint64_t cat1; 1963 uint64_t cat2; 1964 bool conflict = true; 1965 int ret = 1; 1966 1967 GNUNET_assert (NULL != i18n); 1968 TEST_SET_INSTANCE (instance->instance.id, 1969 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 1970 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1971 TALER_MERCHANTDB_insert_category (pg, 1972 instance->instance.id, 1973 "category-conflict-a", 1974 i18n, 1975 &cat1)) || 1976 (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1977 TALER_MERCHANTDB_insert_category (pg, 1978 instance->instance.id, 1979 "category-conflict-b", 1980 i18n, 1981 &cat2)) ) 1982 { 1983 GNUNET_break (0); 1984 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1985 "Insert category failed\n"); 1986 goto cleanup; 1987 } 1988 /* Renaming 'a' to 'b' must be a conflict, not 'unknown category'. */ 1989 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1990 TALER_MERCHANTDB_update_category (pg, 1991 instance->instance.id, 1992 cat1, 1993 "category-conflict-b", 1994 i18n, 1995 &conflict)) 1996 { 1997 GNUNET_break (0); 1998 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1999 "Conflicting category rename not reported as conflict\n"); 2000 goto cleanup; 2001 } 2002 if (! conflict) 2003 { 2004 GNUNET_break (0); 2005 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2006 "Conflicting category rename did not set 'conflict'\n"); 2007 goto cleanup; 2008 } 2009 /* ... and the connection must still be usable afterwards. */ 2010 conflict = true; 2011 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 2012 TALER_MERCHANTDB_update_category (pg, 2013 instance->instance.id, 2014 cat1, 2015 "category-conflict-c", 2016 i18n, 2017 &conflict)) || 2018 conflict) 2019 { 2020 GNUNET_break (0); 2021 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2022 "Category rename after conflict failed\n"); 2023 goto cleanup; 2024 } 2025 /* Unknown category must still be reported as 'no results'. */ 2026 conflict = true; 2027 if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 2028 TALER_MERCHANTDB_update_category (pg, 2029 instance->instance.id, 2030 cat1 + cat2 + 424242, 2031 "category-conflict-d", 2032 i18n, 2033 &conflict)) || 2034 conflict) 2035 { 2036 GNUNET_break (0); 2037 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2038 "Rename of unknown category not reported as 'no results'\n"); 2039 goto cleanup; 2040 } 2041 ret = 0; 2042 cleanup: 2043 json_decref (i18n); 2044 return ret; 2045 } 2046 2047 2048 /** 2049 * Runs the tests for products. 2050 * 2051 * @param cls the container of the test data. 2052 * @return 0 on success, 1 otherwise. 2053 */ 2054 static int 2055 run_test_products (struct TestProducts_Closure *cls) 2056 { 2057 struct GNUNET_Uuid uuid; 2058 struct GNUNET_TIME_Timestamp refund_deadline = 2059 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS); 2060 2061 /* Test that insert without an instance fails */ 2062 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 2063 &cls->products[0], 2064 0, 2065 NULL, 2066 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2067 false, 2068 true, 2069 -1)); 2070 /* Insert the instance */ 2071 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 2072 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2073 /* Test that a conflicting category rename is reported as a conflict */ 2074 TEST_RET_ON_FAIL (test_update_category_conflict (&cls->instance)); 2075 /* Test that duplicate inserts do not poison the transaction */ 2076 TEST_RET_ON_FAIL (test_insert_duplicate_conflicts (&cls->instance)); 2077 TEST_RET_ON_FAIL (test_otp_device_conditional_update (&cls->instance)); 2078 /* Test inserting a product */ 2079 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 2080 &cls->products[0], 2081 0, 2082 NULL, 2083 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2084 false, 2085 false, 2086 -1)); 2087 /* Test that double insert succeeds */ 2088 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 2089 &cls->products[0], 2090 0, 2091 NULL, 2092 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2093 false, 2094 false, 2095 -1)); 2096 /* Test that an insert naming a category that does not exist is 2097 reported via 'no_cat'. 2098 2099 NOTE: the category existence check in insert_product.sql runs 2100 *before* any modification of the database (so that a rejected 2101 request leaves no trace), and therefore also before the 2102 idempotency/conflict check. An unknown category consequently takes 2103 precedence over a conflict: 'no_cat' is the 1-based index into the 2104 supplied category array and 'conflict' stays false. */ 2105 { 2106 uint64_t cat = 42; 2107 2108 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 2109 &cls->products[0], 2110 1, 2111 &cat, 2112 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2113 false, 2114 false, 2115 1)); 2116 } 2117 /* Test that a genuinely conflicting insert -- same product_id, but 2118 different product data -- is reported via 'conflict'. 2119 2120 This case supplies no categories at all, so it cannot be masked by 2121 the category pre-check. The previous version of this test relied on 2122 a non-existent category to provoke the conflict, which stopped 2123 exercising the conflict path once that check was hoisted above the 2124 insert. */ 2125 { 2126 struct ProductData conflicting = cls->products[1]; 2127 2128 conflicting.id = cls->products[0].id; 2129 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 2130 &conflicting, 2131 0, 2132 NULL, 2133 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2134 true, 2135 false, 2136 -1)); 2137 } 2138 /* Test lookup of individual products */ 2139 TEST_RET_ON_FAIL (test_lookup_product (&cls->instance, 2140 &cls->products[0])); 2141 /* Make sure it fails correctly for products that don't exist */ 2142 { 2143 size_t num_categories = 0; 2144 uint64_t *categories = NULL; 2145 2146 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 2147 TALER_MERCHANTDB_get_product (pg, 2148 cls->instance.instance.id, 2149 "nonexistent_product", 2150 NULL, 2151 &num_categories, 2152 &categories)) 2153 { 2154 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2155 "Lookup product failed\n"); 2156 return 1; 2157 } 2158 GNUNET_free (categories); 2159 } 2160 /* Test product update */ 2161 cls->products[0].product.description = 2162 "This is a test product that has been updated!"; 2163 GNUNET_assert (0 == 2164 json_array_append_new ( 2165 cls->products[0].product.description_i18n, 2166 json_string ( 2167 "description in another language"))); 2168 cls->products[0].product.unit = "barrels"; 2169 { 2170 static struct TALER_Amount seven68; 2171 2172 GNUNET_assert (GNUNET_OK == 2173 TALER_string_to_amount ("EUR:7.68", 2174 &seven68)); 2175 cls->products[0].product.price_array = &seven68; 2176 cls->products[0].product.price_array_length = 1; 2177 } 2178 GNUNET_assert (0 == 2179 json_array_append_new (cls->products[0].product.taxes, 2180 json_string ("2% sales tax"))); 2181 cls->products[0].product.total_stock = 100; 2182 cls->products[0].product.total_sold = 0; /* will be ignored! */ 2183 cls->products[0].product.total_lost = 7; 2184 GNUNET_free (cls->products[0].product.image); 2185 cls->products[0].product.image = GNUNET_strdup ("image"); 2186 GNUNET_assert (0 == 2187 json_array_append_new (cls->products[0].product.address, 2188 json_string ("444 Some Street"))); 2189 cls->products[0].product.next_restock = GNUNET_TIME_timestamp_get (); 2190 TEST_RET_ON_FAIL (test_update_product ( 2191 &cls->instance, 2192 &cls->products[0], 2193 0, 2194 NULL, 2195 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2196 false, 2197 false, 2198 false, 2199 false, 2200 false, 2201 -1)); 2202 2203 /* Stock is the pair (total_stock, total_stock_frac): raise the 2204 fractional part, then check that lowering only the fractional part 2205 is refused just like lowering the integer part. */ 2206 cls->products[0].product.total_stock_frac = 500000; 2207 TEST_RET_ON_FAIL (test_update_product ( 2208 &cls->instance, 2209 &cls->products[0], 2210 0, 2211 NULL, 2212 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2213 false, 2214 false, 2215 false, 2216 false, 2217 false, 2218 -1)); 2219 { 2220 struct ProductData frac_dec = cls->products[0]; 2221 2222 frac_dec.product.total_stock_frac = 400000; 2223 TEST_RET_ON_FAIL (test_update_product ( 2224 &cls->instance, 2225 &frac_dec, 2226 0, 2227 NULL, 2228 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2229 false, 2230 false, 2231 false, 2232 false, 2233 true, 2234 -1)); 2235 } 2236 { 2237 struct ProductData stock_dec = cls->products[0]; 2238 2239 stock_dec.product.total_stock = 40; 2240 TEST_RET_ON_FAIL (test_update_product ( 2241 &cls->instance, 2242 &stock_dec, 2243 0, 2244 NULL, 2245 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2246 false, 2247 false, 2248 false, 2249 false, 2250 true, 2251 -1)); 2252 } 2253 { 2254 struct ProductData lost_dec = cls->products[0]; 2255 2256 lost_dec.product.total_lost = 1; 2257 TEST_RET_ON_FAIL (test_update_product ( 2258 &cls->instance, 2259 &lost_dec, 2260 0, 2261 NULL, 2262 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2263 false, 2264 false, 2265 true, 2266 false, 2267 false, 2268 -1)); 2269 } 2270 TEST_RET_ON_FAIL (test_lookup_product (&cls->instance, 2271 &cls->products[0])); 2272 TEST_RET_ON_FAIL (test_update_product ( 2273 &cls->instance, 2274 &cls->products[1], 2275 0, 2276 NULL, 2277 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2278 false, 2279 true, 2280 false, 2281 false, 2282 false, 2283 -1)); 2284 /* Test collective product lookup */ 2285 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 2286 &cls->products[1], 2287 0, 2288 NULL, 2289 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2290 false, 2291 false, 2292 -1)); 2293 TEST_RET_ON_FAIL (test_lookup_products (&cls->instance, 2294 2, 2295 cls->products)); 2296 /* Test locking */ 2297 uuid.value[0] = 0x1287346a; 2298 if (0 != TALER_MERCHANTDB_insert_inventory_lock (pg, 2299 cls->instance.instance.id, 2300 cls->products[0].id, 2301 &uuid, 2302 256, 2303 0, 2304 refund_deadline)) 2305 { 2306 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2307 "Lock product failed\n"); 2308 return 1; 2309 } 2310 if (1 != TALER_MERCHANTDB_insert_inventory_lock (pg, 2311 cls->instance.instance.id, 2312 cls->products[0].id, 2313 &uuid, 2314 1, 2315 0, 2316 refund_deadline)) 2317 { 2318 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2319 "Lock product failed\n"); 2320 return 1; 2321 } 2322 /* Test product deletion of an unlocked product */ 2323 TEST_RET_ON_FAIL (test_delete_product (&cls->instance, 2324 &cls->products[1], 2325 false, 2326 false, 2327 false)); 2328 /* Test double deletion reports 'not found' */ 2329 TEST_RET_ON_FAIL (test_delete_product (&cls->instance, 2330 &cls->products[1], 2331 false, 2332 true, 2333 false)); 2334 TEST_RET_ON_FAIL (test_lookup_products (&cls->instance, 2335 1, 2336 cls->products)); 2337 /* Test unlocking */ 2338 if (1 != TALER_MERCHANTDB_delete_inventory_lock (pg, 2339 &uuid)) 2340 { 2341 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2342 "Unlock inventory failed\n"); 2343 return 1; 2344 } 2345 if (0 != TALER_MERCHANTDB_delete_inventory_lock (pg, 2346 &uuid)) 2347 { 2348 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2349 "Unlock inventory failed\n"); 2350 return 1; 2351 } 2352 /* Re-lock products[0] to exercise deletion of a locked product */ 2353 if (1 != TALER_MERCHANTDB_insert_inventory_lock (pg, 2354 cls->instance.instance.id, 2355 cls->products[0].id, 2356 &uuid, 2357 1, 2358 0, 2359 refund_deadline)) 2360 { 2361 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2362 "Lock product failed\n"); 2363 return 1; 2364 } 2365 /* Deletion without force must be refused while the product is locked */ 2366 TEST_RET_ON_FAIL (test_delete_product (&cls->instance, 2367 &cls->products[0], 2368 false, 2369 false, 2370 true)); 2371 TEST_RET_ON_FAIL (test_lookup_products (&cls->instance, 2372 1, 2373 cls->products)); 2374 /* Forced deletion releases the lock and removes the product */ 2375 TEST_RET_ON_FAIL (test_delete_product (&cls->instance, 2376 &cls->products[0], 2377 true, 2378 false, 2379 false)); 2380 TEST_RET_ON_FAIL (test_lookup_products (&cls->instance, 2381 0, 2382 NULL)); 2383 /* Updating a product that has just been deleted must be reported via 2384 'no_product' (which the HTTP layer turns into a 404), never as a 2385 hard error. merchant_do_update_product re-reads the inventory row 2386 and then ASSERTs that the subsequent UPDATE still finds it; without 2387 the FOR UPDATE on that re-read, a DELETE committing in the window 2388 between the two statements turns this into a P0004 assertion 2389 failure (HTTP 500). Reproducing that window needs a second 2390 database connection, which this harness does not have; what is 2391 asserted here is that the very same code path returns 'no_product' 2392 when the row is gone. */ 2393 TEST_RET_ON_FAIL (test_update_product ( 2394 &cls->instance, 2395 &cls->products[0], 2396 0, 2397 NULL, 2398 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2399 false, 2400 true, 2401 false, 2402 false, 2403 false, 2404 -1)); 2405 return 0; 2406 } 2407 2408 2409 /** 2410 * Takes care of product testing. 2411 * 2412 * @return 0 on success, 1 otherwise. 2413 */ 2414 static int 2415 test_products (void) 2416 { 2417 struct TestProducts_Closure test_cls; 2418 int test_result; 2419 2420 pre_test_products (&test_cls); 2421 test_result = run_test_products (&test_cls); 2422 post_test_products (&test_cls); 2423 return test_result; 2424 } 2425 2426 2427 /* ********** Inventory locks ********** */ 2428 2429 2430 /** 2431 * Container for the data used by the inventory lock tests. 2432 */ 2433 struct TestLocks_Closure 2434 { 2435 /** 2436 * The instance to use for this test. 2437 */ 2438 struct InstanceData instance; 2439 2440 /** 2441 * The products we lock; [0] has a limited stock, [1] carries the 2442 * INT64_MAX "unlimited stock" sentinel. 2443 */ 2444 struct ProductData products[2]; 2445 }; 2446 2447 2448 /** 2449 * Checks that @a product has exactly @a expected_locked units locked. 2450 * 2451 * @param instance the instance owning the product. 2452 * @param product the product to inspect. 2453 * @param expected_locked expected value of total_locked. 2454 * @param expected_locked_frac expected value of total_locked_frac. 2455 * @return 0 when successful, 1 otherwise. 2456 */ 2457 static int 2458 test_product_locked (const struct InstanceData *instance, 2459 const struct ProductData *product, 2460 uint64_t expected_locked, 2461 uint32_t expected_locked_frac) 2462 { 2463 struct TALER_MERCHANTDB_ProductDetails pd; 2464 size_t num_categories = 0; 2465 uint64_t *categories = NULL; 2466 int ret = 0; 2467 2468 memset (&pd, 2469 0, 2470 sizeof (pd)); 2471 TEST_SET_INSTANCE (instance->instance.id, 2472 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 2473 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 2474 TALER_MERCHANTDB_get_product (pg, 2475 instance->instance.id, 2476 product->id, 2477 &pd, 2478 &num_categories, 2479 &categories), 2480 "Lookup of locked product failed\n"); 2481 GNUNET_free (categories); 2482 if ( (expected_locked != pd.total_locked) || 2483 (expected_locked_frac != pd.total_locked_frac) ) 2484 { 2485 GNUNET_break (0); 2486 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2487 "Product `%s' has total_locked=%llu.%06u," 2488 " expected %llu.%06u\n", 2489 product->id, 2490 (unsigned long long) pd.total_locked, 2491 (unsigned int) pd.total_locked_frac, 2492 (unsigned long long) expected_locked, 2493 (unsigned int) expected_locked_frac); 2494 ret = 1; 2495 } 2496 TALER_MERCHANTDB_product_details_free (&pd); 2497 return ret; 2498 } 2499 2500 2501 /** 2502 * Tests locking @a quantity units of @a product under @a uuid. 2503 * 2504 * @param instance the instance owning the product. 2505 * @param product the product to lock. 2506 * @param uuid identifier of the lock. 2507 * @param quantity how many units to lock. 2508 * @param expected_result the result we expect the db to return. 2509 * @return 0 when successful, 1 otherwise. 2510 */ 2511 static int 2512 test_insert_inventory_lock (const struct InstanceData *instance, 2513 const struct ProductData *product, 2514 const struct GNUNET_Uuid *uuid, 2515 uint64_t quantity, 2516 enum GNUNET_DB_QueryStatus expected_result) 2517 { 2518 struct GNUNET_TIME_Timestamp expiration 2519 = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS); 2520 2521 TEST_SET_INSTANCE (instance->instance.id, 2522 expected_result); 2523 TEST_COND_RET_ON_FAIL (expected_result == 2524 TALER_MERCHANTDB_insert_inventory_lock ( 2525 pg, 2526 instance->instance.id, 2527 product->id, 2528 uuid, 2529 quantity, 2530 0, 2531 expiration), 2532 "Insert inventory lock returned unexpected status\n"); 2533 return 0; 2534 } 2535 2536 2537 /** 2538 * Sets up the data structures used in the inventory lock tests. 2539 * 2540 * @param cls the closure to fill with test data. 2541 */ 2542 static void 2543 pre_test_locks (struct TestLocks_Closure *cls) 2544 { 2545 make_instance ("test_inst_locks", 2546 &cls->instance); 2547 make_product ("test_locks_pd_0", 2548 &cls->products[0]); 2549 cls->products[0].product.total_stock = 100; 2550 make_product ("test_locks_pd_1", 2551 &cls->products[1]); 2552 cls->products[1].product.total_stock = INT64_MAX; 2553 } 2554 2555 2556 /** 2557 * Handles all teardown after testing. 2558 * 2559 * @param cls the closure containing memory to be freed. 2560 */ 2561 static void 2562 post_test_locks (struct TestLocks_Closure *cls) 2563 { 2564 free_instance_data (&cls->instance); 2565 free_product_data (&cls->products[0]); 2566 free_product_data (&cls->products[1]); 2567 } 2568 2569 2570 /** 2571 * Runs the tests for inventory locks. 2572 * 2573 * @param cls the container of the test data. 2574 * @return 0 on success, 1 otherwise. 2575 */ 2576 static int 2577 run_test_locks (struct TestLocks_Closure *cls) 2578 { 2579 struct GNUNET_Uuid uuid1; 2580 struct GNUNET_Uuid uuid2; 2581 2582 memset (&uuid1, 2583 0, 2584 sizeof (uuid1)); 2585 memset (&uuid2, 2586 0, 2587 sizeof (uuid2)); 2588 uuid1.value[0] = 0x11111111; 2589 uuid2.value[0] = 0x22222222; 2590 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 2591 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2592 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 2593 &cls->products[0], 2594 0, 2595 NULL, 2596 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2597 false, 2598 false, 2599 -1)); 2600 /* Lock 40 of the 100 units in stock */ 2601 TEST_RET_ON_FAIL (test_insert_inventory_lock ( 2602 &cls->instance, 2603 &cls->products[0], 2604 &uuid1, 2605 40, 2606 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2607 TEST_RET_ON_FAIL (test_product_locked (&cls->instance, 2608 &cls->products[0], 2609 40, 2610 0)); 2611 /* Re-posting a lock for the same UUID *updates* the existing lock, 2612 the locks must not stack up. */ 2613 TEST_RET_ON_FAIL (test_insert_inventory_lock ( 2614 &cls->instance, 2615 &cls->products[0], 2616 &uuid1, 2617 10, 2618 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2619 TEST_RET_ON_FAIL (test_product_locked (&cls->instance, 2620 &cls->products[0], 2621 10, 2622 0)); 2623 /* A second lock competes with the first one for the remaining stock */ 2624 TEST_RET_ON_FAIL (test_insert_inventory_lock ( 2625 &cls->instance, 2626 &cls->products[0], 2627 &uuid2, 2628 91, 2629 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 2630 TEST_RET_ON_FAIL (test_product_locked (&cls->instance, 2631 &cls->products[0], 2632 10, 2633 0)); 2634 TEST_RET_ON_FAIL (test_insert_inventory_lock ( 2635 &cls->instance, 2636 &cls->products[0], 2637 &uuid2, 2638 90, 2639 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2640 TEST_RET_ON_FAIL (test_product_locked (&cls->instance, 2641 &cls->products[0], 2642 100, 2643 0)); 2644 /* Locking a quantity of zero releases the lock */ 2645 TEST_RET_ON_FAIL (test_insert_inventory_lock ( 2646 &cls->instance, 2647 &cls->products[0], 2648 &uuid1, 2649 0, 2650 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2651 TEST_RET_ON_FAIL (test_product_locked (&cls->instance, 2652 &cls->products[0], 2653 90, 2654 0)); 2655 /* Unlocking an UUID that holds no lock is a no-op, not an error */ 2656 TEST_RET_ON_FAIL (test_insert_inventory_lock ( 2657 &cls->instance, 2658 &cls->products[0], 2659 &uuid1, 2660 0, 2661 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2662 TEST_RET_ON_FAIL (test_product_locked (&cls->instance, 2663 &cls->products[0], 2664 90, 2665 0)); 2666 /* Explicitly dropping the remaining lock frees the stock again */ 2667 TEST_SET_INSTANCE (cls->instance.instance.id, 2668 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 2669 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 2670 TALER_MERCHANTDB_delete_inventory_lock (pg, 2671 &uuid2), 2672 "Unlock inventory failed\n"); 2673 TEST_RET_ON_FAIL (test_product_locked (&cls->instance, 2674 &cls->products[0], 2675 0, 2676 0)); 2677 /* Locking an unknown product is reported as 'no results' */ 2678 { 2679 struct ProductData unknown = cls->products[0]; 2680 2681 unknown.id = "test_locks_pd_unknown"; 2682 TEST_RET_ON_FAIL (test_insert_inventory_lock ( 2683 &cls->instance, 2684 &unknown, 2685 &uuid1, 2686 1, 2687 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 2688 } 2689 /* Products with the INT64_MAX "unlimited stock" sentinel skip the 2690 availability check, so two large locks can exceed what the 2691 total_locked counter can represent. That must saturate, not blow 2692 up with 'bigint out of range' (which the caller turns into a 500). */ 2693 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 2694 &cls->products[1], 2695 0, 2696 NULL, 2697 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2698 false, 2699 false, 2700 -1)); 2701 TEST_RET_ON_FAIL (test_insert_inventory_lock ( 2702 &cls->instance, 2703 &cls->products[1], 2704 &uuid1, 2705 INT64_MAX, 2706 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2707 TEST_RET_ON_FAIL (test_product_locked (&cls->instance, 2708 &cls->products[1], 2709 INT64_MAX, 2710 0)); 2711 TEST_RET_ON_FAIL (test_insert_inventory_lock ( 2712 &cls->instance, 2713 &cls->products[1], 2714 &uuid2, 2715 INT64_MAX, 2716 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2717 TEST_RET_ON_FAIL (test_product_locked (&cls->instance, 2718 &cls->products[1], 2719 INT64_MAX, 2720 999999)); 2721 return 0; 2722 } 2723 2724 2725 /** 2726 * Takes care of inventory lock testing. 2727 * 2728 * @return 0 on success, 1 otherwise. 2729 */ 2730 static int 2731 test_inventory_locks (void) 2732 { 2733 struct TestLocks_Closure test_cls; 2734 int test_result; 2735 2736 pre_test_locks (&test_cls); 2737 test_result = run_test_locks (&test_cls); 2738 post_test_locks (&test_cls); 2739 return test_result; 2740 } 2741 2742 2743 /* ********** Tokens ********** */ 2744 2745 2746 /** 2747 * Container for the data used by the token tests. 2748 */ 2749 struct TestTokens_Closure 2750 { 2751 /** 2752 * The instance to use for this test. 2753 */ 2754 struct InstanceData instance; 2755 2756 /** 2757 * Details of the token family we operate on. 2758 */ 2759 struct TALER_MERCHANTDB_TokenFamilyDetails family; 2760 2761 /** 2762 * Public key of the token family key. 2763 */ 2764 struct TALER_TokenIssuePublicKey pub; 2765 2766 /** 2767 * Private key of the token family key. 2768 */ 2769 struct TALER_TokenIssuePrivateKey priv; 2770 2771 /** 2772 * Hash of @e pub, identifies the token family key in the DB. 2773 */ 2774 struct TALER_TokenIssuePublicKeyHashP h_pub; 2775 2776 /** 2777 * Hash of the contract the tokens are issued for. 2778 */ 2779 struct TALER_PrivateContractHashP h_contract_terms; 2780 }; 2781 2782 2783 /** 2784 * Fabricate a blinded token issue signature. Its value is never 2785 * inspected by the database, so a random CS answer is good enough; we 2786 * only care that identical inputs yield identical bytes. 2787 * 2788 * @param[out] bs storage for the blinded signature 2789 * @param[out] sig set to point to @a bs 2790 */ 2791 static void 2792 make_blinded_token_sig (struct GNUNET_CRYPTO_BlindedSignature *bs, 2793 struct TALER_BlindedTokenIssueSignature *sig) 2794 { 2795 memset (bs, 2796 0, 2797 sizeof (*bs)); 2798 bs->cipher = GNUNET_CRYPTO_BSA_CS; 2799 bs->rc = 1; 2800 GNUNET_CRYPTO_random_block (&bs->details.blinded_cs_answer, 2801 sizeof (bs->details.blinded_cs_answer)); 2802 sig->signature = bs; 2803 } 2804 2805 2806 /** 2807 * Fabricate an (unblinded) token issue signature. 2808 * 2809 * @param[out] ub storage for the unblinded signature 2810 * @param[out] sig set to point to @a ub 2811 */ 2812 static void 2813 make_token_issue_sig (struct GNUNET_CRYPTO_UnblindedSignature *ub, 2814 struct TALER_TokenIssueSignature *sig) 2815 { 2816 memset (ub, 2817 0, 2818 sizeof (*ub)); 2819 ub->cipher = GNUNET_CRYPTO_BSA_CS; 2820 ub->rc = 1; 2821 GNUNET_CRYPTO_random_block (&ub->details.cs_signature, 2822 sizeof (ub->details.cs_signature)); 2823 sig->signature = ub; 2824 } 2825 2826 2827 /** 2828 * Checks the issuance/spending counters of the token family. 2829 * 2830 * @param cls the test data. 2831 * @param expected_issued number of issued tokens we expect. 2832 * @param expected_used number of spent tokens we expect. 2833 * @return 0 when successful, 1 otherwise. 2834 */ 2835 static int 2836 test_token_family_counters (const struct TestTokens_Closure *cls, 2837 uint64_t expected_issued, 2838 uint64_t expected_used) 2839 { 2840 struct TALER_MERCHANTDB_TokenFamilyDetails details; 2841 int ret = 0; 2842 2843 memset (&details, 2844 0, 2845 sizeof (details)); 2846 TEST_SET_INSTANCE (cls->instance.instance.id, 2847 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 2848 TEST_COND_RET_ON_FAIL ( 2849 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 2850 TALER_MERCHANTDB_get_token_family (pg, 2851 cls->instance.instance.id, 2852 cls->family.slug, 2853 &details), 2854 "Lookup of token family failed\n"); 2855 if ( (expected_issued != details.issued) || 2856 (expected_used != details.used) ) 2857 { 2858 GNUNET_break (0); 2859 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2860 "Token family counters are issued=%llu/used=%llu," 2861 " expected issued=%llu/used=%llu\n", 2862 (unsigned long long) details.issued, 2863 (unsigned long long) details.used, 2864 (unsigned long long) expected_issued, 2865 (unsigned long long) expected_used); 2866 ret = 1; 2867 } 2868 TALER_MERCHANTDB_token_family_details_free (&details); 2869 return ret; 2870 } 2871 2872 2873 /** 2874 * Tests inserting an issued token. 2875 * 2876 * @param cls the test data. 2877 * @param blind_sig the blinded signature of the issued token. 2878 * @param expected_result the result we expect the db to return. 2879 * @param expect_no_family whether we expect the token family key to be 2880 * reported as unknown. 2881 * @return 0 when successful, 1 otherwise. 2882 */ 2883 static int 2884 test_insert_issued_token (const struct TestTokens_Closure *cls, 2885 const struct TALER_BlindedTokenIssueSignature * 2886 blind_sig, 2887 enum GNUNET_DB_QueryStatus expected_result, 2888 bool expect_no_family) 2889 { 2890 /* Deliberately poisoned: the DB layer must assign the flag on every 2891 path, including the ones where it returns early. Note that the 2892 'qs <= 0' early return itself is not reachable from here: a stored 2893 procedure with OUT parameters always yields exactly one row. */ 2894 bool no_family = true; 2895 2896 TEST_SET_INSTANCE (cls->instance.instance.id, 2897 expected_result); 2898 TEST_COND_RET_ON_FAIL ( 2899 expected_result == 2900 TALER_MERCHANTDB_insert_issued_token (pg, 2901 &cls->h_contract_terms, 2902 &cls->h_pub, 2903 blind_sig, 2904 &no_family), 2905 "Insert issued token returned unexpected status\n"); 2906 TEST_COND_RET_ON_FAIL (expect_no_family == no_family, 2907 "Insert issued token 'no_family' mismatch\n"); 2908 return 0; 2909 } 2910 2911 2912 /** 2913 * Tests inserting a spent token. 2914 * 2915 * @param cls the test data. 2916 * @param use_pub public key of the token being spent. 2917 * @param use_sig signature made with the token use key. 2918 * @param issue_sig signature of the merchant over the token. 2919 * @param expected_result the result we expect the db to return. 2920 * @param expect_no_family whether we expect the token family key to be 2921 * reported as unknown. 2922 * @return 0 when successful, 1 otherwise. 2923 */ 2924 static int 2925 test_insert_used_token (const struct TestTokens_Closure *cls, 2926 const struct TALER_TokenUsePublicKeyP *use_pub, 2927 const struct TALER_TokenUseSignatureP *use_sig, 2928 const struct TALER_TokenIssueSignature *issue_sig, 2929 enum GNUNET_DB_QueryStatus expected_result, 2930 bool expect_no_family) 2931 { 2932 /* Deliberately poisoned: see test_insert_issued_token(). */ 2933 bool no_family = true; 2934 2935 TEST_SET_INSTANCE (cls->instance.instance.id, 2936 expected_result); 2937 TEST_COND_RET_ON_FAIL ( 2938 expected_result == 2939 TALER_MERCHANTDB_insert_used_token (pg, 2940 &cls->h_contract_terms, 2941 &cls->h_pub, 2942 use_pub, 2943 use_sig, 2944 issue_sig, 2945 &no_family), 2946 "Insert used token returned unexpected status\n"); 2947 TEST_COND_RET_ON_FAIL (expect_no_family == no_family, 2948 "Insert used token 'no_family' mismatch\n"); 2949 return 0; 2950 } 2951 2952 2953 /** 2954 * Serialize a previously empty family across independent connections, then 2955 * verify that a waiting creator sees the committed key. Also reject an old 2956 * SERIALIZABLE snapshot rather than allowing it to mint from stale data. 2957 */ 2958 static int 2959 test_token_family_key_lock (struct TestTokens_Closure *cls) 2960 { 2961 struct TALER_MERCHANTDB_TokenFamilyKeyDetails kd; 2962 bool started; 2963 int to_child[2]; 2964 int from_child[2]; 2965 pid_t child; 2966 int status; 2967 char signal; 2968 2969 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 2970 TALER_MERCHANTDB_lock_token_family (pg, 2971 cls->family.slug, 2972 &started)); 2973 GNUNET_assert (started); 2974 /* Re-entering must retain the caller's transaction and its lock. */ 2975 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 2976 TALER_MERCHANTDB_lock_token_family (pg, 2977 cls->family.slug, 2978 &started)); 2979 GNUNET_assert (! started); 2980 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 2981 TALER_MERCHANTDB_get_token_family_key ( 2982 pg, cls->instance.instance.id, cls->family.slug, 2983 cls->family.valid_after, cls->family.valid_before, &kd)); 2984 GNUNET_assert (NULL == kd.pub.public_key); 2985 TALER_MERCHANTDB_token_family_details_free (&kd.token_family); 2986 GNUNET_assert (0 == pipe (to_child)); 2987 GNUNET_assert (0 == pipe (from_child)); 2988 child = fork (); 2989 GNUNET_assert (-1 != child); 2990 if (0 == child) 2991 { 2992 struct TALER_MERCHANTDB_PostgresContext *other; 2993 struct GNUNET_PQ_ExecuteStatement timeout[] = { 2994 GNUNET_PQ_make_execute ("SET lock_timeout='100ms'"), 2995 GNUNET_PQ_EXECUTE_STATEMENT_END 2996 }; 2997 2998 close (to_child[1]); 2999 close (from_child[0]); 3000 alarm (15); 3001 other = TALER_MERCHANTDB_connect (test_cfg); 3002 GNUNET_assert (NULL != other); 3003 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3004 TALER_MERCHANTDB_set_instance (other, 3005 cls->instance.instance.id)); 3006 GNUNET_assert (GNUNET_OK == 3007 GNUNET_PQ_exec_statements (other->conn, 3008 timeout)); 3009 /* No key exists yet, but the second worker must still block. */ 3010 GNUNET_assert (0 > TALER_MERCHANTDB_lock_token_family (other, 3011 cls->family.slug, 3012 &started)); 3013 GNUNET_assert (started); 3014 TALER_MERCHANTDB_rollback (other); 3015 GNUNET_assert (1 == write (from_child[1], "B", 1)); 3016 GNUNET_assert (1 == read (to_child[0], &signal, 1)); 3017 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3018 TALER_MERCHANTDB_lock_token_family (other, 3019 cls->family.slug, 3020 &started)); 3021 GNUNET_assert (started); 3022 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3023 TALER_MERCHANTDB_get_token_family_key ( 3024 other, cls->instance.instance.id, cls->family.slug, 3025 cls->family.valid_after, cls->family.valid_before, &kd)); 3026 GNUNET_assert (NULL != kd.pub.public_key); 3027 GNUNET_assert (0 == GNUNET_memcmp (&cls->h_pub.hash, 3028 &kd.pub.public_key->pub_key_hash)); 3029 TALER_MERCHANTDB_token_family_details_free (&kd.token_family); 3030 GNUNET_CRYPTO_blind_sign_pub_decref (kd.pub.public_key); 3031 GNUNET_CRYPTO_blind_sign_priv_decref (kd.priv.private_key); 3032 GNUNET_assert (0 <= TALER_MERCHANTDB_commit (other)); 3033 /* Establish an old snapshot before the parent's next lock acquisition. */ 3034 GNUNET_assert (GNUNET_OK == TALER_MERCHANTDB_start (other, 3035 "old key snapshot")); 3036 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3037 TALER_MERCHANTDB_get_token_family_key ( 3038 other, cls->instance.instance.id, cls->family.slug, 3039 cls->family.valid_after, cls->family.valid_before, &kd)); 3040 TALER_MERCHANTDB_token_family_details_free (&kd.token_family); 3041 GNUNET_CRYPTO_blind_sign_pub_decref (kd.pub.public_key); 3042 GNUNET_CRYPTO_blind_sign_priv_decref (kd.priv.private_key); 3043 GNUNET_assert (1 == write (from_child[1], "S", 1)); 3044 GNUNET_assert (1 == read (to_child[0], &signal, 1)); 3045 GNUNET_assert (GNUNET_DB_STATUS_SOFT_ERROR == 3046 TALER_MERCHANTDB_lock_token_family (other, 3047 cls->family.slug, 3048 &started)); 3049 GNUNET_assert (! started); 3050 TALER_MERCHANTDB_rollback (other); 3051 TALER_MERCHANTDB_disconnect (other); 3052 _exit (0); 3053 } 3054 close (to_child[0]); 3055 close (from_child[1]); 3056 GNUNET_assert (1 == read (from_child[0], &signal, 1)); 3057 GNUNET_assert ('B' == signal); 3058 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3059 TALER_MERCHANTDB_insert_token_family_key ( 3060 pg, cls->instance.instance.id, cls->family.slug, 3061 &cls->pub, &cls->priv, cls->family.valid_before, 3062 cls->family.valid_after, cls->family.valid_before)); 3063 GNUNET_assert (0 <= TALER_MERCHANTDB_commit (pg)); 3064 GNUNET_assert (1 == write (to_child[1], "C", 1)); 3065 GNUNET_assert (1 == read (from_child[0], &signal, 1)); 3066 GNUNET_assert ('S' == signal); 3067 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3068 TALER_MERCHANTDB_lock_token_family (pg, 3069 cls->family.slug, 3070 &started)); 3071 GNUNET_assert (started); 3072 GNUNET_assert (0 <= TALER_MERCHANTDB_commit (pg)); 3073 GNUNET_assert (1 == write (to_child[1], "C", 1)); 3074 close (to_child[1]); 3075 close (from_child[0]); 3076 GNUNET_assert (child == waitpid (child, &status, 0)); 3077 GNUNET_assert (WIFEXITED (status) && (0 == WEXITSTATUS (status))); 3078 return 0; 3079 } 3080 3081 3082 /** 3083 * Sets up the data structures used in the token tests. 3084 * 3085 * @param cls the closure to fill with test data. 3086 */ 3087 static void 3088 pre_test_tokens (struct TestTokens_Closure *cls) 3089 { 3090 make_instance ("test_inst_tokens", 3091 &cls->instance); 3092 memset (&cls->family, 3093 0, 3094 sizeof (cls->family)); 3095 cls->family.slug = (char *) "test_tokens_family"; 3096 cls->family.name = (char *) "Test token family"; 3097 cls->family.description = (char *) "This is a test token family"; 3098 cls->family.description_i18n = json_object (); 3099 GNUNET_assert (NULL != cls->family.description_i18n); 3100 cls->family.valid_after = GNUNET_TIME_timestamp_get (); 3101 cls->family.valid_before 3102 = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_YEARS); 3103 cls->family.duration = GNUNET_TIME_UNIT_DAYS; 3104 cls->family.validity_granularity = GNUNET_TIME_UNIT_DAYS; 3105 cls->family.start_offset = GNUNET_TIME_UNIT_ZERO; 3106 cls->family.kind = TALER_MERCHANTDB_TFK_Discount; 3107 GNUNET_CRYPTO_blind_sign_keys_create (&cls->priv.private_key, 3108 &cls->pub.public_key, 3109 GNUNET_CRYPTO_BSA_CS); 3110 cls->h_pub.hash = cls->pub.public_key->pub_key_hash; 3111 GNUNET_CRYPTO_random_block (&cls->h_contract_terms, 3112 sizeof (cls->h_contract_terms)); 3113 } 3114 3115 3116 /** 3117 * Handles all teardown after testing. 3118 * 3119 * @param cls the closure containing memory to be freed. 3120 */ 3121 static void 3122 post_test_tokens (struct TestTokens_Closure *cls) 3123 { 3124 GNUNET_CRYPTO_blind_sign_pub_decref (cls->pub.public_key); 3125 GNUNET_CRYPTO_blind_sign_priv_decref (cls->priv.private_key); 3126 json_decref (cls->family.description_i18n); 3127 free_instance_data (&cls->instance); 3128 } 3129 3130 3131 /** 3132 * Runs the tests for issued and spent tokens. 3133 * 3134 * @param cls the container of the test data. 3135 * @return 0 on success, 1 otherwise. 3136 */ 3137 static int 3138 run_test_tokens (struct TestTokens_Closure *cls) 3139 { 3140 struct GNUNET_CRYPTO_BlindedSignature bs1; 3141 struct GNUNET_CRYPTO_BlindedSignature bs2; 3142 struct TALER_BlindedTokenIssueSignature blind_sig1; 3143 struct TALER_BlindedTokenIssueSignature blind_sig2; 3144 struct GNUNET_CRYPTO_UnblindedSignature ub1; 3145 struct TALER_TokenIssueSignature issue_sig; 3146 struct TALER_TokenUsePublicKeyP use_pub; 3147 struct TALER_TokenUseSignatureP use_sig; 3148 struct TALER_TokenUseSignatureP other_sig; 3149 3150 make_blinded_token_sig (&bs1, 3151 &blind_sig1); 3152 make_blinded_token_sig (&bs2, 3153 &blind_sig2); 3154 make_token_issue_sig (&ub1, 3155 &issue_sig); 3156 GNUNET_CRYPTO_random_block (&use_pub, 3157 sizeof (use_pub)); 3158 GNUNET_CRYPTO_random_block (&use_sig, 3159 sizeof (use_sig)); 3160 GNUNET_CRYPTO_random_block (&other_sig, 3161 sizeof (other_sig)); 3162 /* Set up instance, token family and token family key */ 3163 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 3164 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3165 TEST_SET_INSTANCE (cls->instance.instance.id, 3166 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 3167 TEST_COND_RET_ON_FAIL ( 3168 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3169 TALER_MERCHANTDB_insert_token_family (pg, 3170 cls->instance.instance.id, 3171 cls->family.slug, 3172 &cls->family), 3173 "Insert token family failed\n"); 3174 TEST_SET_INSTANCE (cls->instance.instance.id, 3175 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 3176 TEST_RET_ON_FAIL (test_token_family_key_lock (cls)); 3177 TEST_RET_ON_FAIL (test_token_family_counters (cls, 3178 0, 3179 0)); 3180 /* Issuing a token bumps the 'issued' counter */ 3181 TEST_RET_ON_FAIL (test_insert_issued_token ( 3182 cls, 3183 &blind_sig1, 3184 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 3185 false)); 3186 TEST_RET_ON_FAIL (test_token_family_counters (cls, 3187 1, 3188 0)); 3189 /* Re-issuing the very same token must be detected as a duplicate 3190 and must NOT bump the counter a second time. */ 3191 TEST_RET_ON_FAIL (test_insert_issued_token ( 3192 cls, 3193 &blind_sig1, 3194 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, 3195 false)); 3196 TEST_RET_ON_FAIL (test_token_family_counters (cls, 3197 1, 3198 0)); 3199 /* A different blind signature is a different token */ 3200 TEST_RET_ON_FAIL (test_insert_issued_token ( 3201 cls, 3202 &blind_sig2, 3203 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 3204 false)); 3205 TEST_RET_ON_FAIL (test_token_family_counters (cls, 3206 2, 3207 0)); 3208 /* Spending a token bumps the 'used' counter */ 3209 TEST_RET_ON_FAIL (test_insert_used_token ( 3210 cls, 3211 &use_pub, 3212 &use_sig, 3213 &issue_sig, 3214 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 3215 false)); 3216 TEST_RET_ON_FAIL (test_token_family_counters (cls, 3217 2, 3218 1)); 3219 /* Replaying the exact same spend is idempotent: reported as success, 3220 but the 'used' counter must not move a second time. */ 3221 TEST_RET_ON_FAIL (test_insert_used_token ( 3222 cls, 3223 &use_pub, 3224 &use_sig, 3225 &issue_sig, 3226 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 3227 false)); 3228 TEST_RET_ON_FAIL (test_token_family_counters (cls, 3229 2, 3230 1)); 3231 /* Reusing the same token key with a different signature is 3232 double-spending and must be refused */ 3233 TEST_RET_ON_FAIL (test_insert_used_token ( 3234 cls, 3235 &use_pub, 3236 &other_sig, 3237 &issue_sig, 3238 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, 3239 false)); 3240 TEST_RET_ON_FAIL (test_token_family_counters (cls, 3241 2, 3242 1)); 3243 /* Deleting the token family cascades to the token family keys. An 3244 order created before the deletion may still be paid; the missing 3245 key must then be reported to the caller (which turns it into a 404 3246 TOKEN_KEY_UNKNOWN) instead of being an internal hard error. */ 3247 TEST_SET_INSTANCE (cls->instance.instance.id, 3248 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 3249 TEST_COND_RET_ON_FAIL ( 3250 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3251 TALER_MERCHANTDB_delete_token_family (pg, 3252 cls->instance.instance.id, 3253 cls->family.slug), 3254 "Delete token family failed\n"); 3255 TEST_RET_ON_FAIL (test_insert_issued_token ( 3256 cls, 3257 &blind_sig1, 3258 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, 3259 true)); 3260 TEST_RET_ON_FAIL (test_insert_used_token ( 3261 cls, 3262 &use_pub, 3263 &use_sig, 3264 &issue_sig, 3265 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, 3266 true)); 3267 return 0; 3268 } 3269 3270 3271 /** 3272 * Takes care of token testing. 3273 * 3274 * @return 0 on success, 1 otherwise. 3275 */ 3276 static int 3277 test_tokens (void) 3278 { 3279 struct TestTokens_Closure test_cls; 3280 int test_result; 3281 3282 pre_test_tokens (&test_cls); 3283 test_result = run_test_tokens (&test_cls); 3284 post_test_tokens (&test_cls); 3285 return test_result; 3286 } 3287 3288 3289 /* ********** Orders ********** */ 3290 3291 3292 /** 3293 * Container for order data 3294 */ 3295 struct OrderData 3296 { 3297 /** 3298 * The id of the order 3299 */ 3300 const char *id; 3301 3302 /** 3303 * The pay deadline for the order 3304 */ 3305 struct GNUNET_TIME_Timestamp pay_deadline; 3306 3307 /** 3308 * The contract of the order 3309 */ 3310 json_t *contract; 3311 3312 /** 3313 * The claim token for the order. 3314 */ 3315 struct TALER_ClaimTokenP claim_token; 3316 }; 3317 3318 3319 /** 3320 * Builds an order for testing. 3321 * 3322 * @param order_id the identifier to use for the order. 3323 * @param order the container to fill with data. 3324 */ 3325 static void 3326 make_order (const char *order_id, 3327 struct OrderData *order) 3328 { 3329 struct GNUNET_TIME_Timestamp refund_deadline; 3330 3331 order->id = order_id; 3332 order->contract = json_object (); 3333 GNUNET_assert (NULL != order->contract); 3334 order->pay_deadline = GNUNET_TIME_relative_to_timestamp ( 3335 GNUNET_TIME_UNIT_DAYS); 3336 GNUNET_CRYPTO_random_block (&order->claim_token, 3337 sizeof (order->claim_token)); 3338 refund_deadline = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS); 3339 GNUNET_assert (0 == 3340 json_object_set_new (order->contract, 3341 "fulfillment_url", 3342 json_string ("a"))); 3343 GNUNET_assert (0 == 3344 json_object_set_new (order->contract, 3345 "summary", 3346 json_string ("Test order"))); 3347 GNUNET_assert (0 == 3348 json_object_set_new (order->contract, 3349 "order_id", 3350 json_string (order_id))); 3351 GNUNET_assert (0 == 3352 json_object_set_new ( 3353 order->contract, 3354 "pay_deadline", 3355 GNUNET_JSON_from_timestamp (order->pay_deadline)) 3356 ); 3357 GNUNET_assert (0 == 3358 json_object_set_new (order->contract, 3359 "refund_deadline", 3360 GNUNET_JSON_from_timestamp ( 3361 refund_deadline))); 3362 } 3363 3364 3365 /** 3366 * Frees memory associated with an order. 3367 * 3368 * @param the order to free. 3369 */ 3370 static void 3371 free_order_data (struct OrderData *order) 3372 { 3373 json_decref (order->contract); 3374 } 3375 3376 3377 /** 3378 * Tests inserting an order into the database. 3379 * 3380 * @param instance the instance to insert the order for. 3381 * @param order the order to insert. 3382 * @param expected_result the value we expect the db to return. 3383 * @return 0 on success, 1 otherwise. 3384 */ 3385 static int 3386 test_insert_order (const struct InstanceData *instance, 3387 const struct OrderData *order, 3388 enum GNUNET_DB_QueryStatus expected_result) 3389 { 3390 struct TALER_MerchantPostDataHashP h_post; 3391 3392 memset (&h_post, 3393 42, 3394 sizeof (h_post)); 3395 TEST_SET_INSTANCE (instance->instance.id, expected_result); 3396 TEST_COND_RET_ON_FAIL (expected_result == 3397 TALER_MERCHANTDB_insert_order (pg, 3398 instance->instance.id, 3399 order->id, 3400 NULL, /* session_id */ 3401 &h_post, 3402 order->pay_deadline, 3403 &order->claim_token, 3404 order->contract, 3405 NULL, 3406 0, 3407 NULL), 3408 "Insert order failed\n"); 3409 return 0; 3410 } 3411 3412 3413 /** 3414 * Tests looking up an order in the database. 3415 * 3416 * @param instance the instance to lookup from. 3417 * @param order the order that should be looked up. 3418 * @return 0 on success, 1 otherwise. 3419 */ 3420 static int 3421 test_lookup_order (const struct InstanceData *instance, 3422 const struct OrderData *order) 3423 { 3424 struct TALER_ClaimTokenP ct; 3425 json_t *lookup_terms = NULL; 3426 struct TALER_MerchantPostDataHashP oh; 3427 struct TALER_MerchantPostDataHashP wh; 3428 3429 memset (&wh, 3430 42, 3431 sizeof (wh)); 3432 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 3433 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 3434 TALER_MERCHANTDB_get_order (pg, 3435 instance->instance.id, 3436 order->id, 3437 &ct, 3438 &oh, 3439 &lookup_terms)) 3440 { 3441 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3442 "Lookup order failed\n"); 3443 if (NULL != lookup_terms) 3444 json_decref (lookup_terms); 3445 return 1; 3446 } 3447 if ( (1 != json_equal (order->contract, 3448 lookup_terms)) || 3449 (0 != GNUNET_memcmp (&order->claim_token, 3450 &ct)) || 3451 (0 != GNUNET_memcmp (&oh, 3452 &wh)) ) 3453 { 3454 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3455 "Lookup order failed: incorrect order returned\n"); 3456 if (NULL != lookup_terms) 3457 json_decref (lookup_terms); 3458 return 1; 3459 } 3460 json_decref (lookup_terms); 3461 return 0; 3462 } 3463 3464 3465 /** 3466 * Closure for testing order lookup 3467 */ 3468 struct TestLookupOrders_Closure 3469 { 3470 /** 3471 * Number of orders to compare to 3472 */ 3473 unsigned int orders_to_cmp_length; 3474 3475 /** 3476 * Pointer to (ordered) array of order ids 3477 */ 3478 const struct OrderData *orders_to_cmp; 3479 3480 /** 3481 * Pointer to array of bools indicating matches in the correct index 3482 */ 3483 bool *results_match; 3484 3485 /** 3486 * Total number of results returned 3487 */ 3488 unsigned int results_length; 3489 }; 3490 3491 3492 /** 3493 * Called after @e test_lookup_orders. 3494 * 3495 * @param cls the lookup closure. 3496 * @param order_id the identifier of the order found. 3497 * @param order_serial the row number of the order found. 3498 * @param timestamp when the order was added to the database. 3499 */ 3500 static void 3501 lookup_orders_cb (void *cls, 3502 const char *order_id, 3503 uint64_t order_serial, 3504 struct GNUNET_TIME_Timestamp timestamp) 3505 { 3506 struct TestLookupOrders_Closure *cmp = cls; 3507 unsigned int i; 3508 3509 if (NULL == cmp) 3510 return; 3511 i = cmp->results_length; 3512 cmp->results_length += 1; 3513 if (cmp->orders_to_cmp_length > i) 3514 { 3515 /* Compare the orders */ 3516 if (0 == strcmp (cmp->orders_to_cmp[i].id, 3517 order_id)) 3518 cmp->results_match[i] = true; 3519 else 3520 cmp->results_match[i] = false; 3521 } 3522 } 3523 3524 3525 /** 3526 * Tests looking up orders for an instance. 3527 * 3528 * @param instance the instance. 3529 * @param filter the filters applied on the lookup. 3530 * @param orders_length the number of orders we expect to find. 3531 * @param orders the orders we expect to find. 3532 * @return 0 on success, 1 otherwise. 3533 */ 3534 static int 3535 test_lookup_orders (const struct InstanceData *instance, 3536 const struct TALER_MERCHANTDB_OrderFilter *filter, 3537 unsigned int orders_length, 3538 const struct OrderData *orders) 3539 { 3540 bool results_match[GNUNET_NZL (orders_length)]; 3541 struct TestLookupOrders_Closure cls = { 3542 .orders_to_cmp_length = orders_length, 3543 .orders_to_cmp = orders, 3544 .results_match = results_match, 3545 .results_length = 0 3546 }; 3547 memset (results_match, 3548 0, 3549 sizeof (bool) * orders_length); 3550 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 3551 if (0 > TALER_MERCHANTDB_iterate_orders (pg, 3552 instance->instance.id, 3553 filter, 3554 &lookup_orders_cb, 3555 &cls)) 3556 { 3557 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3558 "Lookup orders failed\n"); 3559 return 1; 3560 } 3561 if (orders_length != cls.results_length) 3562 { 3563 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3564 "Lookup orders failed: incorrect number of results (%d)\n", 3565 cls.results_length); 3566 return 1; 3567 } 3568 for (unsigned int i = 0; orders_length > i; ++i) 3569 { 3570 if (false == cls.results_match[i]) 3571 { 3572 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3573 "Lookup orders failed: mismatched data (index %d)\n", 3574 i); 3575 return 1; 3576 } 3577 } 3578 return 0; 3579 } 3580 3581 3582 /** 3583 * Container for data used for looking up the row number of an order. 3584 */ 3585 struct LookupOrderSerial_Closure 3586 { 3587 /** 3588 * The order that is being looked up. 3589 */ 3590 const struct OrderData *order; 3591 3592 /** 3593 * The serial of the order that was found. 3594 */ 3595 uint64_t serial; 3596 }; 3597 3598 3599 /** 3600 * Called after @e test_lookup_orders. 3601 * 3602 * @param cls the lookup closure. 3603 * @param order_id the identifier of the order found. 3604 * @param order_serial the row number of the order found. 3605 * @param timestamp when the order was added to the database. 3606 */ 3607 static void 3608 get_order_serial_cb (void *cls, 3609 const char *order_id, 3610 uint64_t order_serial, 3611 struct GNUNET_TIME_Timestamp timestamp) 3612 { 3613 struct LookupOrderSerial_Closure *lookup_cls = cls; 3614 if (NULL == lookup_cls) 3615 return; 3616 if (0 == strcmp (lookup_cls->order->id, 3617 order_id)) 3618 lookup_cls->serial = order_serial; 3619 } 3620 3621 3622 /** 3623 * Convenience function for getting the row number of an order. 3624 * 3625 * @param instance the instance to look up from. 3626 * @param order the order to lookup the serial for. 3627 * @return the row number of the order. 3628 */ 3629 static uint64_t 3630 get_order_serial (const struct InstanceData *instance, 3631 const struct OrderData *order) 3632 { 3633 struct LookupOrderSerial_Closure lookup_cls = { 3634 .order = order, 3635 .serial = 0 3636 }; 3637 struct TALER_MERCHANTDB_OrderFilter filter = { 3638 .paid = TALER_EXCHANGE_YNA_ALL, 3639 .refunded = TALER_EXCHANGE_YNA_ALL, 3640 .wired = TALER_EXCHANGE_YNA_ALL, 3641 .expired = TALER_EXCHANGE_YNA_ALL, 3642 .date = GNUNET_TIME_UNIT_ZERO_TS, 3643 .start_row = 0, 3644 .delta = 256 3645 }; 3646 3647 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3648 TALER_MERCHANTDB_set_instance (pg, 3649 instance->instance.id)); 3650 GNUNET_assert (0 < TALER_MERCHANTDB_iterate_orders (pg, 3651 instance->instance.id, 3652 &filter, 3653 &get_order_serial_cb, 3654 &lookup_cls)); 3655 GNUNET_assert (0 != lookup_cls.serial); 3656 3657 return lookup_cls.serial; 3658 } 3659 3660 3661 /** 3662 * Tests deleting an order from the database. 3663 * 3664 * @param instance the instance to delete the order from. 3665 * @param order the order to delete. 3666 * @param expected_result the result we expect to receive. 3667 * @return 0 on success, 1 otherwise. 3668 */ 3669 static int 3670 test_delete_order (const struct InstanceData *instance, 3671 const struct OrderData *order, 3672 enum GNUNET_DB_QueryStatus expected_result) 3673 { 3674 TEST_SET_INSTANCE (instance->instance.id, expected_result); 3675 TEST_COND_RET_ON_FAIL (expected_result == 3676 TALER_MERCHANTDB_delete_order (pg, 3677 instance->instance.id, 3678 order->id, 3679 false), 3680 "Delete order failed\n"); 3681 return 0; 3682 } 3683 3684 3685 /** 3686 * Runs @a sql directly on the database connection of the test. Used to 3687 * install the fault-injection trigger of #test_delete_order_atomic(). 3688 * 3689 * @param sql the SQL statement(s) to execute 3690 * @return 0 on success, 1 otherwise. 3691 */ 3692 static int 3693 orders_exec_sql (const char *sql) 3694 { 3695 struct GNUNET_PQ_ExecuteStatement es[] = { 3696 GNUNET_PQ_make_execute (sql), 3697 GNUNET_PQ_EXECUTE_STATEMENT_END 3698 }; 3699 3700 TEST_COND_RET_ON_FAIL (GNUNET_OK == 3701 GNUNET_PQ_exec_statements (pg->conn, 3702 es), 3703 "Direct SQL execution failed\n"); 3704 return 0; 3705 } 3706 3707 3708 /** 3709 * Tests that a forced order deletion removes the row in 3710 * merchant_orders and the row in merchant_contract_terms atomically. 3711 * 3712 * The caller of TALER_MERCHANTDB_delete_order() does not open a 3713 * transaction, so doing this in two statements would run them in two 3714 * separate autocommit transactions: the first would stay committed 3715 * when the second one fails, leaving orphaned contract terms behind. 3716 * We provoke exactly that by installing a trigger that makes the 3717 * deletion of the contract terms fail, and then check that the order 3718 * itself survived. 3719 * 3720 * @param instance the instance to delete the order from. 3721 * @param order the order to delete; must be claimed and unpaid. 3722 * @return 0 on success, 1 otherwise. 3723 */ 3724 static int 3725 test_delete_order_atomic (const struct InstanceData *instance, 3726 const struct OrderData *order) 3727 { 3728 struct TALER_MerchantPostDataHashP h_post_data; 3729 3730 TEST_SET_INSTANCE (instance->instance.id, 3731 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 3732 TEST_RET_ON_FAIL (orders_exec_sql ( 3733 "CREATE FUNCTION test_block_ct_delete ()" 3734 " RETURNS TRIGGER" 3735 " LANGUAGE plpgsql" 3736 " AS $$" 3737 " BEGIN" 3738 " RAISE EXCEPTION" 3739 " 'simulated failure deleting contract terms';" 3740 " END $$;" 3741 "CREATE TRIGGER test_block_ct_delete_trigger" 3742 " BEFORE DELETE ON merchant_contract_terms" 3743 " FOR EACH ROW" 3744 " EXECUTE FUNCTION test_block_ct_delete ();")); 3745 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_HARD_ERROR == 3746 TALER_MERCHANTDB_delete_order (pg, 3747 instance->instance.id, 3748 order->id, 3749 true), 3750 "Deleting the order should have failed\n"); 3751 TEST_RET_ON_FAIL (orders_exec_sql ( 3752 "DROP TRIGGER test_block_ct_delete_trigger" 3753 " ON merchant_contract_terms;" 3754 "DROP FUNCTION test_block_ct_delete ();")); 3755 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3756 TALER_MERCHANTDB_get_order (pg, 3757 instance->instance.id, 3758 order->id, 3759 NULL, 3760 &h_post_data, 3761 NULL), 3762 "Order was deleted even though deleting its" 3763 " contract terms failed\n"); 3764 /* Without the trigger in the way, both rows must now go. */ 3765 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3766 TALER_MERCHANTDB_delete_order (pg, 3767 instance->instance.id, 3768 order->id, 3769 true), 3770 "Forced deletion of a claimed, unpaid order failed\n"); 3771 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 3772 TALER_MERCHANTDB_get_order (pg, 3773 instance->instance.id, 3774 order->id, 3775 NULL, 3776 &h_post_data, 3777 NULL), 3778 "Order survived its deletion\n"); 3779 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 3780 TALER_MERCHANTDB_get_contract_terms ( 3781 pg, 3782 instance->instance.id, 3783 order->id, 3784 NULL, 3785 NULL, 3786 NULL), 3787 "Contract terms survived the order deletion\n"); 3788 return 0; 3789 } 3790 3791 3792 /** 3793 * Test inserting contract terms for an order. 3794 * 3795 * @param instance the instance. 3796 * @param order the order containing the contract terms. 3797 * @param expected_result the result we expect to receive. 3798 * @return 0 on success, 1 otherwise. 3799 */ 3800 static int 3801 test_insert_contract_terms (const struct InstanceData *instance, 3802 const struct OrderData *order, 3803 enum GNUNET_DB_QueryStatus expected_result) 3804 { 3805 uint64_t os; 3806 3807 TEST_SET_INSTANCE (instance->instance.id, expected_result); 3808 TEST_COND_RET_ON_FAIL (expected_result == 3809 TALER_MERCHANTDB_insert_contract_terms (pg, 3810 instance->instance.id, 3811 order->id, 3812 order->contract, 3813 &os), 3814 "Insert contract terms failed\n"); 3815 return 0; 3816 } 3817 3818 3819 /** 3820 * Reject missing challenges for challenge algorithms, including callers 3821 * that do not request contract JSON. An explicitly stored zero challenge 3822 * must remain distinguishable from SQL NULL. 3823 */ 3824 static int 3825 test_contract_terms_challenge (const struct InstanceData *instance, 3826 const struct OrderData *order) 3827 { 3828 TEST_SET_INSTANCE (instance->instance.id, 3829 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 3830 GNUNET_assert (GNUNET_OK == 3831 TALER_MERCHANTDB_start (pg, "test stored POS challenge")); 3832 for (uint32_t alg = TALER_MCA_NONE; 3833 alg <= TALER_MCA_EDDSA_CHALLENGE + 1; 3834 alg++) 3835 { 3836 for (unsigned int missing = 0; missing < 2; missing++) 3837 { 3838 struct TALER_PosChallengeP challenge = {0}; 3839 struct GNUNET_PQ_QueryParam params[] = { 3840 GNUNET_PQ_query_param_string (order->id), 3841 GNUNET_PQ_query_param_uint32 (&alg), 3842 missing ? GNUNET_PQ_query_param_null () 3843 : GNUNET_PQ_query_param_auto_from_type (&challenge), 3844 GNUNET_PQ_query_param_end 3845 }; 3846 3847 GNUNET_assert (GNUNET_OK == 3848 GNUNET_PQ_prepare_anon ( 3849 pg->conn, 3850 "UPDATE merchant_contract_terms" 3851 " SET pos_algorithm=$2, pos_challenge=$3, pos_key='test-key'" 3852 " WHERE order_id=$1")); 3853 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3854 GNUNET_PQ_eval_prepared_non_select (pg->conn, "", params)); 3855 for (unsigned int with_json = 0; with_json < 2; with_json++) 3856 { 3857 json_t *contract = NULL; 3858 char *key = NULL; 3859 uint64_t serial; 3860 bool paid; 3861 enum TALER_MerchantConfirmationAlgorithm parsed_alg; 3862 enum GNUNET_DB_QueryStatus qs; 3863 struct TALER_PosChallengeP parsed_challenge; 3864 3865 memset (&parsed_challenge, 42, sizeof (parsed_challenge)); 3866 qs = TALER_MERCHANTDB_get_contract_terms_pos ( 3867 pg, instance->instance.id, order->id, 3868 with_json ? &contract : NULL, 3869 &serial, &paid, NULL, &key, &parsed_alg, &parsed_challenge); 3870 if ( (alg > TALER_MCA_EDDSA_CHALLENGE) || 3871 (missing && (alg >= TALER_MCA_ECDSA_CHALLENGE)) ) 3872 { 3873 GNUNET_assert (GNUNET_DB_STATUS_HARD_ERROR == qs); 3874 GNUNET_assert (NULL == contract); 3875 GNUNET_assert (NULL == key); 3876 } 3877 else 3878 { 3879 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs); 3880 GNUNET_assert (alg == parsed_alg); 3881 if (! missing) 3882 GNUNET_assert (0 == GNUNET_memcmp (&challenge, &parsed_challenge)); 3883 json_decref (contract); 3884 GNUNET_free (key); 3885 } 3886 } 3887 } 3888 } 3889 TALER_MERCHANTDB_rollback (pg); 3890 return 0; 3891 } 3892 3893 3894 /** 3895 * Test updating contract terms for an order. 3896 * 3897 * @param instance the instance. 3898 * @param order the order containing the contract terms. 3899 * @param expected_result the result we expect to receive. 3900 * @return 0 on success, 1 otherwise. 3901 */ 3902 static int 3903 test_update_contract_terms (const struct InstanceData *instance, 3904 const struct OrderData *order, 3905 enum GNUNET_DB_QueryStatus expected_result) 3906 { 3907 TEST_SET_INSTANCE (instance->instance.id, expected_result); 3908 TEST_COND_RET_ON_FAIL (expected_result == 3909 TALER_MERCHANTDB_update_contract_terms (pg, 3910 instance->instance.id, 3911 order->id, 3912 order->contract), 3913 "Update contract terms failed\n"); 3914 return 0; 3915 } 3916 3917 3918 /** 3919 * Tests lookup of contract terms 3920 * 3921 * @param instance the instance to lookup from. 3922 * @param order the order to lookup for. 3923 * @return 0 on success, 1 otherwise. 3924 */ 3925 static int 3926 test_lookup_contract_terms (const struct InstanceData *instance, 3927 const struct OrderData *order) 3928 { 3929 json_t *contract = NULL; 3930 uint64_t order_serial; 3931 3932 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 3933 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 3934 TALER_MERCHANTDB_get_contract_terms (pg, 3935 instance->instance.id, 3936 order->id, 3937 &contract, 3938 &order_serial, 3939 NULL)) 3940 { 3941 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3942 "Lookup contract terms failed\n"); 3943 GNUNET_assert (NULL == contract); 3944 return 1; 3945 } 3946 if (1 != json_equal (order->contract, 3947 contract)) 3948 { 3949 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3950 "Lookup contract terms failed: mismatched data\n"); 3951 json_decref (contract); 3952 return 1; 3953 } 3954 json_decref (contract); 3955 return 0; 3956 } 3957 3958 3959 /** 3960 * Tests deleting contract terms for an order. 3961 * 3962 * @param instance the instance to delete from. 3963 * @param order the order whose contract terms we should delete. 3964 * @param legal_expiration how long we must wait after creating an order to delete it 3965 * @param expected_result the result we expect to receive. 3966 * @return 0 on success, 1 otherwise. 3967 */ 3968 static int 3969 test_delete_contract_terms (const struct InstanceData *instance, 3970 const struct OrderData *order, 3971 struct GNUNET_TIME_Relative legal_expiration, 3972 enum GNUNET_DB_QueryStatus expected_result) 3973 { 3974 TEST_SET_INSTANCE (instance->instance.id, expected_result); 3975 TEST_COND_RET_ON_FAIL (expected_result == 3976 TALER_MERCHANTDB_delete_contract_terms (pg, 3977 instance->instance.id, 3978 order->id, 3979 legal_expiration), 3980 "Delete contract terms failed\n"); 3981 return 0; 3982 } 3983 3984 3985 /** 3986 * Test marking a contract as paid in the database. 3987 * 3988 * @param instance the instance to use. 3989 * @param order the order whose contract to use. 3990 * @param expected_result the result we expect to receive. 3991 * @return 0 on success, 1 otherwise. 3992 */ 3993 static int 3994 test_mark_contract_paid (const struct InstanceData *instance, 3995 const struct OrderData *order, 3996 enum GNUNET_DB_QueryStatus expected_result) 3997 { 3998 struct TALER_PrivateContractHashP h_contract_terms; 3999 4000 GNUNET_assert (GNUNET_OK == 4001 TALER_JSON_contract_hash (order->contract, 4002 &h_contract_terms)); 4003 TEST_SET_INSTANCE (instance->instance.id, expected_result); 4004 TEST_COND_RET_ON_FAIL (expected_result == 4005 TALER_MERCHANTDB_update_to_contract_terms_paid (pg, 4006 instance->instance.id, 4007 &h_contract_terms, 4008 "test_orders_session", 4009 -1), 4010 "Mark contract paid failed\n"); 4011 return 0; 4012 } 4013 4014 4015 /** 4016 * Tests looking up the status of an order. 4017 * 4018 * @param instance the instance to lookup from. 4019 * @param order the order to lookup. 4020 * @param expected_paid whether the order was paid or not. 4021 * @return 0 on success, 1 otherwise. 4022 */ 4023 static int 4024 test_lookup_order_status (const struct InstanceData *instance, 4025 const struct OrderData *order, 4026 bool expected_paid) 4027 { 4028 struct TALER_PrivateContractHashP h_contract_terms_expected; 4029 struct TALER_PrivateContractHashP h_contract_terms; 4030 bool order_paid = false; 4031 4032 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 4033 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 4034 TALER_MERCHANTDB_get_order_status (pg, 4035 instance->instance.id, 4036 order->id, 4037 &h_contract_terms, 4038 &order_paid)) 4039 { 4040 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4041 "Lookup order status failed\n"); 4042 return 1; 4043 } 4044 GNUNET_assert (GNUNET_OK == 4045 TALER_JSON_contract_hash (order->contract, 4046 &h_contract_terms_expected)); 4047 if ((expected_paid != order_paid) || 4048 (0 != GNUNET_memcmp (&h_contract_terms, 4049 &h_contract_terms_expected))) 4050 { 4051 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4052 "Lookup order status/deposit failed: mismatched data\n"); 4053 return 1; 4054 } 4055 return 0; 4056 } 4057 4058 4059 /** 4060 * Test looking up an order by its fulfillment. 4061 * 4062 * @param instance the instance to lookup from. 4063 * @param order the order to lookup. 4064 * @param the session id associated with the payment. 4065 * @return 0 on success, 1 otherwise. 4066 */ 4067 static int 4068 test_lookup_order_by_fulfillment (const struct InstanceData *instance, 4069 const struct OrderData *order, 4070 const char *session_id) 4071 { 4072 char *order_id; 4073 const char *fulfillment_url = 4074 json_string_value (json_object_get (order->contract, 4075 "fulfillment_url")); 4076 GNUNET_assert (NULL != fulfillment_url); 4077 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 4078 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 4079 TALER_MERCHANTDB_get_order_by_fulfillment (pg, 4080 instance->instance.id, 4081 fulfillment_url, 4082 session_id, 4083 false, 4084 &order_id)) 4085 { 4086 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4087 "Lookup order by fulfillment failed\n"); 4088 GNUNET_free (order_id); 4089 return 1; 4090 } 4091 if (0 != strcmp (order->id, 4092 order_id)) 4093 { 4094 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4095 "Lookup order by fulfillment failed\n"); 4096 GNUNET_free (order_id); 4097 return 1; 4098 } 4099 GNUNET_free (order_id); 4100 return 0; 4101 } 4102 4103 4104 /** 4105 * Test looking up the status of an order. 4106 * 4107 * @param order_id the row of the order in the database. 4108 * @param session_id the session id associated with the payment. 4109 * @param expected_paid whether the order was paid or not. 4110 * @param expected_wired whether the order was wired or not. 4111 * @return 0 on success, 1 otherwise. 4112 */ 4113 static int 4114 test_lookup_payment_status (const char *instance_id, 4115 const char *order_id, 4116 const char *session_id, 4117 bool expected_paid, 4118 bool expected_wired) 4119 { 4120 bool paid; 4121 bool wired; 4122 bool matches; 4123 uint64_t os; 4124 int16_t choice_index; 4125 4126 TEST_SET_INSTANCE (instance_id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 4127 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 4128 TALER_MERCHANTDB_get_contract_terms_status (pg, 4129 instance_id, 4130 order_id, 4131 session_id, 4132 NULL, 4133 &os, 4134 &paid, 4135 &wired, 4136 &matches, 4137 NULL, 4138 &choice_index), 4139 "Lookup payment status failed\n"); 4140 if ( (NULL != session_id) && (! matches) ) 4141 { 4142 paid = false; 4143 wired = false; 4144 } 4145 if (expected_wired != wired) 4146 { 4147 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4148 "Lookup payment status for %s/%s failed: wired status is wrong (expected %d got %d)\n", 4149 instance_id, 4150 order_id, 4151 expected_wired, 4152 wired); 4153 return 1; 4154 } 4155 if (expected_paid != paid) 4156 { 4157 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4158 "Lookup payment status failed: paid status is wrong\n"); 4159 return 1; 4160 } 4161 return 0; 4162 } 4163 4164 4165 /** 4166 * Test marking an order as being wired. 4167 * 4168 * @param order_id the row of the order in the database. 4169 * @param expected_result the result we expect the DB to return. 4170 * @return 0 on success, 1 otherwise. 4171 */ 4172 static int 4173 test_mark_order_wired (uint64_t order_id, 4174 enum GNUNET_DB_QueryStatus expected_result) 4175 { 4176 TEST_COND_RET_ON_FAIL (expected_result == 4177 TALER_MERCHANTDB_update_to_contract_terms_wired (pg, 4178 order_id), 4179 "Mark order wired failed\n"); 4180 return 0; 4181 } 4182 4183 4184 /** 4185 * Closure for order tests. 4186 */ 4187 struct TestOrders_Closure 4188 { 4189 /** 4190 * The instance to use for the order tests. 4191 */ 4192 struct InstanceData instance; 4193 4194 /** 4195 * A product to use for the order tests. 4196 */ 4197 struct ProductData product; 4198 4199 /** 4200 * The array of orders 4201 */ 4202 struct OrderData orders[3]; 4203 }; 4204 4205 4206 /** 4207 * Initializes order test data. 4208 * 4209 * @param cls the order test closure. 4210 */ 4211 static void 4212 pre_test_orders (struct TestOrders_Closure *cls) 4213 { 4214 /* Instance */ 4215 make_instance ("test_inst_orders", 4216 &cls->instance); 4217 4218 /* Product */ 4219 make_product ("test_orders_pd_0", 4220 &cls->product); 4221 4222 /* Orders */ 4223 make_order ("test_orders_od_0", 4224 &cls->orders[0]); 4225 make_order ("test_orders_od_1", 4226 &cls->orders[1]); 4227 make_order ("test_orders_od_2", 4228 &cls->orders[2]); 4229 4230 GNUNET_assert (0 == 4231 json_object_set_new (cls->orders[1].contract, 4232 "other_field", 4233 json_string ("Second contract"))); 4234 4235 cls->orders[2].pay_deadline = GNUNET_TIME_UNIT_ZERO_TS; 4236 GNUNET_assert (0 == 4237 json_object_set_new ( 4238 cls->orders[2].contract, 4239 "pay_deadline", 4240 GNUNET_JSON_from_timestamp (cls->orders[2].pay_deadline))); 4241 } 4242 4243 4244 /** 4245 * Frees memory after order tests. 4246 * 4247 * @param cls the order test closure. 4248 */ 4249 static void 4250 post_test_orders (struct TestOrders_Closure *cls) 4251 { 4252 free_instance_data (&cls->instance); 4253 free_product_data (&cls->product); 4254 free_order_data (&cls->orders[0]); 4255 free_order_data (&cls->orders[1]); 4256 free_order_data (&cls->orders[2]); 4257 } 4258 4259 4260 /** 4261 * Run the tests for orders. 4262 * 4263 * @param cls the order test closure. 4264 * @return 0 on success, 1 on failure. 4265 */ 4266 static int 4267 run_test_orders (struct TestOrders_Closure *cls) 4268 { 4269 struct TALER_MERCHANTDB_OrderFilter filter = { 4270 .paid = TALER_EXCHANGE_YNA_ALL, 4271 .refunded = TALER_EXCHANGE_YNA_ALL, 4272 .wired = TALER_EXCHANGE_YNA_ALL, 4273 .expired = TALER_EXCHANGE_YNA_ALL, 4274 .date = GNUNET_TIME_UNIT_ZERO_TS, 4275 .start_row = 0, 4276 .delta = 8 4277 }; 4278 uint64_t serial; 4279 4280 /* Insert the instance */ 4281 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 4282 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4283 /* Test inserting an order */ 4284 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 4285 &cls->orders[0], 4286 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4287 /* Test double insert fails */ 4288 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 4289 &cls->orders[0], 4290 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 4291 /* Test lookup order */ 4292 TEST_RET_ON_FAIL (test_lookup_order (&cls->instance, 4293 &cls->orders[0])); 4294 /* Make sure it fails correctly for nonexistent orders */ 4295 { 4296 struct TALER_MerchantPostDataHashP unused; 4297 4298 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 4299 TALER_MERCHANTDB_get_order (pg, 4300 cls->instance.instance.id, 4301 cls->orders[1].id, 4302 NULL, 4303 &unused, 4304 NULL)) 4305 { 4306 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4307 "Lookup order failed\n"); 4308 return 1; 4309 } 4310 } 4311 /* Test lookups on multiple orders */ 4312 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 4313 &cls->orders[1], 4314 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4315 filter.expired = TALER_EXCHANGE_YNA_NO; 4316 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 4317 &filter, 4318 2, 4319 cls->orders)); 4320 filter.expired = TALER_EXCHANGE_YNA_YES; 4321 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 4322 &filter, 4323 0, 4324 NULL)); 4325 filter.expired = TALER_EXCHANGE_YNA_ALL; 4326 serial = get_order_serial (&cls->instance, 4327 &cls->orders[0]); 4328 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 4329 &filter, 4330 2, 4331 cls->orders)); 4332 /* Test inserting contract terms */ 4333 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 4334 &cls->orders[0], 4335 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4336 TEST_RET_ON_FAIL (test_contract_terms_challenge (&cls->instance, 4337 &cls->orders[0])); 4338 /* Test double insert fails */ 4339 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 4340 &cls->orders[0], 4341 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 4342 /* Test order lock */ 4343 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 4344 &cls->product, 4345 0, 4346 NULL, 4347 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 4348 false, 4349 false, 4350 -1)); 4351 if (1 != TALER_MERCHANTDB_insert_order_lock (pg, 4352 cls->instance.instance.id, 4353 cls->orders[0].id, 4354 cls->product.id, 4355 5, 4356 0)) 4357 { 4358 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4359 "Insert order lock failed\n"); 4360 return 1; 4361 } 4362 /* Test lookup contract terms */ 4363 TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance, 4364 &cls->orders[0])); 4365 /* Test lookup fails for nonexistent contract terms */ 4366 { 4367 json_t *lookup_contract = NULL; 4368 uint64_t lookup_order_serial; 4369 4370 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 4371 TALER_MERCHANTDB_get_contract_terms (pg, 4372 cls->instance.instance.id, 4373 cls->orders[1].id, 4374 &lookup_contract, 4375 &lookup_order_serial, 4376 NULL)) 4377 { 4378 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4379 "Lookup contract terms failed\n"); 4380 GNUNET_assert (NULL == lookup_contract); 4381 return 1; 4382 } 4383 } 4384 /* Test update contract terms */ 4385 GNUNET_assert (0 == 4386 json_object_set_new (cls->orders[0].contract, 4387 "some_new_field", 4388 json_string ("another value"))); 4389 TEST_RET_ON_FAIL (test_update_contract_terms (&cls->instance, 4390 &cls->orders[0], 4391 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4392 TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance, 4393 &cls->orders[0])); 4394 /* Test lookup order status */ 4395 TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance, 4396 &cls->orders[0], 4397 false)); 4398 { 4399 struct TALER_PrivateContractHashP h_contract_terms; 4400 bool order_paid = false; 4401 4402 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 4403 TALER_MERCHANTDB_get_order_status (pg, 4404 cls->instance.instance.id, 4405 cls->orders[1].id, 4406 &h_contract_terms, 4407 &order_paid)) 4408 { 4409 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4410 "Lookup order status failed\n"); 4411 return 1; 4412 } 4413 } 4414 /* Test lookup payment status */ 4415 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 4416 cls->orders[0].id, 4417 NULL, 4418 false, 4419 false)); 4420 /* Test lookup order status fails for nonexistent order */ 4421 { 4422 struct TALER_PrivateContractHashP h_contract_terms; 4423 bool order_paid; 4424 4425 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 4426 TALER_MERCHANTDB_get_order_status (pg, 4427 cls->instance.instance.id, 4428 cls->orders[1].id, 4429 &h_contract_terms, 4430 &order_paid)) 4431 { 4432 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4433 "Lookup order status failed\n"); 4434 return 1; 4435 } 4436 } 4437 /* Test marking contracts as paid */ 4438 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 4439 &cls->orders[0], 4440 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4441 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 4442 cls->orders[0].id, 4443 NULL, 4444 true, 4445 false)); 4446 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 4447 cls->orders[0].id, 4448 "test_orders_session", 4449 true, 4450 false)); 4451 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 4452 cls->orders[0].id, 4453 "bad_session", 4454 false, 4455 false)); 4456 /* Test lookup order by fulfillment */ 4457 TEST_RET_ON_FAIL (test_lookup_order_by_fulfillment (&cls->instance, 4458 &cls->orders[0], 4459 "test_orders_session")); 4460 { 4461 char *order_id; 4462 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 4463 TALER_MERCHANTDB_get_order_by_fulfillment (pg, 4464 cls->instance.instance.id, 4465 "fulfillment_url", 4466 "test_orders_session", 4467 false, 4468 &order_id)) 4469 { 4470 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4471 "Lookup order by fulfillment failed\n"); 4472 GNUNET_free (order_id); 4473 return 1; 4474 } 4475 } 4476 /* Test mark as paid fails for nonexistent order */ 4477 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 4478 &cls->orders[1], 4479 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 4480 TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance, 4481 &cls->orders[0], 4482 true)); 4483 filter.paid = TALER_EXCHANGE_YNA_ALL; 4484 filter.expired = TALER_EXCHANGE_YNA_YES; 4485 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 4486 &filter, 4487 0, 4488 NULL)); 4489 filter.expired = TALER_EXCHANGE_YNA_ALL; 4490 filter.paid = TALER_EXCHANGE_YNA_YES; 4491 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 4492 &filter, 4493 1, 4494 cls->orders)); 4495 /* Test marking orders as wired */ 4496 TEST_RET_ON_FAIL (test_mark_order_wired (serial, 4497 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)) 4498 ; 4499 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 4500 cls->orders[0].id, 4501 NULL, 4502 true, 4503 true)); 4504 TEST_RET_ON_FAIL (test_mark_order_wired (1007, 4505 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)) 4506 ; 4507 /* If an order has been claimed and we aren't past 4508 the pay deadline, we can't delete it. */ 4509 TEST_RET_ON_FAIL (test_delete_order (&cls->instance, 4510 &cls->orders[0], 4511 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 4512 /* Test we can't delete before the legal expiration */ 4513 TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance, 4514 &cls->orders[0], 4515 GNUNET_TIME_UNIT_MONTHS, 4516 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 4517 /* Test deleting contract terms */ 4518 TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance, 4519 &cls->orders[0], 4520 GNUNET_TIME_UNIT_ZERO, 4521 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4522 /* Test we can't delete something that doesn't exist */ 4523 TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance, 4524 &cls->orders[0], 4525 GNUNET_TIME_UNIT_ZERO, 4526 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 4527 /* Test delete order where we aren't past 4528 the deadline, but the order is unclaimed. */ 4529 TEST_RET_ON_FAIL (test_delete_order (&cls->instance, 4530 &cls->orders[1], 4531 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4532 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 4533 &filter, 4534 0, 4535 NULL)); 4536 /* Test we can't delete something that doesn't exist */ 4537 TEST_RET_ON_FAIL (test_delete_order (&cls->instance, 4538 &cls->orders[1], 4539 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 4540 4541 /* Test we can also delete a claimed order that's past the pay deadline. */ 4542 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 4543 &cls->orders[2], 4544 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4545 filter.paid = TALER_EXCHANGE_YNA_ALL; 4546 filter.expired = TALER_EXCHANGE_YNA_YES; 4547 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 4548 &filter, 4549 1, 4550 &cls->orders[2])); 4551 filter.expired = TALER_EXCHANGE_YNA_NO; 4552 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 4553 &filter, 4554 0, 4555 NULL)); 4556 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 4557 &cls->orders[2], 4558 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4559 filter.expired = TALER_EXCHANGE_YNA_YES; 4560 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 4561 &filter, 4562 1, 4563 &cls->orders[2])); 4564 filter.expired = TALER_EXCHANGE_YNA_ALL; 4565 TEST_RET_ON_FAIL (test_delete_order (&cls->instance, 4566 &cls->orders[2], 4567 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4568 4569 /* Deleting an order and its contract terms must be atomic. 4570 orders[1] was fully removed above, so we can re-create it, this 4571 time claimed (=> with contract terms) and unpaid. */ 4572 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 4573 &cls->orders[1], 4574 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4575 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 4576 &cls->orders[1], 4577 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4578 TEST_RET_ON_FAIL (test_delete_order_atomic (&cls->instance, 4579 &cls->orders[1])); 4580 return 0; 4581 } 4582 4583 4584 /** 4585 * Does all tasks for testing orders. 4586 * 4587 * @return 0 when successful, 1 otherwise. 4588 */ 4589 static int 4590 test_orders (void) 4591 { 4592 struct TestOrders_Closure test_cls; 4593 int test_result; 4594 4595 pre_test_orders (&test_cls); 4596 test_result = run_test_orders (&test_cls); 4597 post_test_orders (&test_cls); 4598 return test_result; 4599 } 4600 4601 4602 /* ********** Deposits ********** */ 4603 4604 4605 /** 4606 * A container for exchange signing key data. 4607 */ 4608 struct ExchangeSignkeyData 4609 { 4610 /** 4611 * The master private key of the exchange. 4612 */ 4613 struct TALER_MasterPrivateKeyP master_priv; 4614 4615 /** 4616 * The master public key of the exchange. 4617 */ 4618 struct TALER_MasterPublicKeyP master_pub; 4619 4620 /** 4621 * A signature made with the master keys. 4622 */ 4623 struct TALER_MasterSignatureP master_sig; 4624 4625 /** 4626 * The private key of the exchange. 4627 */ 4628 struct TALER_ExchangePrivateKeyP exchange_priv; 4629 4630 /** 4631 * The public key of the exchange. 4632 */ 4633 struct TALER_ExchangePublicKeyP exchange_pub; 4634 4635 /** 4636 * When the signing key becomes valid. 4637 */ 4638 struct GNUNET_TIME_Timestamp start_date; 4639 4640 /** 4641 * When the signing key stops being used. 4642 */ 4643 struct GNUNET_TIME_Timestamp expire_date; 4644 4645 /** 4646 * When the signing key becomes invalid for proof. 4647 */ 4648 struct GNUNET_TIME_Timestamp end_date; 4649 }; 4650 4651 4652 /** 4653 * Creates an exchange signing key. 4654 * 4655 * @param signkey the signing key data to fill. 4656 */ 4657 static void 4658 make_exchange_signkey (struct ExchangeSignkeyData *signkey) 4659 { 4660 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 4661 4662 GNUNET_CRYPTO_eddsa_key_create (&signkey->exchange_priv.eddsa_priv); 4663 GNUNET_CRYPTO_eddsa_key_get_public (&signkey->exchange_priv.eddsa_priv, 4664 &signkey->exchange_pub.eddsa_pub); 4665 GNUNET_CRYPTO_eddsa_key_create (&signkey->master_priv.eddsa_priv); 4666 GNUNET_CRYPTO_eddsa_key_get_public (&signkey->master_priv.eddsa_priv, 4667 &signkey->master_pub.eddsa_pub); 4668 signkey->start_date = now; 4669 signkey->expire_date = now; 4670 signkey->end_date = now; 4671 TALER_exchange_offline_signkey_validity_sign ( 4672 &signkey->exchange_pub, 4673 signkey->start_date, 4674 signkey->expire_date, 4675 signkey->end_date, 4676 &signkey->master_priv, 4677 &signkey->master_sig); 4678 } 4679 4680 4681 /** 4682 * A container for deposit data. 4683 */ 4684 struct DepositData 4685 { 4686 /** 4687 * When the deposit was made. 4688 */ 4689 struct GNUNET_TIME_Timestamp timestamp; 4690 4691 /** 4692 * Hash of the associated order's contract terms. 4693 */ 4694 struct TALER_PrivateContractHashP h_contract_terms; 4695 4696 /** 4697 * Public key of the coin that has been deposited. 4698 */ 4699 struct TALER_CoinSpendPublicKeyP coin_pub; 4700 4701 /** 4702 * Signature of the coin that has been deposited. 4703 */ 4704 struct TALER_CoinSpendSignatureP coin_sig; 4705 4706 /** 4707 * URL of the exchange. 4708 */ 4709 const char *exchange_url; 4710 4711 /** 4712 * Value of the coin with fees applied. 4713 */ 4714 struct TALER_Amount amount_with_fee; 4715 4716 /** 4717 * Fee charged for deposit. 4718 */ 4719 struct TALER_Amount deposit_fee; 4720 4721 /** 4722 * Fee to be charged in case of a refund. 4723 */ 4724 struct TALER_Amount refund_fee; 4725 4726 /** 4727 * Fee charged after the money is wired. 4728 */ 4729 struct TALER_Amount wire_fee; 4730 4731 /** 4732 * Hash of the wire details. 4733 */ 4734 struct TALER_MerchantWireHashP h_wire; 4735 4736 /** 4737 * Signature the exchange made on this deposit. 4738 */ 4739 struct TALER_ExchangeSignatureP exchange_sig; 4740 4741 }; 4742 4743 4744 /** 4745 * Generates deposit data for an order. 4746 * 4747 * @param instance the instance to make the deposit to. 4748 * @param account the merchant account to use. 4749 * @param order the order this deposit is for. 4750 * @param signkey the signing key to use. 4751 * @param deposit the deposit data to fill. 4752 */ 4753 static void 4754 make_deposit (const struct InstanceData *instance, 4755 const struct TALER_MERCHANTDB_AccountDetails *account, 4756 const struct OrderData *order, 4757 const struct ExchangeSignkeyData *signkey, 4758 struct DepositData *deposit) 4759 { 4760 struct TALER_CoinSpendPrivateKeyP coin_priv; 4761 struct GNUNET_TIME_Timestamp now; 4762 struct TALER_Amount amount_without_fee; 4763 4764 now = GNUNET_TIME_timestamp_get (); 4765 deposit->timestamp = now; 4766 GNUNET_assert (GNUNET_OK == 4767 TALER_JSON_contract_hash (order->contract, 4768 &deposit->h_contract_terms)); 4769 GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv); 4770 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, 4771 &deposit->coin_pub.eddsa_pub); 4772 deposit->exchange_url = "https://test-exchange/"; 4773 GNUNET_assert (GNUNET_OK == 4774 TALER_string_to_amount ("EUR:50.00", 4775 &deposit->amount_with_fee)); 4776 GNUNET_assert (GNUNET_OK == 4777 TALER_string_to_amount ("EUR:1.00", 4778 &deposit->deposit_fee)); 4779 GNUNET_assert (GNUNET_OK == 4780 TALER_string_to_amount ("EUR:1.50", 4781 &deposit->refund_fee)); 4782 GNUNET_assert (GNUNET_OK == 4783 TALER_string_to_amount ("EUR:2.00", 4784 &deposit->wire_fee)); 4785 GNUNET_assert (0 <= 4786 TALER_amount_subtract (&amount_without_fee, 4787 &deposit->amount_with_fee, 4788 &deposit->deposit_fee)); 4789 deposit->h_wire = account->h_wire; 4790 GNUNET_CRYPTO_random_block (&deposit->exchange_sig, 4791 sizeof (deposit->exchange_sig)); 4792 GNUNET_CRYPTO_random_block (&deposit->coin_sig, 4793 sizeof (deposit->coin_sig)); 4794 } 4795 4796 4797 /** 4798 * Tests inserting an exchange signing key into the database. 4799 * 4800 * @param signkey the signing key to insert. 4801 * @param expected_result the result we expect the database to return. 4802 * @return 0 on success, 1 otherwise. 4803 */ 4804 static int 4805 test_insert_exchange_signkey (const struct ExchangeSignkeyData *signkey, 4806 enum GNUNET_DB_QueryStatus expected_result) 4807 { 4808 TEST_COND_RET_ON_FAIL (expected_result == 4809 TALER_MERCHANTDB_insert_exchange_signing_key (pg, 4810 &signkey->master_pub, 4811 &signkey->exchange_pub 4812 , 4813 signkey->start_date, 4814 signkey->expire_date, 4815 signkey->end_date, 4816 &signkey->master_sig), 4817 "Insert exchange signkey failed\n"); 4818 return 0; 4819 } 4820 4821 4822 /** 4823 * Tests inserting a deposit into the database. 4824 * 4825 * @param instance the instance the deposit was made to. 4826 * @param signkey the signing key used. 4827 * @param deposit the deposit information to insert. 4828 * @param expected_result the result we expect the database to return. 4829 * @return 0 on success, 1 otherwise. 4830 */ 4831 static int 4832 test_insert_deposit (const struct InstanceData *instance, 4833 const struct ExchangeSignkeyData *signkey, 4834 const struct DepositData *deposit, 4835 enum GNUNET_DB_QueryStatus expected_result) 4836 { 4837 uint64_t row; 4838 struct TALER_Amount awf; 4839 4840 GNUNET_assert (0 <= 4841 TALER_amount_subtract (&awf, 4842 &deposit->amount_with_fee, 4843 &deposit->deposit_fee)); 4844 TEST_SET_INSTANCE (instance->instance.id, expected_result); 4845 TEST_COND_RET_ON_FAIL ( 4846 TALER_MERCHANTDB_DCS_SUCCESS == 4847 TALER_MERCHANTDB_insert_deposit_confirmation (pg, 4848 instance->instance.id, 4849 deposit->timestamp, 4850 &deposit->h_contract_terms, 4851 deposit->exchange_url, 4852 deposit->timestamp, 4853 &awf, 4854 &deposit->wire_fee, 4855 &deposit->h_wire, 4856 &deposit->exchange_sig, 4857 &signkey->exchange_pub, 4858 &row), 4859 "Insert deposit confirmation failed\n"); 4860 TEST_COND_RET_ON_FAIL ( 4861 expected_result == 4862 TALER_MERCHANTDB_insert_deposit (pg, 4863 0, 4864 row, 4865 &deposit->coin_pub, 4866 &deposit->coin_sig, 4867 &deposit->amount_with_fee, 4868 &deposit->deposit_fee, 4869 &deposit->refund_fee, 4870 GNUNET_TIME_absolute_get ()), 4871 "Insert deposit failed\n"); 4872 return 0; 4873 } 4874 4875 4876 /** 4877 * Tests that storing a deposit confirmation returns a status that 4878 * identifies the *specific* reason why it could not be stored. 4879 * Collapsing all of them into #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS 4880 * (as we used to) makes the pay handler commit a batch deposit 4881 * without any deposit records, silently losing the money. 4882 * 4883 * @param instance the instance the deposit was made to. 4884 * @param deposit the deposit to derive the (valid) defaults from. 4885 * @param h_contract_terms contract to claim the deposit is for. 4886 * @param h_wire target account to claim the deposit went to. 4887 * @param exchange_pub exchange signing key to use. 4888 * @param wire_fee wire fee to claim was charged. 4889 * @param expected_status status the database should return. 4890 * @return 0 on success, 1 otherwise. 4891 */ 4892 static int 4893 test_insert_deposit_confirmation_status ( 4894 const struct InstanceData *instance, 4895 const struct DepositData *deposit, 4896 const struct TALER_PrivateContractHashP *h_contract_terms, 4897 const struct TALER_MerchantWireHashP *h_wire, 4898 const struct TALER_ExchangePublicKeyP *exchange_pub, 4899 const struct TALER_Amount *wire_fee, 4900 enum TALER_MERCHANTDB_DepositConfirmationStatus expected_status) 4901 { 4902 uint64_t row = 0; 4903 struct TALER_Amount awf; 4904 4905 GNUNET_assert (0 <= 4906 TALER_amount_subtract (&awf, 4907 &deposit->amount_with_fee, 4908 &deposit->deposit_fee)); 4909 TEST_SET_INSTANCE (instance->instance.id, 4910 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 4911 TEST_COND_RET_ON_FAIL ( 4912 expected_status == 4913 TALER_MERCHANTDB_insert_deposit_confirmation (pg, 4914 instance->instance.id, 4915 deposit->timestamp, 4916 h_contract_terms, 4917 deposit->exchange_url, 4918 deposit->timestamp, 4919 &awf, 4920 wire_fee, 4921 h_wire, 4922 &deposit->exchange_sig, 4923 exchange_pub, 4924 &row), 4925 "Insert deposit confirmation returned the wrong status\n"); 4926 return 0; 4927 } 4928 4929 4930 /** 4931 * Closure for testing deposit lookup 4932 */ 4933 struct TestLookupDeposits_Closure 4934 { 4935 /** 4936 * Number of deposits to compare to 4937 */ 4938 unsigned int deposits_to_cmp_length; 4939 4940 /** 4941 * Pointer to array of deposit data 4942 */ 4943 const struct DepositData *deposits_to_cmp; 4944 4945 /** 4946 * Pointer to array of number of matches per deposit 4947 */ 4948 unsigned int *results_matching; 4949 4950 /** 4951 * Total number of results returned 4952 */ 4953 unsigned int results_length; 4954 }; 4955 4956 4957 /** 4958 * Called after 'test_lookup_deposits'. 4959 * 4960 * @param cls pointer to the test lookup closure. 4961 * @param coin_pub public key of the coin deposited. 4962 * @param amount_with_fee amount of the deposit with fees. 4963 * @param deposit_fee fee charged for the deposit. 4964 * @param refund_fee fee charged in case of a refund. 4965 * @param wire_fee wire transfer fee to be paid 4966 */ 4967 static void 4968 lookup_deposits_cb (void *cls, 4969 const char *exchange_url, 4970 const struct TALER_CoinSpendPublicKeyP *coin_pub, 4971 const struct TALER_Amount *amount_with_fee, 4972 const struct TALER_Amount *deposit_fee, 4973 const struct TALER_Amount *refund_fee, 4974 const struct TALER_Amount *wire_fee) 4975 { 4976 struct TestLookupDeposits_Closure *cmp = cls; 4977 4978 if (NULL == cmp) 4979 return; 4980 cmp->results_length += 1; 4981 for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i) 4982 { 4983 if ((GNUNET_OK == 4984 TALER_amount_cmp_currency ( 4985 &cmp->deposits_to_cmp[i].amount_with_fee, 4986 amount_with_fee)) && 4987 (0 == 4988 TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee, 4989 amount_with_fee)) && 4990 (GNUNET_OK == 4991 TALER_amount_cmp_currency ( 4992 &cmp->deposits_to_cmp[i].deposit_fee, 4993 deposit_fee)) && 4994 (0 == 4995 TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee, 4996 deposit_fee)) && 4997 (GNUNET_OK == 4998 TALER_amount_cmp_currency ( 4999 &cmp->deposits_to_cmp[i].refund_fee, 5000 refund_fee)) && 5001 (0 == 5002 TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee, 5003 refund_fee))) 5004 { 5005 cmp->results_matching[i] += 1; 5006 } 5007 5008 } 5009 } 5010 5011 5012 /** 5013 * Tests looking up deposits from the database. 5014 * 5015 * @param instance the instance to lookup deposits from. 5016 * @param h_contract_terms the contract terms that the deposits should have. 5017 * @param deposits_length length of @e deposits. 5018 * @param deposits the deposits we expect to be found. 5019 * @return 0 on success, 1 otherwise. 5020 */ 5021 static int 5022 test_lookup_deposits (const struct InstanceData *instance, 5023 const struct TALER_PrivateContractHashP *h_contract_terms, 5024 unsigned int deposits_length, 5025 const struct DepositData *deposits) 5026 { 5027 unsigned int results_matching[GNUNET_NZL (deposits_length)]; 5028 struct TestLookupDeposits_Closure cmp = { 5029 .deposits_to_cmp_length = deposits_length, 5030 .deposits_to_cmp = deposits, 5031 .results_matching = results_matching, 5032 .results_length = 0 5033 }; 5034 memset (results_matching, 5035 0, 5036 sizeof (unsigned int) * deposits_length); 5037 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 5038 TEST_COND_RET_ON_FAIL (0 <= 5039 TALER_MERCHANTDB_iterate_deposits (pg, 5040 instance->instance.id, 5041 h_contract_terms, 5042 &lookup_deposits_cb, 5043 &cmp), 5044 "Lookup deposits failed\n"); 5045 if (deposits_length != cmp.results_length) 5046 { 5047 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5048 "Lookup deposits failed: incorrect number of results returned (%d)\n", 5049 cmp.results_length); 5050 return 1; 5051 } 5052 for (unsigned int i = 0; deposits_length > i; ++i) 5053 { 5054 if (cmp.results_matching[i] != 1) 5055 { 5056 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5057 "Lookup deposits failed: mismatched data\n"); 5058 return 1; 5059 } 5060 } 5061 return 0; 5062 } 5063 5064 5065 /** 5066 * Called after 'test_lookup_deposits_contract_and_coin'. 5067 * 5068 * @param cls pointer to the test lookup closure. 5069 * @param exchange_url URL to the exchange 5070 * @param amount_with_fee amount of the deposit with fees. 5071 * @param deposit_fee fee charged for the deposit. 5072 * @param refund_fee fee charged in case of a refund. 5073 * @param wire_fee fee charged when the money is wired. 5074 * @param h_wire hash of the wire transfer details. 5075 * @param deposit_timestamp when the deposit was made. 5076 * @param refund_deadline deadline for refunding the deposit. 5077 * @param exchange_sig signature the exchange made on the deposit. 5078 * @param exchange_pub public key of the exchange. 5079 */ 5080 static void 5081 lookup_deposits_contract_coin_cb ( 5082 void *cls, 5083 const char *exchange_url, 5084 const struct TALER_Amount *amount_with_fee, 5085 const struct TALER_Amount *deposit_fee, 5086 const struct TALER_Amount *refund_fee, 5087 const struct TALER_Amount *wire_fee, 5088 const struct TALER_MerchantWireHashP *h_wire, 5089 struct GNUNET_TIME_Timestamp deposit_timestamp, 5090 struct GNUNET_TIME_Timestamp refund_deadline, 5091 const struct TALER_ExchangeSignatureP *exchange_sig, 5092 const struct TALER_ExchangePublicKeyP *exchange_pub) 5093 { 5094 struct TestLookupDeposits_Closure *cmp = cls; 5095 5096 if (NULL == cmp) 5097 return; 5098 cmp->results_length++; 5099 for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i) 5100 { 5101 if ((GNUNET_TIME_timestamp_cmp (cmp->deposits_to_cmp[i].timestamp, 5102 ==, 5103 deposit_timestamp)) && 5104 (0 == strcmp (cmp->deposits_to_cmp[i].exchange_url, 5105 exchange_url)) && 5106 (GNUNET_OK == TALER_amount_cmp_currency ( 5107 &cmp->deposits_to_cmp[i].amount_with_fee, 5108 amount_with_fee)) && 5109 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee, 5110 amount_with_fee)) && 5111 (GNUNET_OK == TALER_amount_cmp_currency ( 5112 &cmp->deposits_to_cmp[i].deposit_fee, 5113 deposit_fee)) && 5114 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee, 5115 deposit_fee)) && 5116 (GNUNET_OK == TALER_amount_cmp_currency ( 5117 &cmp->deposits_to_cmp[i].refund_fee, 5118 refund_fee)) && 5119 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee, 5120 refund_fee)) && 5121 (GNUNET_OK == TALER_amount_cmp_currency ( 5122 &cmp->deposits_to_cmp[i].wire_fee, 5123 wire_fee)) && 5124 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].wire_fee, 5125 wire_fee)) && 5126 (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire, 5127 h_wire)) && 5128 (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].exchange_sig, 5129 exchange_sig))) 5130 { 5131 cmp->results_matching[i]++; 5132 } 5133 } 5134 } 5135 5136 5137 /** 5138 * Tests lookup of deposits by contract and coin. 5139 * 5140 * @param instance the instance to lookup from. 5141 * @param h_contract the contract terms the deposits should have. 5142 * @param coin_pub the public key of the coin the deposits should have. 5143 * @param deposits_length length of @e deposits. 5144 * @param deposits the deposits the db is expected to find. 5145 * @return 0 on success, 1 otherwise. 5146 */ 5147 static int 5148 test_lookup_deposits_contract_and_coin ( 5149 const struct InstanceData *instance, 5150 const struct TALER_PrivateContractHashP *h_contract, 5151 const struct TALER_CoinSpendPublicKeyP *coin_pub, 5152 unsigned int deposits_length, 5153 const struct DepositData *deposits) 5154 { 5155 unsigned int results_matching[deposits_length]; 5156 struct TestLookupDeposits_Closure cmp = { 5157 .deposits_to_cmp_length = deposits_length, 5158 .deposits_to_cmp = deposits, 5159 .results_matching = results_matching, 5160 .results_length = 0 5161 }; 5162 memset (results_matching, 5163 0, 5164 sizeof (unsigned int) * deposits_length); 5165 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 5166 TEST_COND_RET_ON_FAIL ( 5167 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 5168 TALER_MERCHANTDB_iterate_deposits_by_contract_and_coin ( 5169 pg, 5170 instance->instance.id, 5171 h_contract, 5172 coin_pub, 5173 &lookup_deposits_contract_coin_cb, 5174 &cmp), 5175 "Lookup deposits by contract and coin failed\n"); 5176 if (deposits_length != cmp.results_length) 5177 { 5178 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5179 "Lookup deposits failed: incorrect number of results returned\n"); 5180 return 1; 5181 } 5182 for (unsigned int i = 0; deposits_length > i; ++i) 5183 { 5184 if (cmp.results_matching[i] != 1) 5185 { 5186 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5187 "Lookup deposits failed: mismatched data\n"); 5188 return 1; 5189 } 5190 } 5191 return 0; 5192 } 5193 5194 5195 /** 5196 * Called after 'test_lookup_deposits_by_order'. 5197 * 5198 * @param cls pointer to the test lookup closure. 5199 * @param deposit_serial row number of the deposit in the database. 5200 * @param exchange_url URL to the exchange 5201 * @param h_wire hash of the wire transfer details. 5202 * @param deposit_timestamp when was the deposit made 5203 * @param amount_with_fee amount of the deposit with fees. 5204 * @param deposit_fee fee charged for the deposit. 5205 * @param coin_pub public key of the coin deposited. 5206 */ 5207 static void 5208 lookup_deposits_order_cb (void *cls, 5209 uint64_t deposit_serial, 5210 const char *exchange_url, 5211 const struct TALER_MerchantWireHashP *h_wire, 5212 struct GNUNET_TIME_Timestamp deposit_timestamp, 5213 const struct TALER_Amount *amount_with_fee, 5214 const struct TALER_Amount *deposit_fee, 5215 const struct TALER_CoinSpendPublicKeyP *coin_pub) 5216 { 5217 struct TestLookupDeposits_Closure *cmp = cls; 5218 5219 if (NULL == cmp) 5220 return; 5221 cmp->results_length += 1; 5222 for (unsigned int i = 0; i < cmp->deposits_to_cmp_length; ++i) 5223 { 5224 if ((0 == strcmp (cmp->deposits_to_cmp[i].exchange_url, 5225 exchange_url)) && 5226 (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire, 5227 h_wire)) && 5228 (GNUNET_OK == TALER_amount_cmp_currency ( 5229 &cmp->deposits_to_cmp[i].amount_with_fee, 5230 amount_with_fee)) && 5231 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee, 5232 amount_with_fee)) && 5233 (GNUNET_OK == TALER_amount_cmp_currency ( 5234 &cmp->deposits_to_cmp[i].deposit_fee, 5235 deposit_fee)) && 5236 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee, 5237 deposit_fee)) && 5238 (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].coin_pub, 5239 coin_pub))) 5240 cmp->results_matching[i] += 1; 5241 } 5242 } 5243 5244 5245 /** 5246 * Tests looking up deposits by associated order. 5247 * 5248 * @param order_serial row number of the order to lookup for. 5249 * @param deposits_length length of @e deposits_length. 5250 * @param deposits the deposits we expect to be found. 5251 * @return 0 on success, 1 otherwise. 5252 */ 5253 static int 5254 test_lookup_deposits_by_order (uint64_t order_serial, 5255 unsigned int deposits_length, 5256 const struct DepositData *deposits) 5257 { 5258 unsigned int results_matching[deposits_length]; 5259 struct TestLookupDeposits_Closure cmp = { 5260 .deposits_to_cmp_length = deposits_length, 5261 .deposits_to_cmp = deposits, 5262 .results_matching = results_matching, 5263 .results_length = 0 5264 }; 5265 memset (results_matching, 5266 0, 5267 sizeof (unsigned int) * deposits_length); 5268 if (deposits_length != 5269 TALER_MERCHANTDB_iterate_deposits_by_order (pg, 5270 order_serial, 5271 &lookup_deposits_order_cb, 5272 &cmp)) 5273 { 5274 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5275 "Lookup deposits by order failed\n"); 5276 return 1; 5277 } 5278 if (deposits_length != cmp.results_length) 5279 { 5280 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5281 "Lookup deposits by order failed: incorrect number of results\n"); 5282 return 1; 5283 } 5284 for (unsigned int i = 0; i < deposits_length; ++i) 5285 { 5286 if (1 != cmp.results_matching[i]) 5287 { 5288 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5289 "Lookup deposits by order failed: mismatched data\n"); 5290 return 1; 5291 } 5292 } 5293 return 0; 5294 } 5295 5296 5297 /** 5298 * Container for information for looking up the row number of a deposit. 5299 */ 5300 struct LookupDepositSerial_Closure 5301 { 5302 /** 5303 * The deposit we're looking for. 5304 */ 5305 const struct DepositData *deposit; 5306 5307 /** 5308 * The serial found. 5309 */ 5310 uint64_t serial; 5311 }; 5312 5313 5314 /** 5315 * Called after 'get_deposit_serial'. 5316 * 5317 * @param cls pointer to the test lookup closure. 5318 * @param deposit_serial row number of the deposit in the database. 5319 * @param exchange_url URL to the exchange 5320 * @param h_wire hash of the wire transfer details. 5321 * @param deposit_timestamp when was the deposit made. 5322 * @param amount_with_fee amount of the deposit with fees. 5323 * @param deposit_fee fee charged for the deposit. 5324 * @param coin_pub public key of the coin deposited. 5325 */ 5326 static void 5327 get_deposit_serial_cb (void *cls, 5328 uint64_t deposit_serial, 5329 const char *exchange_url, 5330 const struct TALER_MerchantWireHashP *h_wire, 5331 struct GNUNET_TIME_Timestamp deposit_timestamp, 5332 const struct TALER_Amount *amount_with_fee, 5333 const struct TALER_Amount *deposit_fee, 5334 const struct TALER_CoinSpendPublicKeyP *coin_pub) 5335 { 5336 struct LookupDepositSerial_Closure *lookup_cls = cls; 5337 5338 (void) deposit_timestamp; 5339 if (NULL == lookup_cls) 5340 return; 5341 if ((0 == strcmp (lookup_cls->deposit->exchange_url, 5342 exchange_url)) && 5343 (0 == GNUNET_memcmp (&lookup_cls->deposit->h_wire, 5344 h_wire)) && 5345 (GNUNET_OK == TALER_amount_cmp_currency ( 5346 &lookup_cls->deposit->amount_with_fee, 5347 amount_with_fee)) && 5348 (0 == TALER_amount_cmp (&lookup_cls->deposit->amount_with_fee, 5349 amount_with_fee)) && 5350 (GNUNET_OK == TALER_amount_cmp_currency ( 5351 &lookup_cls->deposit->deposit_fee, 5352 deposit_fee)) && 5353 (0 == TALER_amount_cmp (&lookup_cls->deposit->deposit_fee, 5354 deposit_fee)) && 5355 (0 == GNUNET_memcmp (&lookup_cls->deposit->coin_pub, 5356 coin_pub))) 5357 lookup_cls->serial = deposit_serial; 5358 } 5359 5360 5361 /** 5362 * Convenience function to retrieve the row number of a deposit in the database. 5363 * 5364 * @param instance the instance to get deposits from. 5365 * @param order the order associated with the deposit. 5366 * @param deposit the deposit to lookup the serial for. 5367 * @return the row number of the deposit. 5368 */ 5369 static uint64_t 5370 get_deposit_serial (const struct InstanceData *instance, 5371 const struct OrderData *order, 5372 const struct DepositData *deposit) 5373 { 5374 uint64_t order_serial = get_order_serial (instance, 5375 order); 5376 struct LookupDepositSerial_Closure lookup_cls = { 5377 .deposit = deposit, 5378 .serial = 0 5379 }; 5380 5381 GNUNET_assert (0 < 5382 TALER_MERCHANTDB_iterate_deposits_by_order (pg, 5383 order_serial, 5384 &get_deposit_serial_cb, 5385 &lookup_cls)); 5386 GNUNET_assert (0 != lookup_cls.serial); 5387 5388 return lookup_cls.serial; 5389 } 5390 5391 5392 /** 5393 * Closure for deposit tests. 5394 */ 5395 struct TestDeposits_Closure 5396 { 5397 /** 5398 * The instance settings 5399 */ 5400 struct InstanceData instance; 5401 5402 /** 5403 * The merchant account 5404 */ 5405 struct TALER_MERCHANTDB_AccountDetails account; 5406 5407 /** 5408 * The exchange signing key 5409 */ 5410 struct ExchangeSignkeyData signkey; 5411 5412 /** 5413 * The order data 5414 */ 5415 struct OrderData orders[2]; 5416 5417 /** 5418 * The array of deposits 5419 */ 5420 struct DepositData deposits[3]; 5421 }; 5422 5423 5424 /** 5425 * Initializes data for testing deposits. 5426 * 5427 * @param cls the test closure to initialize. 5428 */ 5429 static void 5430 pre_test_deposits (struct TestDeposits_Closure *cls) 5431 { 5432 /* Instance */ 5433 make_instance ("test_inst_deposits", 5434 &cls->instance); 5435 5436 /* Account */ 5437 make_account (&cls->account); 5438 cls->account.instance_id = cls->instance.instance.id; 5439 /* Signing key */ 5440 make_exchange_signkey (&cls->signkey); 5441 5442 /* Order */ 5443 make_order ("test_deposits_od_1", 5444 &cls->orders[0]); 5445 make_order ("test_deposits_od_2", 5446 &cls->orders[1]); 5447 5448 /* Deposit */ 5449 make_deposit (&cls->instance, 5450 &cls->account, 5451 &cls->orders[0], 5452 &cls->signkey, 5453 &cls->deposits[0]); 5454 make_deposit (&cls->instance, 5455 &cls->account, 5456 &cls->orders[0], 5457 &cls->signkey, 5458 &cls->deposits[1]); 5459 GNUNET_assert (GNUNET_OK == 5460 TALER_string_to_amount ("EUR:29.00", 5461 &cls->deposits[1].amount_with_fee)); 5462 make_deposit (&cls->instance, 5463 &cls->account, 5464 &cls->orders[1], 5465 &cls->signkey, 5466 &cls->deposits[2]); 5467 } 5468 5469 5470 /** 5471 * Cleans up memory after testing deposits. 5472 * 5473 * @param cls the closure containing memory to free. 5474 */ 5475 static void 5476 post_test_deposits (struct TestDeposits_Closure *cls) 5477 { 5478 free_instance_data (&cls->instance); 5479 json_decref (cls->orders[0].contract); 5480 json_decref (cls->orders[1].contract); 5481 } 5482 5483 5484 /** 5485 * Counts the pending deposits we are called for. 5486 * 5487 * @param cls a `unsigned int *` counter 5488 */ 5489 static void 5490 count_pending_deposits_cb ( 5491 void *cls, 5492 uint64_t deposit_serial, 5493 struct GNUNET_TIME_Absolute wire_deadline, 5494 struct GNUNET_TIME_Absolute retry_time, 5495 const struct TALER_PrivateContractHashP *h_contract_terms, 5496 const struct TALER_MerchantPrivateKeyP *merchant_priv, 5497 const char *instance_id, 5498 const struct TALER_MerchantWireHashP *h_wire, 5499 const struct TALER_Amount *amount_with_fee, 5500 const struct TALER_Amount *deposit_fee, 5501 const struct TALER_CoinSpendPublicKeyP *coin_pub) 5502 { 5503 unsigned int *count = cls; 5504 5505 (void) deposit_serial; 5506 (void) wire_deadline; 5507 (void) retry_time; 5508 (void) h_contract_terms; 5509 (void) merchant_priv; 5510 (void) instance_id; 5511 (void) h_wire; 5512 (void) amount_with_fee; 5513 (void) deposit_fee; 5514 (void) coin_pub; 5515 (*count)++; 5516 } 5517 5518 5519 /** 5520 * Tests that pending deposits of an instance whose private key was 5521 * deleted do not break the iteration. 5522 * 5523 * Regression test: 'merchant_priv' is nullable and DELETE 5524 * /management/instances/$ID sets it to NULL, but the result spec did 5525 * not allow NULL. Extraction then failed with a hard error, which 5526 * makes taler-merchant-depositcheck shut down merchant-wide and 5527 * crash-loop on the very same row. 5528 * 5529 * @param instance the instance whose private key gets deleted. 5530 * @param exchange_url the exchange the deposits were made to. 5531 * @return 0 on success, 1 otherwise. 5532 */ 5533 static int 5534 test_pending_deposits_without_private_key (const struct InstanceData *instance, 5535 const char *exchange_url) 5536 { 5537 unsigned int count = 0; 5538 5539 /* Before the deletion the deposits are pending. */ 5540 TEST_COND_RET_ON_FAIL (0 < 5541 TALER_MERCHANTDB_iterate_pending_deposits ( 5542 pg, 5543 exchange_url, 5544 1024, 5545 true, 5546 &count_pending_deposits_cb, 5547 &count), 5548 "Iterating over pending deposits failed\n"); 5549 TEST_COND_RET_ON_FAIL (0 < count, 5550 "No pending deposits found\n"); 5551 TEST_RET_ON_FAIL (test_delete_instance_private_key ( 5552 instance, 5553 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5554 /* Now they are unsettleable, but that must not be an error. */ 5555 count = 0; 5556 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 5557 TALER_MERCHANTDB_iterate_pending_deposits ( 5558 pg, 5559 exchange_url, 5560 1024, 5561 true, 5562 &count_pending_deposits_cb, 5563 &count), 5564 "Pending deposits of an instance without a private key" 5565 " were not skipped\n"); 5566 TEST_COND_RET_ON_FAIL (0 == count, 5567 "Deposit of an instance without a private key" 5568 " was returned\n"); 5569 return 0; 5570 } 5571 5572 5573 /** 5574 * Runs tests for deposits. 5575 * 5576 * @param cls the closure containing test data. 5577 * @return 0 on success, 1 otherwise. 5578 */ 5579 static int 5580 run_test_deposits (struct TestDeposits_Closure *cls) 5581 { 5582 /* Insert the instance */ 5583 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 5584 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5585 /* Insert an account */ 5586 TEST_RET_ON_FAIL (test_insert_account (&cls->instance, 5587 &cls->account, 5588 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5589 /* Insert a signing key */ 5590 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 5591 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5592 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 5593 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 5594 /* Insert an order */ 5595 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 5596 &cls->orders[0], 5597 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5598 /* Insert contract terms */ 5599 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 5600 &cls->orders[0], 5601 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5602 /* Test inserting a deposit */ 5603 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 5604 &cls->signkey, 5605 &cls->deposits[0], 5606 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5607 /* Test double inserts are idempotent */ 5608 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 5609 &cls->signkey, 5610 &cls->deposits[0], 5611 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 5612 /* Each reason for not storing a deposit confirmation must be 5613 reported distinctly, and none of them may look like success. */ 5614 { 5615 struct TALER_PrivateContractHashP bogus_h_contract; 5616 struct TALER_MerchantWireHashP bogus_h_wire; 5617 struct TALER_ExchangePublicKeyP bogus_exchange_pub; 5618 struct TALER_Amount other_wire_fee; 5619 5620 GNUNET_CRYPTO_random_block (&bogus_h_contract, 5621 sizeof (bogus_h_contract)); 5622 GNUNET_CRYPTO_random_block (&bogus_h_wire, 5623 sizeof (bogus_h_wire)); 5624 GNUNET_CRYPTO_random_block (&bogus_exchange_pub, 5625 sizeof (bogus_exchange_pub)); 5626 GNUNET_assert (GNUNET_OK == 5627 TALER_string_to_amount ("EUR:3.00", 5628 &other_wire_fee)); 5629 /* unknown merchant account */ 5630 TEST_RET_ON_FAIL (test_insert_deposit_confirmation_status ( 5631 &cls->instance, 5632 &cls->deposits[0], 5633 &cls->deposits[0].h_contract_terms, 5634 &bogus_h_wire, 5635 &cls->signkey.exchange_pub, 5636 &cls->deposits[0].wire_fee, 5637 TALER_MERCHANTDB_DCS_NO_ACCOUNT)); 5638 /* unknown exchange signing key */ 5639 TEST_RET_ON_FAIL (test_insert_deposit_confirmation_status ( 5640 &cls->instance, 5641 &cls->deposits[0], 5642 &cls->deposits[0].h_contract_terms, 5643 &cls->deposits[0].h_wire, 5644 &bogus_exchange_pub, 5645 &cls->deposits[0].wire_fee, 5646 TALER_MERCHANTDB_DCS_NO_SIGNKEY)); 5647 /* unknown order */ 5648 TEST_RET_ON_FAIL (test_insert_deposit_confirmation_status ( 5649 &cls->instance, 5650 &cls->deposits[0], 5651 &bogus_h_contract, 5652 &cls->deposits[0].h_wire, 5653 &cls->signkey.exchange_pub, 5654 &cls->deposits[0].wire_fee, 5655 TALER_MERCHANTDB_DCS_NO_ORDER)); 5656 /* conflicting deposit confirmation (different wire fee) */ 5657 TEST_RET_ON_FAIL (test_insert_deposit_confirmation_status ( 5658 &cls->instance, 5659 &cls->deposits[0], 5660 &cls->deposits[0].h_contract_terms, 5661 &cls->deposits[0].h_wire, 5662 &cls->signkey.exchange_pub, 5663 &other_wire_fee, 5664 TALER_MERCHANTDB_DCS_CONFLICT)); 5665 } 5666 /* Test lookup deposits */ 5667 TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance, 5668 &cls->deposits[0].h_contract_terms, 5669 1, 5670 cls->deposits)); 5671 TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance, 5672 &cls->deposits[2].h_contract_terms, 5673 0, 5674 NULL)); 5675 /* Test lookup deposits by contract and coins */ 5676 TEST_RET_ON_FAIL (test_lookup_deposits_contract_and_coin ( 5677 &cls->instance, 5678 &cls->deposits[0].h_contract_terms, 5679 &cls->deposits[0].coin_pub, 5680 1, 5681 cls->deposits)); 5682 /* Test multiple deposits */ 5683 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 5684 &cls->signkey, 5685 &cls->deposits[1], 5686 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5687 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 5688 &cls->orders[1], 5689 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5690 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 5691 &cls->orders[1], 5692 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5693 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 5694 &cls->signkey, 5695 &cls->deposits[2], 5696 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5697 TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance, 5698 &cls->deposits[0].h_contract_terms, 5699 2, 5700 cls->deposits)); 5701 TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance, 5702 &cls->deposits[2].h_contract_terms, 5703 1, 5704 &cls->deposits[2])); 5705 /* Test lookup deposits by order */ 5706 { 5707 uint64_t order_serial = get_order_serial (&cls->instance, 5708 &cls->orders[0]); 5709 TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial, 5710 2, 5711 cls->deposits)); 5712 order_serial = get_order_serial (&cls->instance, 5713 &cls->orders[1]); 5714 TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial, 5715 1, 5716 &cls->deposits[2])); 5717 } 5718 /* Must be last: deletes the private key of the instance. */ 5719 TEST_RET_ON_FAIL (test_pending_deposits_without_private_key ( 5720 &cls->instance, 5721 cls->deposits[0].exchange_url)); 5722 return 0; 5723 } 5724 5725 5726 /** 5727 * Handles functionality for testing deposits. 5728 * 5729 * @return 0 on success, 1 otherwise. 5730 */ 5731 static int 5732 test_deposits (void) 5733 { 5734 struct TestDeposits_Closure test_cls; 5735 int test_result; 5736 5737 pre_test_deposits (&test_cls); 5738 test_result = run_test_deposits (&test_cls); 5739 post_test_deposits (&test_cls); 5740 return test_result; 5741 } 5742 5743 5744 /* *********** Transfers ********** */ 5745 5746 5747 /** 5748 * Container for wire fee data for an exchange. 5749 */ 5750 struct WireFeeData 5751 { 5752 /** 5753 * The method used. 5754 */ 5755 const char *wire_method; 5756 5757 /** 5758 * Hash of the wire method. 5759 */ 5760 struct GNUNET_HashCode h_wire_method; 5761 5762 /** 5763 * Wire fees charged. 5764 */ 5765 struct TALER_WireFeeSet fees; 5766 5767 /** 5768 * Start date of the wire fee. 5769 */ 5770 struct GNUNET_TIME_Timestamp wire_fee_start; 5771 5772 /** 5773 * End date of the wire fee. 5774 */ 5775 struct GNUNET_TIME_Timestamp wire_fee_end; 5776 5777 /** 5778 * Signature on the wire fee. 5779 */ 5780 struct TALER_MasterSignatureP fee_sig; 5781 }; 5782 5783 5784 /** 5785 * Creates data for an exchange wire fee. 5786 * 5787 * @param signkey the exchange signing key data. 5788 * @param wire_fee where to store the wire fee data. 5789 */ 5790 static void 5791 make_wire_fee (const struct ExchangeSignkeyData *signkey, 5792 struct WireFeeData *wire_fee) 5793 { 5794 wire_fee->wire_method = "wire-method"; 5795 GNUNET_CRYPTO_hash (wire_fee->wire_method, 5796 strlen (wire_fee->wire_method) + 1, 5797 &wire_fee->h_wire_method); 5798 GNUNET_assert (GNUNET_OK == 5799 TALER_string_to_amount ("EUR:0.49", 5800 &wire_fee->fees.wire)); 5801 GNUNET_assert (GNUNET_OK == 5802 TALER_string_to_amount ("EUR:0.49", 5803 &wire_fee->fees.closing)); 5804 wire_fee->wire_fee_start = GNUNET_TIME_timestamp_get (); 5805 wire_fee->wire_fee_end = GNUNET_TIME_relative_to_timestamp ( 5806 GNUNET_TIME_UNIT_MONTHS); 5807 TALER_exchange_offline_wire_fee_sign ( 5808 wire_fee->wire_method, 5809 wire_fee->wire_fee_start, 5810 wire_fee->wire_fee_end, 5811 &wire_fee->fees, 5812 &signkey->master_priv, 5813 &wire_fee->fee_sig); 5814 } 5815 5816 5817 /** 5818 * Container for wire transfer data. 5819 */ 5820 struct TransferData 5821 { 5822 /** 5823 * Id of the transfer. 5824 */ 5825 struct TALER_WireTransferIdentifierRawP wtid; 5826 5827 /** 5828 * The main data for the transfer. 5829 */ 5830 struct TALER_EXCHANGE_TransferData data; 5831 5832 /** 5833 * URL to the exchange the transfer was made through. 5834 */ 5835 const char *exchange_url; 5836 5837 /** 5838 * How much the fee for the deposit was. 5839 */ 5840 struct TALER_Amount deposit_fee; 5841 5842 /** 5843 * Whether the transfer has been confirmed. 5844 */ 5845 bool confirmed; 5846 5847 /** 5848 * Whether the transfer has been verified. 5849 */ 5850 bool verified; 5851 }; 5852 5853 5854 /** 5855 * Creates a transfer for use with testing. 5856 * 5857 * @param deposits_length length of @e deposits. 5858 * @param deposits list of deposits to combine into one transfer. 5859 * @param transfer where to write the transfer data. 5860 */ 5861 static void 5862 make_transfer (const struct ExchangeSignkeyData *signkey, 5863 unsigned int deposits_length, 5864 const struct DepositData deposits[static deposits_length], 5865 struct TransferData *transfer) 5866 { 5867 struct TALER_TrackTransferDetails *details = NULL; 5868 5869 GNUNET_CRYPTO_random_block (&transfer->wtid, 5870 sizeof (struct TALER_WireTransferIdentifierRawP)); 5871 transfer->exchange_url = deposits[0].exchange_url; 5872 transfer->verified = false; 5873 transfer->confirmed = false; 5874 transfer->data.details_length = 0; 5875 GNUNET_assert (GNUNET_OK == 5876 TALER_amount_set_zero (deposits[0].amount_with_fee.currency, 5877 &transfer->data.total_amount)); 5878 GNUNET_assert (GNUNET_OK == 5879 TALER_amount_set_zero (deposits[0].amount_with_fee.currency, 5880 &transfer->deposit_fee)); 5881 for (unsigned int i = 0; i < deposits_length; ++i) 5882 { 5883 GNUNET_array_grow (details, 5884 transfer->data.details_length, 5885 i + 1); 5886 details[i].h_contract_terms = deposits[i].h_contract_terms; 5887 details[i].coin_pub = deposits[i].coin_pub; 5888 details[i].coin_value = deposits[i].amount_with_fee; 5889 details[i].coin_fee = deposits[i].deposit_fee; 5890 GNUNET_assert (0 <= 5891 TALER_amount_add (&transfer->data.total_amount, 5892 &transfer->data.total_amount, 5893 &deposits[i].amount_with_fee)); 5894 GNUNET_assert (0 <= 5895 TALER_amount_add (&transfer->deposit_fee, 5896 &transfer->deposit_fee, 5897 &deposits[i].deposit_fee)); 5898 } 5899 transfer->data.exchange_pub = signkey->exchange_pub; 5900 transfer->data.execution_time = GNUNET_TIME_timestamp_get (); 5901 transfer->data.details = details; 5902 GNUNET_assert (GNUNET_OK == 5903 TALER_string_to_amount ("EUR:0.50", 5904 &transfer->data.wire_fee)); 5905 } 5906 5907 5908 /** 5909 * Closure for testing 'iterate_transfer_summaries' 5910 */ 5911 struct TestLookupTransferSummary_Closure 5912 { 5913 /** 5914 * Id of the order the transfer was made for. 5915 */ 5916 const char *order_id; 5917 5918 /** 5919 * The value of the deposit made. 5920 */ 5921 const struct TALER_Amount *deposit_value; 5922 5923 /** 5924 * The fee on the deposit made. 5925 */ 5926 const struct TALER_Amount *deposit_fee; 5927 5928 /** 5929 * 0 if the comparison is true, 1 if false. 5930 */ 5931 int result; 5932 }; 5933 5934 5935 /** 5936 * Called after 'test_lookup_transfer_summary'. 5937 * 5938 * @param cls pointer to 'TestLookupTransferSummary_Closure'. 5939 * @param order_id id of the order the transfer was made for. 5940 * @param deposit_value the value of the deposit made. 5941 * @param deposit_fee the fee on the deposit made. 5942 */ 5943 static void 5944 lookup_transfer_summary_cb (void *cls, 5945 const char *order_id, 5946 const struct TALER_Amount *deposit_value, 5947 const struct TALER_Amount *deposit_fee) 5948 { 5949 struct TestLookupTransferSummary_Closure *cmp = cls; 5950 if (NULL == cmp) 5951 return; 5952 if ((0 == strcmp (cmp->order_id, 5953 order_id)) && 5954 (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_value, 5955 deposit_value)) && 5956 (0 == TALER_amount_cmp (cmp->deposit_value, 5957 deposit_value)) && 5958 (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_fee, 5959 deposit_fee)) && 5960 (0 == TALER_amount_cmp (cmp->deposit_fee, 5961 deposit_fee))) 5962 cmp->result = 0; 5963 else 5964 cmp->result = 1; 5965 } 5966 5967 5968 /** 5969 * Tests looking up a transfer's summary. 5970 * 5971 * @param exchange_url url to the exchange for the transfer. 5972 * @param wtid identifier of the transfer. 5973 * @param expected_order_id the id of the order associated with the transfer. 5974 * @param expected_deposit_value the amount of the deposit made for the transfer. 5975 * @param expected_deposit_fee the fee on the deposit made for the transfer. 5976 * @return 1 on success, 0 otherwise. 5977 */ 5978 static int 5979 test_lookup_transfer_summary ( 5980 const char *exchange_url, 5981 const struct TALER_WireTransferIdentifierRawP *wtid, 5982 const char *expected_order_id, 5983 const struct TALER_Amount *expected_deposit_value, 5984 const struct TALER_Amount *expected_deposit_fee) 5985 { 5986 struct TestLookupTransferSummary_Closure cmp = { 5987 .order_id = expected_order_id, 5988 .deposit_value = expected_deposit_value, 5989 .deposit_fee = expected_deposit_fee, 5990 .result = 0 5991 }; 5992 if (1 != TALER_MERCHANTDB_iterate_transfer_summaries (pg, 5993 exchange_url, 5994 wtid, 5995 &lookup_transfer_summary_cb, 5996 &cmp)) 5997 { 5998 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5999 "Lookup transfer summary failed\n"); 6000 return 1; 6001 } 6002 if (0 != cmp.result) 6003 { 6004 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6005 "Lookup transfer summary failed: mismatched data\n"); 6006 return 1; 6007 } 6008 return 0; 6009 } 6010 6011 6012 /** 6013 * Closure for testing 'iterate_transfer_details'. 6014 */ 6015 struct TestLookupTransferDetails_Closure 6016 { 6017 /** 6018 * Length of @e details_to_cmp. 6019 */ 6020 unsigned int details_to_cmp_length; 6021 6022 /** 6023 * The details we expect to find. 6024 */ 6025 const struct TALER_TrackTransferDetails *details_to_cmp; 6026 6027 /** 6028 * Number of results matching each detail in @e details_to_cmp. 6029 */ 6030 unsigned int *results_matching; 6031 6032 /** 6033 * Total number of results found. 6034 */ 6035 unsigned int results_length; 6036 }; 6037 6038 6039 /** 6040 * Called after 'test_lookup_transfer_details'. 6041 * 6042 * @param cls pointer to 'TestLookupTransferDetails_Closure'. 6043 * @param current_offset offset within transfer details. 6044 * @param details the details that were found. 6045 */ 6046 static void 6047 lookup_transfer_details_cb (void *cls, 6048 unsigned int current_offset, 6049 const struct TALER_TrackTransferDetails *details) 6050 { 6051 struct TestLookupTransferDetails_Closure *cmp = cls; 6052 if (NULL == cmp) 6053 return; 6054 for (unsigned int i = 0; cmp->details_to_cmp_length > i; ++i) 6055 { 6056 if ((0 == GNUNET_memcmp (&cmp->details_to_cmp[i].h_contract_terms, 6057 &details->h_contract_terms)) && 6058 (0 == GNUNET_memcmp (&cmp->details_to_cmp[i].coin_pub, 6059 &details->coin_pub)) && 6060 (GNUNET_OK == TALER_amount_cmp_currency ( 6061 &cmp->details_to_cmp[i].coin_value, 6062 &details->coin_value)) && 6063 (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_value, 6064 &details->coin_value)) && 6065 (GNUNET_OK == TALER_amount_cmp_currency ( 6066 &cmp->details_to_cmp[i].coin_fee, 6067 &details->coin_fee)) && 6068 (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_fee, 6069 &details->coin_fee))) 6070 { 6071 cmp->results_matching[i] += 1; 6072 } 6073 } 6074 cmp->results_length += 1; 6075 } 6076 6077 6078 /** 6079 * Tests looking up details for a wire transfer. 6080 * 6081 * @param exchange_url url to the exchange. 6082 * @param wtid id of the transfer. 6083 * @param details_length the length of @e details. 6084 * @param details the details we expect to be returned. 6085 * @return 1 on success, 0 otherwise. 6086 */ 6087 static int 6088 test_lookup_transfer_details ( 6089 const char *exchange_url, 6090 const struct TALER_WireTransferIdentifierRawP *wtid, 6091 unsigned int details_length, 6092 const struct TALER_TrackTransferDetails *details) 6093 { 6094 unsigned int results_matching[details_length]; 6095 struct TestLookupTransferDetails_Closure cmp = { 6096 .details_to_cmp_length = details_length, 6097 .details_to_cmp = details, 6098 .results_matching = results_matching, 6099 .results_length = 0 6100 }; 6101 memset (results_matching, 6102 0, 6103 sizeof (unsigned int) * details_length); 6104 if (1 != TALER_MERCHANTDB_iterate_transfer_details (pg, 6105 exchange_url, 6106 wtid, 6107 &lookup_transfer_details_cb, 6108 &cmp)) 6109 { 6110 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6111 "Lookup transfer details failed\n"); 6112 return 1; 6113 } 6114 if (details_length != cmp.results_length) 6115 { 6116 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6117 "Lookup transfer details failed: incorrect number of results (%d)\n", 6118 cmp.results_length); 6119 return 1; 6120 } 6121 for (unsigned int i = 0; details_length > i; ++i) 6122 { 6123 if (1 != cmp.results_matching[i]) 6124 { 6125 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6126 "Lookup transfer details failed: mismatched data\n"); 6127 return 1; 6128 } 6129 } 6130 return 0; 6131 } 6132 6133 6134 /** 6135 * Closure for 'iterate_transfer_details_by_order'. 6136 */ 6137 struct TestLookupTransferDetailsByOrder_Closure 6138 { 6139 /** 6140 * Length of @e transfers_to_cmp. 6141 */ 6142 unsigned int transfers_to_cmp_length; 6143 6144 /** 6145 * List of transfers that we expect to find. 6146 */ 6147 const struct TransferData *transfers_to_cmp; 6148 6149 /** 6150 * How many results match the corresponding element of @e transfers_to_cmp. 6151 */ 6152 unsigned int *results_matching; 6153 6154 /** 6155 * Total number of results found. 6156 */ 6157 unsigned int results_length; 6158 }; 6159 6160 6161 /** 6162 * Called after 'test_lookup_transfer_details_by_order'. 6163 * 6164 * @param cls pointer to 'TestLookupTransferDetailsByOrder_Closure'. 6165 * @param wtid identifier of the transfer found. 6166 * @param exchange_url exchange url of the transfer found. 6167 * @param execution_time when the transfer found occurred. 6168 * @param deposit_value amount of the deposit for the transfer found. 6169 * @param deposit_fee amount of the fee for the deposit of the transfer. 6170 * @param transfer_confirmed did the merchant confirm that a wire transfer with 6171 * @a wtid over the total amount happened? 6172 */ 6173 static void 6174 lookup_transfer_details_order_cb ( 6175 void *cls, 6176 const struct TALER_WireTransferIdentifierRawP *wtid, 6177 const char *exchange_url, 6178 struct GNUNET_TIME_Timestamp execution_time, 6179 const struct TALER_Amount *deposit_value, 6180 const struct TALER_Amount *deposit_fee, 6181 bool transfer_confirmed, 6182 uint64_t expected_transfer_serial_id) 6183 { 6184 struct TestLookupTransferDetailsByOrder_Closure *cmp = cls; 6185 6186 if (NULL == cmp) 6187 return; 6188 cmp->results_length += 1; 6189 for (unsigned int i = 0; i < cmp->transfers_to_cmp_length; ++i) 6190 { 6191 /* Right now iterate_transfer_details_by_order leaves execution_time 6192 uninitialized */ 6193 if ((0 == GNUNET_memcmp (&cmp->transfers_to_cmp[i].wtid, 6194 wtid)) && 6195 (0 == strcmp (cmp->transfers_to_cmp[i].exchange_url, 6196 exchange_url)) && 6197 (GNUNET_OK == 6198 TALER_amount_cmp_currency ( 6199 &cmp->transfers_to_cmp[i].data.total_amount, 6200 deposit_value)) && 6201 (0 == 6202 TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount, 6203 deposit_value)) && 6204 (GNUNET_OK == 6205 TALER_amount_cmp_currency ( 6206 &cmp->transfers_to_cmp[i].deposit_fee, 6207 deposit_fee)) && 6208 (0 == 6209 TALER_amount_cmp (&cmp->transfers_to_cmp[i].deposit_fee, 6210 deposit_fee)) ) 6211 cmp->results_matching[i] += 1; 6212 } 6213 } 6214 6215 6216 /** 6217 * Tests looking up wire transfers associated with an order. 6218 * 6219 * @param order_serial the order to be queried. 6220 * @param transfers_length length of @e transfers. 6221 * @param transfers the transfers we expect to be found. 6222 * @return 0 on success, 1 otherwise. 6223 */ 6224 static int 6225 test_lookup_transfer_details_by_order ( 6226 uint64_t order_serial, 6227 unsigned int transfers_length, 6228 const struct TransferData *transfers) 6229 { 6230 unsigned int results_matching[transfers_length]; 6231 struct TestLookupTransferDetailsByOrder_Closure cmp = { 6232 .transfers_to_cmp_length = transfers_length, 6233 .transfers_to_cmp = transfers, 6234 .results_matching = results_matching, 6235 .results_length = 0 6236 }; 6237 memset (results_matching, 6238 0, 6239 sizeof (unsigned int) * transfers_length); 6240 if (transfers_length != 6241 TALER_MERCHANTDB_iterate_transfer_details_by_order ( 6242 pg, 6243 order_serial, 6244 &lookup_transfer_details_order_cb, 6245 &cmp)) 6246 { 6247 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6248 "Lookup transfer details by order failed\n"); 6249 return 1; 6250 } 6251 if (transfers_length != cmp.results_length) 6252 { 6253 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6254 "Lookup transfer details by order failed: incorrect number of results\n"); 6255 return 1; 6256 } 6257 for (unsigned int i = 0; i < transfers_length; ++i) 6258 { 6259 if (1 != cmp.results_matching[i]) 6260 { 6261 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6262 "Lookup transfer details by order failed: mismatched data\n"); 6263 return 1; 6264 } 6265 } 6266 return 0; 6267 } 6268 6269 6270 /** 6271 * Tests inserting wire fee data for an exchange. 6272 * 6273 * @param signkey the signing key for the exchange. 6274 * @param wire_fee the wire fee data. 6275 * @param expected_result what the database should return. 6276 * @return 0 on success, 1 otherwise. 6277 */ 6278 static int 6279 test_insert_wire_fee (const struct ExchangeSignkeyData *signkey, 6280 const struct WireFeeData *wire_fee, 6281 enum GNUNET_DB_QueryStatus expected_result) 6282 { 6283 TEST_COND_RET_ON_FAIL (expected_result == 6284 TALER_MERCHANTDB_insert_exchange_wire_fee ( 6285 pg, 6286 &signkey->master_pub, 6287 &wire_fee->h_wire_method, 6288 &wire_fee->fees, 6289 wire_fee->wire_fee_start, 6290 wire_fee->wire_fee_end, 6291 &wire_fee->fee_sig), 6292 "Store wire fee by exchange failed\n"); 6293 return 0; 6294 } 6295 6296 6297 /** 6298 * Tests looking up wire fee data for an exchange. 6299 * 6300 * @param signkey the signing key to use for lookup. 6301 * @param wire_fee_data the wire fee data we expect to find. 6302 * @return 0 on success, 1 otherwise. 6303 */ 6304 static int 6305 test_lookup_wire_fee (const struct ExchangeSignkeyData *signkey, 6306 const struct WireFeeData *wire_fee_data) 6307 { 6308 struct TALER_WireFeeSet fees; 6309 struct GNUNET_TIME_Timestamp start_date; 6310 struct GNUNET_TIME_Timestamp end_date; 6311 struct TALER_MasterSignatureP master_sig; 6312 if (1 != TALER_MERCHANTDB_get_exchange_wire_fee (pg, 6313 &signkey->master_pub, 6314 wire_fee_data->wire_method, 6315 GNUNET_TIME_timestamp_get (), 6316 &fees, 6317 &start_date, 6318 &end_date, 6319 &master_sig)) 6320 { 6321 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6322 "Lookup wire fee failed\n"); 6323 return 1; 6324 } 6325 if ((0 != 6326 TALER_wire_fee_set_cmp (&wire_fee_data->fees, 6327 &fees)) || 6328 (GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_start, 6329 !=, 6330 start_date)) || 6331 (GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_end, 6332 !=, 6333 end_date)) || 6334 (0 != GNUNET_memcmp (&wire_fee_data->fee_sig, 6335 &master_sig))) 6336 { 6337 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6338 "Lookup wire fee failed: mismatched data\n"); 6339 return 1; 6340 } 6341 return 0; 6342 } 6343 6344 6345 /** 6346 * Closure for 'iterate_transfers'. 6347 */ 6348 struct TestLookupTransfers_Closure 6349 { 6350 /** 6351 * Length of @e transfers_to_cmp. 6352 */ 6353 unsigned int transfers_to_cmp_length; 6354 6355 /** 6356 * The transfers we expect to find. 6357 */ 6358 const struct TransferData *transfers_to_cmp; 6359 6360 /** 6361 * Number of results matching each transfer. 6362 */ 6363 unsigned int *results_matching; 6364 6365 /** 6366 * Total number of results found. 6367 */ 6368 unsigned int results_length; 6369 }; 6370 6371 6372 /** 6373 * Function called after 'test_lookup_transfers'. 6374 * 6375 * @param cls pointer to 'TestLookupTransfers_Closure'. 6376 * @param credit_amount how much was wired to the merchant (minus fees) 6377 * @param wtid wire transfer identifier 6378 * @param payto_uri target account that received the wire transfer 6379 * @param exchange_payto_uri account of the exchange that was debited, 6380 * `full_payto` NULL if unknown; unused here 6381 * @param exchange_url base URL of the exchange that made the wire transfer 6382 * @param transfer_serial_id serial number identifying the transfer in the backend 6383 * @param expected_transfer_serial_id serial number identifying the expected transfer in the backend, 0 if not @a expected 6384 * @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_TS 6385 * if it did not yet happen 6386 * @param expected true if the merchant acknowledged the wire transfer reception 6387 */ 6388 static void 6389 lookup_transfers_cb (void *cls, 6390 const struct TALER_Amount *credit_amount, 6391 const struct TALER_WireTransferIdentifierRawP *wtid, 6392 struct TALER_FullPayto payto_uri, 6393 struct TALER_FullPayto exchange_payto_uri, 6394 const char *exchange_url, 6395 uint64_t transfer_serial_id, 6396 uint64_t expected_transfer_serial_id, 6397 struct GNUNET_TIME_Absolute execution_time, 6398 bool expected) 6399 { 6400 struct TestLookupTransfers_Closure *cmp = cls; 6401 if (NULL == cmp) 6402 return; 6403 for (unsigned int i = 0; cmp->transfers_to_cmp_length > i; ++i) 6404 { 6405 if ( (GNUNET_OK == 6406 TALER_amount_cmp_currency ( 6407 &cmp->transfers_to_cmp[i].data.total_amount, 6408 credit_amount)) && 6409 (0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount, 6410 credit_amount)) ) 6411 { 6412 cmp->results_matching[i]++; 6413 } 6414 } 6415 cmp->results_length++; 6416 } 6417 6418 6419 /** 6420 * Tests looking up transfers from the database. 6421 * 6422 * @param instance the instance to lookup from. 6423 * @param account the account the transfer was made to. 6424 * @param before do not return transfers before this time. 6425 * @param after do not return transfers after this time. 6426 * @param limit maximum number of transfers to return. 6427 * @param offset row in the database to start with. 6428 * @param filter_verified how to filter verified transfers. 6429 * @param transfers_length length of @e transfers. 6430 * @param transfers the transfers we expect to find. 6431 * @return 0 on success, 1 otherwise. 6432 */ 6433 static int 6434 test_lookup_transfers (const struct InstanceData *instance, 6435 const struct TALER_MERCHANTDB_AccountDetails *account, 6436 struct GNUNET_TIME_Timestamp before, 6437 struct GNUNET_TIME_Timestamp after, 6438 int64_t limit, 6439 uint64_t offset, 6440 unsigned int transfers_length, 6441 const struct TransferData *transfers) 6442 { 6443 unsigned int results_matching[transfers_length]; 6444 struct TestLookupTransfers_Closure cmp = { 6445 .transfers_to_cmp_length = transfers_length, 6446 .transfers_to_cmp = transfers, 6447 .results_matching = results_matching, 6448 .results_length = 0 6449 }; 6450 memset (results_matching, 6451 0, 6452 sizeof (unsigned int) * transfers_length); 6453 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 6454 if (1 != TALER_MERCHANTDB_iterate_transfers (pg, 6455 instance->instance.id, 6456 account->payto_uri, 6457 before, 6458 after, 6459 limit, 6460 offset, 6461 TALER_EXCHANGE_YNA_ALL, 6462 &lookup_transfers_cb, 6463 &cmp)) 6464 { 6465 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6466 "Lookup transfers failed\n"); 6467 return 1; 6468 } 6469 if (transfers_length != cmp.results_length) 6470 { 6471 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6472 "Lookup transfers failed: incorrect number of results\n"); 6473 return 1; 6474 } 6475 for (unsigned int i = 0; transfers_length > i; ++i) 6476 { 6477 if (1 != cmp.results_matching[i]) 6478 { 6479 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6480 "Lookup transfers failed: mismatched data\n"); 6481 return 1; 6482 } 6483 } 6484 return 0; 6485 } 6486 6487 6488 /** 6489 * Tests inserting a transfer into the database. 6490 * 6491 * @param instance the instance to use. 6492 * @param account the account to transfer to. 6493 * @param transfer the transfer to insert. 6494 * @param expected_result the result we expect the db to return. 6495 * @return 0 on success, 1 otherwise. 6496 */ 6497 static int 6498 test_insert_transfer (const struct InstanceData *instance, 6499 const struct TALER_MERCHANTDB_AccountDetails *account, 6500 const struct TransferData *transfer, 6501 enum GNUNET_DB_QueryStatus expected_result) 6502 { 6503 bool no_account; 6504 bool conflict; 6505 6506 TEST_SET_INSTANCE (instance->instance.id, expected_result); 6507 TEST_COND_RET_ON_FAIL (expected_result == 6508 TALER_MERCHANTDB_insert_transfer ( 6509 pg, 6510 instance->instance.id, 6511 transfer->exchange_url, 6512 &transfer->wtid, 6513 &transfer->data.total_amount, 6514 account->payto_uri, 6515 transfer->confirmed, 6516 &no_account, 6517 &conflict), 6518 "Insert transfer failed\n"); 6519 return 0; 6520 } 6521 6522 6523 /** 6524 * Tests that inserting a transfer crediting a payto URI which belongs to 6525 * no account of the instance is reported via the @e no_account flag, 6526 * instead of being silently attributed to an arbitrary account. 6527 * 6528 * Regression test: the account lookup in insert_transfer.sql compared 6529 * REGEXP_REPLACE(payto_uri,'\\?.*',''), a pattern which matches an 6530 * *optional* backslash followed by anything. It therefore matched at 6531 * offset 0, reduced every URI to the empty string, and made the 6532 * comparison unconditionally true. 6533 * 6534 * @param instance the instance to use. 6535 * @param transfer the transfer to attempt to insert. 6536 * @return 0 on success, 1 otherwise. 6537 */ 6538 static int 6539 test_insert_transfer_unknown_account ( 6540 const struct InstanceData *instance, 6541 const struct TransferData *transfer) 6542 { 6543 bool no_account = false; 6544 bool conflict = false; 6545 struct TALER_FullPayto unknown = { 6546 .full_payto = (char *) "payto://iban/DE/NO-SUCH-ACCOUNT" 6547 "?receiver-name=Not+My+Account" 6548 }; 6549 6550 TEST_SET_INSTANCE (instance->instance.id, 6551 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 6552 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 6553 TALER_MERCHANTDB_insert_transfer ( 6554 pg, 6555 instance->instance.id, 6556 transfer->exchange_url, 6557 &transfer->wtid, 6558 &transfer->data.total_amount, 6559 unknown, 6560 transfer->confirmed, 6561 &no_account, 6562 &conflict), 6563 "Insert transfer for unknown account failed\n"); 6564 TEST_COND_RET_ON_FAIL (no_account, 6565 "Insert transfer accepted a payto URI belonging to " 6566 "no account of the instance\n"); 6567 return 0; 6568 } 6569 6570 6571 /** 6572 * Tests linking a deposit to a transfer. 6573 * 6574 * @param instance the instance that the deposit and transfer are for. 6575 * @param signkey the signing used on the deposit. 6576 * @param order the order the deposit and transfer were made for. 6577 * @param transfer the transfer. 6578 * @param expected_result the result the database should return. 6579 * @param expected_cleared clearance status the database should return. 6580 * @return 0 on success, 1 otherwise. 6581 */ 6582 static int 6583 test_insert_deposit_to_transfer (const struct InstanceData *instance, 6584 const struct ExchangeSignkeyData *signkey, 6585 const struct OrderData *order, 6586 const struct DepositData *deposit, 6587 const struct TransferData *transfer, 6588 enum GNUNET_DB_QueryStatus expected_result, 6589 bool expect_cleared) 6590 { 6591 const struct TALER_EXCHANGE_DepositData deposit_data = { 6592 .exchange_pub = signkey->exchange_pub, 6593 .exchange_sig = deposit->exchange_sig, 6594 .wtid = transfer->wtid, 6595 .execution_time = transfer->data.execution_time, 6596 .coin_contribution = deposit->amount_with_fee 6597 }; 6598 uint64_t deposit_serial; 6599 6600 TEST_SET_INSTANCE (instance->instance.id, expected_result); 6601 deposit_serial = get_deposit_serial (instance, 6602 order, 6603 deposit); 6604 6605 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_DTTS_SETTLED == 6606 TALER_MERCHANTDB_insert_deposit_to_transfer ( 6607 pg, 6608 deposit_serial, 6609 &deposit->h_wire, 6610 deposit->exchange_url, 6611 &deposit_data), 6612 "insert deposit to transfer failed\n"); 6613 return 0; 6614 } 6615 6616 6617 /** 6618 * Tests linking a deposit to a transfer that the exchange claims to 6619 * have wired to an account we do not know. This is a *permanent* 6620 * failure: the caller must be able to tell it apart from a successful 6621 * settlement, and the deposit must not make its order count as wired. 6622 * 6623 * @param instance the instance that the deposit and transfer are for. 6624 * @param signkey the signing key used on the deposit. 6625 * @param order the order the deposit was made for. 6626 * @param deposit the deposit. 6627 * @param transfer the transfer the exchange claims to have made. 6628 * @return 0 on success, 1 otherwise. 6629 */ 6630 static int 6631 test_insert_deposit_to_unknown_account ( 6632 const struct InstanceData *instance, 6633 const struct ExchangeSignkeyData *signkey, 6634 const struct OrderData *order, 6635 const struct DepositData *deposit, 6636 const struct TransferData *transfer) 6637 { 6638 const struct TALER_EXCHANGE_DepositData deposit_data = { 6639 .exchange_pub = signkey->exchange_pub, 6640 .exchange_sig = deposit->exchange_sig, 6641 .wtid = transfer->wtid, 6642 .execution_time = transfer->data.execution_time, 6643 .coin_contribution = deposit->amount_with_fee 6644 }; 6645 struct TALER_MerchantWireHashP bogus_h_wire; 6646 uint64_t deposit_serial; 6647 6648 GNUNET_CRYPTO_random_block (&bogus_h_wire, 6649 sizeof (bogus_h_wire)); 6650 TEST_SET_INSTANCE (instance->instance.id, 6651 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 6652 deposit_serial = get_deposit_serial (instance, 6653 order, 6654 deposit); 6655 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_DTTS_ACCOUNT_UNKNOWN == 6656 TALER_MERCHANTDB_insert_deposit_to_transfer ( 6657 pg, 6658 deposit_serial, 6659 &bogus_h_wire, 6660 deposit->exchange_url, 6661 &deposit_data), 6662 "insert deposit to transfer with an unknown target account must report ACCOUNT_UNKNOWN\n"); 6663 return 0; 6664 } 6665 6666 6667 /** 6668 * Inserts details for a transfer into the database. 6669 * 6670 * @param instance the instance the transfer is in. 6671 * @param account the destination account for the transfer. 6672 * @param transfer the transfer we are adding details to. 6673 * @param expected_result the result expected from the db. 6674 * @return 0 on success, 1 otherwise. 6675 */ 6676 static int 6677 test_insert_transfer_details ( 6678 const struct InstanceData *instance, 6679 const struct TALER_MERCHANTDB_AccountDetails *account, 6680 const struct TransferData *transfer, 6681 enum GNUNET_DB_QueryStatus expected_result) 6682 { 6683 TEST_SET_INSTANCE (instance->instance.id, expected_result); 6684 TEST_COND_RET_ON_FAIL (expected_result == 6685 TALER_MERCHANTDB_insert_transfer_details ( 6686 pg, 6687 instance->instance.id, 6688 transfer->exchange_url, 6689 account->payto_uri, 6690 &transfer->wtid, 6691 &transfer->data, 6692 NULL), 6693 "Insert transfer details failed\n"); 6694 return 0; 6695 } 6696 6697 6698 /** 6699 * Inserts details for a transfer into the database and checks how 6700 * many orders were reported as fully settled by it. An aggregated 6701 * transfer settles several orders at once and every one of them must 6702 * be reported, otherwise clients long-polling on the others never 6703 * learn that the order was wired. 6704 * 6705 * @param instance the instance the transfer is in. 6706 * @param account the destination account for the transfer. 6707 * @param transfer the transfer we are adding details to. 6708 * @param expected_result the result expected from the db. 6709 * @param expected_settled number of orders that must be reported. 6710 * @return 0 on success, 1 otherwise. 6711 */ 6712 static int 6713 test_insert_transfer_details_settling ( 6714 const struct InstanceData *instance, 6715 const struct TALER_MERCHANTDB_AccountDetails *account, 6716 const struct TransferData *transfer, 6717 enum GNUNET_DB_QueryStatus expected_result, 6718 unsigned int expected_settled) 6719 { 6720 unsigned int num_settled = 0; 6721 6722 TEST_SET_INSTANCE (instance->instance.id, expected_result); 6723 TEST_COND_RET_ON_FAIL (expected_result == 6724 TALER_MERCHANTDB_insert_transfer_details ( 6725 pg, 6726 instance->instance.id, 6727 transfer->exchange_url, 6728 account->payto_uri, 6729 &transfer->wtid, 6730 &transfer->data, 6731 &num_settled), 6732 "Insert transfer details failed\n"); 6733 TEST_COND_RET_ON_FAIL (expected_settled == num_settled, 6734 "Wrong number of settled orders reported\n"); 6735 return 0; 6736 } 6737 6738 6739 /** 6740 * State used to count notifications that wake the webhook helper. 6741 */ 6742 struct WebhookPendingEventState 6743 { 6744 /** 6745 * Number of notifications received. 6746 */ 6747 unsigned int fired; 6748 }; 6749 6750 6751 /** 6752 * Count a notification for a newly pending webhook. 6753 * 6754 * @param cls a `struct WebhookPendingEventState *` 6755 * @param extra unused notification payload 6756 * @param extra_size size of @a extra 6757 */ 6758 static void 6759 webhook_pending_event_cb (void *cls, 6760 const void *extra, 6761 size_t extra_size) 6762 { 6763 struct WebhookPendingEventState *state = cls; 6764 6765 (void) extra; 6766 (void) extra_size; 6767 state->fired++; 6768 } 6769 6770 6771 /** 6772 * Container for data used when testing transfers. 6773 */ 6774 struct TestTransfers_Closure 6775 { 6776 /** 6777 * The instance. 6778 */ 6779 struct InstanceData instance; 6780 6781 /** 6782 * The account. 6783 */ 6784 struct TALER_MERCHANTDB_AccountDetails account; 6785 6786 /** 6787 * The exchange signing key. 6788 */ 6789 struct ExchangeSignkeyData signkey; 6790 6791 /** 6792 * The order data. 6793 */ 6794 struct OrderData order; 6795 6796 /** 6797 * Two more orders, both settled by one aggregated transfer. 6798 */ 6799 struct OrderData orders_agg[2]; 6800 6801 /** 6802 * Order with one deposit that settles and one that fails 6803 * permanently. 6804 */ 6805 struct OrderData order_perm; 6806 6807 /** 6808 * The deposit data. 6809 */ 6810 struct DepositData deposit; 6811 6812 /** 6813 * A second deposit for the same order, used to test an 6814 * exchange response that lists the same coin twice. 6815 */ 6816 struct DepositData deposit2; 6817 6818 /** 6819 * One deposit for each of @e orders_agg. 6820 */ 6821 struct DepositData deposits_agg[2]; 6822 6823 /** 6824 * Two deposits for @e order_perm: the first one settles, the 6825 * second one is wired to an account we do not know. 6826 */ 6827 struct DepositData deposits_perm[2]; 6828 6829 /** 6830 * Wire fee data. 6831 */ 6832 struct WireFeeData wire_fee[2]; 6833 6834 /** 6835 * The transfers. 6836 */ 6837 struct TransferData transfers[5]; 6838 }; 6839 6840 6841 /** 6842 * Prepares for testing transfers. 6843 * 6844 * @param cls the test data. 6845 */ 6846 static void 6847 pre_test_transfers (struct TestTransfers_Closure *cls) 6848 { 6849 /* Instance */ 6850 make_instance ("test_inst_transfers", 6851 &cls->instance); 6852 6853 /* Account */ 6854 make_account (&cls->account); 6855 cls->account.instance_id = cls->instance.instance.id; 6856 /* Order */ 6857 make_order ("test_transfers_od_1", 6858 &cls->order); 6859 6860 /* Signing key */ 6861 make_exchange_signkey (&cls->signkey); 6862 6863 /* Deposit */ 6864 make_deposit (&cls->instance, 6865 &cls->account, 6866 &cls->order, 6867 &cls->signkey, 6868 &cls->deposit); 6869 6870 /* Wire fee */ 6871 make_wire_fee (&cls->signkey, 6872 &cls->wire_fee[0]); 6873 make_wire_fee (&cls->signkey, 6874 &cls->wire_fee[1]); 6875 cls->wire_fee[1].wire_method = "wire-method-2"; 6876 GNUNET_CRYPTO_hash (cls->wire_fee[1].wire_method, 6877 strlen (cls->wire_fee[1].wire_method) + 1, 6878 &cls->wire_fee[1].h_wire_method); 6879 6880 /* Transfers */ 6881 make_transfer (&cls->signkey, 6882 1, 6883 &cls->deposit, 6884 &cls->transfers[0]); 6885 cls->transfers[0].confirmed = true; 6886 /* A transfer the exchange reports without any deposits. */ 6887 make_transfer (&cls->signkey, 6888 0, 6889 &cls->deposit, 6890 &cls->transfers[1]); 6891 cls->transfers[1].confirmed = true; 6892 /* A transfer where the exchange lists the same coin twice. */ 6893 make_deposit (&cls->instance, 6894 &cls->account, 6895 &cls->order, 6896 &cls->signkey, 6897 &cls->deposit2); 6898 { 6899 struct DepositData dup[2]; 6900 6901 dup[0] = cls->deposit2; 6902 dup[1] = cls->deposit2; 6903 make_transfer (&cls->signkey, 6904 2, 6905 dup, 6906 &cls->transfers[2]); 6907 } 6908 cls->transfers[2].confirmed = true; 6909 /* Two orders settled by one aggregated wire transfer. */ 6910 make_order ("test_transfers_od_agg_1", 6911 &cls->orders_agg[0]); 6912 make_order ("test_transfers_od_agg_2", 6913 &cls->orders_agg[1]); 6914 for (unsigned int i = 0; i < 2; i++) 6915 make_deposit (&cls->instance, 6916 &cls->account, 6917 &cls->orders_agg[i], 6918 &cls->signkey, 6919 &cls->deposits_agg[i]); 6920 make_transfer (&cls->signkey, 6921 2, 6922 cls->deposits_agg, 6923 &cls->transfers[3]); 6924 cls->transfers[3].confirmed = true; 6925 /* An order with two deposits, one of which the exchange wires to an 6926 account we do not know (permanent failure). */ 6927 make_order ("test_transfers_od_perm", 6928 &cls->order_perm); 6929 for (unsigned int i = 0; i < 2; i++) 6930 make_deposit (&cls->instance, 6931 &cls->account, 6932 &cls->order_perm, 6933 &cls->signkey, 6934 &cls->deposits_perm[i]); 6935 make_transfer (&cls->signkey, 6936 1, 6937 cls->deposits_perm, 6938 &cls->transfers[4]); 6939 cls->transfers[4].confirmed = true; 6940 } 6941 6942 6943 /** 6944 * Cleans up after testing transfers. 6945 * 6946 * @param cls the test data. 6947 */ 6948 static void 6949 post_test_transfers (struct TestTransfers_Closure *cls) 6950 { 6951 GNUNET_array_grow (cls->transfers->data.details, 6952 cls->transfers->data.details_length, 6953 0); 6954 GNUNET_array_grow (cls->transfers[2].data.details, 6955 cls->transfers[2].data.details_length, 6956 0); 6957 GNUNET_array_grow (cls->transfers[3].data.details, 6958 cls->transfers[3].data.details_length, 6959 0); 6960 GNUNET_array_grow (cls->transfers[4].data.details, 6961 cls->transfers[4].data.details_length, 6962 0); 6963 free_instance_data (&cls->instance); 6964 free_order_data (&cls->order); 6965 free_order_data (&cls->orders_agg[0]); 6966 free_order_data (&cls->orders_agg[1]); 6967 free_order_data (&cls->order_perm); 6968 } 6969 6970 6971 /** 6972 * Runs the tests for transfers. 6973 * 6974 * @param cls the test data. 6975 * @return 0 on success, 1 otherwise. 6976 */ 6977 static int 6978 run_test_transfers (struct TestTransfers_Closure *cls) 6979 { 6980 uint64_t order_serial; 6981 struct TALER_WireFeeSet fees; 6982 6983 /* Test lookup wire fee fails when it isn't in the db */ 6984 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 6985 TALER_MERCHANTDB_get_exchange_wire_fee (pg, 6986 &cls->signkey.master_pub, 6987 cls->wire_fee[0].wire_method, 6988 GNUNET_TIME_timestamp_get (), 6989 &fees, 6990 NULL, 6991 NULL, 6992 NULL), 6993 "Lookup wire fee failed\n"); 6994 /* Test store wire fee by exchange */ 6995 TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey, 6996 &cls->wire_fee[0], 6997 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6998 /* Test double insertion fails */ 6999 TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey, 7000 &cls->wire_fee[0], 7001 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 7002 /* Test lookup wire fee by exchange */ 7003 TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey, 7004 &cls->wire_fee[0])); 7005 /* A *different* fee for the same (master_pub, h_wire_method, 7006 start_date) must be reported, not silently dropped: the stored fee 7007 is the one the exchange signed, and continuing to compute with a 7008 stale fee is an accounting error. Regression test: the INSERT used 7009 a bare 'ON CONFLICT DO NOTHING' and returned NO_RESULTS here. */ 7010 { 7011 struct WireFeeData changed = cls->wire_fee[0]; 7012 7013 GNUNET_assert (GNUNET_OK == 7014 TALER_string_to_amount ("EUR:0.99", 7015 &changed.fees.wire)); 7016 TALER_exchange_offline_wire_fee_sign (changed.wire_method, 7017 changed.wire_fee_start, 7018 changed.wire_fee_end, 7019 &changed.fees, 7020 &cls->signkey.master_priv, 7021 &changed.fee_sig); 7022 TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey, 7023 &changed, 7024 GNUNET_DB_STATUS_HARD_ERROR)); 7025 /* ... and the fee on file must be untouched. */ 7026 TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey, 7027 &cls->wire_fee[0])); 7028 } 7029 /* Test different wire fees for different methods. */ 7030 TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey, 7031 &cls->wire_fee[1], 7032 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7033 TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey, 7034 &cls->wire_fee[1])); 7035 /* Insert the instance */ 7036 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 7037 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7038 /* Insert the account */ 7039 TEST_RET_ON_FAIL (test_insert_account (&cls->instance, 7040 &cls->account, 7041 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7042 /* Insert a signing key */ 7043 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 7044 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7045 /* Insert an order */ 7046 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 7047 &cls->order, 7048 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7049 /* Insert contract terms */ 7050 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 7051 &cls->order, 7052 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7053 order_serial = get_order_serial (&cls->instance, 7054 &cls->order); 7055 /* Insert the deposit */ 7056 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 7057 &cls->signkey, 7058 &cls->deposit, 7059 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7060 /* Mark as paid, otherwise wiring doesn't make sense. */ 7061 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 7062 &cls->order, 7063 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7064 /* A payto URI that matches no account must be rejected, not silently 7065 attributed to some arbitrary account of the instance. */ 7066 TEST_RET_ON_FAIL (test_insert_transfer_unknown_account (&cls->instance, 7067 &cls->transfers[0])); 7068 /* Insert the transfer */ 7069 TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance, 7070 &cls->account, 7071 &cls->transfers[0], 7072 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7073 TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance, 7074 &cls->account, 7075 &cls->transfers[0], 7076 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7077 TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, 7078 &cls->signkey, 7079 &cls->order, 7080 &cls->deposit, 7081 &cls->transfers[0], 7082 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 7083 false)); 7084 TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, 7085 &cls->signkey, 7086 &cls->order, 7087 &cls->deposit, 7088 &cls->transfers[0], 7089 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 7090 false)); 7091 TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance, 7092 &cls->account, 7093 &cls->transfers[0], 7094 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7095 /* Inserting deposits/transfers will automatically mark as wired. */ 7096 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 7097 cls->order.id, 7098 NULL, 7099 true, 7100 true)); 7101 TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, 7102 &cls->signkey, 7103 &cls->order, 7104 &cls->deposit, 7105 &cls->transfers[0], 7106 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 7107 true)); 7108 7109 TEST_RET_ON_FAIL (test_lookup_transfer_summary (cls->deposit.exchange_url, 7110 &cls->transfers[0].wtid, 7111 cls->order.id, 7112 &cls->deposit.amount_with_fee, 7113 &cls->deposit.deposit_fee)); 7114 TEST_RET_ON_FAIL (test_lookup_transfer_details (cls->deposit.exchange_url, 7115 &cls->transfers[0].wtid, 7116 1, 7117 &cls->transfers[0].data. 7118 details[0])); 7119 TEST_RET_ON_FAIL (test_lookup_transfer_details_by_order (order_serial, 7120 1, 7121 &cls->transfers[0])); 7122 TEST_RET_ON_FAIL (test_lookup_transfers (&cls->instance, 7123 &cls->account, 7124 GNUNET_TIME_UNIT_FOREVER_TS, 7125 GNUNET_TIME_UNIT_ZERO_TS, 7126 8, 7127 0, 7128 1, 7129 &cls->transfers[0])); 7130 /* The exchange is allowed to report a wire transfer with an empty 7131 list of deposits; that must not fail the transaction (which used 7132 to abort taler-merchant-reconciliation for all instances). 7133 The deposit-to-transfer insert is what makes us expect the 7134 transfer in the first place, so that we get past the account 7135 lookup and actually reach the per-coin loop. */ 7136 TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, 7137 &cls->signkey, 7138 &cls->order, 7139 &cls->deposit, 7140 &cls->transfers[1], 7141 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 7142 false)); 7143 TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance, 7144 &cls->account, 7145 &cls->transfers[1], 7146 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7147 /* The exchange may list the same coin twice in one response; 7148 merchant_expected_transfer_to_coin is UNIQUE on deposit_serial, 7149 so this used to be a hard error killing the reconciliation 7150 daemon for all instances. */ 7151 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 7152 &cls->signkey, 7153 &cls->deposit2, 7154 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7155 TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, 7156 &cls->signkey, 7157 &cls->order, 7158 &cls->deposit2, 7159 &cls->transfers[2], 7160 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 7161 false)); 7162 TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance, 7163 &cls->account, 7164 &cls->transfers[2], 7165 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7166 /* One aggregated transfer settles two orders: BOTH of them must be 7167 reported, otherwise clients long-polling on the order that is not 7168 reported never learn that it was wired. */ 7169 for (unsigned int i = 0; i < 2; i++) 7170 { 7171 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 7172 &cls->orders_agg[i], 7173 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7174 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 7175 &cls->orders_agg[i], 7176 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7177 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 7178 &cls->signkey, 7179 &cls->deposits_agg[i], 7180 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7181 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 7182 &cls->orders_agg[i], 7183 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7184 /* Reconciliation learns about the second deposit from the signed 7185 aggregate before depositcheck has queried it individually. */ 7186 if (0 == i) 7187 TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, 7188 &cls->signkey, 7189 &cls->orders_agg[i], 7190 &cls->deposits_agg[i], 7191 &cls->transfers[3], 7192 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 7193 false)); 7194 } 7195 { 7196 static const char *webhook_id = "test-transfer-order-settled"; 7197 static char webhook_url[] 7198 = "https://example.com/order-settled-event-test"; 7199 struct TALER_MERCHANTDB_WebhookDetails wb = { 7200 .event_type = "order_settled", 7201 .url = webhook_url, 7202 .http_method = "POST", 7203 .header_template = NULL, 7204 .body_template = "{{order_id}}" 7205 }; 7206 struct GNUNET_DB_EventHeaderP es = { 7207 .size = htons (sizeof (es)), 7208 .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING) 7209 }; 7210 struct WebhookPendingEventState event_state = { 0 }; 7211 struct GNUNET_DB_EventHandler *eh; 7212 uint64_t pending_count; 7213 int webhook_test_result = 1; 7214 7215 TEST_SET_INSTANCE (cls->instance.instance.id, 7216 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 7217 TEST_COND_RET_ON_FAIL ( 7218 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 7219 TALER_MERCHANTDB_insert_webhook (pg, 7220 cls->instance.instance.id, 7221 webhook_id, 7222 &wb), 7223 "Failed to insert order_settled webhook\n"); 7224 eh = TALER_MERCHANTDB_event_listen (pg, 7225 &es, 7226 GNUNET_TIME_UNIT_FOREVER_REL, 7227 &webhook_pending_event_cb, 7228 &event_state); 7229 if (NULL == eh) 7230 { 7231 GNUNET_break (0); 7232 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7233 "Failed to listen for pending webhook events\n"); 7234 goto webhook_cleanup; 7235 } 7236 7237 TEST_WITH_FAIL_CLAUSE ( 7238 test_insert_transfer_details_settling ( 7239 &cls->instance, 7240 &cls->account, 7241 &cls->transfers[3], 7242 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 7243 2), 7244 goto webhook_listener_cleanup; 7245 ); 7246 GNUNET_PQ_event_do_poll (pg->conn); 7247 if (1 != event_state.fired) 7248 { 7249 GNUNET_break (0); 7250 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7251 "Settlement generated %u pending webhook events, expected 1\n", 7252 event_state.fired); 7253 goto webhook_listener_cleanup; 7254 } 7255 TEST_WITH_FAIL_CLAUSE ( 7256 query_sql_num ( 7257 "SELECT COUNT(*) AS num" 7258 " FROM merchant.merchant_pending_webhooks" 7259 " WHERE url='https://example.com/order-settled-event-test'", 7260 &pending_count), 7261 goto webhook_listener_cleanup; 7262 ); 7263 if (2 != pending_count) 7264 { 7265 GNUNET_break (0); 7266 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7267 "Settlement queued %llu webhooks, expected 2\n", 7268 (unsigned long long) pending_count); 7269 goto webhook_listener_cleanup; 7270 } 7271 7272 /* A deposit that failed permanently (exchange wired the money to an 7273 account we do not know) must NOT make the order count as wired, 7274 queue an order_settled webhook, or wake the webhook helper. */ 7275 TEST_WITH_FAIL_CLAUSE ( 7276 test_insert_order (&cls->instance, 7277 &cls->order_perm, 7278 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT), 7279 goto webhook_listener_cleanup; 7280 ); 7281 TEST_WITH_FAIL_CLAUSE ( 7282 test_insert_contract_terms (&cls->instance, 7283 &cls->order_perm, 7284 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT), 7285 goto webhook_listener_cleanup; 7286 ); 7287 for (unsigned int i = 0; i < 2; i++) 7288 TEST_WITH_FAIL_CLAUSE ( 7289 test_insert_deposit (&cls->instance, 7290 &cls->signkey, 7291 &cls->deposits_perm[i], 7292 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT), 7293 goto webhook_listener_cleanup; 7294 ); 7295 TEST_WITH_FAIL_CLAUSE ( 7296 test_mark_contract_paid (&cls->instance, 7297 &cls->order_perm, 7298 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT), 7299 goto webhook_listener_cleanup; 7300 ); 7301 TEST_WITH_FAIL_CLAUSE ( 7302 test_insert_deposit_to_transfer (&cls->instance, 7303 &cls->signkey, 7304 &cls->order_perm, 7305 &cls->deposits_perm[0], 7306 &cls->transfers[4], 7307 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 7308 false), 7309 goto webhook_listener_cleanup; 7310 ); 7311 TEST_WITH_FAIL_CLAUSE ( 7312 test_insert_deposit_to_unknown_account ( 7313 &cls->instance, 7314 &cls->signkey, 7315 &cls->order_perm, 7316 &cls->deposits_perm[1], 7317 &cls->transfers[4]), 7318 goto webhook_listener_cleanup; 7319 ); 7320 TEST_WITH_FAIL_CLAUSE ( 7321 test_insert_transfer_details_settling ( 7322 &cls->instance, 7323 &cls->account, 7324 &cls->transfers[4], 7325 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 7326 0), 7327 goto webhook_listener_cleanup; 7328 ); 7329 GNUNET_PQ_event_do_poll (pg->conn); 7330 if (1 != event_state.fired) 7331 { 7332 GNUNET_break (0); 7333 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7334 "Unsettled order generated a pending webhook event\n"); 7335 goto webhook_listener_cleanup; 7336 } 7337 TEST_WITH_FAIL_CLAUSE ( 7338 query_sql_num ( 7339 "SELECT COUNT(*) AS num" 7340 " FROM merchant.merchant_pending_webhooks" 7341 " WHERE url='https://example.com/order-settled-event-test'", 7342 &pending_count), 7343 goto webhook_listener_cleanup; 7344 ); 7345 if (2 != pending_count) 7346 { 7347 GNUNET_break (0); 7348 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7349 "Unsettled order changed the pending webhook count\n"); 7350 goto webhook_listener_cleanup; 7351 } 7352 TEST_WITH_FAIL_CLAUSE ( 7353 test_lookup_payment_status (cls->instance.instance.id, 7354 cls->order_perm.id, 7355 NULL, 7356 true, 7357 false), 7358 goto webhook_listener_cleanup; 7359 ); 7360 webhook_test_result = 0; 7361 7362 webhook_listener_cleanup: 7363 TALER_MERCHANTDB_event_listen_cancel (eh); 7364 webhook_cleanup: 7365 TEST_COND_RET_ON_FAIL ( 7366 0 == exec_sql ( 7367 "DELETE FROM merchant.merchant_pending_webhooks" 7368 " WHERE url='https://example.com/order-settled-event-test'"), 7369 "Failed to delete pending order_settled webhooks\n"); 7370 TEST_SET_INSTANCE (cls->instance.instance.id, 7371 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 7372 TEST_COND_RET_ON_FAIL ( 7373 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 7374 TALER_MERCHANTDB_delete_webhook (pg, 7375 cls->instance.instance.id, 7376 webhook_id), 7377 "Failed to delete order_settled webhook\n"); 7378 if (0 != webhook_test_result) 7379 return 1; 7380 } 7381 return 0; 7382 } 7383 7384 7385 /** 7386 * Takes care of all work for testing transfers. 7387 * 7388 * @return 0 on success, 1 otherwise. 7389 */ 7390 static int 7391 test_transfers (void) 7392 { 7393 struct TestTransfers_Closure test_cls; 7394 int test_result; 7395 7396 pre_test_transfers (&test_cls); 7397 test_result = run_test_transfers (&test_cls); 7398 post_test_transfers (&test_cls); 7399 return test_result; 7400 } 7401 7402 7403 /** 7404 * Closure for testing iterate_refunds. 7405 */ 7406 struct TestLookupRefunds_Closure 7407 { 7408 /** 7409 * Length of @e coin_pub_to_cmp and @e refund_amount_to_cmp. 7410 */ 7411 unsigned int refunds_to_cmp_length; 7412 7413 /** 7414 * Public keys of the refunded coins. 7415 */ 7416 const struct TALER_CoinSpendPublicKeyP *coin_pub_to_cmp; 7417 7418 /** 7419 * Amount of each refund. 7420 */ 7421 const struct TALER_Amount *refund_amount_to_cmp; 7422 7423 /** 7424 * Number of results matching each refund provided. 7425 */ 7426 unsigned int *results_matching; 7427 7428 /** 7429 * Total number of results returned; 7430 */ 7431 unsigned int results_length; 7432 }; 7433 7434 7435 /** 7436 * Called after test_lookup_refunds. 7437 * @param cls pointer to a TestLookupRefunds_Closure. 7438 * @param coin_pub the public key of the coin for the refund found. 7439 * @param refund_amount the amount of the refund found. 7440 */ 7441 static void 7442 lookup_refunds_cb (void *cls, 7443 const struct TALER_CoinSpendPublicKeyP *coin_pub, 7444 const struct TALER_Amount *refund_amount) 7445 { 7446 struct TestLookupRefunds_Closure *cmp = cls; 7447 if (NULL == cmp) 7448 return; 7449 cmp->results_length += 1; 7450 for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i) 7451 { 7452 if ((0 == GNUNET_memcmp (&cmp->coin_pub_to_cmp[i], 7453 coin_pub)) && 7454 (GNUNET_OK == TALER_amount_cmp_currency (&cmp->refund_amount_to_cmp[i], 7455 refund_amount)) && 7456 (0 == TALER_amount_cmp (&cmp->refund_amount_to_cmp[i], 7457 refund_amount))) 7458 { 7459 cmp->results_matching[i] += 1; 7460 } 7461 } 7462 } 7463 7464 7465 /** 7466 * Tests looking up refunds from the database. 7467 * @param instance the instance to look up refunds for. 7468 * @param h_contract_terms hash of the contract terms the refunds are for. 7469 * @param refunds_length length of @e coin_pubs and @e refund_amounts. 7470 * @param coin_pubs the public keys of the coins that were refunded. 7471 * @param refund_amounts the amounts of the coins that were refunded. 7472 * 7473 * @return 0 on success, 1 otherwise. 7474 */ 7475 static int 7476 test_lookup_refunds (const struct InstanceData *instance, 7477 const struct TALER_PrivateContractHashP *h_contract_terms, 7478 unsigned int refunds_length, 7479 const struct TALER_CoinSpendPublicKeyP *coin_pubs, 7480 const struct TALER_Amount *refund_amounts) 7481 { 7482 unsigned int results_matching[refunds_length]; 7483 struct TestLookupRefunds_Closure cmp = { 7484 .refunds_to_cmp_length = refunds_length, 7485 .coin_pub_to_cmp = coin_pubs, 7486 .refund_amount_to_cmp = refund_amounts, 7487 .results_matching = results_matching, 7488 .results_length = 0 7489 }; 7490 memset (results_matching, 0, sizeof (unsigned int) * refunds_length); 7491 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 7492 if (1 != TALER_MERCHANTDB_iterate_refunds (pg, 7493 instance->instance.id, 7494 h_contract_terms, 7495 &lookup_refunds_cb, 7496 &cmp)) 7497 { 7498 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7499 "Lookup refunds failed\n"); 7500 return 1; 7501 } 7502 if (refunds_length != cmp.results_length) 7503 { 7504 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7505 "Lookup refunds failed: incorrect number of results returned\n") 7506 ; 7507 return 1; 7508 } 7509 for (unsigned int i = 0; refunds_length > i; ++i) 7510 { 7511 if (1 != cmp.results_matching[i]) 7512 { 7513 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7514 "Lookup refunds failed: mismatched data\n"); 7515 return 1; 7516 } 7517 } 7518 return 0; 7519 } 7520 7521 7522 /** 7523 * Container for refund data. 7524 */ 7525 struct RefundData 7526 { 7527 /** 7528 * When the refund occurred. 7529 */ 7530 struct GNUNET_TIME_Timestamp timestamp; 7531 7532 /** 7533 * Reason for the refund. 7534 */ 7535 const char *reason; 7536 7537 /** 7538 * Amount of the refund. 7539 */ 7540 struct TALER_Amount refund_amount; 7541 7542 /** 7543 * Public key of the coin that was refunded. 7544 */ 7545 const struct TALER_CoinSpendPublicKeyP *coin_pub; 7546 7547 /** 7548 * URL to the exchange that did the refund. 7549 */ 7550 const char *exchange_url; 7551 }; 7552 7553 7554 /** 7555 * Creates a refund for testing with. 7556 * @param deposit the deposit being refunded. 7557 * @param refund the data to set. 7558 */ 7559 static void 7560 make_refund (const struct DepositData *deposit, 7561 struct RefundData *refund) 7562 { 7563 refund->timestamp = GNUNET_TIME_timestamp_get (); 7564 refund->reason = "some reason"; 7565 refund->refund_amount = deposit->amount_with_fee; 7566 refund->coin_pub = &deposit->coin_pub; 7567 refund->exchange_url = deposit->exchange_url; 7568 } 7569 7570 7571 /** 7572 * Container for proof of a refund. 7573 */ 7574 struct RefundProofData 7575 { 7576 /** 7577 * Fee charged for the refund. 7578 */ 7579 struct TALER_Amount refund_fee; 7580 7581 /** 7582 * The exchange's signature on the refund. 7583 */ 7584 struct TALER_ExchangeSignatureP exchange_sig; 7585 }; 7586 7587 7588 /** 7589 * Closure for testing iterate_refunds_detailed. 7590 */ 7591 struct TestLookupRefundsDetailed_Closure 7592 { 7593 /** 7594 * Length of @e refunds_to_cmp. 7595 */ 7596 unsigned int refunds_to_cmp_length; 7597 7598 /** 7599 * The refunds we expect to find. 7600 */ 7601 const struct RefundData *refunds_to_cmp; 7602 7603 /** 7604 * Whether to compare the timestamps or not (if we don't have direct control 7605 * of the timestamps, there will be differences on the order of microseconds 7606 * that can be ignored). 7607 */ 7608 bool cmp_timestamps; 7609 7610 /** 7611 * The number of results matching each refund. 7612 */ 7613 unsigned int *results_matching; 7614 7615 /** 7616 * The total number of results from the db. 7617 */ 7618 unsigned int results_length; 7619 }; 7620 7621 7622 /** 7623 * Called after test_lookup_refunds_detailed. 7624 * @param cls pointer to a TestLookupRefundsDetailed_Closure. 7625 * @param refund_serial unique serial number of the refund 7626 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 7627 * @param coin_pub public coin from which the refund comes from 7628 * @param exchange_url URL of the exchange that issued @a coin_pub 7629 * @param rtransaction_id identificator of the refund 7630 * @param reason human-readable explanation of the refund 7631 * @param refund_amount refund amount which is being taken from @a coin_pub 7632 * @param pending true if this refund has not been processed by the wallet/exchange 7633 */ 7634 static void 7635 lookup_refunds_detailed_cb (void *cls, 7636 uint64_t refund_serial, 7637 struct GNUNET_TIME_Timestamp timestamp, 7638 const struct TALER_CoinSpendPublicKeyP *coin_pub, 7639 const char *exchange_url, 7640 uint64_t rtransaction_id, 7641 const char *reason, 7642 const struct TALER_Amount *refund_amount, 7643 bool pending) 7644 { 7645 struct TestLookupRefundsDetailed_Closure *cmp = cls; 7646 if (NULL == cmp) 7647 return; 7648 cmp->results_length += 1; 7649 for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i) 7650 { 7651 if (((GNUNET_TIME_timestamp_cmp (cmp->refunds_to_cmp[i].timestamp, 7652 ==, 7653 timestamp)) || 7654 ! cmp->cmp_timestamps) && 7655 (0 == GNUNET_memcmp (cmp->refunds_to_cmp[i].coin_pub, 7656 coin_pub)) && 7657 (0 == strcmp (cmp->refunds_to_cmp[i].exchange_url, 7658 exchange_url)) && 7659 (0 == strcmp (cmp->refunds_to_cmp[i].reason, 7660 reason)) && 7661 (GNUNET_OK == 7662 TALER_amount_cmp_currency ( 7663 &cmp->refunds_to_cmp[i].refund_amount, 7664 refund_amount)) && 7665 (0 == TALER_amount_cmp (&cmp->refunds_to_cmp[i].refund_amount, 7666 refund_amount))) 7667 { 7668 cmp->results_matching[i] += 1; 7669 } 7670 } 7671 } 7672 7673 7674 /** 7675 * Tests looking up refunds with details from the database. 7676 * @param instance the instance to lookup from. 7677 * @param h_contract_terms the contract terms the refunds were made for. 7678 * @param cmp_timestamps whether to compare timestamps or not. 7679 * @param refunds_length length of @e refunds. 7680 * @param refunds the refunds we expect to be returned. 7681 * 7682 * @return 0 on success, 1 otherwise. 7683 */ 7684 static int 7685 test_lookup_refunds_detailed ( 7686 const struct InstanceData *instance, 7687 const struct TALER_PrivateContractHashP *h_contract_terms, 7688 bool cmp_timestamps, 7689 unsigned int refunds_length, 7690 const struct RefundData *refunds) 7691 { 7692 unsigned int results_matching[refunds_length]; 7693 struct TestLookupRefundsDetailed_Closure cmp = { 7694 .refunds_to_cmp_length = refunds_length, 7695 .refunds_to_cmp = refunds, 7696 .cmp_timestamps = cmp_timestamps, 7697 .results_matching = results_matching, 7698 .results_length = 0 7699 }; 7700 memset (results_matching, 0, sizeof (unsigned int) * refunds_length); 7701 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 7702 if (0 > TALER_MERCHANTDB_iterate_refunds_detailed (pg, 7703 instance->instance.id, 7704 h_contract_terms, 7705 &lookup_refunds_detailed_cb, 7706 &cmp)) 7707 { 7708 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7709 "Lookup refunds detailed failed\n"); 7710 return 1; 7711 } 7712 if (refunds_length != cmp.results_length) 7713 { 7714 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7715 "Lookup refunds detailed failed: incorrect number of results\n") 7716 ; 7717 return 1; 7718 } 7719 for (unsigned int i = 0; refunds_length > i; ++i) 7720 { 7721 if (1 != cmp.results_matching[i]) 7722 { 7723 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7724 "Lookup refunds detailed failed: mismatched data\n"); 7725 return 1; 7726 } 7727 } 7728 return 0; 7729 } 7730 7731 7732 /** 7733 * Closure for get_refund_serial. 7734 */ 7735 struct LookupRefundSerial_Closure 7736 { 7737 /** 7738 * The refund we are looking up the id for. 7739 */ 7740 const struct RefundData *refund; 7741 7742 /** 7743 * The row number found. 7744 */ 7745 uint64_t serial; 7746 }; 7747 7748 7749 /** 7750 * Called after get_refund_serial. 7751 * @param cls pointer to a LookupRefundSerial_Closure. 7752 * @param refund_serial unique serial number of the refund 7753 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 7754 * @param coin_pub public coin from which the refund comes from 7755 * @param exchange_url URL of the exchange that issued @a coin_pub 7756 * @param rtransaction_id identificator of the refund 7757 * @param reason human-readable explanation of the refund 7758 * @param refund_amount refund amount which is being taken from @a coin_pub 7759 * @param pending true if this refund has not been processed by the wallet/exchange 7760 */ 7761 static void 7762 get_refund_serial_cb (void *cls, 7763 uint64_t refund_serial, 7764 struct GNUNET_TIME_Timestamp timestamp, 7765 const struct TALER_CoinSpendPublicKeyP *coin_pub, 7766 const char *exchange_url, 7767 uint64_t rtransaction_id, 7768 const char *reason, 7769 const struct TALER_Amount *refund_amount, 7770 bool pending) 7771 { 7772 struct LookupRefundSerial_Closure *lookup_cls = cls; 7773 if (NULL == lookup_cls) 7774 return; 7775 if ((GNUNET_TIME_timestamp_cmp (lookup_cls->refund->timestamp, 7776 ==, 7777 timestamp)) && 7778 (0 == GNUNET_memcmp (lookup_cls->refund->coin_pub, 7779 coin_pub)) && 7780 (0 == strcmp (lookup_cls->refund->exchange_url, 7781 exchange_url)) && 7782 (0 == strcmp (lookup_cls->refund->reason, 7783 reason)) && 7784 (GNUNET_OK == 7785 TALER_amount_cmp_currency ( 7786 &lookup_cls->refund->refund_amount, 7787 refund_amount)) && 7788 (0 == TALER_amount_cmp (&lookup_cls->refund->refund_amount, 7789 refund_amount))) 7790 lookup_cls->serial = refund_serial; 7791 } 7792 7793 7794 /** 7795 * Utility function for getting the database row number of a refund. 7796 * @param instance the instance associated with the refund. 7797 * @param h_contract_terms the contract terms the refund was made with. 7798 * @param refund the refund we are querying the row number of. 7799 * 7800 * @return the row number of the refund. 7801 */ 7802 static uint64_t 7803 get_refund_serial (const struct InstanceData *instance, 7804 const struct TALER_PrivateContractHashP *h_contract_terms, 7805 const struct RefundData *refund) 7806 { 7807 struct LookupRefundSerial_Closure lookup_cls = { 7808 .refund = refund, 7809 .serial = 0 7810 }; 7811 7812 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 7813 TALER_MERCHANTDB_set_instance (pg, 7814 instance->instance.id)); 7815 GNUNET_assert (0 < TALER_MERCHANTDB_iterate_refunds_detailed (pg, 7816 instance->instance.id, 7817 h_contract_terms, 7818 &get_refund_serial_cb, 7819 &lookup_cls)); 7820 GNUNET_assert (0 != lookup_cls.serial); 7821 7822 return lookup_cls.serial; 7823 } 7824 7825 7826 /** 7827 * Tests looking up proof of a refund. 7828 * @param refund_serial the row number of the refund. 7829 * @param expected_exchange_sig the exchange signature we are expecting. 7830 * @param expected_exchange_pub the exchange public key we are expecting. 7831 * 7832 * @return 0 on success, 1 otherwise. 7833 */ 7834 static int 7835 test_lookup_refund_proof (uint64_t refund_serial, 7836 const struct 7837 TALER_ExchangeSignatureP *expected_exchange_sig, 7838 const struct 7839 TALER_ExchangePublicKeyP *expected_exchange_pub) 7840 { 7841 struct TALER_ExchangeSignatureP exchange_sig; 7842 struct TALER_ExchangePublicKeyP exchange_pub; 7843 if (1 != TALER_MERCHANTDB_get_refund_proof (pg, 7844 refund_serial, 7845 &exchange_sig, 7846 &exchange_pub)) 7847 { 7848 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7849 "Lookup refund proof failed\n"); 7850 return 1; 7851 } 7852 if ((0 != GNUNET_memcmp (expected_exchange_sig, 7853 &exchange_sig)) || 7854 (0 != GNUNET_memcmp (expected_exchange_pub, 7855 &exchange_pub))) 7856 { 7857 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7858 "Lookup refund proof failed: mismatched data\n"); 7859 return 1; 7860 } 7861 return 0; 7862 } 7863 7864 7865 /** 7866 * Closure for testing refund functionality. 7867 */ 7868 struct TestRefunds_Closure 7869 { 7870 /** 7871 * The instance. 7872 */ 7873 struct InstanceData instance; 7874 7875 /** 7876 * The merchant account. 7877 */ 7878 struct TALER_MERCHANTDB_AccountDetails account; 7879 7880 /** 7881 * The exchange signing key. 7882 */ 7883 struct ExchangeSignkeyData signkey; 7884 7885 /** 7886 * The order data. 7887 */ 7888 struct OrderData orders[3]; 7889 7890 /** 7891 * The deposit data. 7892 */ 7893 struct DepositData deposits[5]; 7894 7895 /** 7896 * The refund data. 7897 */ 7898 struct RefundData refunds[3]; 7899 7900 /** 7901 * The refund proof data. 7902 */ 7903 struct RefundProofData refund_proof; 7904 }; 7905 7906 7907 /** 7908 * Prepares for testing refunds. 7909 * @param cls the closure to initialize. 7910 */ 7911 static void 7912 pre_test_refunds (struct TestRefunds_Closure *cls) 7913 { 7914 /* Instance */ 7915 make_instance ("test_inst_refunds", 7916 &cls->instance); 7917 7918 /* Account */ 7919 make_account (&cls->account); 7920 cls->account.instance_id = cls->instance.instance.id; 7921 /* Signing key */ 7922 make_exchange_signkey (&cls->signkey); 7923 7924 /* Order */ 7925 make_order ("test_refunds_od_0", 7926 &cls->orders[0]); 7927 make_order ("test_refunds_od_1", 7928 &cls->orders[1]); 7929 make_order ("test_refunds_od_2", 7930 &cls->orders[2]); 7931 7932 /* Deposit */ 7933 make_deposit (&cls->instance, 7934 &cls->account, 7935 &cls->orders[0], 7936 &cls->signkey, 7937 &cls->deposits[0]); 7938 make_deposit (&cls->instance, 7939 &cls->account, 7940 &cls->orders[0], 7941 &cls->signkey, 7942 &cls->deposits[1]); 7943 make_deposit (&cls->instance, 7944 &cls->account, 7945 &cls->orders[1], 7946 &cls->signkey, 7947 &cls->deposits[2]); 7948 /* Two deposits of the *same* coin on order 2, via two different 7949 exchanges (and thus two deposit confirmations). The schema allows 7950 this: merchant_deposits is unique only on 7951 (deposit_confirmation_serial, coin_pub). It makes 7952 do_increase_refund derive the same rtransaction_id twice, which is 7953 exactly the collision a concurrent refund on another connection 7954 produces. */ 7955 make_deposit (&cls->instance, 7956 &cls->account, 7957 &cls->orders[2], 7958 &cls->signkey, 7959 &cls->deposits[3]); 7960 make_deposit (&cls->instance, 7961 &cls->account, 7962 &cls->orders[2], 7963 &cls->signkey, 7964 &cls->deposits[4]); 7965 cls->deposits[4].exchange_url = "https://test-exchange-two/"; 7966 cls->deposits[4].coin_pub = cls->deposits[3].coin_pub; 7967 cls->deposits[4].coin_sig = cls->deposits[3].coin_sig; 7968 7969 /* Refund */ 7970 make_refund (&cls->deposits[0], 7971 &cls->refunds[0]); 7972 make_refund (&cls->deposits[2], 7973 &cls->refunds[1]); 7974 make_refund (&cls->deposits[2], 7975 &cls->refunds[2]); 7976 GNUNET_assert (GNUNET_OK == 7977 TALER_string_to_amount ("EUR:10.00", 7978 &cls->refunds[1].refund_amount)); 7979 cls->refunds[1].reason = "refund 1"; 7980 GNUNET_assert (GNUNET_OK == 7981 TALER_string_to_amount ("EUR:10.00", 7982 &cls->refunds[2].refund_amount)); 7983 cls->refunds[2].reason = "refund 2"; 7984 7985 /* Refund proof */ 7986 GNUNET_assert (GNUNET_OK == 7987 TALER_string_to_amount ("EUR:0.02", 7988 &cls->refund_proof.refund_fee)); 7989 memset (&cls->refund_proof.exchange_sig, 7990 42, 7991 sizeof (cls->refund_proof.exchange_sig)); 7992 } 7993 7994 7995 /** 7996 * Cleans up after testing refunds. 7997 * @param cls the closure. 7998 */ 7999 static void 8000 post_test_refunds (struct TestRefunds_Closure *cls) 8001 { 8002 free_instance_data (&cls->instance); 8003 free_order_data (&cls->orders[0]); 8004 free_order_data (&cls->orders[1]); 8005 free_order_data (&cls->orders[2]); 8006 } 8007 8008 8009 /** 8010 * Runs the refund tests. 8011 * @param cls the closure. 8012 * 8013 * @return 0 on success, 1 otherwise. 8014 */ 8015 static int 8016 run_test_refunds (struct TestRefunds_Closure *cls) 8017 { 8018 struct TALER_Amount inc; 8019 uint64_t refund_serial; 8020 8021 /* Insert an instance */ 8022 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 8023 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8024 /* Insert an account */ 8025 TEST_RET_ON_FAIL (test_insert_account (&cls->instance, 8026 &cls->account, 8027 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8028 /* Insert an order */ 8029 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 8030 &cls->orders[0], 8031 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8032 /* Insert contract terms */ 8033 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 8034 &cls->orders[0], 8035 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8036 /* Insert a signing key */ 8037 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 8038 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8039 /* Insert a deposit */ 8040 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 8041 &cls->signkey, 8042 &cls->deposits[0], 8043 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8044 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 8045 &cls->signkey, 8046 &cls->deposits[1], 8047 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8048 /* Mark as paid */ 8049 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 8050 &cls->orders[0], 8051 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8052 /* Test refund coin */ 8053 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 8054 TALER_MERCHANTDB_insert_refund_by_coin (pg, 8055 cls->instance.instance.id, 8056 &cls->deposits[0].h_contract_terms 8057 , 8058 cls->refunds[0].timestamp, 8059 cls->refunds[0].coin_pub, 8060 cls->refunds[0].reason), 8061 "Refund coin failed\n"); 8062 refund_serial = get_refund_serial (&cls->instance, 8063 &cls->deposits[0].h_contract_terms, 8064 &cls->refunds[0]); 8065 /* Test double refund fails */ 8066 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 8067 TALER_MERCHANTDB_insert_refund_by_coin (pg, 8068 cls->instance.instance.id, 8069 &cls->deposits[0].h_contract_terms 8070 , 8071 cls->refunds[0].timestamp, 8072 cls->refunds[0].coin_pub, 8073 cls->refunds[0].reason), 8074 "Refund coin failed\n"); 8075 /* Test lookup refunds */ 8076 TEST_RET_ON_FAIL (test_lookup_refunds (&cls->instance, 8077 &cls->deposits[0].h_contract_terms, 8078 1, 8079 cls->refunds[0].coin_pub, 8080 &cls->refunds[0].refund_amount)); 8081 /* Test lookup refunds detailed */ 8082 TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance, 8083 &cls->deposits[0]. 8084 h_contract_terms, 8085 true, 8086 1, 8087 &cls->refunds[0])); 8088 /* Test insert refund proof */ 8089 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 8090 TALER_MERCHANTDB_insert_refund_proof (pg, 8091 refund_serial, 8092 &cls->refund_proof. 8093 exchange_sig, 8094 &cls->signkey.exchange_pub 8095 ), 8096 "Insert refund proof failed\n"); 8097 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 8098 TALER_MERCHANTDB_insert_refund_proof (pg, 8099 refund_serial, 8100 &cls->refund_proof. 8101 exchange_sig, 8102 &cls->signkey.exchange_pub 8103 ), 8104 "Insert refund proof failed\n"); 8105 /* Test that we can't give too much in refunds */ 8106 GNUNET_assert (GNUNET_OK == 8107 TALER_string_to_amount ("EUR:1000.00", 8108 &inc)); 8109 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_TOO_HIGH == 8110 TALER_MERCHANTDB_do_increase_refund (pg, 8111 cls->instance.instance.id, 8112 cls->orders[0].id, 8113 &inc, 8114 NULL, 8115 NULL, 8116 "more"), 8117 "Increase refund failed\n"); 8118 /* Test increase refund */ 8119 GNUNET_assert (GNUNET_OK == 8120 TALER_string_to_amount ("EUR:1.00", 8121 &inc)); 8122 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS == 8123 TALER_MERCHANTDB_do_increase_refund (pg, 8124 cls->instance.instance.id, 8125 cls->orders[0].id, 8126 &inc, 8127 NULL, 8128 NULL, 8129 "more"), 8130 "Increase refund failed\n"); 8131 /* Test lookup refund proof */ 8132 TEST_RET_ON_FAIL (test_lookup_refund_proof (1, 8133 &cls->refund_proof.exchange_sig, 8134 &cls->signkey.exchange_pub)); 8135 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 8136 &cls->orders[1], 8137 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8138 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 8139 &cls->orders[1], 8140 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8141 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 8142 &cls->signkey, 8143 &cls->deposits[2], 8144 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8145 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 8146 &cls->orders[1], 8147 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8148 /* Test refunding a small amount of the coin, then increasing it */ 8149 GNUNET_assert (GNUNET_OK == 8150 TALER_string_to_amount ("EUR:10.00", 8151 &inc)); 8152 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS == 8153 TALER_MERCHANTDB_do_increase_refund (pg, 8154 cls->instance.instance.id, 8155 cls->orders[1].id, 8156 &inc, 8157 NULL, 8158 NULL, 8159 cls->refunds[1].reason), 8160 "Increase refund failed\n"); 8161 GNUNET_assert (GNUNET_OK == 8162 TALER_string_to_amount ("EUR:20.00", 8163 &inc)); 8164 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS == 8165 TALER_MERCHANTDB_do_increase_refund (pg, 8166 cls->instance.instance.id, 8167 cls->orders[1].id, 8168 &inc, 8169 NULL, 8170 NULL, 8171 cls->refunds[2].reason), 8172 "Increase refund failed\n"); 8173 TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance, 8174 &cls->deposits[2]. 8175 h_contract_terms, 8176 false, 8177 2, 8178 &cls->refunds[1])); 8179 /* Two deposits of the same coin on the same order make 8180 do_increase_refund pick the same rtransaction_id twice, which is 8181 exactly what a concurrent refund on another connection does. The 8182 resulting unique violation must NOT be reported as 8183 #TALER_MERCHANTDB_RS_NO_SUCH_ORDER (a bogus 404 for an order that 8184 obviously exists), but as a serialization failure the caller can 8185 retry. */ 8186 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 8187 &cls->orders[2], 8188 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8189 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 8190 &cls->orders[2], 8191 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8192 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 8193 &cls->signkey, 8194 &cls->deposits[3], 8195 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8196 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 8197 &cls->signkey, 8198 &cls->deposits[4], 8199 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8200 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 8201 &cls->orders[2], 8202 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8203 /* More than a single deposit is worth, so that the second (duplicate) 8204 deposit has to be refunded as well. */ 8205 GNUNET_assert (GNUNET_OK == 8206 TALER_string_to_amount ("EUR:60.00", 8207 &inc)); 8208 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SOFT_ERROR == 8209 TALER_MERCHANTDB_do_increase_refund (pg, 8210 cls->instance.instance.id, 8211 cls->orders[2].id, 8212 &inc, 8213 NULL, 8214 NULL, 8215 "duplicate coin"), 8216 "Duplicate rtransaction_id not reported as serialization failure\n"); 8217 return 0; 8218 } 8219 8220 8221 /** 8222 * All logic for testing refunds. 8223 * 8224 * @return 0 on success, 1 otherwise. 8225 */ 8226 static int 8227 test_refunds (void) 8228 { 8229 struct TestRefunds_Closure test_cls; 8230 int test_result; 8231 8232 pre_test_refunds (&test_cls); 8233 test_result = run_test_refunds (&test_cls); 8234 post_test_refunds (&test_cls); 8235 return test_result; 8236 } 8237 8238 8239 /** 8240 * Convenience function that reverses an array of orders. 8241 * @param orders_length length of @e orders. 8242 * @param orders the array to reverse. 8243 */ 8244 static void 8245 reverse_order_data_array (unsigned int orders_length, 8246 struct OrderData *orders) 8247 { 8248 struct OrderData tmp[orders_length]; 8249 for (unsigned int i = 0; i < orders_length; ++i) 8250 tmp[i] = orders[orders_length - 1 - i]; 8251 for (unsigned int i = 0; i < orders_length; ++i) 8252 orders[i] = tmp[i]; 8253 } 8254 8255 8256 /** 8257 * Closure for testing all the filters for looking up orders. 8258 */ 8259 struct TestLookupOrdersAllFilters_Closure 8260 { 8261 /** 8262 * The instance. 8263 */ 8264 struct InstanceData instance; 8265 8266 /** 8267 * The merchant account. 8268 */ 8269 struct TALER_MERCHANTDB_AccountDetails account; 8270 8271 /** 8272 * The exchange signing key. 8273 */ 8274 struct ExchangeSignkeyData signkey; 8275 8276 /** 8277 * The array of order ids. 8278 */ 8279 char *order_ids[64]; 8280 8281 /** 8282 * The array of orders. 8283 */ 8284 struct OrderData orders[64]; 8285 8286 /** 8287 * The array of deposits. 8288 */ 8289 struct DepositData deposits[64]; 8290 8291 /** 8292 * The array of refunds. 8293 */ 8294 struct RefundData refunds[64]; 8295 }; 8296 8297 8298 /** 8299 * Sets up for testing lookup order filters. 8300 * @param cls the closure. 8301 */ 8302 static void 8303 pre_test_lookup_orders_all_filters ( 8304 struct TestLookupOrdersAllFilters_Closure *cls) 8305 { 8306 make_instance ("test_inst_lookup_orders_all_filters", 8307 &cls->instance); 8308 make_account (&cls->account); 8309 cls->account.instance_id = cls->instance.instance.id; 8310 make_exchange_signkey (&cls->signkey); 8311 for (unsigned int i = 0; i < 64; ++i) 8312 { 8313 (void) GNUNET_asprintf (&cls->order_ids[i], 8314 "test_orders_filter_od_%u", 8315 i); 8316 make_order (cls->order_ids[i], 8317 &cls->orders[i]); 8318 GNUNET_assert (0 == 8319 json_object_set_new (cls->orders[i].contract, 8320 "order_id", 8321 json_string (cls->order_ids[i]))); 8322 make_deposit (&cls->instance, 8323 &cls->account, 8324 &cls->orders[i], 8325 &cls->signkey, 8326 &cls->deposits[i]); 8327 make_refund (&cls->deposits[i], 8328 &cls->refunds[i]); 8329 } 8330 } 8331 8332 8333 /** 8334 * Cleans up after testing lookup order filters. 8335 * @param cls the closure. 8336 */ 8337 static void 8338 post_test_lookup_orders_all_filters ( 8339 struct TestLookupOrdersAllFilters_Closure *cls) 8340 { 8341 free_instance_data (&cls->instance); 8342 for (unsigned int i = 0; i < 64; ++i) 8343 { 8344 free_order_data (&cls->orders[i]); 8345 GNUNET_free (cls->order_ids[i]); 8346 } 8347 } 8348 8349 8350 /** 8351 * Runs the tests for lookup order filters. 8352 * @param cls the closure. 8353 * 8354 * @return 0 on success, 1 otherwise. 8355 */ 8356 static int 8357 run_test_lookup_orders_all_filters ( 8358 struct TestLookupOrdersAllFilters_Closure *cls) 8359 { 8360 /* Order filter extravaganza */ 8361 struct 8362 { 8363 bool claimed; 8364 bool paid; 8365 bool refunded; 8366 bool wired; 8367 } order_status[64]; 8368 unsigned int *permutation; 8369 8370 /* Pseudorandomly generate variations for the filter to differentiate */ 8371 permutation = GNUNET_CRYPTO_random_permute (64); 8372 for (unsigned int i = 0; i < 64; ++i) 8373 { 8374 unsigned int dest = permutation[i]; 8375 order_status[dest].claimed = (i & 1) ? true : false; 8376 order_status[dest].paid = (3 == (i & 3)) ? true : false; 8377 order_status[dest].refunded = (5 == (i & 5)) ? true : false; 8378 order_status[dest].wired = (9 == (i & 9)) ? true : false; 8379 } 8380 GNUNET_free (permutation); 8381 8382 8383 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 8384 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8385 TEST_RET_ON_FAIL (test_insert_account (&cls->instance, 8386 &cls->account, 8387 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8388 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 8389 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8390 for (unsigned int i = 0; i < 64; ++i) 8391 { 8392 uint64_t order_serial; 8393 8394 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 8395 &cls->orders[i], 8396 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8397 order_serial = get_order_serial (&cls->instance, 8398 &cls->orders[i]); 8399 8400 if (order_status[i].claimed) 8401 { 8402 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 8403 &cls->orders[i], 8404 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8405 } 8406 else 8407 { 8408 continue; 8409 } 8410 8411 if (order_status[i].paid) 8412 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 8413 &cls->orders[i], 8414 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8415 if (order_status[i].refunded) 8416 { 8417 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 8418 &cls->signkey, 8419 &cls->deposits[i], 8420 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8421 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 8422 TALER_MERCHANTDB_insert_refund_by_coin (pg, 8423 cls->instance.instance.id, 8424 &cls->deposits[i]. 8425 h_contract_terms, 8426 cls->refunds[i].timestamp, 8427 cls->refunds[i].coin_pub, 8428 cls->refunds[i].reason), 8429 "Refund coin failed\n"); 8430 } 8431 8432 if (order_status[i].wired) 8433 TEST_RET_ON_FAIL (test_mark_order_wired (order_serial, 8434 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8435 } 8436 8437 /* There are 3^3 = 27 possibilities here, not counting inc/dec and start row */ 8438 for (unsigned int i = 0; i < 27; ++i) 8439 { 8440 struct TALER_MERCHANTDB_OrderFilter filter = { 8441 .paid = (i % 3) + 1, 8442 .refunded = ((i / 3) % 3) + 1, 8443 .wired = ((i / 9) % 3) + 1, 8444 .expired = TALER_EXCHANGE_YNA_ALL, 8445 .date = GNUNET_TIME_UNIT_ZERO_TS, 8446 .start_row = 0, 8447 .delta = 64 8448 }; 8449 unsigned int orders_length = 0; 8450 struct OrderData orders[64]; 8451 8452 /* Now figure out which orders should make it through the filter */ 8453 for (unsigned int j = 0; j < 64; ++j) 8454 { 8455 if (((TALER_EXCHANGE_YNA_YES == filter.paid) && 8456 (true != order_status[j].paid)) || 8457 ((TALER_EXCHANGE_YNA_NO == filter.paid) && 8458 (false != order_status[j].paid)) || 8459 ((TALER_EXCHANGE_YNA_YES == filter.refunded) && 8460 (true != order_status[j].refunded)) || 8461 ((TALER_EXCHANGE_YNA_NO == filter.refunded) && 8462 (false != order_status[j].refunded)) || 8463 ((TALER_EXCHANGE_YNA_YES == filter.wired) && 8464 (true != order_status[j].wired)) || 8465 ((TALER_EXCHANGE_YNA_NO == filter.wired) && 8466 (false != order_status[j].wired))) 8467 continue; 8468 orders[orders_length] = cls->orders[j]; 8469 orders_length += 1; 8470 } 8471 8472 /* Test the lookup */ 8473 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 8474 &filter, 8475 orders_length, 8476 orders)); 8477 8478 /* Now test decreasing */ 8479 filter.start_row = 256; 8480 filter.date = GNUNET_TIME_UNIT_FOREVER_TS; 8481 filter.delta = -64; 8482 8483 reverse_order_data_array (orders_length, 8484 orders); 8485 8486 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 8487 &filter, 8488 orders_length, 8489 orders)); 8490 } 8491 8492 return 0; 8493 } 8494 8495 8496 /** 8497 * Handles all logic for testing lookup order filters. 8498 * 8499 * @return 0 on success, 1 otherwise. 8500 */ 8501 static int 8502 test_lookup_orders_all_filters (void) 8503 { 8504 struct TestLookupOrdersAllFilters_Closure test_cls; 8505 int test_result; 8506 8507 memset (&test_cls, 8508 0, 8509 sizeof (test_cls)); 8510 pre_test_lookup_orders_all_filters (&test_cls); 8511 test_result = run_test_lookup_orders_all_filters (&test_cls); 8512 post_test_lookup_orders_all_filters (&test_cls); 8513 return test_result; 8514 } 8515 8516 8517 static void 8518 kyc_status_ok ( 8519 void *cls, 8520 const struct TALER_MerchantWireHashP *h_wire, 8521 struct TALER_FullPayto payto_uri, 8522 const char *exchange_url, 8523 struct GNUNET_TIME_Timestamp last_check, 8524 bool kyc_ok, 8525 const struct TALER_AccountAccessTokenP *access_token, 8526 unsigned int last_http_status, 8527 enum TALER_ErrorCode last_ec, 8528 bool in_aml_review, 8529 const json_t *jlimits) 8530 { 8531 bool *fail = cls; 8532 8533 if (kyc_ok) 8534 *fail = false; 8535 } 8536 8537 8538 static void 8539 kyc_status_fail ( 8540 void *cls, 8541 const struct TALER_MerchantWireHashP *h_wire, 8542 struct TALER_FullPayto payto_uri, 8543 const char *exchange_url, 8544 struct GNUNET_TIME_Timestamp last_check, 8545 bool kyc_ok, 8546 const struct TALER_AccountAccessTokenP *access_token, 8547 unsigned int last_http_status, 8548 enum TALER_ErrorCode last_ec, 8549 bool in_aml_review, 8550 const json_t *jlimits) 8551 { 8552 bool *fail = cls; 8553 8554 if (! kyc_ok) 8555 *fail = false; 8556 } 8557 8558 8559 /** 8560 * Expected result for a KYC failure lookup. 8561 */ 8562 struct KycFailureExpected 8563 { 8564 /** 8565 * Account the result must belong to. 8566 */ 8567 const struct TALER_MERCHANTDB_AccountDetails *account; 8568 8569 /** 8570 * Exchange the result must belong to. 8571 */ 8572 const char *exchange_url; 8573 8574 /** 8575 * Expected time of the failed KYC check. 8576 */ 8577 struct GNUNET_TIME_Timestamp timestamp; 8578 8579 /** 8580 * Expected HTTP status returned by the exchange. 8581 */ 8582 unsigned int exchange_http_status; 8583 8584 /** 8585 * Number of results received. 8586 */ 8587 unsigned int called; 8588 8589 /** 8590 * Set if a result did not match expectations. 8591 */ 8592 bool failed; 8593 }; 8594 8595 8596 /** 8597 * Check a KYC failure record returned by iterate_kyc_statuses(). 8598 * 8599 * @param cls a `struct KycFailureExpected *` 8600 * @param h_wire hash of the wire account 8601 * @param payto_uri payto URI of the merchant account 8602 * @param exchange_url exchange the status applies to 8603 * @param last_check time of the last KYC check 8604 * @param kyc_ok true if KYC was successful 8605 * @param access_token access token, if available 8606 * @param last_http_status HTTP status of the last check 8607 * @param last_ec Taler error code of the last check 8608 * @param in_aml_review true if the account is under AML review 8609 * @param jlimits account limits, if known 8610 */ 8611 static void 8612 kyc_failure_check ( 8613 void *cls, 8614 const struct TALER_MerchantWireHashP *h_wire, 8615 struct TALER_FullPayto payto_uri, 8616 const char *exchange_url, 8617 struct GNUNET_TIME_Timestamp last_check, 8618 bool kyc_ok, 8619 const struct TALER_AccountAccessTokenP *access_token, 8620 unsigned int last_http_status, 8621 enum TALER_ErrorCode last_ec, 8622 bool in_aml_review, 8623 const json_t *jlimits) 8624 { 8625 struct KycFailureExpected *expected = cls; 8626 8627 expected->called++; 8628 if ( (0 != GNUNET_memcmp (&expected->account->h_wire, 8629 h_wire)) || 8630 (0 != strcmp (expected->account->payto_uri.full_payto, 8631 payto_uri.full_payto)) || 8632 (0 != strcmp (expected->exchange_url, 8633 exchange_url)) || 8634 (GNUNET_TIME_timestamp_cmp (expected->timestamp, 8635 !=, 8636 last_check)) || 8637 kyc_ok || 8638 (NULL != access_token) || 8639 (expected->exchange_http_status != last_http_status) || 8640 (TALER_EC_NONE != last_ec) || 8641 in_aml_review || 8642 (NULL != jlimits) ) 8643 { 8644 GNUNET_break (0); 8645 expected->failed = true; 8646 } 8647 } 8648 8649 8650 /** 8651 * Count a database notification. 8652 * 8653 * @param cls an `unsigned int *` 8654 * @param extra unused notification payload 8655 * @param extra_size size of @a extra 8656 */ 8657 static void 8658 kyc_event_cb (void *cls, 8659 const void *extra, 8660 size_t extra_size) 8661 { 8662 unsigned int *fired = cls; 8663 8664 (void) extra; 8665 (void) extra_size; 8666 (*fired)++; 8667 } 8668 8669 8670 /** 8671 * Check the notification encoding used by the SQL refresh request. 8672 */ 8673 static void 8674 kyc_refresh_event_cb (void *cls, 8675 const void *extra, 8676 size_t extra_size) 8677 { 8678 uint64_t serial; 8679 8680 GNUNET_assert (sizeof (serial) == extra_size); 8681 memcpy (&serial, extra, sizeof (serial)); 8682 GNUNET_assert (pg->current_merchant_serial == GNUNET_ntohll (serial)); 8683 kyc_event_cb (cls, extra, extra_size); 8684 } 8685 8686 8687 /** 8688 * Test inserting and updating a failed KYC check. 8689 * Regression test for #10761, which reported that the operation formerly 8690 * named account_kyc_set_failed had no test coverage. 8691 * 8692 * @param instance instance that owns @a account 8693 * @param account merchant account to use 8694 * @return 0 on success, 1 otherwise 8695 */ 8696 static int 8697 test_insert_kyc_failure ( 8698 const struct InstanceData *instance, 8699 const struct TALER_MERCHANTDB_AccountDetails *account) 8700 { 8701 static const char exchange_url[] = "https://exchange2.com/"; 8702 struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP account_event = { 8703 .header.size = htons (sizeof (account_event)), 8704 .header.type = htons ( 8705 TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED), 8706 .merchant_pub = instance->merchant_pub, 8707 .h_wire = account->h_wire 8708 }; 8709 struct TALER_MERCHANTDB_InstanceKycStatusChangeEventP general_event = { 8710 .header.size = htons (sizeof (general_event)), 8711 .header.type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED), 8712 .merchant_pub = instance->merchant_pub 8713 }; 8714 struct GNUNET_DB_EventHeaderP refresh_event = { 8715 .size = htons (sizeof (refresh_event)), 8716 .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_UPDATE_FORCED) 8717 }; 8718 struct GNUNET_DB_EventHandler *account_eh = NULL; 8719 struct GNUNET_DB_EventHandler *general_eh = NULL; 8720 struct GNUNET_DB_EventHandler *refresh_eh = NULL; 8721 unsigned int account_events = 0; 8722 unsigned int general_events = 0; 8723 unsigned int refresh_events = 0; 8724 struct KycFailureExpected expected = { 8725 .account = account, 8726 .exchange_url = exchange_url, 8727 .exchange_http_status = MHD_HTTP_BAD_GATEWAY 8728 }; 8729 int ret = 1; 8730 8731 TEST_SET_INSTANCE (instance->instance.id, 8732 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 8733 account_eh = TALER_MERCHANTDB_event_listen ( 8734 pg, 8735 &account_event.header, 8736 GNUNET_TIME_UNIT_FOREVER_REL, 8737 &kyc_event_cb, 8738 &account_events); 8739 if (NULL == account_eh) 8740 { 8741 GNUNET_break (0); 8742 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8743 "Failed to listen for account KYC events\n"); 8744 goto cleanup; 8745 } 8746 general_eh = TALER_MERCHANTDB_event_listen ( 8747 pg, 8748 &general_event.header, 8749 GNUNET_TIME_UNIT_FOREVER_REL, 8750 &kyc_event_cb, 8751 &general_events); 8752 if (NULL == general_eh) 8753 { 8754 GNUNET_break (0); 8755 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8756 "Failed to listen for general KYC events\n"); 8757 goto cleanup; 8758 } 8759 refresh_eh = TALER_MERCHANTDB_event_listen ( 8760 pg, 8761 &refresh_event, 8762 GNUNET_TIME_UNIT_FOREVER_REL, 8763 &kyc_refresh_event_cb, 8764 &refresh_events); 8765 if (NULL == refresh_eh) 8766 { 8767 GNUNET_break (0); 8768 goto cleanup; 8769 } 8770 8771 expected.timestamp = GNUNET_TIME_timestamp_get (); 8772 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 8773 TALER_MERCHANTDB_insert_kyc_failure ( 8774 pg, 8775 instance->instance.id, 8776 &account->h_wire, 8777 exchange_url, 8778 expected.timestamp, 8779 expected.exchange_http_status, 8780 false)) 8781 { 8782 GNUNET_break (0); 8783 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8784 "Inserting KYC failure failed\n"); 8785 goto cleanup; 8786 } 8787 GNUNET_PQ_event_do_poll (pg->conn); 8788 if ( (1 != account_events) || 8789 (1 != general_events) ) 8790 { 8791 GNUNET_break (0); 8792 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8793 "Inserting KYC failure generated %u account and %u general" 8794 " events, expected one each\n", 8795 account_events, 8796 general_events); 8797 goto cleanup; 8798 } 8799 if (1 != TALER_MERCHANTDB_iterate_kyc_statuses ( 8800 pg, 8801 instance->instance.id, 8802 &account->h_wire, 8803 exchange_url, 8804 true, 8805 &kyc_failure_check, 8806 &expected)) 8807 { 8808 GNUNET_break (0); 8809 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8810 "Looking up inserted KYC failure failed\n"); 8811 goto cleanup; 8812 } 8813 if (expected.failed || 8814 (1 != expected.called)) 8815 { 8816 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8817 "Inserted KYC failure did not match expectations\n"); 8818 goto cleanup; 8819 } 8820 /* Reading the status must wake the KYC checker, including an inquiry 8821 sleeping until its next periodic check. An ACCOUNTS_CHANGED event 8822 reloads the account list but leaves existing inquiries asleep. */ 8823 GNUNET_PQ_event_do_poll (pg->conn); 8824 if (1 != refresh_events) 8825 { 8826 GNUNET_break (0); 8827 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8828 "Reading KYC status generated %u forced refresh events," 8829 " expected one\n", 8830 refresh_events); 8831 goto cleanup; 8832 } 8833 8834 expected.timestamp = GNUNET_TIME_timestamp_get (); 8835 expected.exchange_http_status = MHD_HTTP_ACCEPTED; 8836 expected.called = 0; 8837 expected.failed = false; 8838 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 8839 TALER_MERCHANTDB_insert_kyc_failure ( 8840 pg, 8841 instance->instance.id, 8842 &account->h_wire, 8843 exchange_url, 8844 expected.timestamp, 8845 expected.exchange_http_status, 8846 false)) 8847 { 8848 GNUNET_break (0); 8849 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8850 "Updating KYC failure failed\n"); 8851 goto cleanup; 8852 } 8853 GNUNET_PQ_event_do_poll (pg->conn); 8854 if ( (2 != account_events) || 8855 (2 != general_events) ) 8856 { 8857 GNUNET_break (0); 8858 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8859 "Updating KYC failure generated %u account and %u general" 8860 " events, expected two each\n", 8861 account_events, 8862 general_events); 8863 goto cleanup; 8864 } 8865 if (1 != TALER_MERCHANTDB_iterate_kyc_statuses ( 8866 pg, 8867 instance->instance.id, 8868 &account->h_wire, 8869 exchange_url, 8870 true, 8871 &kyc_failure_check, 8872 &expected)) 8873 { 8874 GNUNET_break (0); 8875 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8876 "Looking up updated KYC failure failed\n"); 8877 goto cleanup; 8878 } 8879 if (expected.failed || 8880 (1 != expected.called)) 8881 { 8882 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8883 "Updated KYC failure did not match expectations\n"); 8884 goto cleanup; 8885 } 8886 GNUNET_PQ_event_do_poll (pg->conn); 8887 if (2 != refresh_events) 8888 { 8889 GNUNET_break (0); 8890 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 8891 "Reading updated KYC status generated %u forced refresh" 8892 " events, expected two\n", 8893 refresh_events); 8894 goto cleanup; 8895 } 8896 /* A notification-driven reread must neither request another refresh nor 8897 move the persisted due time. Repeated identical failures are silent. */ 8898 { 8899 uint64_t before; 8900 uint64_t after; 8901 8902 TEST_RET_ON_FAIL (query_sql_num ( 8903 "SELECT next_kyc_poll AS num FROM merchant_kyc" 8904 " WHERE exchange_url='https://exchange2.com/'", &before)); 8905 expected.called = 0; 8906 GNUNET_assert (1 == TALER_MERCHANTDB_iterate_kyc_statuses ( 8907 pg, instance->instance.id, &account->h_wire, 8908 exchange_url, false, &kyc_failure_check, &expected)); 8909 GNUNET_assert ((! expected.failed) && (1 == expected.called)); 8910 TEST_RET_ON_FAIL (query_sql_num ( 8911 "SELECT next_kyc_poll AS num FROM merchant_kyc" 8912 " WHERE exchange_url='https://exchange2.com/'", &after)); 8913 GNUNET_assert (before == after); 8914 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 8915 TALER_MERCHANTDB_insert_kyc_failure ( 8916 pg, instance->instance.id, &account->h_wire, 8917 exchange_url, GNUNET_TIME_timestamp_get (), 8918 expected.exchange_http_status, false)); 8919 GNUNET_PQ_event_do_poll (pg->conn); 8920 GNUNET_assert (2 == refresh_events); 8921 GNUNET_assert (2 == account_events); 8922 GNUNET_assert (2 == general_events); 8923 } 8924 ret = 0; 8925 8926 cleanup: 8927 if (NULL != refresh_eh) 8928 TALER_MERCHANTDB_event_listen_cancel (refresh_eh); 8929 if (NULL != general_eh) 8930 TALER_MERCHANTDB_event_listen_cancel (general_eh); 8931 if (NULL != account_eh) 8932 TALER_MERCHANTDB_event_listen_cancel (account_eh); 8933 return ret; 8934 } 8935 8936 8937 /** 8938 * Function that tests the KYC table. 8939 * 8940 * @return 0 on success, 1 otherwise. 8941 */ 8942 static int 8943 test_kyc (void) 8944 { 8945 struct InstanceData instance; 8946 struct TALER_MERCHANTDB_AccountDetails account; 8947 bool fail; 8948 struct GNUNET_TIME_Timestamp now; 8949 uint64_t alerts; 8950 8951 make_instance ("test_kyc", 8952 &instance); 8953 make_account (&account); 8954 account.instance_id = instance.instance.id; 8955 TEST_RET_ON_FAIL (test_insert_instance (&instance, 8956 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8957 TEST_RET_ON_FAIL (test_insert_account (&instance, 8958 &account, 8959 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 8960 /* Arm the KYC alert path: merchant_send_kyc_notification() returns 8961 early unless the instance has BOTH an e-mail address and a 8962 notification language. No C entry point writes 8963 'notification_language', hence the direct UPDATE. */ 8964 TEST_RET_ON_FAIL (exec_sql ("UPDATE merchant.merchant_instances" 8965 " SET email='kyc-alerts@example.com'" 8966 " ,notification_language='en'" 8967 " WHERE merchant_id='test_kyc'")); 8968 now = GNUNET_TIME_timestamp_get (); 8969 TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 8970 TALER_MERCHANTDB_insert_kyc_status (pg, 8971 instance.instance.id, 8972 &account.h_wire, 8973 "https://exchange.net/", 8974 now, 8975 GNUNET_TIME_UNIT_FOREVER_ABS, 8976 GNUNET_TIME_UNIT_HOURS, 8977 MHD_HTTP_OK, 8978 TALER_EC_NONE, 8979 42, 8980 NULL, 8981 NULL, 8982 false, 8983 false)); 8984 /* The AFTER INSERT trigger must have queued exactly one alert. 8985 Regression test: merchant_send_kyc_notification() used to call 8986 random_bytea()/uri_escape()/base32_crockford() unqualified -- they 8987 live in schema 'merchant' only, while the search_path holds just the 8988 per-instance schema -- and to insert a 'merchant_serial' column that 8989 merchant-0036 dropped. Either turned this call into a HARD_ERROR, 8990 which taler-merchant-kyccheck treats as fatal. */ 8991 TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num" 8992 " FROM merchant_reports" 8993 " WHERE one_shot_hidden", 8994 &alerts)); 8995 TEST_COND_RET_ON_FAIL (1 == alerts, 8996 "KYC status change did not queue an alert report\n"); 8997 TEST_RET_ON_FAIL (test_insert_kyc_failure (&instance, 8998 &account)); 8999 TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 9000 TALER_MERCHANTDB_insert_kyc_status (pg, 9001 instance.instance.id, 9002 &account.h_wire, 9003 "https://exchange.net/", 9004 now, 9005 GNUNET_TIME_UNIT_FOREVER_ABS, 9006 GNUNET_TIME_UNIT_HOURS, 9007 MHD_HTTP_OK, 9008 TALER_EC_NONE, 9009 42, 9010 NULL, 9011 NULL, 9012 false, 9013 true)); 9014 fail = true; 9015 TEST_RET_ON_FAIL (1 != 9016 TALER_MERCHANTDB_iterate_kyc_statuses (pg, 9017 instance.instance.id, 9018 &account.h_wire, 9019 "https://exchange.net/", 9020 true, 9021 &kyc_status_ok, 9022 &fail)); 9023 TEST_RET_ON_FAIL (fail); 9024 fail = true; 9025 TEST_RET_ON_FAIL (1 != 9026 TALER_MERCHANTDB_iterate_kyc_statuses (pg, 9027 instance.instance.id, 9028 NULL, 9029 "https://exchange2.com/", 9030 true, 9031 &kyc_status_fail, 9032 &fail)); 9033 TEST_RET_ON_FAIL (fail); 9034 fail = true; 9035 TEST_RET_ON_FAIL (2 != 9036 TALER_MERCHANTDB_iterate_kyc_statuses (pg, 9037 instance.instance.id, 9038 NULL, 9039 NULL, 9040 true, 9041 &kyc_status_fail, 9042 &fail)); 9043 TEST_RET_ON_FAIL (fail); 9044 fail = true; 9045 TEST_RET_ON_FAIL (2 != 9046 TALER_MERCHANTDB_iterate_kyc_statuses (pg, 9047 instance.instance.id, 9048 NULL, 9049 NULL, 9050 true, 9051 &kyc_status_ok, 9052 &fail)); 9053 TEST_RET_ON_FAIL (fail); 9054 /* A bookkeeping-only re-poll -- kyc_ok, last_rule_gen, aml_review and 9055 jaccount_limits are unchanged, only the timestamps move -- must NOT 9056 queue another alert. Regression test: pg_triggers.sql used to 9057 install a second, unguarded merchant_kyc_update_trigger() on top of 9058 the guarded one in pg_merchant_kyc_trigger.sql, and being loaded 9059 last it won, so every next_kyc_poll update alerted the merchant. */ 9060 TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num" 9061 " FROM merchant_reports" 9062 " WHERE report_description" 9063 " ='automatically triggered KYC alert'", 9064 &alerts)); 9065 TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 9066 TALER_MERCHANTDB_insert_kyc_status (pg, 9067 instance.instance.id, 9068 &account.h_wire, 9069 "https://exchange.net/", 9070 GNUNET_TIME_timestamp_get (), 9071 GNUNET_TIME_UNIT_FOREVER_ABS, 9072 GNUNET_TIME_UNIT_MINUTES, 9073 MHD_HTTP_OK, 9074 TALER_EC_NONE, 9075 42, 9076 NULL, 9077 NULL, 9078 false, 9079 true)); 9080 { 9081 uint64_t alerts2; 9082 9083 TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num" 9084 " FROM merchant_reports" 9085 " WHERE report_description" 9086 " ='automatically triggered KYC alert'", 9087 &alerts2)); 9088 TEST_COND_RET_ON_FAIL (alerts == alerts2, 9089 "Bookkeeping-only KYC update queued an alert\n"); 9090 } 9091 /* Adding an account must queue an account-change alert. Regression 9092 test: pg_merchant_account_trigger.sql used to (re)define 9093 'merchant_kyc_update_trigger' rather than 9094 'merchant_account_change_trigger', so merchant_send_account_notification() 9095 had no callers at all and the trigger installed by merchant-0041 was 9096 still its 'RETURN NULL' stub. */ 9097 { 9098 struct TALER_MERCHANTDB_AccountDetails account2; 9099 uint64_t acc_alerts; 9100 9101 make_account (&account2); 9102 account2.instance_id = instance.instance.id; 9103 /* make_account() hands out a fixed payto_uri, which would collide. */ 9104 account2.payto_uri.full_payto 9105 = (char *) "payto://x-taler-bank/bank.demo.taler.net/5"; 9106 TEST_RET_ON_FAIL (test_insert_account (&instance, 9107 &account2, 9108 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 9109 TEST_RET_ON_FAIL (query_sql_num ("SELECT COUNT(*) AS num" 9110 " FROM merchant_reports" 9111 " WHERE report_description" 9112 " ='automatically triggered account" 9113 " change alert'", 9114 &acc_alerts)); 9115 TEST_COND_RET_ON_FAIL (1 == acc_alerts, 9116 "Adding an account did not queue an alert\n"); 9117 } 9118 json_decref (instance.instance.address); 9119 json_decref (instance.instance.jurisdiction); 9120 return 0; 9121 } 9122 9123 9124 /* *********** Templates ********** */ 9125 9126 /** 9127 * A container for data relevant to a template. 9128 */ 9129 struct TemplateData 9130 { 9131 /** 9132 * The identifier of the template. 9133 */ 9134 const char *id; 9135 9136 /** 9137 * The details of the template. 9138 */ 9139 struct TALER_MERCHANTDB_TemplateDetails template; 9140 }; 9141 9142 9143 /** 9144 * Creates a template for testing with. 9145 * 9146 * @param id the id of the template. 9147 * @param template the template data to fill. 9148 */ 9149 static void 9150 make_template (const char *id, 9151 struct TemplateData *template) 9152 { 9153 template->id = id; 9154 template->template.template_description = "This is a test template"; 9155 template->template.otp_id = NULL; 9156 template->template.template_contract = json_array (); 9157 GNUNET_assert (NULL != template->template.template_contract); 9158 } 9159 9160 9161 /** 9162 * Frees memory associated with @e TemplateData. 9163 * 9164 * @param template the container to free. 9165 */ 9166 static void 9167 free_template_data (struct TemplateData *template) 9168 { 9169 GNUNET_free (template->template.otp_id); 9170 json_decref (template->template.template_contract); 9171 } 9172 9173 9174 /** 9175 * Compare two templates for equality. 9176 * 9177 * @param a the first template. 9178 * @param b the second template. 9179 * @return 0 on equality, 1 otherwise. 9180 */ 9181 static int 9182 check_templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *a, 9183 const struct TALER_MERCHANTDB_TemplateDetails *b) 9184 { 9185 if ((0 != strcmp (a->template_description, 9186 b->template_description)) || 9187 ( (NULL == a->otp_id) && (NULL != b->otp_id)) || 9188 ( (NULL != a->otp_id) && (NULL == b->otp_id)) || 9189 ( (NULL != a->otp_id) && (0 != strcmp (a->otp_id, 9190 b->otp_id))) || 9191 (1 != json_equal (a->template_contract, 9192 b->template_contract))) 9193 return 1; 9194 return 0; 9195 } 9196 9197 9198 /** 9199 * Tests inserting template data into the database. 9200 * 9201 * @param instance the instance to insert the template for. 9202 * @param template the template data to insert. 9203 * @param expected_result the result we expect the db to return. 9204 * @return 0 when successful, 1 otherwise. 9205 */ 9206 static int 9207 test_insert_template (const struct InstanceData *instance, 9208 const struct TemplateData *template, 9209 enum GNUNET_DB_QueryStatus expected_result) 9210 { 9211 TEST_SET_INSTANCE (instance->instance.id, expected_result); 9212 TEST_COND_RET_ON_FAIL (expected_result == 9213 TALER_MERCHANTDB_insert_template (pg, 9214 instance->instance.id, 9215 template->id, 9216 0, 9217 &template->template), 9218 "Insert template failed\n"); 9219 return 0; 9220 } 9221 9222 9223 /** 9224 * Tests updating template data in the database. 9225 * 9226 * @param instance the instance to update the template for. 9227 * @param template the template data to update. 9228 * @param expected_result the result we expect the db to return. 9229 * @return 0 when successful, 1 otherwise. 9230 */ 9231 static int 9232 test_update_template (const struct InstanceData *instance, 9233 const struct TemplateData *template, 9234 enum GNUNET_DB_QueryStatus expected_result) 9235 { 9236 TEST_SET_INSTANCE (instance->instance.id, expected_result); 9237 TEST_COND_RET_ON_FAIL (expected_result == 9238 TALER_MERCHANTDB_update_template (pg, 9239 instance->instance.id, 9240 template->id, 9241 &template->template), 9242 "Update template failed\n"); 9243 return 0; 9244 } 9245 9246 9247 /** 9248 * Tests looking up a template from the db. 9249 * 9250 * @param instance the instance to query from. 9251 * @param template the template to query and compare to. 9252 * @return 0 when successful, 1 otherwise. 9253 */ 9254 static int 9255 test_lookup_template (const struct InstanceData *instance, 9256 const struct TemplateData *template) 9257 { 9258 const struct TALER_MERCHANTDB_TemplateDetails *to_cmp 9259 = &template->template; 9260 struct TALER_MERCHANTDB_TemplateDetails lookup_result; 9261 9262 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 9263 if (0 > TALER_MERCHANTDB_get_template (pg, 9264 instance->instance.id, 9265 template->id, 9266 &lookup_result)) 9267 { 9268 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9269 "Lookup template failed\n"); 9270 TALER_MERCHANTDB_template_details_free (&lookup_result); 9271 return 1; 9272 } 9273 if (0 != check_templates_equal (&lookup_result, 9274 to_cmp)) 9275 { 9276 GNUNET_break (0); 9277 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9278 "Lookup template failed: incorrect template returned\n"); 9279 TALER_MERCHANTDB_template_details_free (&lookup_result); 9280 return 1; 9281 } 9282 TALER_MERCHANTDB_template_details_free (&lookup_result); 9283 return 0; 9284 } 9285 9286 9287 /** 9288 * Closure for testing template lookup 9289 */ 9290 struct TestLookupTemplates_Closure 9291 { 9292 /** 9293 * Number of template ids to compare to 9294 */ 9295 unsigned int templates_to_cmp_length; 9296 9297 /** 9298 * Pointer to array of template ids 9299 */ 9300 const struct TemplateData *templates_to_cmp; 9301 9302 /** 9303 * Pointer to array of number of matches for each template 9304 */ 9305 unsigned int *results_matching; 9306 9307 /** 9308 * Total number of results returned 9309 */ 9310 unsigned int results_length; 9311 }; 9312 9313 9314 /** 9315 * Function called after calling @e test_lookup_templates 9316 * 9317 * @param cls a pointer to the lookup closure. 9318 * @param template_id the identifier of the template found. 9319 */ 9320 static void 9321 lookup_templates_cb (void *cls, 9322 const char *template_id, 9323 const char *template_description) 9324 { 9325 struct TestLookupTemplates_Closure *cmp = cls; 9326 9327 if (NULL == cmp) 9328 return; 9329 cmp->results_length += 1; 9330 for (unsigned int i = 0; cmp->templates_to_cmp_length > i; ++i) 9331 { 9332 if ( (0 == strcmp (cmp->templates_to_cmp[i].id, 9333 template_id)) && 9334 (0 == strcmp (cmp->templates_to_cmp[i].template.template_description, 9335 template_description)) ) 9336 cmp->results_matching[i] += 1; 9337 } 9338 } 9339 9340 9341 /** 9342 * Tests looking up all templates for an instance. 9343 * 9344 * @param instance the instance to query from. 9345 * @param templates_length the number of templates we are expecting. 9346 * @param templates the list of templates that we expect to be found. 9347 * @return 0 when successful, 1 otherwise. 9348 */ 9349 static int 9350 test_lookup_templates (const struct InstanceData *instance, 9351 unsigned int templates_length, 9352 const struct TemplateData *templates) 9353 { 9354 unsigned int results_matching[templates_length]; 9355 struct TestLookupTemplates_Closure cls = { 9356 .templates_to_cmp_length = templates_length, 9357 .templates_to_cmp = templates, 9358 .results_matching = results_matching, 9359 .results_length = 0 9360 }; 9361 9362 memset (results_matching, 9363 0, 9364 sizeof (unsigned int) * templates_length); 9365 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 9366 if (0 > TALER_MERCHANTDB_iterate_templates (pg, 9367 instance->instance.id, 9368 &lookup_templates_cb, 9369 &cls)) 9370 { 9371 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9372 "Lookup templates failed\n"); 9373 return 1; 9374 } 9375 if (templates_length != cls.results_length) 9376 { 9377 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9378 "Lookup templates failed: incorrect number of results\n"); 9379 return 1; 9380 } 9381 for (unsigned int i = 0; templates_length > i; ++i) 9382 { 9383 if (1 != cls.results_matching[i]) 9384 { 9385 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9386 "Lookup templates failed: mismatched data\n"); 9387 return 1; 9388 } 9389 } 9390 return 0; 9391 } 9392 9393 9394 /** 9395 * Tests deleting a template. 9396 * 9397 * @param instance the instance to delete the template from. 9398 * @param template the template that should be deleted. 9399 * @param expected_result the result that we expect the DB to return. 9400 * @return 0 when successful, 1 otherwise. 9401 */ 9402 static int 9403 test_delete_template (const struct InstanceData *instance, 9404 const struct TemplateData *template, 9405 enum GNUNET_DB_QueryStatus expected_result) 9406 { 9407 TEST_SET_INSTANCE (instance->instance.id, expected_result); 9408 TEST_COND_RET_ON_FAIL (expected_result == 9409 TALER_MERCHANTDB_delete_template (pg, 9410 instance->instance.id, 9411 template->id), 9412 "Delete template failed\n"); 9413 return 0; 9414 } 9415 9416 9417 /** 9418 * Closure for template tests. 9419 */ 9420 struct TestTemplates_Closure 9421 { 9422 /** 9423 * The instance to use for this test. 9424 */ 9425 struct InstanceData instance; 9426 9427 /** 9428 * The array of templates. 9429 */ 9430 struct TemplateData templates[2]; 9431 }; 9432 9433 9434 /** 9435 * Sets up the data structures used in the template tests. 9436 * 9437 * @param cls the closure to fill with test data. 9438 */ 9439 static void 9440 pre_test_templates (struct TestTemplates_Closure *cls) 9441 { 9442 /* Instance */ 9443 make_instance ("test_inst_templates", 9444 &cls->instance); 9445 9446 /* Templates */ 9447 make_template ("test_templates_pd_0", 9448 &cls->templates[0]); 9449 9450 make_template ("test_templates_pd_1", 9451 &cls->templates[1]); 9452 cls->templates[1].template.template_description = 9453 "This is a another test template"; 9454 } 9455 9456 9457 /** 9458 * Handles all teardown after testing. 9459 * 9460 * @param cls the closure containing memory to be freed. 9461 */ 9462 static void 9463 post_test_templates (struct TestTemplates_Closure *cls) 9464 { 9465 free_instance_data (&cls->instance); 9466 free_template_data (&cls->templates[0]); 9467 free_template_data (&cls->templates[1]); 9468 } 9469 9470 9471 /** 9472 * Runs the tests for templates. 9473 * 9474 * @param cls the container of the test data. 9475 * @return 0 on success, 1 otherwise. 9476 */ 9477 static int 9478 run_test_templates (struct TestTemplates_Closure *cls) 9479 { 9480 9481 /* Test that insert without an instance fails */ 9482 TEST_RET_ON_FAIL (test_insert_template (&cls->instance, 9483 &cls->templates[0], 9484 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 9485 /* Insert the instance */ 9486 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 9487 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 9488 /* Test inserting a template */ 9489 TEST_RET_ON_FAIL (test_insert_template (&cls->instance, 9490 &cls->templates[0], 9491 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 9492 /* Test that double insert fails */ 9493 TEST_RET_ON_FAIL (test_insert_template (&cls->instance, 9494 &cls->templates[0], 9495 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 9496 /* Test lookup of individual templates */ 9497 TEST_RET_ON_FAIL (test_lookup_template (&cls->instance, 9498 &cls->templates[0])); 9499 /* Make sure it fails correctly for templates that don't exist */ 9500 { 9501 struct TALER_MERCHANTDB_TemplateDetails tx; 9502 9503 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 9504 TALER_MERCHANTDB_get_template (pg, 9505 cls->instance.instance.id, 9506 "nonexistent_template", 9507 &tx)) 9508 { 9509 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9510 "Lookup template failed\n"); 9511 return 1; 9512 } 9513 } 9514 /* Test template update */ 9515 cls->templates[0].template.template_description = 9516 "This is a test template that has been updated!"; 9517 GNUNET_free (cls->templates[0].template.otp_id); 9518 cls->templates[0].template.otp_id = GNUNET_strdup ("otp_id"); 9519 { 9520 /* ensure OTP device exists */ 9521 struct TALER_MERCHANTDB_OtpDeviceDetails td = { 9522 .otp_description = "my otp", 9523 .otp_key = "my key", 9524 .otp_algorithm = 1, 9525 .otp_ctr = 42 9526 }; 9527 GNUNET_assert (0 <= 9528 TALER_MERCHANTDB_insert_otp_device (pg, 9529 cls->instance.instance.id, 9530 "otp_id", 9531 &td)); 9532 } 9533 GNUNET_assert (0 == 9534 json_array_append_new ( 9535 cls->templates[0].template.template_contract, 9536 json_string ("This is a test. 3CH."))); 9537 TEST_RET_ON_FAIL (test_update_template (&cls->instance, 9538 &cls->templates[0], 9539 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 9540 TEST_RET_ON_FAIL (test_lookup_template (&cls->instance, 9541 &cls->templates[0])); 9542 TEST_RET_ON_FAIL (test_update_template (&cls->instance, 9543 &cls->templates[1], 9544 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 9545 /* Test collective template lookup */ 9546 TEST_RET_ON_FAIL (test_insert_template (&cls->instance, 9547 &cls->templates[1], 9548 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 9549 TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance, 9550 2, 9551 cls->templates)); 9552 9553 /* Test template deletion */ 9554 TEST_RET_ON_FAIL (test_delete_template (&cls->instance, 9555 &cls->templates[1], 9556 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 9557 /* Test double deletion fails */ 9558 TEST_RET_ON_FAIL (test_delete_template (&cls->instance, 9559 &cls->templates[1], 9560 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 9561 TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance, 9562 1, 9563 cls->templates)); 9564 return 0; 9565 } 9566 9567 9568 /** 9569 * Takes care of template testing. 9570 * 9571 * @return 0 on success, 1 otherwise. 9572 */ 9573 static int 9574 test_templates (void) 9575 { 9576 struct TestTemplates_Closure test_cls; 9577 int test_result; 9578 9579 memset (&test_cls, 9580 0, 9581 sizeof (test_cls)); 9582 pre_test_templates (&test_cls); 9583 test_result = run_test_templates (&test_cls); 9584 post_test_templates (&test_cls); 9585 return test_result; 9586 } 9587 9588 9589 /* *********** Webhooks ********** */ 9590 9591 /** 9592 * A container for data relevant to a webhook. 9593 */ 9594 struct WebhookData 9595 { 9596 9597 /** 9598 * The identifier of the webhook. 9599 */ 9600 const char *id; 9601 9602 /** 9603 * The details of the webhook. 9604 */ 9605 struct TALER_MERCHANTDB_WebhookDetails webhook; 9606 }; 9607 9608 9609 /** 9610 * Creates a webhook for testing with. 9611 * 9612 * @param id the id of the webhook. 9613 * @param webhook the webhook data to fill. 9614 */ 9615 static void 9616 make_webhook (const char *id, 9617 struct WebhookData *webhook) 9618 { 9619 webhook->id = id; 9620 webhook->webhook.event_type = "Paid"; 9621 webhook->webhook.url = "https://exampletest.com"; 9622 webhook->webhook.http_method = "POST"; 9623 webhook->webhook.header_template = "Authorization:XYJAORKJEO"; 9624 webhook->webhook.body_template = "$Amount"; 9625 } 9626 9627 9628 /** 9629 * Compare two webhooks for equality. 9630 * 9631 * @param a the first webhook. 9632 * @param b the second webhook. 9633 * @return 0 on equality, 1 otherwise. 9634 */ 9635 static int 9636 check_webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *a, 9637 const struct TALER_MERCHANTDB_WebhookDetails *b) 9638 { 9639 if ((0 != strcmp (a->event_type, 9640 b->event_type)) || 9641 (0 != strcmp (a->url, 9642 b->url)) || 9643 (0 != strcmp (a->http_method, 9644 b->http_method)) || 9645 (0 != strcmp (a->header_template, 9646 b->header_template)) || 9647 (0 != strcmp (a->body_template, 9648 b->body_template))) 9649 return 1; 9650 return 0; 9651 } 9652 9653 9654 /** 9655 * Tests inserting webhook data into the database. 9656 * 9657 * @param instance the instance to insert the webhook for. 9658 * @param webhook the webhook data to insert. 9659 * @param expected_result the result we expect the db to return. 9660 * @return 0 when successful, 1 otherwise. 9661 */ 9662 static int 9663 test_insert_webhook (const struct InstanceData *instance, 9664 const struct WebhookData *webhook, 9665 enum GNUNET_DB_QueryStatus expected_result) 9666 { 9667 TEST_SET_INSTANCE (instance->instance.id, expected_result); 9668 TEST_COND_RET_ON_FAIL (expected_result == 9669 TALER_MERCHANTDB_insert_webhook (pg, 9670 instance->instance.id, 9671 webhook->id, 9672 &webhook->webhook), 9673 "Insert webhook failed\n"); 9674 return 0; 9675 } 9676 9677 9678 /** 9679 * Tests updating webhook data in the database. 9680 * 9681 * @param instance the instance to update the webhook for. 9682 * @param webhook the webhook data to update. 9683 * @param expected_result the result we expect the db to return. 9684 * @return 0 when successful, 1 otherwise. 9685 */ 9686 static int 9687 test_update_webhook (const struct InstanceData *instance, 9688 const struct WebhookData *webhook, 9689 enum GNUNET_DB_QueryStatus expected_result) 9690 { 9691 TEST_SET_INSTANCE (instance->instance.id, expected_result); 9692 TEST_COND_RET_ON_FAIL (expected_result == 9693 TALER_MERCHANTDB_update_webhook (pg, 9694 instance->instance.id, 9695 webhook->id, 9696 &webhook->webhook), 9697 "Update webhook failed\n"); 9698 return 0; 9699 } 9700 9701 9702 /** 9703 * Tests looking up a webhook from the db. 9704 * 9705 * @param instance the instance to query from. 9706 * @param webhook the webhook to query and compare to. 9707 * @return 0 when successful, 1 otherwise. 9708 */ 9709 static int 9710 test_lookup_webhook (const struct InstanceData *instance, 9711 const struct WebhookData *webhook) 9712 { 9713 const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook; 9714 struct TALER_MERCHANTDB_WebhookDetails lookup_result; 9715 9716 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 9717 if (0 > TALER_MERCHANTDB_get_webhook (pg, 9718 instance->instance.id, 9719 webhook->id, 9720 &lookup_result)) 9721 { 9722 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9723 "Lookup webhook failed\n"); 9724 TALER_MERCHANTDB_webhook_details_free (&lookup_result); 9725 return 1; 9726 } 9727 if (0 != check_webhooks_equal (&lookup_result, 9728 to_cmp)) 9729 { 9730 GNUNET_break (0); 9731 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9732 "Lookup webhook failed: incorrect webhook returned\n"); 9733 TALER_MERCHANTDB_webhook_details_free (&lookup_result); 9734 return 1; 9735 } 9736 TALER_MERCHANTDB_webhook_details_free (&lookup_result); 9737 return 0; 9738 } 9739 9740 9741 /** 9742 * Closure for testing webhook lookup 9743 */ 9744 struct TestLookupWebhooks_Closure 9745 { 9746 /** 9747 * Number of webhook ids to compare to 9748 */ 9749 unsigned int webhooks_to_cmp_length; 9750 9751 /** 9752 * Pointer to array of webhook ids 9753 */ 9754 const struct WebhookData *webhooks_to_cmp; 9755 9756 /** 9757 * Pointer to array of number of matches for each webhook 9758 */ 9759 unsigned int *results_matching; 9760 9761 /** 9762 * Total number of results returned 9763 */ 9764 unsigned int results_length; 9765 }; 9766 9767 9768 /** 9769 * Function called after calling @e test_lookup_webhooks 9770 * 9771 * @param cls a pointer to the lookup closure. 9772 * @param webhook_id the identifier of the webhook found. 9773 */ 9774 static void 9775 lookup_webhooks_cb (void *cls, 9776 const char *webhook_id, 9777 const char *event_type) 9778 { 9779 struct TestLookupWebhooks_Closure *cmp = cls; 9780 if (NULL == cmp) 9781 return; 9782 cmp->results_length += 1; 9783 for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i) 9784 { 9785 if ((0 == strcmp (cmp->webhooks_to_cmp[i].id, 9786 webhook_id)) && 9787 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type, 9788 event_type)) ) 9789 cmp->results_matching[i] += 1; 9790 } 9791 } 9792 9793 9794 /** 9795 * Tests looking up all webhooks for an instance. 9796 * 9797 * @param instance the instance to query from. 9798 * @param webhooks_length the number of webhooks we are expecting. 9799 * @param webhooks the list of webhooks that we expect to be found. 9800 * @return 0 when successful, 1 otherwise. 9801 */ 9802 static int 9803 test_lookup_webhooks (const struct InstanceData *instance, 9804 unsigned int webhooks_length, 9805 const struct WebhookData *webhooks) 9806 { 9807 unsigned int results_matching[webhooks_length]; 9808 struct TestLookupWebhooks_Closure cls = { 9809 .webhooks_to_cmp_length = webhooks_length, 9810 .webhooks_to_cmp = webhooks, 9811 .results_matching = results_matching, 9812 .results_length = 0 9813 }; 9814 memset (results_matching, 0, sizeof (unsigned int) * webhooks_length); 9815 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 9816 if (0 > TALER_MERCHANTDB_iterate_webhooks (pg, 9817 instance->instance.id, 9818 &lookup_webhooks_cb, 9819 &cls)) 9820 { 9821 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9822 "Lookup webhooks failed\n"); 9823 return 1; 9824 } 9825 if (webhooks_length != cls.results_length) 9826 { 9827 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9828 "Lookup webhooks failed: incorrect number of results\n"); 9829 return 1; 9830 } 9831 for (unsigned int i = 0; webhooks_length > i; ++i) 9832 { 9833 if (1 != cls.results_matching[i]) 9834 { 9835 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9836 "Lookup webhooks failed: mismatched data\n"); 9837 return 1; 9838 } 9839 } 9840 return 0; 9841 } 9842 9843 9844 /** 9845 * Function called after calling @e test_lookup_webhooks 9846 * 9847 * @param cls a pointer to the lookup closure. 9848 * @param webhook_id the identifier of the webhook found. 9849 */ 9850 static void 9851 lookup_webhook_by_event_cb (void *cls, 9852 uint64_t webhook_serial, 9853 const char *event_type, 9854 const char *url, 9855 const char *http_method, 9856 const char *header_template, 9857 const char *body_template) 9858 { 9859 struct TestLookupWebhooks_Closure *cmp = cls; 9860 if (NULL == cmp) 9861 return; 9862 cmp->results_length += 1; 9863 for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i) 9864 { 9865 if ((0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type, 9866 event_type)) && 9867 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.url, 9868 url)) && 9869 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.http_method, 9870 http_method)) && 9871 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.header_template, 9872 header_template)) && 9873 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.body_template, 9874 body_template)) ) 9875 cmp->results_matching[i] += 1; 9876 } 9877 } 9878 9879 9880 /** 9881 * Tests looking up webhooks by event for an instance. 9882 * 9883 * @param instance the instance to query from. 9884 * @param webhooks_length the number of webhooks we are expecting. 9885 * @param webhooks the list of webhooks that we expect to be found. 9886 * @return 0 when successful, 1 otherwise. 9887 */ 9888 static int 9889 test_lookup_webhook_by_event (const struct InstanceData *instance, 9890 unsigned int webhooks_length, 9891 const struct WebhookData *webhooks) 9892 { 9893 unsigned int results_matching[webhooks_length]; 9894 struct TestLookupWebhooks_Closure cls = { 9895 .webhooks_to_cmp_length = webhooks_length, 9896 .webhooks_to_cmp = webhooks, 9897 .results_matching = results_matching, 9898 .results_length = 0 9899 }; 9900 memset (results_matching, 0, sizeof (unsigned int) * webhooks_length); 9901 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 9902 if (0 > TALER_MERCHANTDB_iterate_webhooks_by_event (pg, 9903 instance->instance.id, 9904 webhooks->webhook.event_type, 9905 &lookup_webhook_by_event_cb, 9906 &cls)) 9907 { 9908 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9909 "Lookup webhooks by event failed\n"); 9910 return 1; 9911 } 9912 9913 if (webhooks_length != cls.results_length) 9914 { 9915 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9916 "Lookup webhooks by event failed: incorrect number of results\n"); 9917 return 1; 9918 } 9919 for (unsigned int i = 0; webhooks_length > i; ++i) 9920 { 9921 if (1 != cls.results_matching[i]) 9922 { 9923 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 9924 "Lookup webhooks by event failed: mismatched data\n"); 9925 return 1; 9926 } 9927 } 9928 return 0; 9929 } 9930 9931 9932 /** 9933 * Tests deleting a webhook. 9934 * 9935 * @param instance the instance to delete the webhook from. 9936 * @param webhook the webhook that should be deleted. 9937 * @param expected_result the result that we expect the DB to return. 9938 * @return 0 when successful, 1 otherwise. 9939 */ 9940 static int 9941 test_delete_webhook (const struct InstanceData *instance, 9942 const struct WebhookData *webhook, 9943 enum GNUNET_DB_QueryStatus expected_result) 9944 { 9945 TEST_SET_INSTANCE (instance->instance.id, expected_result); 9946 TEST_COND_RET_ON_FAIL (expected_result == 9947 TALER_MERCHANTDB_delete_webhook (pg, 9948 instance->instance.id, 9949 webhook->id), 9950 "Delete webhook failed\n"); 9951 return 0; 9952 } 9953 9954 9955 /** 9956 * Tests that deleting a product queues an 'inventory_deleted' webhook 9957 * whose placeholders were resolved from the row that went away. 9958 * 9959 * Regression test: handle_inventory_changes() set both substitution 9960 * flags to FALSE for TG_OP='DELETE', so the pending webhook shipped 9961 * body_template verbatim -- not even {{webhook_type}} was replaced. 9962 * 9963 * Note: this runs on its own instance and *after* the pending webhook 9964 * tests, which rely on the global merchant_pending_webhooks identity 9965 * sequence not having been advanced by anybody else. 9966 * 9967 * @return 0 when successful, 1 otherwise. 9968 */ 9969 static int 9970 test_inventory_deleted_webhook (void) 9971 { 9972 struct InstanceData instance; 9973 struct WebhookData webhook; 9974 struct ProductData product; 9975 char *body = NULL; 9976 int ret = 1; 9977 9978 make_instance ("test_inst_inventory_webhooks", 9979 &instance); 9980 TEST_WITH_FAIL_CLAUSE (test_insert_instance (&instance, 9981 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT), 9982 free_instance_data (&instance); 9983 return 1; 9984 ); 9985 make_webhook ("test_webhooks_wb_inventory_deleted", 9986 &webhook); 9987 webhook.webhook.event_type = "inventory_deleted"; 9988 webhook.webhook.url = "https://example.com/inventory-deleted"; 9989 webhook.webhook.body_template = 9990 "{\"type\":\"{{webhook_type}}\"" 9991 ",\"product_id\":\"{{product_id}}\"" 9992 ",\"description\":\"{{description}}\"}"; 9993 TEST_WITH_FAIL_CLAUSE (test_insert_webhook (&instance, 9994 &webhook, 9995 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT), 9996 free_instance_data (&instance); 9997 return 1; 9998 ); 9999 make_product ("test_webhooks_deleted_product", 10000 &product); 10001 TEST_WITH_FAIL_CLAUSE (test_insert_product (&instance, 10002 &product, 10003 0, 10004 NULL, 10005 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 10006 false, 10007 false, 10008 -1), 10009 goto cleanup; 10010 ); 10011 TEST_WITH_FAIL_CLAUSE (test_delete_product (&instance, 10012 &product, 10013 false, 10014 false, 10015 false), 10016 goto cleanup; 10017 ); 10018 TEST_WITH_FAIL_CLAUSE ( 10019 query_sql_text ("SELECT body AS txt" 10020 " FROM merchant.merchant_pending_webhooks" 10021 " WHERE url='https://example.com/inventory-deleted'", 10022 &body), 10023 goto cleanup; 10024 ); 10025 if (0 != strcmp ("{\"type\":\"inventory_deleted\"" 10026 ",\"product_id\":\"test_webhooks_deleted_product\"" 10027 ",\"description\":\"This is a test product\"}", 10028 body)) 10029 { 10030 GNUNET_break (0); 10031 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10032 "inventory_deleted webhook body not resolved: `%s'\n", 10033 body); 10034 goto cleanup; 10035 } 10036 ret = 0; 10037 cleanup: 10038 GNUNET_free (body); 10039 free_product_data (&product); 10040 free_instance_data (&instance); 10041 return ret; 10042 } 10043 10044 10045 /** 10046 * Closure for webhook tests. 10047 */ 10048 struct TestWebhooks_Closure 10049 { 10050 /** 10051 * The instance to use for this test. 10052 */ 10053 struct InstanceData instance; 10054 10055 /** 10056 * The array of webhooks. 10057 */ 10058 struct WebhookData webhooks[3]; 10059 }; 10060 10061 10062 /** 10063 * Sets up the data structures used in the webhook tests. 10064 * 10065 * @param cls the closure to fill with test data. 10066 */ 10067 static void 10068 pre_test_webhooks (struct TestWebhooks_Closure *cls) 10069 { 10070 /* Instance */ 10071 make_instance ("test_inst_webhooks", 10072 &cls->instance); 10073 10074 /* Webhooks */ 10075 make_webhook ("test_webhooks_wb_0", 10076 &cls->webhooks[0]); 10077 10078 make_webhook ("test_webhooks_wb_1", 10079 &cls->webhooks[1]); 10080 cls->webhooks[1].webhook.event_type = "Test paid"; 10081 cls->webhooks[1].webhook.url = "https://example.com"; 10082 cls->webhooks[1].webhook.http_method = "POST"; 10083 cls->webhooks[1].webhook.header_template = "Authorization:1XYJAOR493O"; 10084 cls->webhooks[1].webhook.body_template = "$Amount"; 10085 10086 make_webhook ("test_webhooks_wb_2", 10087 &cls->webhooks[2]); 10088 cls->webhooks[2].webhook.event_type = "Test paid"; 10089 cls->webhooks[2].webhook.url = "https://examplerefund.com"; 10090 cls->webhooks[2].webhook.http_method = "POST"; 10091 cls->webhooks[2].webhook.header_template = "Authorization:XY6ORK52JEO"; 10092 cls->webhooks[2].webhook.body_template = "$Amount"; 10093 } 10094 10095 10096 /** 10097 * Handles all teardown after testing. 10098 * 10099 * @param cls the closure containing memory to be freed. 10100 */ 10101 static void 10102 post_test_webhooks (struct TestWebhooks_Closure *cls) 10103 { 10104 free_instance_data (&cls->instance); 10105 } 10106 10107 10108 /** 10109 * Runs the tests for webhooks. 10110 * 10111 * @param cls the container of the test data. 10112 * @return 0 on success, 1 otherwise. 10113 */ 10114 static int 10115 run_test_webhooks (struct TestWebhooks_Closure *cls) 10116 { 10117 10118 /* Test that insert without an instance fails */ 10119 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 10120 &cls->webhooks[0], 10121 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 10122 /* Insert the instance */ 10123 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 10124 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10125 /* Test inserting a webhook */ 10126 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 10127 &cls->webhooks[0], 10128 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10129 /* Test that double insert fails */ 10130 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 10131 &cls->webhooks[0], 10132 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 10133 /* Test lookup of individual webhooks */ 10134 TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance, 10135 &cls->webhooks[0])); 10136 /* Make sure it fails correctly for webhooks that don't exist */ 10137 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 10138 TALER_MERCHANTDB_get_webhook (pg, 10139 cls->instance.instance.id, 10140 "nonexistent_webhook", 10141 NULL)) 10142 { 10143 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10144 "Lookup webhook failed\n"); 10145 return 1; 10146 } 10147 /* Test webhook update */ 10148 cls->webhooks[0].webhook.event_type = 10149 "Test paid"; 10150 cls->webhooks[0].webhook.url = 10151 "example.com"; 10152 cls->webhooks[0].webhook.http_method = 10153 "POST"; 10154 cls->webhooks[0].webhook.header_template = 10155 "Authorization:WEKFOEKEXZ"; 10156 cls->webhooks[0].webhook.body_template = 10157 "$Amount"; 10158 TEST_RET_ON_FAIL (test_update_webhook (&cls->instance, 10159 &cls->webhooks[0], 10160 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10161 10162 TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance, 10163 &cls->webhooks[0])); 10164 TEST_RET_ON_FAIL (test_update_webhook (&cls->instance, 10165 &cls->webhooks[1], 10166 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 10167 /* Test collective webhook lookup */ 10168 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 10169 &cls->webhooks[1], 10170 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10171 TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance, 10172 2, 10173 cls->webhooks)); 10174 TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance, 10175 2, 10176 cls->webhooks)); 10177 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 10178 &cls->webhooks[2], 10179 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10180 10181 /* Test webhook deletion */ 10182 TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance, 10183 &cls->webhooks[1], 10184 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10185 /* Test double deletion fails */ 10186 TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance, 10187 &cls->webhooks[1], 10188 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 10189 cls->webhooks[1] = cls->webhooks[2]; 10190 TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance, 10191 2, 10192 cls->webhooks)); 10193 TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance, 10194 2, 10195 cls->webhooks)); 10196 return 0; 10197 } 10198 10199 10200 /** 10201 * Takes care of webhook testing. 10202 * 10203 * @return 0 on success, 1 otherwise. 10204 */ 10205 static int 10206 test_webhooks (void) 10207 { 10208 struct TestWebhooks_Closure test_cls; 10209 int test_result; 10210 10211 pre_test_webhooks (&test_cls); 10212 test_result = run_test_webhooks (&test_cls); 10213 post_test_webhooks (&test_cls); 10214 return test_result; 10215 } 10216 10217 10218 /* *********** Pending Webhooks ********** */ 10219 10220 /** 10221 * A container for data relevant to a pending webhook. 10222 */ 10223 struct PendingWebhookData 10224 { 10225 /** 10226 * Reference to the configured webhook template. 10227 */ 10228 uint64_t webhook_serial; 10229 10230 /** 10231 * The details of the pending webhook. 10232 */ 10233 struct TALER_MERCHANTDB_PendingWebhookDetails pwebhook; 10234 }; 10235 10236 10237 /** 10238 * Creates a pending webhook for testing with. 10239 * 10240 * @param serial reference to the configured webhook template. 10241 * @param pwebhook the pending webhook data to fill. 10242 */ 10243 static void 10244 make_pending_webhook (uint64_t webhook_serial, 10245 struct PendingWebhookData *pwebhook) 10246 { 10247 pwebhook->webhook_serial = webhook_serial; 10248 pwebhook->pwebhook.next_attempt = GNUNET_TIME_UNIT_ZERO_ABS; 10249 pwebhook->pwebhook.retries = 0; 10250 pwebhook->pwebhook.url = "https://exampletest.com"; 10251 pwebhook->pwebhook.http_method = "POST"; 10252 pwebhook->pwebhook.header = "Authorization:XYJAORKJEO"; 10253 pwebhook->pwebhook.body = "$Amount"; 10254 } 10255 10256 10257 /** 10258 * Tests inserting pending webhook data into the database. 10259 * 10260 * @param instance the instance to insert the pending webhook for. 10261 * @param pending webhook the pending webhook data to insert. 10262 * @param expected_result the result we expect the db to return. 10263 * @return 0 when successful, 1 otherwise. 10264 */ 10265 static int 10266 test_insert_pending_webhook (const struct InstanceData *instance, 10267 struct PendingWebhookData *pwebhook, 10268 enum GNUNET_DB_QueryStatus expected_result) 10269 { 10270 10271 TEST_SET_INSTANCE (instance->instance.id, expected_result); 10272 TEST_COND_RET_ON_FAIL (expected_result == 10273 TALER_MERCHANTDB_insert_pending_webhook (pg, 10274 instance->instance.id, 10275 pwebhook-> 10276 webhook_serial, 10277 pwebhook->pwebhook.url, 10278 pwebhook->pwebhook. 10279 http_method, 10280 pwebhook->pwebhook. 10281 header, 10282 pwebhook->pwebhook.body 10283 ), 10284 "Insert pending webhook failed\n"); 10285 return 0; 10286 } 10287 10288 10289 /** 10290 * Tests updating pending webhook data in the database. 10291 * 10292 * @param instance the instance to update the pending webhook for. 10293 * @param pending webhook the pending webhook data to update. 10294 * @param expected_result the result we expect the db to return. 10295 * @return 0 when successful, 1 otherwise. 10296 */ 10297 static int 10298 test_update_pending_webhook (const struct InstanceData *instance, 10299 struct PendingWebhookData *pwebhook, 10300 enum GNUNET_DB_QueryStatus expected_result) 10301 { 10302 pwebhook->pwebhook.next_attempt = GNUNET_TIME_relative_to_absolute ( 10303 GNUNET_TIME_UNIT_HOURS); 10304 pwebhook->pwebhook.retries++; 10305 TEST_SET_INSTANCE (instance->instance.id, expected_result); 10306 TEST_COND_RET_ON_FAIL (expected_result == 10307 TALER_MERCHANTDB_update_pending_webhook (pg, 10308 pwebhook-> 10309 webhook_serial, 10310 pwebhook->pwebhook. 10311 next_attempt), 10312 "Update pending webhook failed\n"); 10313 return 0; 10314 } 10315 10316 10317 /** 10318 * Container for information for looking up the row number of a deposit. 10319 */ 10320 struct LookupPendingWebhookSerial_Closure 10321 { 10322 /** 10323 * The pending webhook we're looking for. 10324 */ 10325 const struct PendingWebhookData *pwebhook; 10326 10327 /** 10328 * The serial found. 10329 */ 10330 uint64_t webhook_pending_serial; 10331 }; 10332 10333 10334 /** 10335 * Function called after calling @e test_lookup_all_webhook, 10336 * test_iterate_pending_webhooks_next and test_lookup_pending_webhook 10337 * 10338 * @param cls a pointer to the lookup closure. 10339 * @param webhook_serial reference to the configured webhook template. 10340 */ 10341 static void 10342 get_pending_serial_cb (void *cls, 10343 uint64_t webhook_pending_serial, 10344 struct GNUNET_TIME_Absolute next_attempt, 10345 uint32_t retries, 10346 const char *url, 10347 const char *http_method, 10348 const char *header, 10349 const char *body) 10350 { 10351 struct LookupPendingWebhookSerial_Closure *lpw = cls; 10352 10353 if ((0 == strcmp (lpw->pwebhook->pwebhook.url, 10354 url)) && 10355 (0 == strcmp (lpw->pwebhook->pwebhook.http_method, 10356 http_method)) && 10357 (0 == strcmp (lpw->pwebhook->pwebhook.header, 10358 header)) && 10359 (0 == strcmp (lpw->pwebhook->pwebhook.body, 10360 body)) ) 10361 { 10362 lpw->webhook_pending_serial = webhook_pending_serial; 10363 } 10364 /* else 10365 { 10366 fprintf(stdout, "next_attempt: %lu vs %lu\n", lpw->pwebhook->pwebhook.next_attempt.abs_value_us, next_attempt.abs_value_us); 10367 fprintf(stdout, "retries: %d vs %d\n", lpw->pwebhook->pwebhook.retries, retries); 10368 fprintf(stdout, "url: %s vs %s\n", lpw->pwebhook->pwebhook.url, url); 10369 fprintf(stdout, "http_method: %s vs %s\n", lpw->pwebhook->pwebhook.http_method, http_method); 10370 fprintf(stdout, "header: %s vs %s\n", lpw->pwebhook->pwebhook.header, header); 10371 fprintf(stdout, "body: %s vs %s\n", lpw->pwebhook->pwebhook.body, body); 10372 }*/ 10373 } 10374 10375 10376 /** 10377 * Convenience function to retrieve the row number of a webhook pending in the database. 10378 * 10379 * @param instance the instance to get webhook pending(wp) from. 10380 * @param webhook pending the wp to lookup the serial for. 10381 * @return the row number of the deposit. 10382 */ 10383 static uint64_t 10384 get_pending_serial (const struct InstanceData *instance, 10385 const struct PendingWebhookData *pwebhook) 10386 { 10387 struct LookupPendingWebhookSerial_Closure lpw = { 10388 .pwebhook = pwebhook, 10389 .webhook_pending_serial = 0 10390 }; 10391 10392 GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 10393 TALER_MERCHANTDB_set_instance (pg, 10394 instance->instance.id)); 10395 GNUNET_assert (0 < 10396 TALER_MERCHANTDB_iterate_pending_webhooks_above_serial_id (pg, 10397 instance->instance.id, 10398 0, 10399 INT_MAX, 10400 &get_pending_serial_cb, 10401 &lpw)); 10402 GNUNET_assert (0 != lpw.webhook_pending_serial); 10403 10404 return lpw.webhook_pending_serial; 10405 } 10406 10407 10408 /** 10409 * Closure for testing pending webhook lookup 10410 */ 10411 struct TestLookupPendingWebhooks_Closure 10412 { 10413 /** 10414 * Number of webhook serial to compare to 10415 */ 10416 unsigned int webhooks_to_cmp_length; 10417 10418 /** 10419 * Pointer to array of webhook serials 10420 */ 10421 const struct PendingWebhookData *webhooks_to_cmp; 10422 10423 /** 10424 * Pointer to array of number of matches for each pending webhook 10425 */ 10426 unsigned int *results_matching; 10427 10428 /** 10429 * Total number of results returned 10430 */ 10431 unsigned int results_length; 10432 }; 10433 10434 10435 /** 10436 * Function called after calling @e test_lookup_all_webhook, 10437 * test_iterate_pending_webhooks_next and test_lookup_pending_webhook 10438 * 10439 * @param cls a pointer to the lookup closure. 10440 * @param webhook_serial reference to the configured webhook template. 10441 */ 10442 static void 10443 lookup_pending_webhooks_cb (void *cls, 10444 uint64_t webhook_pending_serial, 10445 struct GNUNET_TIME_Absolute next_attempt, 10446 uint32_t retries, 10447 const char *url, 10448 const char *http_method, 10449 const char *header, 10450 const char *body) 10451 { 10452 struct TestLookupPendingWebhooks_Closure *cmp = cls; 10453 10454 cmp->results_length++; 10455 for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i) 10456 { 10457 if ((0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.url, 10458 url)) && 10459 (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.http_method, 10460 http_method)) && 10461 (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.header, 10462 header)) && 10463 (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.body, 10464 body)) ) 10465 { 10466 cmp->results_matching[i]++; 10467 } 10468 } 10469 } 10470 10471 10472 /** 10473 * Tests looking up the pending webhook for an instance. 10474 * 10475 * @param instance the instance to query from. 10476 * @param pwebhooks_length the number of pending webhook we are expecting. 10477 * @param pwebhooks the list of pending webhooks that we expect to be found. 10478 * @return 0 when successful, 1 otherwise. 10479 */ 10480 static int 10481 test_lookup_pending_webhooks (const struct InstanceData *instance, 10482 unsigned int pwebhooks_length, 10483 const struct PendingWebhookData *pwebhooks) 10484 { 10485 unsigned int results_matching[pwebhooks_length]; 10486 struct TestLookupPendingWebhooks_Closure cls = { 10487 .webhooks_to_cmp_length = pwebhooks_length, 10488 .webhooks_to_cmp = pwebhooks, 10489 .results_matching = results_matching, 10490 .results_length = 0 10491 }; 10492 10493 memset (results_matching, 0, sizeof (results_matching)); 10494 if (0 > TALER_MERCHANTDB_iterate_pending_webhooks (pg, 10495 1024, /* limit */ 10496 &lookup_pending_webhooks_cb, 10497 &cls)) 10498 { 10499 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10500 "Lookup pending webhook failed\n"); 10501 return 1; 10502 } 10503 if (pwebhooks_length != cls.results_length) 10504 { 10505 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10506 "Lookup pending webhook failed: incorrect number of results\n"); 10507 return 1; 10508 } 10509 for (unsigned int i = 0; i < pwebhooks_length; i++) 10510 { 10511 if (1 != cls.results_matching[i]) 10512 { 10513 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10514 "Lookup pending webhook failed: mismatched data\n"); 10515 return 1; 10516 } 10517 } 10518 return 0; 10519 } 10520 10521 10522 /** 10523 * Tests looking up the future webhook to send for an instance. 10524 * 10525 * @param instance the instance to query from. 10526 * @param pwebhooks_length the number of pending webhook we are expecting. 10527 * @param pwebhooks the list of pending webhooks that we expect to be found. 10528 * @return 0 when successful, 1 otherwise. 10529 */ 10530 static int 10531 test_iterate_pending_webhooks_next (const struct InstanceData *instance, 10532 unsigned int pwebhooks_length, 10533 const struct PendingWebhookData *pwebhooks) 10534 { 10535 unsigned int results_matching[pwebhooks_length]; 10536 struct TestLookupPendingWebhooks_Closure cls = { 10537 .webhooks_to_cmp_length = pwebhooks_length, 10538 .webhooks_to_cmp = pwebhooks, 10539 .results_matching = results_matching, 10540 .results_length = 0 10541 }; 10542 10543 memset (results_matching, 0, sizeof (results_matching)); 10544 if (0 > TALER_MERCHANTDB_iterate_pending_webhooks_next (pg, 10545 &lookup_pending_webhooks_cb, 10546 &cls)) 10547 { 10548 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10549 "Lookup future webhook failed\n"); 10550 return 1; 10551 } 10552 if (pwebhooks_length != cls.results_length) 10553 { 10554 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10555 "Lookup future webhook failed: incorrect number of results\n"); 10556 return 1; 10557 } 10558 for (unsigned int i = 0; pwebhooks_length > i; ++i) 10559 { 10560 if (1 != cls.results_matching[i]) 10561 { 10562 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10563 "Lookup future webhook failed: mismatched data\n"); 10564 return 1; 10565 } 10566 } 10567 return 0; 10568 } 10569 10570 10571 /** 10572 * Tests looking up all the pending webhook for an instance. 10573 * 10574 * @param instance the instance to query from. 10575 * @param pwebhooks_length the number of pending webhook we are expecting. 10576 * @param pwebhooks the list of pending webhooks that we expect to be found. 10577 * @return 0 when successful, 1 otherwise. 10578 */ 10579 static int 10580 test_lookup_all_webhooks (const struct InstanceData *instance, 10581 unsigned int pwebhooks_length, 10582 const struct PendingWebhookData *pwebhooks) 10583 { 10584 uint64_t max_results = 2; 10585 uint64_t min_row = 0; 10586 unsigned int results_matching[GNUNET_NZL (pwebhooks_length)]; 10587 struct TestLookupPendingWebhooks_Closure cls = { 10588 .webhooks_to_cmp_length = pwebhooks_length, 10589 .webhooks_to_cmp = pwebhooks, 10590 .results_matching = results_matching, 10591 .results_length = 0 10592 }; 10593 10594 memset (results_matching, 10595 0, 10596 sizeof (results_matching)); 10597 TEST_SET_INSTANCE (instance->instance.id, GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 10598 if (0 > TALER_MERCHANTDB_iterate_pending_webhooks_above_serial_id (pg, 10599 instance->instance.id, 10600 min_row, 10601 max_results, 10602 &lookup_pending_webhooks_cb, 10603 &cls)) 10604 { 10605 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10606 "Lookup all webhooks failed\n"); 10607 return 1; 10608 } 10609 if (pwebhooks_length != cls.results_length) 10610 { 10611 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10612 "Lookup all webhooks failed: incorrect number of results\n"); 10613 return 1; 10614 } 10615 for (unsigned int i = 0; pwebhooks_length > i; ++i) 10616 { 10617 if (1 != cls.results_matching[i]) 10618 { 10619 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 10620 "Lookup all webhooks failed: mismatched data\n"); 10621 return 1; 10622 } 10623 } 10624 return 0; 10625 } 10626 10627 10628 /** 10629 * Tests deleting a pending webhook. 10630 * 10631 * @param instance the instance to delete the pending webhook from. 10632 * @param pwebhook the pending webhook that should be deleted. 10633 * @param expected_result the result that we expect the DB to return. 10634 * @return 0 when successful, 1 otherwise. 10635 */ 10636 static int 10637 test_delete_pending_webhook (uint64_t webhooks_pending_serial, 10638 enum GNUNET_DB_QueryStatus expected_result) 10639 { 10640 10641 TEST_COND_RET_ON_FAIL (expected_result == 10642 TALER_MERCHANTDB_delete_pending_webhook (pg, 10643 webhooks_pending_serial), 10644 "Delete webhook failed\n"); 10645 return 0; 10646 } 10647 10648 10649 /** 10650 * Closure for pending webhook tests. 10651 */ 10652 struct TestPendingWebhooks_Closure 10653 { 10654 /** 10655 * The instance to use for this test. 10656 */ 10657 struct InstanceData instance; 10658 10659 /** 10660 * Webhooks that the pending webhooks reference via foreign key. 10661 * Each per-instance schema has its own webhook_serial sequence 10662 * starting at 1, so we must populate webhooks before inserting 10663 * pending webhooks. 10664 */ 10665 struct WebhookData webhooks[4]; 10666 10667 /** 10668 * The array of pending webhooks. 10669 */ 10670 struct PendingWebhookData pwebhooks[2]; 10671 }; 10672 10673 10674 /** 10675 * Sets up the data structures used in the pending webhook tests. 10676 * 10677 * @param cls the closure to fill with test data. 10678 */ 10679 static void 10680 pre_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) 10681 { 10682 /* Instance */ 10683 make_instance ("test_inst_pending_webhooks", 10684 &cls->instance); 10685 10686 /* Webhooks the pending webhooks reference (serials 1..4) */ 10687 make_webhook ("test_pwh_wb_0", &cls->webhooks[0]); 10688 make_webhook ("test_pwh_wb_1", &cls->webhooks[1]); 10689 make_webhook ("test_pwh_wb_2", &cls->webhooks[2]); 10690 make_webhook ("test_pwh_wb_3", &cls->webhooks[3]); 10691 10692 /* Webhooks */ 10693 make_pending_webhook (1, 10694 &cls->pwebhooks[0]); 10695 10696 make_pending_webhook (4, 10697 &cls->pwebhooks[1]); 10698 cls->pwebhooks[1].pwebhook.url = "https://test.com"; 10699 cls->pwebhooks[1].pwebhook.http_method = "POST"; 10700 cls->pwebhooks[1].pwebhook.header = "Authorization:XYJAO5R06EO"; 10701 cls->pwebhooks[1].pwebhook.body = "$Amount"; 10702 } 10703 10704 10705 /** 10706 * Handles all teardown after testing. 10707 * 10708 * @param cls the closure containing memory to be freed. 10709 */ 10710 static void 10711 post_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) 10712 { 10713 free_instance_data (&cls->instance); 10714 } 10715 10716 10717 /** 10718 * Runs the tests for pending webhooks. 10719 * 10720 * @param cls the container of the test data. 10721 * @return 0 on success, 1 otherwise. 10722 */ 10723 static int 10724 run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) 10725 { 10726 uint64_t webhook_pending_serial0; 10727 uint64_t webhook_pending_serial1; 10728 10729 /* Test that insert without an instance fails */ 10730 TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance, 10731 &cls->pwebhooks[0], 10732 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 10733 10734 /* Insert the instance */ 10735 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 10736 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10737 10738 /* Insert the referenced webhooks so pending webhook FKs resolve. */ 10739 for (unsigned int i = 0; i < 4; i++) 10740 TEST_RET_ON_FAIL ( 10741 test_insert_webhook (&cls->instance, 10742 &cls->webhooks[i], 10743 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10744 10745 /* Test inserting a pending webhook */ 10746 TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance, 10747 &cls->pwebhooks[0], 10748 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10749 TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance, 10750 &cls->pwebhooks[1], 10751 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10752 /* Test collective pending webhook lookup */ 10753 TEST_RET_ON_FAIL (test_lookup_pending_webhooks (&cls->instance, 10754 2, 10755 cls->pwebhooks)); 10756 /* Test pending webhook update */ 10757 TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance, 10758 &cls->pwebhooks[0], 10759 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10760 TEST_RET_ON_FAIL (test_iterate_pending_webhooks_next (&cls->instance, 10761 1, 10762 &cls->pwebhooks[1])); 10763 TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance, 10764 &cls->pwebhooks[1], 10765 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 10766 // ??? 10767 TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance, 10768 2, 10769 cls->pwebhooks)); 10770 10771 webhook_pending_serial0 = get_pending_serial (&cls->instance, 10772 &cls->pwebhooks[0]); 10773 webhook_pending_serial1 = get_pending_serial (&cls->instance, 10774 &cls->pwebhooks[1]); 10775 10776 /* Test webhook deletion */ 10777 TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1, 10778 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10779 /* Test double deletion fails */ 10780 TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1, 10781 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 10782 TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial0, 10783 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 10784 TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance, 10785 0, 10786 NULL)); 10787 return 0; 10788 } 10789 10790 10791 /** 10792 * Takes care of pending webhook testing. 10793 * 10794 * @return 0 on success, 1 otherwise. 10795 */ 10796 static int 10797 test_pending_webhooks (void) 10798 { 10799 struct TestPendingWebhooks_Closure test_cls; 10800 int test_result; 10801 10802 pre_test_pending_webhooks (&test_cls); 10803 test_result = run_test_pending_webhooks (&test_cls); 10804 post_test_pending_webhooks (&test_cls); 10805 return test_result; 10806 } 10807 10808 10809 /** 10810 * GC must remove expired finite fountain counters even when another 10811 * counter has an indefinite duration. An overflow used to roll back 10812 * the entire per-instance GC block, preserving the expired counter too. 10813 * Replayable withdrawal signatures must be retained as a complete group 10814 * until the last issue key of that withdrawal expires. 10815 * 10816 * @param instance instance for the test 10817 * @return 0 on success, 1 otherwise 10818 */ 10819 static int 10820 test_fountain_gc (const struct InstanceData *instance) 10821 { 10822 TEST_SET_INSTANCE (instance->instance.id, 10823 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 10824 TEST_RET_ON_FAIL (exec_sql ( 10825 "INSERT INTO merchant_token_families" 10826 " (slug, name, description_i18n, valid_after," 10827 " valid_before, duration, kind)" 10828 " VALUES ('gc-finite', 'GC finite', '{}', 0," 10829 " 9223372036854775807, 60000000, 'discount')," 10830 " ('gc-forever', 'GC forever', '{}', 0," 10831 " 9223372036854775807, 9223372036854775807," 10832 " 'discount');" 10833 "INSERT INTO merchant_fountains" 10834 " (fountain_id, h_fountain_secret, description," 10835 " poll_freq)" 10836 " VALUES ('gc-fountain'," 10837 " decode(repeat('01',64),'hex')," 10838 " 'GC test', 60000000);" 10839 "INSERT INTO merchant_fountain_withdrawals" 10840 " (fountain_serial, token_family_serial, slot_start," 10841 " num_withdrawn)" 10842 " SELECT f.fountain_serial, tf.token_family_serial," 10843 " 1000000, 1" 10844 " FROM merchant_fountains f" 10845 " CROSS JOIN merchant_token_families tf" 10846 " WHERE f.fountain_id='gc-fountain'" 10847 " AND tf.slug IN ('gc-finite','gc-forever');" 10848 /* One issue key long expired, one still valid. */ 10849 "INSERT INTO merchant_token_family_keys" 10850 " (token_family_serial, pub, h_pub, priv, cipher," 10851 " signature_validity_start, signature_validity_end)" 10852 " SELECT tf.token_family_serial, '\\x00'::BYTEA," 10853 " decode(repeat('04',64),'hex')," 10854 " '\\x00'::BYTEA, 'rsa', 0, 1000000" 10855 " FROM merchant_token_families tf" 10856 " WHERE tf.slug='gc-finite';" 10857 "INSERT INTO merchant_token_family_keys" 10858 " (token_family_serial, pub, h_pub, priv, cipher," 10859 " signature_validity_start, signature_validity_end)" 10860 " SELECT tf.token_family_serial, '\\x00'::BYTEA," 10861 " decode(repeat('05',64),'hex')," 10862 " '\\x00'::BYTEA, 'rsa', 0," 10863 " 9223372036854775807" 10864 " FROM merchant_token_families tf" 10865 " WHERE tf.slug='gc-forever';" 10866 "INSERT INTO merchant_fountain_withdraw_sigs" 10867 " (fountain_serial, h_request, grant_index," 10868 " token_index, token_family_slug, h_issue," 10869 " signature_validity_end," 10870 " token_blinded_signature)" 10871 " SELECT f.fountain_serial," 10872 " decode(repeat('02',64),'hex'), 0, 0," 10873 " 'gc-finite', k.h_pub, k.signature_validity_end," 10874 " '\\x00'::BYTEA" 10875 " FROM merchant_fountains f" 10876 " CROSS JOIN merchant_token_family_keys k" 10877 " WHERE f.fountain_id='gc-fountain'" 10878 " AND k.h_pub=decode(repeat('04',64),'hex');" 10879 "INSERT INTO merchant_fountain_withdraw_sigs" 10880 " (fountain_serial, h_request, grant_index," 10881 " token_index, token_family_slug, h_issue," 10882 " signature_validity_end," 10883 " token_blinded_signature)" 10884 " SELECT f.fountain_serial," 10885 " decode(repeat('03',64),'hex'), 0, 0," 10886 " 'gc-forever', k.h_pub, k.signature_validity_end," 10887 " '\\x00'::BYTEA" 10888 " FROM merchant_fountains f" 10889 " CROSS JOIN merchant_token_family_keys k" 10890 " WHERE f.fountain_id='gc-fountain'" 10891 " AND k.h_pub=decode(repeat('05',64),'hex');" 10892 /* One request spanning an expired and an indefinite key. */ 10893 "INSERT INTO merchant_fountain_withdraw_sigs" 10894 " (fountain_serial, h_request, grant_index," 10895 " token_index, token_family_slug, h_issue," 10896 " signature_validity_end," 10897 " token_blinded_signature)" 10898 " SELECT f.fountain_serial," 10899 " decode(repeat('06',64),'hex')," 10900 " CASE WHEN k.signature_validity_end=1000000" 10901 " THEN 0 ELSE 1 END, 0," 10902 " tf.slug, k.h_pub, k.signature_validity_end," 10903 " '\\x00'::BYTEA" 10904 " FROM merchant_fountains f" 10905 " CROSS JOIN merchant_token_family_keys k" 10906 " JOIN merchant_token_families tf USING(token_family_serial)" 10907 " WHERE f.fountain_id='gc-fountain'" 10908 " AND k.h_pub IN (decode(repeat('04',64),'hex')," 10909 " decode(repeat('05',64),'hex'));" 10910 /* The same hash in another fountain is independent. */ 10911 "INSERT INTO merchant_fountains" 10912 " (fountain_id, h_fountain_secret, description, poll_freq)" 10913 " VALUES ('gc-other-fountain'," 10914 " decode(repeat('07',64),'hex'), 'GC test', 60000000);" 10915 "INSERT INTO merchant_fountain_withdraw_sigs" 10916 " (fountain_serial, h_request, grant_index, token_index," 10917 " token_family_slug, h_issue, signature_validity_end," 10918 " token_blinded_signature)" 10919 " SELECT f.fountain_serial, saved.h_request," 10920 " saved.grant_index, saved.token_index," 10921 " saved.token_family_slug, saved.h_issue," 10922 " saved.signature_validity_end," 10923 " saved.token_blinded_signature" 10924 " FROM merchant_fountains f" 10925 " CROSS JOIN merchant_fountain_withdraw_sigs saved" 10926 " WHERE f.fountain_id='gc-other-fountain'" 10927 " AND saved.h_request=decode(repeat('06',64),'hex')" 10928 " AND saved.grant_index=0;")); 10929 TEST_COND_RET_ON_FAIL (GNUNET_OK == TALER_MERCHANTDB_gc (pg), 10930 "Fountain garbage collection failed\n"); 10931 TEST_SET_INSTANCE (instance->instance.id, 10932 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 10933 TEST_RET_ON_FAIL (exec_sql ( 10934 "DO $$ BEGIN" 10935 " IF (SELECT count(*)" 10936 " FROM merchant_fountain_withdrawals fw" 10937 " JOIN merchant_token_families tf" 10938 " USING(token_family_serial)" 10939 " WHERE tf.slug='gc-finite') <> 0 THEN" 10940 " RAISE EXCEPTION" 10941 " 'Expired fountain counter survived GC';" 10942 " END IF;" 10943 " IF (SELECT count(*)" 10944 " FROM merchant_fountain_withdrawals fw" 10945 " JOIN merchant_token_families tf" 10946 " USING(token_family_serial)" 10947 " WHERE tf.slug='gc-forever'" 10948 " AND num_withdrawn=1) <> 1 THEN" 10949 " RAISE EXCEPTION" 10950 " 'Indefinite fountain counter was lost';" 10951 " END IF;" 10952 " IF (SELECT count(*)" 10953 " FROM merchant_fountain_withdraw_sigs fws" 10954 " WHERE fws.h_request=decode(repeat('02',64),'hex')) <> 0" 10955 " THEN" 10956 " RAISE EXCEPTION" 10957 " 'Expired withdrawal signature survived GC';" 10958 " END IF;" 10959 " IF (SELECT count(*)" 10960 " FROM merchant_fountain_withdraw_sigs fws" 10961 " WHERE fws.h_request=decode(repeat('03',64),'hex')) <> 1 THEN" 10962 " RAISE EXCEPTION" 10963 " 'Replayable withdrawal signature was collected';" 10964 " END IF;" 10965 " IF (SELECT count(*)" 10966 " FROM merchant_fountain_withdraw_sigs fws" 10967 " JOIN merchant_fountains f USING(fountain_serial)" 10968 " WHERE f.fountain_id='gc-fountain'" 10969 " AND fws.h_request=decode(repeat('06',64),'hex')) <> 2 THEN" 10970 " RAISE EXCEPTION 'Mixed-expiry replay was partially collected';" 10971 " END IF;" 10972 " IF EXISTS (SELECT FROM merchant_fountain_withdraw_sigs" 10973 " JOIN merchant_fountains USING(fountain_serial)" 10974 " WHERE fountain_id='gc-other-fountain') THEN" 10975 " RAISE EXCEPTION 'Replay expiry was shared across fountains';" 10976 " END IF;" 10977 " END $$;" 10978 /* Deleting a family must not truncate a saved response. */ 10979 "DELETE FROM merchant_token_families WHERE slug='gc-finite';" 10980 "DO $$ BEGIN" 10981 " IF (SELECT count(*) FROM merchant_fountain_withdraw_sigs" 10982 " WHERE h_request=decode(repeat('06',64),'hex')) <> 2 THEN" 10983 " RAISE EXCEPTION 'Family deletion truncated replay';" 10984 " END IF;" 10985 " END $$;")); 10986 TEST_COND_RET_ON_FAIL (GNUNET_OK == TALER_MERCHANTDB_gc (pg), 10987 "Fountain garbage collection failed\n"); 10988 TEST_SET_INSTANCE (instance->instance.id, 10989 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 10990 TEST_RET_ON_FAIL (exec_sql ( 10991 "DO $$ BEGIN" 10992 " IF (SELECT count(*) FROM merchant_fountain_withdraw_sigs" 10993 " WHERE h_request=decode(repeat('06',64),'hex')) <> 2 THEN" 10994 " RAISE EXCEPTION 'GC truncated replay after family deletion';" 10995 " END IF;" 10996 " END $$;" 10997 /* Simulate the last saved expiry passing. */ 10998 "UPDATE merchant_fountain_withdraw_sigs" 10999 " SET signature_validity_end=1000000" 11000 " WHERE h_issue=decode(repeat('05',64),'hex');")); 11001 TEST_COND_RET_ON_FAIL (GNUNET_OK == TALER_MERCHANTDB_gc (pg), 11002 "Fountain garbage collection failed\n"); 11003 TEST_SET_INSTANCE (instance->instance.id, 11004 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 11005 TEST_RET_ON_FAIL (exec_sql ( 11006 "DO $$ BEGIN" 11007 " IF EXISTS (SELECT FROM merchant_fountain_withdraw_sigs" 11008 " JOIN merchant_fountains USING(fountain_serial)" 11009 " WHERE fountain_id='gc-fountain') THEN" 11010 " RAISE EXCEPTION 'Expired replay group survived GC';" 11011 " END IF;" 11012 " END $$;" 11013 "DELETE FROM merchant_fountains" 11014 " WHERE fountain_id IN ('gc-fountain','gc-other-fountain');" 11015 "DELETE FROM merchant_token_families" 11016 " WHERE slug IN ('gc-finite','gc-forever');")); 11017 return 0; 11018 } 11019 11020 11021 /* ********** Statistics ********** */ 11022 11023 11024 /** 11025 * Closure for the statistics tests. 11026 */ 11027 struct TestStatistics_Closure 11028 { 11029 /** 11030 * Instance the statistics are collected for. 11031 */ 11032 struct InstanceData instance; 11033 }; 11034 11035 11036 /** 11037 * Closure for #count_bucket_counter_cb(). 11038 */ 11039 struct CountBuckets_Closure 11040 { 11041 /** 11042 * Number of bucket rows we were called with. 11043 */ 11044 unsigned int count; 11045 }; 11046 11047 11048 /** 11049 * Runs @a sql directly on the database connection of the test. Used to 11050 * set up statistics state that has no dedicated MERCHANTDB entry point 11051 * (the statistics tables are only ever written by triggers). 11052 * 11053 * @param sql the SQL statement(s) to execute 11054 * @return 0 on success, 1 otherwise. 11055 */ 11056 static int 11057 statistics_exec_sql (const char *sql) 11058 { 11059 struct GNUNET_PQ_ExecuteStatement es[] = { 11060 GNUNET_PQ_make_execute (sql), 11061 GNUNET_PQ_EXECUTE_STATEMENT_END 11062 }; 11063 11064 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11065 GNUNET_PQ_exec_statements (pg->conn, 11066 es), 11067 "Direct SQL execution failed\n"); 11068 return 0; 11069 } 11070 11071 11072 /** 11073 * Counts the bucket rows we are called for. 11074 * 11075 * @param cls a `struct CountBuckets_Closure *` 11076 * @param description description of the statistic 11077 * @param bucket_start start time of the bucket 11078 * @param bucket_end end time of the bucket 11079 * @param bucket_range range of the bucket 11080 * @param cumulative_counter counter value 11081 */ 11082 static void 11083 count_bucket_counter_cb (void *cls, 11084 const char *description, 11085 struct GNUNET_TIME_Timestamp bucket_start, 11086 struct GNUNET_TIME_Timestamp bucket_end, 11087 const char *bucket_range, 11088 uint64_t cumulative_counter) 11089 { 11090 struct CountBuckets_Closure *cbc = cls; 11091 11092 (void) description; 11093 (void) bucket_start; 11094 (void) bucket_end; 11095 (void) bucket_range; 11096 (void) cumulative_counter; 11097 cbc->count++; 11098 } 11099 11100 11101 /** 11102 * Determines how many bucket rows exist for @a slug. 11103 * 11104 * @param instance the instance to look at. 11105 * @param slug the statistic to count the buckets of. 11106 * @param[out] count set to the number of bucket rows. 11107 * @return 0 on success, 1 otherwise. 11108 */ 11109 static int 11110 count_bucket_counters (const struct InstanceData *instance, 11111 const char *slug, 11112 unsigned int *count) 11113 { 11114 struct CountBuckets_Closure cbc = { 11115 .count = 0 11116 }; 11117 11118 TEST_SET_INSTANCE (instance->instance.id, 11119 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 11120 TEST_COND_RET_ON_FAIL (0 <= 11121 TALER_MERCHANTDB_iterate_statistic_bucket_counters ( 11122 pg, 11123 instance->instance.id, 11124 slug, 11125 &count_bucket_counter_cb, 11126 &cbc), 11127 "Lookup of bucket statistics failed\n"); 11128 *count = cbc.count; 11129 return 0; 11130 } 11131 11132 11133 /** 11134 * Tests that garbage collection does not throw away bucket statistics 11135 * that are well within their retention age. Regression test: the 11136 * retention cut-off in merchant_statistic_bucket_gc() used to be 11137 * computed with EXTRACT(SECONDS FROM ...), which is the seconds *field* 11138 * of the interval and thus zero for every range we use, so the cut-off 11139 * degenerated to "now" and every bucket row was deleted on every run. 11140 * 11141 * @param instance the instance to collect statistics for. 11142 * @return 0 on success, 1 otherwise. 11143 */ 11144 static int 11145 test_statistics_bucket_gc (const struct InstanceData *instance) 11146 { 11147 unsigned int before; 11148 unsigned int after; 11149 11150 TEST_SET_INSTANCE (instance->instance.id, 11151 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 11152 TEST_RET_ON_FAIL (statistics_exec_sql ( 11153 "CALL merchant_do_bump_number_stat" 11154 " ('tokens-issued'" 11155 " ,CURRENT_TIMESTAMP(0)::TIMESTAMP" 11156 " ,1)")); 11157 TEST_RET_ON_FAIL (count_bucket_counters (instance, 11158 "tokens-issued", 11159 &before)); 11160 TEST_COND_RET_ON_FAIL (0 < before, 11161 "Bumping a bucket statistic recorded nothing\n"); 11162 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11163 TALER_MERCHANTDB_gc (pg), 11164 "Garbage collection failed\n"); 11165 TEST_RET_ON_FAIL (count_bucket_counters (instance, 11166 "tokens-issued", 11167 &after)); 11168 TEST_COND_RET_ON_FAIL (before == after, 11169 "Garbage collection deleted bucket statistics\n"); 11170 return 0; 11171 } 11172 11173 11174 /** 11175 * Counts the amount buckets we are called for. 11176 * 11177 * @param cls a `struct CountBuckets_Closure *` 11178 * @param description description of the statistic 11179 * @param bucket_start start time of the bucket 11180 * @param bucket_end end time of the bucket 11181 * @param bucket_range range of the bucket 11182 * @param cumulative_amounts_len length of @a cumulative_amounts 11183 * @param cumulative_amounts the cumulative amounts 11184 */ 11185 static void 11186 count_bucket_amount_cb (void *cls, 11187 const char *description, 11188 struct GNUNET_TIME_Timestamp bucket_start, 11189 struct GNUNET_TIME_Timestamp bucket_end, 11190 const char *bucket_range, 11191 unsigned int cumulative_amounts_len, 11192 const struct TALER_Amount cumulative_amounts[static 11193 cumulative_amounts_len]) 11194 { 11195 struct CountBuckets_Closure *cbc = cls; 11196 11197 (void) description; 11198 (void) bucket_start; 11199 (void) bucket_end; 11200 (void) bucket_range; 11201 (void) cumulative_amounts_len; 11202 (void) cumulative_amounts; 11203 cbc->count++; 11204 } 11205 11206 11207 /** 11208 * Bumps the amount statistic @a slug and checks that the bump was 11209 * actually recorded in the bucket statistics. Fails if @a slug is not 11210 * registered in @a instance, as the bump procedures silently ignore 11211 * statistics nobody registered. 11212 * 11213 * @param instance the instance to collect statistics for. 11214 * @param slug the statistic to bump. 11215 * @return 0 on success, 1 otherwise. 11216 */ 11217 static int 11218 test_statistics_amount_slug_collected (const struct InstanceData *instance, 11219 const char *slug) 11220 { 11221 struct CountBuckets_Closure cbc = { 11222 .count = 0 11223 }; 11224 char *sql; 11225 11226 TEST_SET_INSTANCE (instance->instance.id, 11227 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 11228 GNUNET_asprintf (&sql, 11229 "CALL merchant_do_bump_amount_stat" 11230 " ('%s'" 11231 " ,CURRENT_TIMESTAMP(0)::TIMESTAMP" 11232 " ,ROW(5,0,'EUR')::merchant.taler_amount_currency)", 11233 slug); 11234 { 11235 int ret; 11236 11237 ret = statistics_exec_sql (sql); 11238 GNUNET_free (sql); 11239 TEST_RET_ON_FAIL (ret); 11240 } 11241 TEST_COND_RET_ON_FAIL (0 <= 11242 TALER_MERCHANTDB_iterate_statistic_bucket_amounts ( 11243 pg, 11244 instance->instance.id, 11245 slug, 11246 &count_bucket_amount_cb, 11247 &cbc), 11248 "Lookup of bucket statistics failed\n"); 11249 TEST_COND_RET_ON_FAIL (0 < cbc.count, 11250 "Statistic bumped by a trigger is not registered\n"); 11251 return 0; 11252 } 11253 11254 11255 /** 11256 * Tests that bumping a statistic only uses the bucket ranges of the 11257 * registration with the matching type. Regression test: the cursor 11258 * over the ranges did not filter on the type, so a slug registered 11259 * both as a number and as an amount statistic got buckets for the 11260 * union of both range sets. 11261 * 11262 * @param instance the instance to collect statistics for. 11263 * @return 0 on success, 1 otherwise. 11264 */ 11265 static int 11266 test_statistics_dual_type_slug (const struct InstanceData *instance) 11267 { 11268 unsigned int count; 11269 11270 TEST_SET_INSTANCE (instance->instance.id, 11271 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 11272 TEST_RET_ON_FAIL (statistics_exec_sql ( 11273 "INSERT INTO merchant_statistic_bucket_meta" 11274 " (slug" 11275 " ,description" 11276 " ,stype" 11277 " ,ranges" 11278 " ,ages)" 11279 " VALUES" 11280 " ('test-dual-type'" 11281 " ,'registered as a number statistic'" 11282 " ,'number'" 11283 " ,ARRAY['hour'::merchant.statistic_range]" 11284 " ,ARRAY[72])" 11285 " ,('test-dual-type'" 11286 " ,'registered as an amount statistic'" 11287 " ,'amount'" 11288 " ,ARRAY['day'::merchant.statistic_range,'week','month']" 11289 " ,ARRAY[14,12,24])")); 11290 TEST_RET_ON_FAIL (statistics_exec_sql ( 11291 "CALL merchant_do_bump_number_stat" 11292 " ('test-dual-type'" 11293 " ,CURRENT_TIMESTAMP(0)::TIMESTAMP" 11294 " ,1)")); 11295 TEST_RET_ON_FAIL (count_bucket_counters (instance, 11296 "test-dual-type", 11297 &count)); 11298 TEST_COND_RET_ON_FAIL (1 == count, 11299 "Bump used the ranges of the wrong registration\n"); 11300 return 0; 11301 } 11302 11303 11304 /** 11305 * Determines how many interval events are stored for @a slug. 11306 * 11307 * @param instance the instance to look at. 11308 * @param slug the statistic to count the events of. 11309 * @param[out] count set to the number of events. 11310 * @return 0 on success, 1 otherwise. 11311 */ 11312 static int 11313 statistics_count_counter_events (const struct InstanceData *instance, 11314 const char *slug, 11315 uint64_t *count) 11316 { 11317 struct GNUNET_PQ_QueryParam params[] = { 11318 GNUNET_PQ_query_param_string (slug), 11319 GNUNET_PQ_query_param_end 11320 }; 11321 struct GNUNET_PQ_ResultSpec rs[] = { 11322 GNUNET_PQ_result_spec_uint64 ("num", 11323 count), 11324 GNUNET_PQ_result_spec_end 11325 }; 11326 11327 TEST_SET_INSTANCE (instance->instance.id, 11328 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 11329 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11330 GNUNET_PQ_prepare_anon ( 11331 pg->conn, 11332 "SELECT COUNT(*) AS num" 11333 " FROM merchant_statistic_counter_event" 11334 " WHERE imeta_serial_id =" 11335 " (SELECT imeta_serial_id" 11336 " FROM merchant_statistic_interval_meta" 11337 " WHERE slug=$1" 11338 " AND stype='number')"), 11339 "Preparing event count failed\n"); 11340 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 11341 GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 11342 "", 11343 params, 11344 rs), 11345 "Counting interval events failed\n"); 11346 return 0; 11347 } 11348 11349 11350 /** 11351 * Tests that garbage collection survives compacting an interval event 11352 * that an interval counter still uses as its watermark. Regression 11353 * test: merchant_statistic_counter_gc() deletes all but the lowest 11354 * serial of a window, which trips the ON DELETE RESTRICT foreign key 11355 * of merchant_statistic_interval_counter.event_delimiter as soon as 11356 * the watermark is not that lowest serial. merchant_do_gc() does not 11357 * trap that error, so garbage collection was abandoned for all 11358 * remaining instances. 11359 * 11360 * @param instance the instance to collect statistics for. 11361 * @return 0 on success, 1 otherwise. 11362 */ 11363 static int 11364 test_statistics_counter_gc_delimiter (const struct InstanceData *instance) 11365 { 11366 TEST_SET_INSTANCE (instance->instance.id, 11367 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 11368 /* Two events in the same coarsening window, with the watermark of the 11369 interval counter on the younger (higher serial) one. */ 11370 TEST_RET_ON_FAIL (statistics_exec_sql ( 11371 "DO $$\n" 11372 "DECLARE\n" 11373 " my_time INT8 := ROUND(EXTRACT(epoch FROM" 11374 " CURRENT_TIMESTAMP(0)::TIMESTAMP))::INT8;\n" 11375 " my_meta INT8;\n" 11376 " my_first INT8;\n" 11377 " my_second INT8;\n" 11378 "BEGIN\n" 11379 " SELECT imeta_serial_id\n" 11380 " INTO my_meta\n" 11381 " FROM merchant_statistic_interval_meta\n" 11382 " WHERE slug='orders-created'\n" 11383 " AND stype='number';\n" 11384 " INSERT INTO merchant_statistic_counter_event\n" 11385 " (imeta_serial_id, slot, delta)\n" 11386 " VALUES (my_meta, my_time - 7000, 1)\n" 11387 " RETURNING nevent_serial_id INTO my_first;\n" 11388 " INSERT INTO merchant_statistic_counter_event\n" 11389 " (imeta_serial_id, slot, delta)\n" 11390 " VALUES (my_meta, my_time - 6900, 1)\n" 11391 " RETURNING nevent_serial_id INTO my_second;\n" 11392 " INSERT INTO merchant_statistic_interval_counter\n" 11393 " (imeta_serial_id, range, event_delimiter," 11394 " cumulative_number)\n" 11395 " VALUES (my_meta, 7200, my_second, 1);\n" 11396 "END $$;")); 11397 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11398 TALER_MERCHANTDB_gc (pg), 11399 "Garbage collection failed\n"); 11400 { 11401 uint64_t num_events; 11402 11403 TEST_RET_ON_FAIL (statistics_count_counter_events (instance, 11404 "orders-created", 11405 &num_events)); 11406 TEST_COND_RET_ON_FAIL (1 == num_events, 11407 "Garbage collection did not compact the events\n"); 11408 } 11409 return 0; 11410 } 11411 11412 11413 /** 11414 * SQL bumping the 'tokens-used' statistic by one. 11415 */ 11416 #define BUMP_TOKENS_USED \ 11417 "CALL merchant_do_bump_number_stat" \ 11418 " ('tokens-used'" \ 11419 " ,CURRENT_TIMESTAMP(0)::TIMESTAMP" \ 11420 " ,1)" 11421 11422 11423 /** 11424 * Determines the largest counter value stored for @a slug. 11425 * 11426 * @param instance the instance to look at. 11427 * @param slug the statistic to look at. 11428 * @param[out] value set to the largest bucket counter. 11429 * @return 0 on success, 1 otherwise. 11430 */ 11431 static int 11432 statistics_max_bucket_counter (const struct InstanceData *instance, 11433 const char *slug, 11434 uint64_t *value) 11435 { 11436 struct GNUNET_PQ_QueryParam params[] = { 11437 GNUNET_PQ_query_param_string (slug), 11438 GNUNET_PQ_query_param_end 11439 }; 11440 struct GNUNET_PQ_ResultSpec rs[] = { 11441 GNUNET_PQ_result_spec_uint64 ("num", 11442 value), 11443 GNUNET_PQ_result_spec_end 11444 }; 11445 11446 TEST_SET_INSTANCE (instance->instance.id, 11447 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 11448 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11449 GNUNET_PQ_prepare_anon ( 11450 pg->conn, 11451 "SELECT COALESCE(MAX(cumulative_number),0) AS num" 11452 " FROM merchant_statistic_bucket_counter" 11453 " WHERE bmeta_serial_id =" 11454 " (SELECT bmeta_serial_id" 11455 " FROM merchant_statistic_bucket_meta" 11456 " WHERE slug=$1" 11457 " AND stype='number')"), 11458 "Preparing counter lookup failed\n"); 11459 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 11460 GNUNET_PQ_eval_prepared_singleton_select (pg->conn, 11461 "", 11462 params, 11463 rs), 11464 "Looking up bucket counter failed\n"); 11465 return 0; 11466 } 11467 11468 11469 /** 11470 * Bumps the 'tokens-used' statistic from a second process while this 11471 * process still holds the very first (uncommitted) bump of the same 11472 * bucket. Regression test: the bucket statistics used to be written 11473 * as UPDATE-then-INSERT, so the second writer got a unique violation 11474 * (which GNUnet maps to SUCCESS_NO_RESULTS) and its event was lost; 11475 * in the pay path the very same pattern surfaces to the wallet as a 11476 * bogus 409 on a perfectly valid payment. 11477 * 11478 * @param instance the instance to collect statistics for. 11479 * @return 0 on success, 1 otherwise. 11480 */ 11481 static int 11482 test_statistics_bucket_upsert_race (const struct InstanceData *instance) 11483 { 11484 pid_t child; 11485 int status; 11486 uint64_t value; 11487 11488 TEST_SET_INSTANCE (instance->instance.id, 11489 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT); 11490 /* Make sure the bucket does not exist yet, only then do both writers 11491 race to create it. */ 11492 TEST_RET_ON_FAIL (statistics_exec_sql ( 11493 "DELETE FROM merchant_statistic_bucket_counter" 11494 " WHERE bmeta_serial_id =" 11495 " (SELECT bmeta_serial_id" 11496 " FROM merchant_statistic_bucket_meta" 11497 " WHERE slug='tokens-used'" 11498 " AND stype='number')")); 11499 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11500 TALER_MERCHANTDB_start_read_committed ( 11501 pg, 11502 "bucket statistics upsert race"), 11503 "Failed to start transaction\n"); 11504 TEST_RET_ON_FAIL (statistics_exec_sql (BUMP_TOKENS_USED)); 11505 child = fork (); 11506 if (-1 == child) 11507 { 11508 TALER_MERCHANTDB_rollback (pg); 11509 GNUNET_break (0); 11510 return 1; 11511 } 11512 if (0 == child) 11513 { 11514 struct TALER_MERCHANTDB_PostgresContext *cpg; 11515 int ret = 1; 11516 11517 /* Second writer: blocks on the uncommitted row of the parent until 11518 the parent commits below. */ 11519 cpg = TALER_MERCHANTDB_connect (test_cfg); 11520 if ( (NULL != cpg) && 11521 (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 11522 TALER_MERCHANTDB_set_instance (cpg, 11523 instance->instance.id)) ) 11524 { 11525 struct GNUNET_PQ_ExecuteStatement es[] = { 11526 GNUNET_PQ_make_execute (BUMP_TOKENS_USED), 11527 GNUNET_PQ_EXECUTE_STATEMENT_END 11528 }; 11529 11530 if (GNUNET_OK == 11531 GNUNET_PQ_exec_statements (cpg->conn, 11532 es)) 11533 ret = 0; 11534 } 11535 _exit (ret); 11536 } 11537 sleep (1); /* give the child time to reach the conflicting INSERT */ 11538 TEST_COND_RET_ON_FAIL (0 <= 11539 TALER_MERCHANTDB_commit (pg), 11540 "Failed to commit transaction\n"); 11541 if (0 >= waitpid (child, 11542 &status, 11543 0)) 11544 sleep (2); /* cannot reap the child, just give it time to finish */ 11545 TEST_RET_ON_FAIL (statistics_max_bucket_counter (instance, 11546 "tokens-used", 11547 &value)); 11548 TEST_COND_RET_ON_FAIL (2 == value, 11549 "Concurrent statistics bump was lost\n"); 11550 return 0; 11551 } 11552 11553 11554 /** 11555 * Prepares for the statistics tests. 11556 * 11557 * @param[out] cls the closure to initialize. 11558 */ 11559 static void 11560 pre_test_statistics (struct TestStatistics_Closure *cls) 11561 { 11562 make_instance ("test_inst_statistics", 11563 &cls->instance); 11564 } 11565 11566 11567 /** 11568 * Cleans up after the statistics tests. 11569 * 11570 * @param[in,out] cls the closure to clean up. 11571 */ 11572 static void 11573 post_test_statistics (struct TestStatistics_Closure *cls) 11574 { 11575 free_instance_data (&cls->instance); 11576 } 11577 11578 11579 /** 11580 * Runs the statistics tests. 11581 * 11582 * @param cls the closure to use. 11583 * @return 0 on success, 1 otherwise. 11584 */ 11585 static int 11586 run_test_statistics (struct TestStatistics_Closure *cls) 11587 { 11588 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 11589 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 11590 TEST_RET_ON_FAIL (test_statistics_bucket_gc (&cls->instance)); 11591 TEST_RET_ON_FAIL (test_fountain_gc (&cls->instance)); 11592 /* Every amount statistic bumped by a trigger in pg_triggers.sql must 11593 be registered in the per-instance schema. */ 11594 TEST_RET_ON_FAIL (test_statistics_amount_slug_collected ( 11595 &cls->instance, 11596 "payments-received-after-deposit-fee")); 11597 TEST_RET_ON_FAIL (test_statistics_amount_slug_collected ( 11598 &cls->instance, 11599 "total-deposit-fees-paid")); 11600 TEST_RET_ON_FAIL (test_statistics_amount_slug_collected ( 11601 &cls->instance, 11602 "total-wire-fees-paid")); 11603 TEST_RET_ON_FAIL (test_statistics_amount_slug_collected ( 11604 &cls->instance, 11605 "refunds-granted")); 11606 TEST_RET_ON_FAIL (test_statistics_amount_slug_collected ( 11607 &cls->instance, 11608 "deposits-received")); 11609 TEST_RET_ON_FAIL (test_statistics_amount_slug_collected ( 11610 &cls->instance, 11611 "deposits-fees-paid")); 11612 TEST_RET_ON_FAIL (test_statistics_counter_gc_delimiter (&cls->instance)); 11613 TEST_RET_ON_FAIL (test_statistics_dual_type_slug (&cls->instance)); 11614 TEST_RET_ON_FAIL (test_statistics_bucket_upsert_race (&cls->instance)); 11615 return 0; 11616 } 11617 11618 11619 /** 11620 * Takes care of statistics testing. 11621 * 11622 * @return 0 on success, 1 otherwise. 11623 */ 11624 static int 11625 test_statistics (void) 11626 { 11627 struct TestStatistics_Closure test_cls; 11628 int test_result; 11629 11630 pre_test_statistics (&test_cls); 11631 test_result = run_test_statistics (&test_cls); 11632 post_test_statistics (&test_cls); 11633 return test_result; 11634 } 11635 11636 11637 /* ********** MFA challenges ********** */ 11638 11639 11640 /** 11641 * Tests solving multi-factor authentication (MFA) challenges. 11642 * 11643 * Besides the obvious cases, this pins down the *idempotency* of 11644 * confirming a challenge that has already been confirmed: any TAN is 11645 * then accepted and the retry counter is left alone. That is 11646 * intentional (see the comment in do_solve_mfa_challenge.sql): the 11647 * authorization is carried by the confirmation already stored in the 11648 * database, which this path does not change, and reaching it already 11649 * requires knowing the challenge ID together with the matching body 11650 * hash. The assertions are here so that the contract cannot silently 11651 * drift. 11652 * 11653 * @return 0 on success, 1 otherwise. 11654 */ 11655 static int 11656 test_mfa_challenges (void) 11657 { 11658 struct TALER_MERCHANT_MFA_BodyHash h_body; 11659 struct TALER_MERCHANT_MFA_BodySalt salt; 11660 struct TALER_MERCHANT_MFA_BodyHash other_h_body; 11661 uint64_t challenge_id; 11662 bool solved; 11663 uint32_t retry_counter; 11664 11665 GNUNET_CRYPTO_random_block (&h_body, 11666 sizeof (h_body)); 11667 GNUNET_CRYPTO_random_block (&other_h_body, 11668 sizeof (other_h_body)); 11669 GNUNET_CRYPTO_random_block (&salt, 11670 sizeof (salt)); 11671 TEST_COND_RET_ON_FAIL ( 11672 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 11673 TALER_MERCHANTDB_insert_mfa_challenge ( 11674 pg, 11675 "test_inst_mfa", 11676 TALER_MERCHANT_MFA_CO_AUTH_CONFIGURATION, 11677 &h_body, 11678 &salt, 11679 "1234-5678", 11680 GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS), 11681 GNUNET_TIME_UNIT_ZERO_ABS, 11682 TALER_MERCHANT_MFA_CHANNEL_EMAIL, 11683 "test@example.com", 11684 &challenge_id), 11685 "Insert MFA challenge failed\n"); 11686 /* A challenge is only addressable with the matching body hash. */ 11687 TEST_COND_RET_ON_FAIL ( 11688 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 11689 TALER_MERCHANTDB_do_solve_mfa_challenge (pg, 11690 challenge_id, 11691 &other_h_body, 11692 "1234-5678", 11693 &solved, 11694 &retry_counter), 11695 "Challenge was solvable with a different body hash\n"); 11696 TEST_COND_RET_ON_FAIL (! solved, 11697 "Challenge solved with a different body hash\n"); 11698 /* A wrong TAN is rejected and costs one of the three attempts. */ 11699 TEST_COND_RET_ON_FAIL ( 11700 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 11701 TALER_MERCHANTDB_do_solve_mfa_challenge (pg, 11702 challenge_id, 11703 &h_body, 11704 "0000-0000", 11705 &solved, 11706 &retry_counter), 11707 "Solving MFA challenge failed\n"); 11708 TEST_COND_RET_ON_FAIL (! solved, 11709 "A wrong TAN solved the challenge\n"); 11710 TEST_COND_RET_ON_FAIL (2 == retry_counter, 11711 "A wrong TAN did not cost an attempt\n"); 11712 /* The correct TAN confirms the challenge. */ 11713 TEST_COND_RET_ON_FAIL ( 11714 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 11715 TALER_MERCHANTDB_do_solve_mfa_challenge (pg, 11716 challenge_id, 11717 &h_body, 11718 "1234-5678", 11719 &solved, 11720 &retry_counter), 11721 "Solving MFA challenge failed\n"); 11722 TEST_COND_RET_ON_FAIL (solved, 11723 "The correct TAN did not solve the challenge\n"); 11724 TEST_COND_RET_ON_FAIL (2 == retry_counter, 11725 "The correct TAN cost an attempt\n"); 11726 /* Re-confirming an already confirmed challenge is idempotent. */ 11727 TEST_COND_RET_ON_FAIL ( 11728 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 11729 TALER_MERCHANTDB_do_solve_mfa_challenge (pg, 11730 challenge_id, 11731 &h_body, 11732 "0000-0000", 11733 &solved, 11734 &retry_counter), 11735 "Solving MFA challenge failed\n"); 11736 TEST_COND_RET_ON_FAIL (solved, 11737 "Re-confirming a confirmed challenge is not" 11738 " idempotent\n"); 11739 TEST_COND_RET_ON_FAIL (2 == retry_counter, 11740 "Re-confirming a confirmed challenge cost an" 11741 " attempt\n"); 11742 /* ... but still only for the challenge's own body hash. */ 11743 TEST_COND_RET_ON_FAIL ( 11744 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 11745 TALER_MERCHANTDB_do_solve_mfa_challenge (pg, 11746 challenge_id, 11747 &other_h_body, 11748 "1234-5678", 11749 &solved, 11750 &retry_counter), 11751 "Confirmed challenge was addressable with a different body hash\n"); 11752 TEST_COND_RET_ON_FAIL (! solved, 11753 "Confirmed challenge solved with a different body" 11754 " hash\n"); 11755 return 0; 11756 } 11757 11758 11759 /** 11760 * Test that COMMIT on a transaction PostgreSQL has already aborted is not 11761 * reported as a success -- and that a clean COMMIT still returns exactly 11762 * GNUNET_DB_STATUS_SUCCESS_NO_RESULTS, which is what every caller that 11763 * compares the result against 0 relies on. 11764 * 11765 * @return 0 on success 11766 */ 11767 static int 11768 test_commit_on_aborted_transaction (void) 11769 { 11770 struct GNUNET_PQ_ExecuteStatement mk[] = { 11771 GNUNET_PQ_make_execute ("CREATE TABLE test_commit_rollback (x INT)"), 11772 GNUNET_PQ_EXECUTE_STATEMENT_END 11773 }; 11774 struct GNUNET_PQ_ExecuteStatement bad[] = { 11775 GNUNET_PQ_make_execute ("SELECT 1/0"), 11776 GNUNET_PQ_EXECUTE_STATEMENT_END 11777 }; 11778 struct GNUNET_PQ_ExecuteStatement chk[] = { 11779 GNUNET_PQ_make_execute ( 11780 "DO $$ BEGIN" 11781 " IF EXISTS (SELECT FROM pg_class" 11782 " WHERE relname='test_commit_rollback')" 11783 " THEN RAISE EXCEPTION 'table survived a rollback';" 11784 " END IF; END $$"), 11785 GNUNET_PQ_EXECUTE_STATEMENT_END 11786 }; 11787 11788 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11789 TALER_MERCHANTDB_start (pg, 11790 "test-commit-aborted"), 11791 "Failed to start transaction\n"); 11792 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11793 GNUNET_PQ_exec_statements (pg->conn, 11794 mk), 11795 "Failed to create the scratch table\n"); 11796 /* provoke an error; from here on the transaction is doomed */ 11797 TEST_COND_RET_ON_FAIL (GNUNET_OK != 11798 GNUNET_PQ_exec_statements (pg->conn, 11799 bad), 11800 "Division by zero unexpectedly succeeded\n"); 11801 /* before the fix this returned GNUNET_DB_STATUS_SUCCESS_NO_RESULTS (0) */ 11802 TEST_COND_RET_ON_FAIL (0 > 11803 TALER_MERCHANTDB_commit (pg), 11804 "COMMIT of an aborted transaction reported success\n"); 11805 /* ...and the table is indeed gone, so 'success' would have been a lie */ 11806 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11807 GNUNET_PQ_exec_statements (pg->conn, 11808 chk), 11809 "Scratch table survived the rollback\n"); 11810 TEST_COND_RET_ON_FAIL (GNUNET_OK == 11811 TALER_MERCHANTDB_start (pg, 11812 "test-commit-clean"), 11813 "Failed to start transaction\n"); 11814 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 11815 TALER_MERCHANTDB_commit (pg), 11816 "COMMIT changed its return value on success\n"); 11817 return 0; 11818 } 11819 11820 11821 /** 11822 * Function that runs all tests. 11823 * 11824 * @return 0 on success, 1 otherwise. 11825 */ 11826 static int 11827 run_tests (void) 11828 { 11829 TEST_RET_ON_FAIL (test_instances ()); 11830 TEST_RET_ON_FAIL (test_products ()); 11831 TEST_RET_ON_FAIL (test_inventory_locks ()); 11832 TEST_RET_ON_FAIL (test_tokens ()); 11833 TEST_RET_ON_FAIL (test_orders ()); 11834 TEST_RET_ON_FAIL (test_deposits ()); 11835 /* This test relies on the global pending-webhook identity sequence not 11836 having been advanced by an earlier test. */ 11837 TEST_RET_ON_FAIL (test_pending_webhooks ()); 11838 TEST_RET_ON_FAIL (test_transfers ()); 11839 TEST_RET_ON_FAIL (test_refunds ()); 11840 TEST_RET_ON_FAIL (test_lookup_orders_all_filters ()); 11841 TEST_RET_ON_FAIL (test_kyc ()); 11842 TEST_RET_ON_FAIL (test_templates ()); 11843 TEST_RET_ON_FAIL (test_webhooks ()); 11844 TEST_RET_ON_FAIL (test_inventory_deleted_webhook ()); 11845 TEST_RET_ON_FAIL (test_statistics ()); 11846 TEST_RET_ON_FAIL (test_mfa_challenges ()); 11847 TEST_RET_ON_FAIL (test_commit_on_aborted_transaction ()); 11848 return 0; 11849 } 11850 11851 11852 /** 11853 * Main function that will be run by the scheduler. 11854 * 11855 * @param cls closure with config 11856 */ 11857 static void 11858 run (void *cls) 11859 { 11860 struct GNUNET_CONFIGURATION_Handle *cfg = cls; 11861 11862 test_cfg = cfg; 11863 /* The test initializes its own schema, which may not exist yet. */ 11864 if (NULL == (pg = TALER_MERCHANTDB_connect_admin (cfg))) 11865 { 11866 result = 77; 11867 return; 11868 } 11869 /* Drop the tables to cleanup anything that might cause issues */ 11870 if ( (GNUNET_OK != TALER_MERCHANTDB_drop_tables (pg)) || 11871 (GNUNET_OK != TALER_MERCHANTDB_create_tables (pg)) ) 11872 { 11873 TALER_MERCHANTDB_disconnect (pg); 11874 pg = NULL; 11875 result = 1; 11876 return; 11877 } 11878 /* Run the preflight */ 11879 TALER_MERCHANTDB_preflight (pg); 11880 11881 result = run_tests (); 11882 /* if (0 == result) result = run_test_templates (); */ 11883 if (0 == result) 11884 { 11885 if (GNUNET_OK != 11886 TALER_MERCHANTDB_drop_tables (pg)) 11887 { 11888 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 11889 "Dropping tables failed\n"); 11890 result = 1; 11891 } 11892 } 11893 TALER_MERCHANTDB_disconnect (pg); 11894 pg = NULL; 11895 } 11896 11897 11898 /** 11899 * Entry point for the tests. 11900 */ 11901 int 11902 main (int argc, 11903 char *const argv[]) 11904 { 11905 struct GNUNET_CONFIGURATION_Handle *cfg; 11906 11907 GNUNET_log_setup (argv[0], 11908 "DEBUG", 11909 NULL); 11910 cfg = GNUNET_CONFIGURATION_create (TALER_MERCHANT_project_data ()); 11911 if (GNUNET_OK != 11912 GNUNET_CONFIGURATION_parse (cfg, 11913 "test-merchantdb-postgres.conf")) 11914 { 11915 GNUNET_break (0); 11916 return 2; 11917 } 11918 GNUNET_SCHEDULER_run (&run, 11919 cfg); 11920 GNUNET_CONFIGURATION_destroy (cfg); 11921 return result; 11922 } 11923 11924 11925 /* end of test_merchantdb.c */