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