test_merchantdb.c (252419B)
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_signatures.h> 28 #include "taler/taler_merchant_util.h" 29 #include "merchantdb_lib.h" 30 #include "merchant-database/account_kyc_get_status.h" 31 #include "merchant-database/account_kyc_set_status.h" 32 #include "merchant-database/delete_contract_terms.h" 33 #include "merchant-database/delete_instance_private_key.h" 34 #include "merchant-database/delete_order.h" 35 #include "merchant-database/delete_pending_webhook.h" 36 #include "merchant-database/delete_product.h" 37 #include "merchant-database/delete_template.h" 38 #include "merchant-database/delete_webhook.h" 39 #include "merchant-database/inactivate_account.h" 40 #include "merchant-database/increase_refund.h" 41 #include "merchant-database/insert_account.h" 42 #include "merchant-database/insert_contract_terms.h" 43 #include "merchant-database/insert_deposit.h" 44 #include "merchant-database/insert_deposit_confirmation.h" 45 #include "merchant-database/insert_deposit_to_transfer.h" 46 #include "merchant-database/insert_exchange_signkey.h" 47 #include "merchant-database/insert_instance.h" 48 #include "merchant-database/insert_order.h" 49 #include "merchant-database/insert_order_lock.h" 50 #include "merchant-database/insert_otp.h" 51 #include "merchant-database/insert_pending_webhook.h" 52 #include "merchant-database/insert_product.h" 53 #include "merchant-database/insert_refund_proof.h" 54 #include "merchant-database/insert_template.h" 55 #include "merchant-database/insert_transfer.h" 56 #include "merchant-database/insert_transfer_details.h" 57 #include "merchant-database/insert_webhook.h" 58 #include "merchant-database/lock_product.h" 59 #include "merchant-database/lookup_account.h" 60 #include "merchant-database/lookup_contract_terms.h" 61 #include "merchant-database/lookup_contract_terms3.h" 62 #include "merchant-database/lookup_deposits.h" 63 #include "merchant-database/lookup_deposits_by_contract_and_coin.h" 64 #include "merchant-database/lookup_deposits_by_order.h" 65 #include "merchant-database/lookup_instances.h" 66 #include "merchant-database/lookup_order.h" 67 #include "merchant-database/lookup_order_by_fulfillment.h" 68 #include "merchant-database/lookup_order_status.h" 69 #include "merchant-database/lookup_orders.h" 70 #include "merchant-database/lookup_pending_webhooks.h" 71 #include "merchant-database/lookup_product.h" 72 #include "merchant-database/lookup_products.h" 73 #include "merchant-database/lookup_refund_proof.h" 74 #include "merchant-database/lookup_refunds.h" 75 #include "merchant-database/lookup_refunds_detailed.h" 76 #include "merchant-database/lookup_template.h" 77 #include "merchant-database/lookup_templates.h" 78 #include "merchant-database/lookup_transfer_details.h" 79 #include "merchant-database/lookup_transfer_details_by_order.h" 80 #include "merchant-database/lookup_transfer_summary.h" 81 #include "merchant-database/lookup_transfers.h" 82 #include "merchant-database/lookup_webhook.h" 83 #include "merchant-database/lookup_webhook_by_event.h" 84 #include "merchant-database/lookup_webhooks.h" 85 #include "merchant-database/lookup_wire_fee.h" 86 #include "merchant-database/mark_contract_paid.h" 87 #include "merchant-database/mark_order_wired.h" 88 #include "merchant-database/refund_coin.h" 89 #include "merchant-database/store_wire_fee_by_exchange.h" 90 #include "merchant-database/unlock_inventory.h" 91 #include "merchant-database/update_contract_terms.h" 92 #include "merchant-database/update_instance.h" 93 #include "merchant-database/update_pending_webhook.h" 94 #include "merchant-database/update_product.h" 95 #include "merchant-database/update_template.h" 96 #include "merchant-database/update_webhook.h" 97 #include "merchant-database/create_tables.h" 98 #include "merchant-database/drop_tables.h" 99 #include "merchant-database/preflight.h" 100 #include "merchant-database/purge_instance.h" 101 102 103 /** 104 * Global return value for the test. Initially -1, set to 0 upon 105 * completion. Other values indicate some kind of error. 106 */ 107 static int result; 108 109 /** 110 * Handle to the database we are testing. 111 */ 112 static struct TALER_MERCHANTDB_PostgresContext *pg; 113 114 /** 115 * @param test 0 on success, non-zero on failure 116 */ 117 #define TEST_WITH_FAIL_CLAUSE(test, on_fail) \ 118 if ((test)) \ 119 { \ 120 GNUNET_break (0); \ 121 on_fail \ 122 } 123 124 #define TEST_COND_RET_ON_FAIL(cond, msg) \ 125 if (! (cond)) \ 126 { \ 127 GNUNET_break (0); \ 128 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \ 129 msg); \ 130 return 1; \ 131 } 132 133 /** 134 * @param __test 0 on success, non-zero on failure 135 */ 136 #define TEST_RET_ON_FAIL(__test) \ 137 TEST_WITH_FAIL_CLAUSE (__test, \ 138 return 1; \ 139 ) 140 141 142 /* ********** Instances ********** */ 143 144 145 /** 146 * Container for instance settings along with keys. 147 */ 148 struct InstanceData 149 { 150 /** 151 * The instance settings. 152 */ 153 struct TALER_MERCHANTDB_InstanceSettings instance; 154 155 /** 156 * The public key for the instance. 157 */ 158 struct TALER_MerchantPublicKeyP merchant_pub; 159 160 /** 161 * The private key for the instance. 162 */ 163 struct TALER_MerchantPrivateKeyP merchant_priv; 164 }; 165 166 167 /** 168 * Creates data for an instance to use with testing. 169 * 170 * @param instance_id the identifier for this instance. 171 * @param instance the instance data to be filled. 172 */ 173 static void 174 make_instance (const char *instance_id, 175 struct InstanceData *instance) 176 { 177 memset (instance, 178 0, 179 sizeof (*instance)); 180 GNUNET_CRYPTO_eddsa_key_create (&instance->merchant_priv.eddsa_priv); 181 GNUNET_CRYPTO_eddsa_key_get_public (&instance->merchant_priv.eddsa_priv, 182 &instance->merchant_pub.eddsa_pub); 183 instance->instance.id = (char *) instance_id; 184 instance->instance.name = (char *) "Test"; 185 instance->instance.address = json_array (); 186 GNUNET_assert (NULL != instance->instance.address); 187 GNUNET_assert (0 == json_array_append_new (instance->instance.address, 188 json_string ("123 Example St"))); 189 instance->instance.jurisdiction = json_array (); 190 GNUNET_assert (NULL != instance->instance.jurisdiction); 191 GNUNET_assert (0 == json_array_append_new (instance->instance.jurisdiction, 192 json_string ("Ohio"))); 193 instance->instance.use_stefan = true; 194 instance->instance.default_wire_transfer_delay = 195 GNUNET_TIME_relative_get_minute_ (); 196 instance->instance.default_pay_delay = GNUNET_TIME_UNIT_SECONDS; 197 instance->instance.default_refund_delay = GNUNET_TIME_UNIT_MINUTES; 198 } 199 200 201 /** 202 * Frees memory allocated when creating an instance for testing. 203 * 204 * @param instance the instance containing the memory to be freed. 205 */ 206 static void 207 free_instance_data (struct InstanceData *instance) 208 { 209 json_decref (instance->instance.address); 210 json_decref (instance->instance.jurisdiction); 211 } 212 213 214 /** 215 * Creates an account with test data for an instance. 216 * 217 * @param account the account to initialize. 218 */ 219 static void 220 make_account (struct TALER_MERCHANTDB_AccountDetails *account) 221 { 222 memset (account, 223 0, 224 sizeof (*account)); 225 GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, 226 &account->h_wire.hash); 227 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG, 228 &account->salt, 229 sizeof (account->salt)); 230 account->payto_uri.full_payto 231 = (char *) "payto://x-taler-bank/bank.demo.taler.net/4"; 232 account->active = true; 233 } 234 235 236 /** 237 * Instance settings along with corresponding accounts. 238 */ 239 struct InstanceWithAccounts 240 { 241 /** 242 * Pointer to the instance settings. 243 */ 244 const struct TALER_MERCHANTDB_InstanceSettings *instance; 245 246 /** 247 * Length of the array of accounts. 248 */ 249 unsigned int accounts_length; 250 251 /** 252 * Pointer to the array of accounts. 253 */ 254 const struct TALER_MERCHANTDB_AccountDetails *accounts; 255 256 }; 257 258 259 /** 260 * Closure for testing instance lookup. 261 */ 262 struct TestLookupInstances_Closure 263 { 264 /** 265 * Number of instances to compare to. 266 */ 267 unsigned int instances_to_cmp_length; 268 269 /** 270 * Pointer to array of instances. 271 */ 272 const struct InstanceWithAccounts *instances_to_cmp; 273 274 /** 275 * Pointer to array of number of matches for each instance. 276 */ 277 unsigned int *results_matching; 278 279 /** 280 * Total number of results returned. 281 */ 282 unsigned int results_length; 283 }; 284 285 286 /** 287 * Compares two instances for equality. 288 * 289 * @param a the first instance. 290 * @param b the second instance. 291 * @return 0 on equality, 1 otherwise. 292 */ 293 static int 294 check_instances_equal (const struct TALER_MERCHANTDB_InstanceSettings *a, 295 const struct TALER_MERCHANTDB_InstanceSettings *b) 296 { 297 if ((0 != strcmp (a->id, 298 b->id)) || 299 (0 != strcmp (a->name, 300 b->name)) || 301 (1 != json_equal (a->address, 302 b->address)) || 303 (1 != json_equal (a->jurisdiction, 304 b->jurisdiction)) || 305 (a->use_stefan != b->use_stefan) || 306 (a->default_wire_transfer_delay.rel_value_us != 307 b->default_wire_transfer_delay.rel_value_us) || 308 (a->default_pay_delay.rel_value_us != b->default_pay_delay.rel_value_us)) 309 return 1; 310 return 0; 311 } 312 313 314 #if 0 315 /** 316 * Compares two accounts for equality. 317 * 318 * @param a the first account. 319 * @param b the second account. 320 * @return 0 on equality, 1 otherwise. 321 */ 322 static int 323 check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a, 324 const struct TALER_MERCHANTDB_AccountDetails *b) 325 { 326 if ((0 != GNUNET_memcmp (&a->h_wire, 327 &b->h_wire)) || 328 (0 != GNUNET_memcmp (&a->salt, 329 &b->salt)) || 330 (0 != TALER_full_payto_cmp (a->payto_uri, 331 b->payto_uri)) || 332 (a->active != b->active)) 333 return 1; 334 return 0; 335 } 336 337 338 #endif 339 340 341 /** 342 * Called after testing 'lookup_instances'. 343 * 344 * @param cls pointer to 'struct TestLookupInstances_Closure'. 345 * @param merchant_pub public key of the instance 346 * @param merchant_priv private key of the instance, NULL if not available 347 * @param is general instance settings 348 * @param ias instance authentication settings 349 */ 350 static void 351 lookup_instances_cb (void *cls, 352 const struct TALER_MerchantPublicKeyP *merchant_pub, 353 const struct TALER_MerchantPrivateKeyP *merchant_priv, 354 const struct TALER_MERCHANTDB_InstanceSettings *is, 355 const struct TALER_MERCHANTDB_InstanceAuthSettings *ias) 356 { 357 struct TestLookupInstances_Closure *cmp = cls; 358 359 if (NULL == cmp) 360 return; 361 cmp->results_length += 1; 362 /* Look through the closure and test each instance for equality */ 363 for (unsigned int i = 0; cmp->instances_to_cmp_length > i; ++i) 364 { 365 if (0 != check_instances_equal (cmp->instances_to_cmp[i].instance, 366 is)) 367 continue; 368 cmp->results_matching[i] += 1; 369 } 370 } 371 372 373 /** 374 * Tests @e insert_instance. 375 * 376 * @param instance the instance data to insert. 377 * @param expected_result the result that should be returned from the DB. 378 * @return 0 on success, 1 on failure. 379 */ 380 static int 381 test_insert_instance (const struct InstanceData *instance, 382 enum GNUNET_DB_QueryStatus expected_result) 383 { 384 struct TALER_MERCHANTDB_InstanceAuthSettings ias = { 0 }; 385 386 TEST_COND_RET_ON_FAIL (expected_result == 387 TALER_MERCHANTDB_insert_instance (pg, 388 &instance->merchant_pub, 389 &instance->merchant_priv, 390 &instance->instance, 391 &ias, 392 false), 393 "Insert instance failed\n"); 394 return 0; 395 } 396 397 398 /** 399 * Tests @e update_instance. 400 * 401 * @param updated_data the instance data to update the row in the database to. 402 * @param expected_result the result that should be returned from the DB. 403 * @return 0 on success, 1 on failure. 404 */ 405 static int 406 test_update_instance (const struct InstanceData *updated_data, 407 enum GNUNET_DB_QueryStatus expected_result) 408 { 409 TEST_COND_RET_ON_FAIL (expected_result == 410 TALER_MERCHANTDB_update_instance (pg, 411 &updated_data->instance), 412 "Update instance failed\n"); 413 return 0; 414 } 415 416 417 /** 418 * Tests @e lookup_instances. 419 * 420 * @param active_only whether to lookup all instance, or only active ones. 421 * @param instances_length number of instances to compare to in @e instances. 422 * @param instances a list of instances that will be compared with the results 423 * found. 424 * @return 0 on success, 1 otherwise. 425 */ 426 static int 427 test_lookup_instances (bool active_only, 428 unsigned int instances_length, 429 struct InstanceWithAccounts instances[]) 430 { 431 unsigned int results_matching[GNUNET_NZL (instances_length)]; 432 struct TestLookupInstances_Closure cmp = { 433 .instances_to_cmp_length = instances_length, 434 .instances_to_cmp = instances, 435 .results_matching = results_matching, 436 .results_length = 0 437 }; 438 memset (results_matching, 0, sizeof (unsigned int) * instances_length); 439 if (0 > TALER_MERCHANTDB_lookup_instances (pg, 440 active_only, 441 &lookup_instances_cb, 442 &cmp)) 443 { 444 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 445 "Lookup instances failed\n"); 446 return 1; 447 } 448 if (instances_length != cmp.results_length) 449 { 450 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 451 "Lookup instances failed: incorrect number of results\n"); 452 return 1; 453 } 454 for (unsigned int i = 0; instances_length > i; ++i) 455 { 456 if (1 != cmp.results_matching[i]) 457 { 458 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 459 "Lookup instances failed: mismatched data\n"); 460 return 1; 461 } 462 } 463 return 0; 464 } 465 466 467 /** 468 * Tests removing the private key of the given instance from the database. 469 * 470 * @param instance the instance whose private key to delete. 471 * @param expected_result the result we expect the db to return. 472 * @return 0 on success, 1 otherwise. 473 */ 474 static int 475 test_delete_instance_private_key (const struct InstanceData *instance, 476 enum GNUNET_DB_QueryStatus expected_result) 477 { 478 TEST_COND_RET_ON_FAIL (expected_result == 479 TALER_MERCHANTDB_delete_instance_private_key ( 480 pg, 481 instance->instance.id), 482 "Delete instance private key failed\n"); 483 return 0; 484 } 485 486 487 /** 488 * Tests purging all data for an instance from the database. 489 * 490 * @param instance the instance to purge. 491 * @param expected_result the result we expect the db to return. 492 * @return 0 on success, 1 otherwise. 493 */ 494 static int 495 test_purge_instance (const struct InstanceData *instance, 496 enum GNUNET_DB_QueryStatus expected_result) 497 { 498 TEST_COND_RET_ON_FAIL (expected_result == 499 TALER_MERCHANTDB_purge_instance (pg, 500 instance->instance.id), 501 "Purge instance failed\n"); 502 return 0; 503 } 504 505 506 /** 507 * Tests inserting an account for a merchant instance. 508 * 509 * @param instance the instance to associate the account with. 510 * @param account the account to insert. 511 * @param expected_result the result expected from the db. 512 * @return 0 on success, 1 otherwise. 513 */ 514 static int 515 test_insert_account (const struct InstanceData *instance, 516 const struct TALER_MERCHANTDB_AccountDetails *account, 517 enum GNUNET_DB_QueryStatus expected_result) 518 { 519 TEST_COND_RET_ON_FAIL (expected_result == 520 TALER_MERCHANTDB_insert_account (pg, 521 account), 522 "Insert account failed\n"); 523 return 0; 524 } 525 526 527 /** 528 * Tests deactivating an account. 529 * 530 * @param account the account to deactivate. 531 * @param expected_result the result expected from the DB. 532 * @return 0 on success, 1 otherwise. 533 */ 534 static int 535 test_inactivate_account (const struct InstanceData *instance, 536 const struct TALER_MERCHANTDB_AccountDetails *account, 537 enum GNUNET_DB_QueryStatus expected_result) 538 { 539 TEST_COND_RET_ON_FAIL (expected_result == 540 TALER_MERCHANTDB_inactivate_account (pg, 541 instance->instance.id, 542 &account->h_wire), 543 "Deactivate account failed\n"); 544 return 0; 545 } 546 547 548 /** 549 * Closure for instance tests 550 */ 551 struct TestInstances_Closure 552 { 553 /** 554 * The list of instances that we use for testing instances. 555 */ 556 struct InstanceData instances[2]; 557 558 /** 559 * The list of accounts to use with the instances. 560 */ 561 struct TALER_MERCHANTDB_AccountDetails accounts[2]; 562 563 }; 564 565 566 /** 567 * Sets up the data structures used in the instance tests 568 * 569 * @cls the closure to initialize with test data. 570 */ 571 static void 572 pre_test_instances (struct TestInstances_Closure *cls) 573 { 574 /* Instance */ 575 make_instance ("test_instances_inst0", 576 &cls->instances[0]); 577 make_instance ("test_instances_inst1", 578 &cls->instances[1]); 579 580 /* Accounts */ 581 make_account (&cls->accounts[0]); 582 cls->accounts[0].instance_id 583 = cls->instances[0].instance.id; 584 make_account (&cls->accounts[1]); 585 cls->accounts[1].instance_id 586 = cls->instances[1].instance.id; 587 } 588 589 590 /** 591 * Handles all teardown after testing 592 * 593 * @cls the closure to free data from 594 */ 595 static void 596 post_test_instances (struct TestInstances_Closure *cls) 597 { 598 free_instance_data (&cls->instances[0]); 599 free_instance_data (&cls->instances[1]); 600 } 601 602 603 /** 604 * Function that tests instances. 605 * 606 * @param cls closure with config 607 * @return 0 on success, 1 if failure. 608 */ 609 static int 610 run_test_instances (struct TestInstances_Closure *cls) 611 { 612 struct InstanceWithAccounts instances[2] = { 613 { 614 .accounts_length = 0, 615 .accounts = cls->accounts, 616 .instance = &cls->instances[0].instance 617 }, 618 { 619 .accounts_length = 0, 620 .accounts = cls->accounts, 621 .instance = &cls->instances[1].instance 622 } 623 }; 624 uint64_t account_serial; 625 626 /* Test inserting an instance */ 627 TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0], 628 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 629 /* Test double insertion fails */ 630 TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0], 631 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 632 /* Test lookup instances- is our new instance there? */ 633 TEST_RET_ON_FAIL (test_lookup_instances (false, 634 1, 635 instances)); 636 /* Test update instance */ 637 cls->instances[0].instance.name = "Test - updated"; 638 json_array_append_new (cls->instances[0].instance.address, 639 json_pack ("{s:s, s:I}", 640 "this", "is", 641 "more data", 47)); 642 json_array_append_new (cls->instances[0].instance.jurisdiction, 643 json_pack ("{s:s}", 644 "vegetables", "bad")); 645 cls->instances[0].instance.use_stefan = false; 646 cls->instances[0].instance.default_wire_transfer_delay = 647 GNUNET_TIME_UNIT_HOURS; 648 cls->instances[0].instance.default_pay_delay = GNUNET_TIME_UNIT_MINUTES; 649 650 TEST_RET_ON_FAIL (test_update_instance (&cls->instances[0], 651 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 652 TEST_RET_ON_FAIL (test_lookup_instances (false, 653 1, 654 instances)); 655 TEST_RET_ON_FAIL (test_update_instance (&cls->instances[1], 656 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 657 /* Test account creation */ 658 TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0], 659 &cls->accounts[0], 660 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 661 /* Test double account insertion fails */ 662 TEST_RET_ON_FAIL (test_insert_account (&cls->instances[1], 663 &cls->accounts[1], 664 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 665 TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0], 666 &cls->accounts[0], 667 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 668 instances[0].accounts_length = 1; 669 TEST_RET_ON_FAIL (test_lookup_instances (false, 670 1, 671 instances)); 672 /* Test deactivate account */ 673 TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[0], 674 &cls->accounts[0], 675 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 676 TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[1], 677 &cls->accounts[1], 678 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 679 cls->accounts[0].active = false; 680 TEST_RET_ON_FAIL (test_lookup_instances (false, 681 1, 682 instances)); 683 /* Test lookup account */ 684 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 685 TALER_MERCHANTDB_lookup_account (pg, 686 cls->instances[0].instance.id, 687 cls->accounts[0].payto_uri, 688 &account_serial)) 689 { 690 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 691 "Lookup account failed\n"); 692 return 1; 693 } 694 if (1 != account_serial) 695 { 696 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 697 "Lookup account failed: incorrect serial number found\n"); 698 return 1; 699 } 700 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 701 TALER_MERCHANTDB_lookup_account (pg, 702 cls->instances[0].instance.id, 703 (struct TALER_FullPayto) { 704 (char *) "payto://other-uri" 705 }, 706 &account_serial)) 707 { 708 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 709 "Lookup account failed: account found where there is none\n"); 710 return 1; 711 } 712 /* Test instance private key deletion */ 713 TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[0], 714 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 715 TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[1], 716 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 717 TEST_RET_ON_FAIL (test_lookup_instances (true, 718 0, 719 NULL)); 720 TEST_RET_ON_FAIL (test_lookup_instances (false, 721 1, 722 instances)); 723 TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[1], 724 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 725 TEST_RET_ON_FAIL (test_lookup_instances (false, 726 2, 727 instances)); 728 TEST_RET_ON_FAIL (test_lookup_instances (true, 729 1, 730 &instances[1])); 731 TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1], 732 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 733 TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1], 734 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 735 /* Test that the instance is gone. */ 736 TEST_RET_ON_FAIL (test_lookup_instances (false, 737 1, 738 instances)); 739 return 0; 740 } 741 742 743 /** 744 * Function that tests instances. 745 * 746 * @return 0 on success, 1 otherwise. 747 */ 748 static int 749 test_instances (void) 750 { 751 struct TestInstances_Closure test_cls; 752 int test_result; 753 754 pre_test_instances (&test_cls); 755 test_result = run_test_instances (&test_cls); 756 post_test_instances (&test_cls); 757 return test_result; 758 } 759 760 761 /* *********** Products ********** */ 762 763 764 /** 765 * A container for data relevant to a product. 766 */ 767 struct ProductData 768 { 769 /** 770 * The identifier of the product. 771 */ 772 const char *id; 773 774 /** 775 * The details of the product. 776 */ 777 struct TALER_MERCHANTDB_ProductDetails product; 778 }; 779 780 781 /** 782 * Creates a product for testing with. 783 * 784 * @param id the id of the product. 785 * @param product the product data to fill. 786 */ 787 static void 788 make_product (const char *id, 789 struct ProductData *product) 790 { 791 static struct TALER_Amount htwenty40; 792 793 GNUNET_assert (GNUNET_OK == 794 TALER_string_to_amount ("EUR:120.40", 795 &htwenty40)); 796 797 memset (product, 798 0, 799 sizeof (*product)); 800 product->id = id; 801 product->product.product_name = "Test product"; 802 product->product.description = "This is a test product"; 803 product->product.description_i18n = json_array (); 804 GNUNET_assert (NULL != product->product.description_i18n); 805 product->product.unit = "boxes"; 806 product->product.minimum_age = 0; 807 product->product.price_array = &htwenty40; 808 product->product.price_array_length = 1; 809 product->product.taxes = json_array (); 810 GNUNET_assert (NULL != product->product.taxes); 811 product->product.total_stock = 55; 812 product->product.total_sold = 0; 813 product->product.total_lost = 0; 814 product->product.image = GNUNET_strdup (""); 815 GNUNET_assert (NULL != product->product.image); 816 product->product.address = json_array (); 817 GNUNET_assert (NULL != product->product.address); 818 product->product.next_restock = GNUNET_TIME_UNIT_ZERO_TS; 819 } 820 821 822 /** 823 * Frees memory associated with @e ProductData. 824 * 825 * @param product the container to free. 826 */ 827 static void 828 free_product_data (struct ProductData *product) 829 { 830 json_decref (product->product.description_i18n); 831 json_decref (product->product.taxes); 832 GNUNET_free (product->product.image); 833 json_decref (product->product.address); 834 } 835 836 837 /** 838 * Compare two products for equality. 839 * 840 * @param a the first product. 841 * @param b the second product. 842 * @return 0 on equality, 1 otherwise. 843 */ 844 static int 845 check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a, 846 const struct TALER_MERCHANTDB_ProductDetails *b) 847 { 848 if ((0 != strcmp (a->description, 849 b->description)) || 850 (1 != json_equal (a->description_i18n, 851 b->description_i18n)) || 852 (0 != strcmp (a->unit, 853 b->unit)) || 854 (a->price_array_length != b->price_array_length) || 855 (1 != json_equal (a->taxes, 856 b->taxes)) || 857 (a->total_stock != b->total_stock) || 858 (a->total_sold != b->total_sold) || 859 (a->total_lost != b->total_lost) || 860 (0 != strcmp (a->image, 861 b->image)) || 862 (1 != json_equal (a->address, 863 b->address)) || 864 (GNUNET_TIME_timestamp_cmp (a->next_restock, 865 !=, 866 b->next_restock))) 867 868 return 1; 869 for (size_t i = 0; i<a->price_array_length; i++) 870 if ( (GNUNET_OK != 871 TALER_amount_cmp_currency (&a->price_array[i], 872 &b->price_array[i])) || 873 (0 != TALER_amount_cmp (&a->price_array[i], 874 &b->price_array[i])) ) 875 return 1; 876 return 0; 877 } 878 879 880 /** 881 * Tests inserting product data into the database. 882 * 883 * @param instance the instance to insert the product for. 884 * @param product the product data to insert. 885 * @param num_cats length of the @a cats array 886 * @param cats array of categories for the product 887 * @param expected_result the result we expect the db to return. 888 * @param expect_conflict expected conflict status 889 * @param expect_no_instance expected instance missing status 890 * @param expected_no_cat expected category missing index 891 * @return 0 when successful, 1 otherwise. 892 */ 893 static int 894 test_insert_product (const struct InstanceData *instance, 895 const struct ProductData *product, 896 unsigned int num_cats, 897 const uint64_t *cats, 898 enum GNUNET_DB_QueryStatus expected_result, 899 bool expect_conflict, 900 bool expect_no_instance, 901 ssize_t expected_no_cat) 902 { 903 bool conflict; 904 bool no_instance; 905 ssize_t no_cat; 906 bool no_group; 907 bool no_pot; 908 909 TEST_COND_RET_ON_FAIL (expected_result == 910 TALER_MERCHANTDB_insert_product (pg, 911 instance->instance.id, 912 product->id, 913 &product->product, 914 num_cats, 915 cats, 916 &no_instance, 917 &conflict, 918 &no_cat, 919 &no_group, 920 &no_pot), 921 "Insert product failed\n"); 922 if (expected_result > 0) 923 { 924 TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance, 925 "No instance wrong"); 926 TEST_COND_RET_ON_FAIL (conflict == expect_conflict, 927 "Conflict wrong"); 928 TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat, 929 "Wrong category missing returned"); 930 } 931 return 0; 932 } 933 934 935 /** 936 * Tests updating product data in the database. 937 * 938 * @param instance the instance to update the product for. 939 * @param product the product data to update. 940 * @param expected_result the result we expect the db to return. 941 * @return 0 when successful, 1 otherwise. 942 */ 943 static int 944 test_update_product (const struct InstanceData *instance, 945 const struct ProductData *product, 946 unsigned int num_cats, 947 const uint64_t *cats, 948 enum GNUNET_DB_QueryStatus expected_result, 949 bool expect_no_instance, 950 bool expect_no_product, 951 bool expect_lost_reduced, 952 bool expect_sold_reduced, 953 bool expect_stocked_reduced, 954 ssize_t expected_no_cat) 955 956 { 957 bool no_instance; 958 ssize_t no_cat; 959 bool no_product; 960 bool lost_reduced; 961 bool sold_reduced; 962 bool stocked_reduced; 963 bool no_group; 964 bool no_pot; 965 966 TEST_COND_RET_ON_FAIL ( 967 expected_result == 968 TALER_MERCHANTDB_update_product (pg, 969 instance->instance.id, 970 product->id, 971 &product->product, 972 num_cats, 973 cats, 974 &no_instance, 975 &no_cat, 976 &no_product, 977 &lost_reduced, 978 &sold_reduced, 979 &stocked_reduced, 980 &no_group, 981 &no_pot), 982 "Update product failed\n"); 983 if (expected_result > 0) 984 { 985 TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance, 986 "No instance wrong"); 987 TEST_COND_RET_ON_FAIL (no_product == expect_no_product, 988 "No product wrong"); 989 TEST_COND_RET_ON_FAIL (lost_reduced == expect_lost_reduced, 990 "No product wrong"); 991 TEST_COND_RET_ON_FAIL (stocked_reduced == expect_stocked_reduced, 992 "Stocked reduced wrong"); 993 TEST_COND_RET_ON_FAIL (sold_reduced == expect_sold_reduced, 994 "Sold reduced wrong"); 995 TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat, 996 "Wrong category missing returned"); 997 } 998 return 0; 999 } 1000 1001 1002 /** 1003 * Tests looking up a product from the db. 1004 * 1005 * @param instance the instance to query from. 1006 * @param product the product to query and compare to. 1007 * @return 0 when successful, 1 otherwise. 1008 */ 1009 static int 1010 test_lookup_product (const struct InstanceData *instance, 1011 const struct ProductData *product) 1012 { 1013 struct TALER_MERCHANTDB_ProductDetails lookup_result; 1014 size_t num_categories = 0; 1015 uint64_t *categories = NULL; 1016 1017 if (0 > TALER_MERCHANTDB_lookup_product (pg, 1018 instance->instance.id, 1019 product->id, 1020 &lookup_result, 1021 &num_categories, 1022 &categories)) 1023 { 1024 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1025 "Lookup product failed\n"); 1026 TALER_MERCHANTDB_product_details_free (&lookup_result); 1027 return 1; 1028 } 1029 GNUNET_free (categories); 1030 { 1031 const struct TALER_MERCHANTDB_ProductDetails *to_cmp = &product->product; 1032 1033 if (0 != check_products_equal (&lookup_result, 1034 to_cmp)) 1035 { 1036 GNUNET_break (0); 1037 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1038 "Lookup product failed: incorrect product returned\n"); 1039 TALER_MERCHANTDB_product_details_free (&lookup_result); 1040 return 1; 1041 } 1042 } 1043 TALER_MERCHANTDB_product_details_free (&lookup_result); 1044 return 0; 1045 } 1046 1047 1048 /** 1049 * Closure for testing product lookup 1050 */ 1051 struct TestLookupProducts_Closure 1052 { 1053 /** 1054 * Number of product ids to compare to 1055 */ 1056 unsigned int products_to_cmp_length; 1057 1058 /** 1059 * Pointer to array of product ids 1060 */ 1061 const struct ProductData *products_to_cmp; 1062 1063 /** 1064 * Pointer to array of number of matches for each product 1065 */ 1066 unsigned int *results_matching; 1067 1068 /** 1069 * Total number of results returned 1070 */ 1071 unsigned int results_length; 1072 }; 1073 1074 1075 /** 1076 * Function called after calling @e test_lookup_products 1077 * 1078 * @param cls a pointer to the lookup closure. 1079 * @param product_serial DB row ID 1080 * @param product_id the identifier of the product found. 1081 */ 1082 static void 1083 lookup_products_cb (void *cls, 1084 uint64_t product_serial, 1085 const char *product_id) 1086 { 1087 struct TestLookupProducts_Closure *cmp = cls; 1088 1089 GNUNET_assert (product_serial > 0); 1090 if (NULL == cmp) 1091 return; 1092 cmp->results_length += 1; 1093 for (unsigned int i = 0; cmp->products_to_cmp_length > i; ++i) 1094 { 1095 if (0 == strcmp (cmp->products_to_cmp[i].id, 1096 product_id)) 1097 cmp->results_matching[i] += 1; 1098 } 1099 } 1100 1101 1102 /** 1103 * Tests looking up all products for an instance. 1104 * 1105 * @param instance the instance to query from. 1106 * @param products_length the number of products we are expecting. 1107 * @param products the list of products that we expect to be found. 1108 * @return 0 when successful, 1 otherwise. 1109 */ 1110 static int 1111 test_lookup_products (const struct InstanceData *instance, 1112 unsigned int products_length, 1113 const struct ProductData *products) 1114 { 1115 unsigned int results_matching[GNUNET_NZL (products_length)]; 1116 struct TestLookupProducts_Closure cls = { 1117 .products_to_cmp_length = products_length, 1118 .products_to_cmp = products, 1119 .results_matching = results_matching, 1120 .results_length = 0 1121 }; 1122 memset (results_matching, 0, sizeof (unsigned int) * products_length); 1123 if (0 > TALER_MERCHANTDB_lookup_products (pg, 1124 instance->instance.id, 1125 0, 1126 20, 1127 NULL, 1128 NULL, 1129 NULL, 1130 0, 1131 &lookup_products_cb, 1132 &cls)) 1133 { 1134 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1135 "Lookup products failed\n"); 1136 return 1; 1137 } 1138 if (products_length != cls.results_length) 1139 { 1140 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1141 "Lookup products failed: incorrect number of results\n"); 1142 return 1; 1143 } 1144 for (unsigned int i = 0; products_length > i; ++i) 1145 { 1146 if (1 != cls.results_matching[i]) 1147 { 1148 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1149 "Lookup products failed: mismatched data\n"); 1150 return 1; 1151 } 1152 } 1153 return 0; 1154 } 1155 1156 1157 /** 1158 * Tests deleting a product. 1159 * 1160 * @param instance the instance to delete the product from. 1161 * @param product the product that should be deleted. 1162 * @param expected_result the result that we expect the DB to return. 1163 * @return 0 when successful, 1 otherwise. 1164 */ 1165 static int 1166 test_delete_product (const struct InstanceData *instance, 1167 const struct ProductData *product, 1168 enum GNUNET_DB_QueryStatus expected_result) 1169 { 1170 TEST_COND_RET_ON_FAIL (expected_result == 1171 TALER_MERCHANTDB_delete_product (pg, 1172 instance->instance.id, 1173 product->id), 1174 "Delete product failed\n"); 1175 return 0; 1176 } 1177 1178 1179 /** 1180 * Closure for product tests. 1181 */ 1182 struct TestProducts_Closure 1183 { 1184 /** 1185 * The instance to use for this test. 1186 */ 1187 struct InstanceData instance; 1188 1189 /** 1190 * The array of products. 1191 */ 1192 struct ProductData products[2]; 1193 }; 1194 1195 1196 /** 1197 * Sets up the data structures used in the product tests. 1198 * 1199 * @param cls the closure to fill with test data. 1200 */ 1201 static void 1202 pre_test_products (struct TestProducts_Closure *cls) 1203 { 1204 static struct TALER_Amount four95; 1205 1206 /* Instance */ 1207 make_instance ("test_inst_products", 1208 &cls->instance); 1209 1210 /* Products */ 1211 make_product ("test_products_pd_0", 1212 &cls->products[0]); 1213 1214 make_product ("test_products_pd_1", 1215 &cls->products[1]); 1216 cls->products[1].product.description = "This is a another test product"; 1217 cls->products[1].product.unit = "cans"; 1218 cls->products[1].product.minimum_age = 0; 1219 1220 GNUNET_assert (GNUNET_OK == 1221 TALER_string_to_amount ("EUR:4.95", 1222 &four95)); 1223 cls->products[1].product.price_array = &four95; 1224 cls->products[1].product.price_array_length = 1; 1225 cls->products[1].product.total_stock = 5001; 1226 } 1227 1228 1229 /** 1230 * Handles all teardown after testing. 1231 * 1232 * @param cls the closure containing memory to be freed. 1233 */ 1234 static void 1235 post_test_products (struct TestProducts_Closure *cls) 1236 { 1237 free_instance_data (&cls->instance); 1238 free_product_data (&cls->products[0]); 1239 free_product_data (&cls->products[1]); 1240 } 1241 1242 1243 /** 1244 * Runs the tests for products. 1245 * 1246 * @param cls the container of the test data. 1247 * @return 0 on success, 1 otherwise. 1248 */ 1249 static int 1250 run_test_products (struct TestProducts_Closure *cls) 1251 { 1252 struct GNUNET_Uuid uuid; 1253 struct GNUNET_TIME_Timestamp refund_deadline = 1254 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS); 1255 1256 /* Test that insert without an instance fails */ 1257 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 1258 &cls->products[0], 1259 0, 1260 NULL, 1261 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 1262 false, 1263 true, 1264 -1)); 1265 /* Insert the instance */ 1266 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 1267 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1268 /* Test inserting a product */ 1269 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 1270 &cls->products[0], 1271 0, 1272 NULL, 1273 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 1274 false, 1275 false, 1276 -1)); 1277 /* Test that double insert succeeds */ 1278 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 1279 &cls->products[0], 1280 0, 1281 NULL, 1282 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 1283 false, 1284 false, 1285 -1)); 1286 /* Test that conflicting insert fails */ 1287 { 1288 uint64_t cat = 42; 1289 1290 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 1291 &cls->products[0], 1292 1, 1293 &cat, 1294 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 1295 true, 1296 false, 1297 -1)); 1298 } 1299 /* Test lookup of individual products */ 1300 TEST_RET_ON_FAIL (test_lookup_product (&cls->instance, 1301 &cls->products[0])); 1302 /* Make sure it fails correctly for products that don't exist */ 1303 { 1304 size_t num_categories = 0; 1305 uint64_t *categories = NULL; 1306 1307 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 1308 TALER_MERCHANTDB_lookup_product (pg, 1309 cls->instance.instance.id, 1310 "nonexistent_product", 1311 NULL, 1312 &num_categories, 1313 &categories)) 1314 { 1315 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1316 "Lookup product failed\n"); 1317 return 1; 1318 } 1319 GNUNET_free (categories); 1320 } 1321 /* Test product update */ 1322 cls->products[0].product.description = 1323 "This is a test product that has been updated!"; 1324 GNUNET_assert (0 == 1325 json_array_append_new ( 1326 cls->products[0].product.description_i18n, 1327 json_string ( 1328 "description in another language"))); 1329 cls->products[0].product.unit = "barrels"; 1330 { 1331 static struct TALER_Amount seven68; 1332 1333 GNUNET_assert (GNUNET_OK == 1334 TALER_string_to_amount ("EUR:7.68", 1335 &seven68)); 1336 cls->products[0].product.price_array = &seven68; 1337 cls->products[0].product.price_array_length = 1; 1338 } 1339 GNUNET_assert (0 == 1340 json_array_append_new (cls->products[0].product.taxes, 1341 json_string ("2% sales tax"))); 1342 cls->products[0].product.total_stock = 100; 1343 cls->products[0].product.total_sold = 0; /* will be ignored! */ 1344 cls->products[0].product.total_lost = 7; 1345 GNUNET_free (cls->products[0].product.image); 1346 cls->products[0].product.image = GNUNET_strdup ("image"); 1347 GNUNET_assert (0 == 1348 json_array_append_new (cls->products[0].product.address, 1349 json_string ("444 Some Street"))); 1350 cls->products[0].product.next_restock = GNUNET_TIME_timestamp_get (); 1351 TEST_RET_ON_FAIL (test_update_product ( 1352 &cls->instance, 1353 &cls->products[0], 1354 0, 1355 NULL, 1356 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 1357 false, 1358 false, 1359 false, 1360 false, 1361 false, 1362 -1)); 1363 1364 { 1365 struct ProductData stock_dec = cls->products[0]; 1366 1367 stock_dec.product.total_stock = 40; 1368 TEST_RET_ON_FAIL (test_update_product ( 1369 &cls->instance, 1370 &stock_dec, 1371 0, 1372 NULL, 1373 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 1374 false, 1375 false, 1376 false, 1377 false, 1378 true, 1379 -1)); 1380 } 1381 { 1382 struct ProductData lost_dec = cls->products[0]; 1383 1384 lost_dec.product.total_lost = 1; 1385 TEST_RET_ON_FAIL (test_update_product ( 1386 &cls->instance, 1387 &lost_dec, 1388 0, 1389 NULL, 1390 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 1391 false, 1392 false, 1393 true, 1394 false, 1395 false, 1396 -1)); 1397 } 1398 TEST_RET_ON_FAIL (test_lookup_product (&cls->instance, 1399 &cls->products[0])); 1400 TEST_RET_ON_FAIL (test_update_product ( 1401 &cls->instance, 1402 &cls->products[1], 1403 0, 1404 NULL, 1405 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 1406 false, 1407 true, 1408 false, 1409 false, 1410 false, 1411 -1)); 1412 /* Test collective product lookup */ 1413 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 1414 &cls->products[1], 1415 0, 1416 NULL, 1417 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 1418 false, 1419 false, 1420 -1)); 1421 TEST_RET_ON_FAIL (test_lookup_products (&cls->instance, 1422 2, 1423 cls->products)); 1424 /* Test locking */ 1425 uuid.value[0] = 0x1287346a; 1426 if (0 != TALER_MERCHANTDB_lock_product (pg, 1427 cls->instance.instance.id, 1428 cls->products[0].id, 1429 &uuid, 1430 256, 1431 0, 1432 refund_deadline)) 1433 { 1434 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1435 "Lock product failed\n"); 1436 return 1; 1437 } 1438 if (1 != TALER_MERCHANTDB_lock_product (pg, 1439 cls->instance.instance.id, 1440 cls->products[0].id, 1441 &uuid, 1442 1, 1443 0, 1444 refund_deadline)) 1445 { 1446 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1447 "Lock product failed\n"); 1448 return 1; 1449 } 1450 /* Test product deletion */ 1451 TEST_RET_ON_FAIL (test_delete_product (&cls->instance, 1452 &cls->products[1], 1453 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1454 /* Test double deletion fails */ 1455 TEST_RET_ON_FAIL (test_delete_product (&cls->instance, 1456 &cls->products[1], 1457 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 1458 TEST_RET_ON_FAIL (test_lookup_products (&cls->instance, 1459 1, 1460 cls->products)); 1461 /* Test unlocking */ 1462 if (1 != TALER_MERCHANTDB_unlock_inventory (pg, 1463 &uuid)) 1464 { 1465 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1466 "Unlock inventory failed\n"); 1467 return 1; 1468 } 1469 if (0 != TALER_MERCHANTDB_unlock_inventory (pg, 1470 &uuid)) 1471 { 1472 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1473 "Unlock inventory failed\n"); 1474 return 1; 1475 } 1476 TEST_RET_ON_FAIL (test_delete_product (&cls->instance, 1477 &cls->products[0], 1478 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 1479 TEST_RET_ON_FAIL (test_lookup_products (&cls->instance, 1480 0, 1481 NULL)); 1482 return 0; 1483 } 1484 1485 1486 /** 1487 * Takes care of product testing. 1488 * 1489 * @return 0 on success, 1 otherwise. 1490 */ 1491 static int 1492 test_products (void) 1493 { 1494 struct TestProducts_Closure test_cls; 1495 int test_result; 1496 1497 pre_test_products (&test_cls); 1498 test_result = run_test_products (&test_cls); 1499 post_test_products (&test_cls); 1500 return test_result; 1501 } 1502 1503 1504 /* ********** Orders ********** */ 1505 1506 1507 /** 1508 * Container for order data 1509 */ 1510 struct OrderData 1511 { 1512 /** 1513 * The id of the order 1514 */ 1515 const char *id; 1516 1517 /** 1518 * The pay deadline for the order 1519 */ 1520 struct GNUNET_TIME_Timestamp pay_deadline; 1521 1522 /** 1523 * The contract of the order 1524 */ 1525 json_t *contract; 1526 1527 /** 1528 * The claim token for the order. 1529 */ 1530 struct TALER_ClaimTokenP claim_token; 1531 }; 1532 1533 1534 /** 1535 * Builds an order for testing. 1536 * 1537 * @param order_id the identifier to use for the order. 1538 * @param order the container to fill with data. 1539 */ 1540 static void 1541 make_order (const char *order_id, 1542 struct OrderData *order) 1543 { 1544 struct GNUNET_TIME_Timestamp refund_deadline; 1545 1546 order->id = order_id; 1547 order->contract = json_object (); 1548 GNUNET_assert (NULL != order->contract); 1549 order->pay_deadline = GNUNET_TIME_relative_to_timestamp ( 1550 GNUNET_TIME_UNIT_DAYS); 1551 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, 1552 &order->claim_token, 1553 sizeof (order->claim_token)); 1554 refund_deadline = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS); 1555 GNUNET_assert (0 == 1556 json_object_set_new (order->contract, 1557 "fulfillment_url", 1558 json_string ("a"))); 1559 GNUNET_assert (0 == 1560 json_object_set_new (order->contract, 1561 "summary", 1562 json_string ("Test order"))); 1563 GNUNET_assert (0 == 1564 json_object_set_new (order->contract, 1565 "order_id", 1566 json_string (order_id))); 1567 GNUNET_assert (0 == 1568 json_object_set_new ( 1569 order->contract, 1570 "pay_deadline", 1571 GNUNET_JSON_from_timestamp (order->pay_deadline)) 1572 ); 1573 GNUNET_assert (0 == 1574 json_object_set_new (order->contract, 1575 "refund_deadline", 1576 GNUNET_JSON_from_timestamp ( 1577 refund_deadline))); 1578 } 1579 1580 1581 /** 1582 * Frees memory associated with an order. 1583 * 1584 * @param the order to free. 1585 */ 1586 static void 1587 free_order_data (struct OrderData *order) 1588 { 1589 json_decref (order->contract); 1590 } 1591 1592 1593 /** 1594 * Tests inserting an order into the database. 1595 * 1596 * @param instance the instance to insert the order for. 1597 * @param order the order to insert. 1598 * @param expected_result the value we expect the db to return. 1599 * @return 0 on success, 1 otherwise. 1600 */ 1601 static int 1602 test_insert_order (const struct InstanceData *instance, 1603 const struct OrderData *order, 1604 enum GNUNET_DB_QueryStatus expected_result) 1605 { 1606 struct TALER_MerchantPostDataHashP h_post; 1607 1608 memset (&h_post, 1609 42, 1610 sizeof (h_post)); 1611 TEST_COND_RET_ON_FAIL (expected_result == 1612 TALER_MERCHANTDB_insert_order (pg, 1613 instance->instance.id, 1614 order->id, 1615 NULL, /* session_id */ 1616 &h_post, 1617 order->pay_deadline, 1618 &order->claim_token, 1619 order->contract, 1620 NULL, 1621 0), 1622 "Insert order failed\n"); 1623 return 0; 1624 } 1625 1626 1627 /** 1628 * Tests looking up an order in the database. 1629 * 1630 * @param instance the instance to lookup from. 1631 * @param order the order that should be looked up. 1632 * @return 0 on success, 1 otherwise. 1633 */ 1634 static int 1635 test_lookup_order (const struct InstanceData *instance, 1636 const struct OrderData *order) 1637 { 1638 struct TALER_ClaimTokenP ct; 1639 json_t *lookup_terms = NULL; 1640 struct TALER_MerchantPostDataHashP oh; 1641 struct TALER_MerchantPostDataHashP wh; 1642 1643 memset (&wh, 1644 42, 1645 sizeof (wh)); 1646 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1647 TALER_MERCHANTDB_lookup_order (pg, 1648 instance->instance.id, 1649 order->id, 1650 &ct, 1651 &oh, 1652 &lookup_terms)) 1653 { 1654 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1655 "Lookup order failed\n"); 1656 if (NULL != lookup_terms) 1657 json_decref (lookup_terms); 1658 return 1; 1659 } 1660 if ( (1 != json_equal (order->contract, 1661 lookup_terms)) || 1662 (0 != GNUNET_memcmp (&order->claim_token, 1663 &ct)) || 1664 (0 != GNUNET_memcmp (&oh, 1665 &wh)) ) 1666 { 1667 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1668 "Lookup order failed: incorrect order returned\n"); 1669 if (NULL != lookup_terms) 1670 json_decref (lookup_terms); 1671 return 1; 1672 } 1673 json_decref (lookup_terms); 1674 return 0; 1675 } 1676 1677 1678 /** 1679 * Closure for testing order lookup 1680 */ 1681 struct TestLookupOrders_Closure 1682 { 1683 /** 1684 * Number of orders to compare to 1685 */ 1686 unsigned int orders_to_cmp_length; 1687 1688 /** 1689 * Pointer to (ordered) array of order ids 1690 */ 1691 const struct OrderData *orders_to_cmp; 1692 1693 /** 1694 * Pointer to array of bools indicating matches in the correct index 1695 */ 1696 bool *results_match; 1697 1698 /** 1699 * Total number of results returned 1700 */ 1701 unsigned int results_length; 1702 }; 1703 1704 1705 /** 1706 * Called after @e test_lookup_orders. 1707 * 1708 * @param cls the lookup closure. 1709 * @param order_id the identifier of the order found. 1710 * @param order_serial the row number of the order found. 1711 * @param timestamp when the order was added to the database. 1712 */ 1713 static void 1714 lookup_orders_cb (void *cls, 1715 const char *order_id, 1716 uint64_t order_serial, 1717 struct GNUNET_TIME_Timestamp timestamp) 1718 { 1719 struct TestLookupOrders_Closure *cmp = cls; 1720 unsigned int i; 1721 1722 if (NULL == cmp) 1723 return; 1724 i = cmp->results_length; 1725 cmp->results_length += 1; 1726 if (cmp->orders_to_cmp_length > i) 1727 { 1728 /* Compare the orders */ 1729 if (0 == strcmp (cmp->orders_to_cmp[i].id, 1730 order_id)) 1731 cmp->results_match[i] = true; 1732 else 1733 cmp->results_match[i] = false; 1734 } 1735 } 1736 1737 1738 /** 1739 * Tests looking up orders for an instance. 1740 * 1741 * @param instance the instance. 1742 * @param filter the filters applied on the lookup. 1743 * @param orders_length the number of orders we expect to find. 1744 * @param orders the orders we expect to find. 1745 * @return 0 on success, 1 otherwise. 1746 */ 1747 static int 1748 test_lookup_orders (const struct InstanceData *instance, 1749 const struct TALER_MERCHANTDB_OrderFilter *filter, 1750 unsigned int orders_length, 1751 const struct OrderData *orders) 1752 { 1753 bool results_match[GNUNET_NZL (orders_length)]; 1754 struct TestLookupOrders_Closure cls = { 1755 .orders_to_cmp_length = orders_length, 1756 .orders_to_cmp = orders, 1757 .results_match = results_match, 1758 .results_length = 0 1759 }; 1760 memset (results_match, 1761 0, 1762 sizeof (bool) * orders_length); 1763 if (0 > TALER_MERCHANTDB_lookup_orders (pg, 1764 instance->instance.id, 1765 filter, 1766 &lookup_orders_cb, 1767 &cls)) 1768 { 1769 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1770 "Lookup orders failed\n"); 1771 return 1; 1772 } 1773 if (orders_length != cls.results_length) 1774 { 1775 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1776 "Lookup orders failed: incorrect number of results (%d)\n", 1777 cls.results_length); 1778 return 1; 1779 } 1780 for (unsigned int i = 0; orders_length > i; ++i) 1781 { 1782 if (false == cls.results_match[i]) 1783 { 1784 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1785 "Lookup orders failed: mismatched data (index %d)\n", 1786 i); 1787 return 1; 1788 } 1789 } 1790 return 0; 1791 } 1792 1793 1794 /** 1795 * Container for data used for looking up the row number of an order. 1796 */ 1797 struct LookupOrderSerial_Closure 1798 { 1799 /** 1800 * The order that is being looked up. 1801 */ 1802 const struct OrderData *order; 1803 1804 /** 1805 * The serial of the order that was found. 1806 */ 1807 uint64_t serial; 1808 }; 1809 1810 1811 /** 1812 * Called after @e test_lookup_orders. 1813 * 1814 * @param cls the lookup closure. 1815 * @param order_id the identifier of the order found. 1816 * @param order_serial the row number of the order found. 1817 * @param timestamp when the order was added to the database. 1818 */ 1819 static void 1820 get_order_serial_cb (void *cls, 1821 const char *order_id, 1822 uint64_t order_serial, 1823 struct GNUNET_TIME_Timestamp timestamp) 1824 { 1825 struct LookupOrderSerial_Closure *lookup_cls = cls; 1826 if (NULL == lookup_cls) 1827 return; 1828 if (0 == strcmp (lookup_cls->order->id, 1829 order_id)) 1830 lookup_cls->serial = order_serial; 1831 } 1832 1833 1834 /** 1835 * Convenience function for getting the row number of an order. 1836 * 1837 * @param instance the instance to look up from. 1838 * @param order the order to lookup the serial for. 1839 * @return the row number of the order. 1840 */ 1841 static uint64_t 1842 get_order_serial (const struct InstanceData *instance, 1843 const struct OrderData *order) 1844 { 1845 struct LookupOrderSerial_Closure lookup_cls = { 1846 .order = order, 1847 .serial = 0 1848 }; 1849 struct TALER_MERCHANTDB_OrderFilter filter = { 1850 .paid = TALER_EXCHANGE_YNA_ALL, 1851 .refunded = TALER_EXCHANGE_YNA_ALL, 1852 .wired = TALER_EXCHANGE_YNA_ALL, 1853 .date = GNUNET_TIME_UNIT_ZERO_TS, 1854 .start_row = 0, 1855 .delta = 256 1856 }; 1857 1858 GNUNET_assert (0 < TALER_MERCHANTDB_lookup_orders (pg, 1859 instance->instance.id, 1860 &filter, 1861 &get_order_serial_cb, 1862 &lookup_cls)); 1863 GNUNET_assert (0 != lookup_cls.serial); 1864 1865 return lookup_cls.serial; 1866 } 1867 1868 1869 /** 1870 * Tests deleting an order from the database. 1871 * 1872 * @param instance the instance to delete the order from. 1873 * @param order the order to delete. 1874 * @param expected_result the result we expect to receive. 1875 * @return 0 on success, 1 otherwise. 1876 */ 1877 static int 1878 test_delete_order (const struct InstanceData *instance, 1879 const struct OrderData *order, 1880 enum GNUNET_DB_QueryStatus expected_result) 1881 { 1882 TEST_COND_RET_ON_FAIL (expected_result == 1883 TALER_MERCHANTDB_delete_order (pg, 1884 instance->instance.id, 1885 order->id, 1886 false), 1887 "Delete order failed\n"); 1888 return 0; 1889 } 1890 1891 1892 /** 1893 * Test inserting contract terms for an order. 1894 * 1895 * @param instance the instance. 1896 * @param order the order containing the contract terms. 1897 * @param expected_result the result we expect to receive. 1898 * @return 0 on success, 1 otherwise. 1899 */ 1900 static int 1901 test_insert_contract_terms (const struct InstanceData *instance, 1902 const struct OrderData *order, 1903 enum GNUNET_DB_QueryStatus expected_result) 1904 { 1905 uint64_t os; 1906 1907 TEST_COND_RET_ON_FAIL (expected_result == 1908 TALER_MERCHANTDB_insert_contract_terms (pg, 1909 instance->instance.id, 1910 order->id, 1911 order->contract, 1912 &os), 1913 "Insert contract terms failed\n"); 1914 return 0; 1915 } 1916 1917 1918 /** 1919 * Test updating contract terms for an order. 1920 * 1921 * @param instance the instance. 1922 * @param order the order containing the contract terms. 1923 * @param expected_result the result we expect to receive. 1924 * @return 0 on success, 1 otherwise. 1925 */ 1926 static int 1927 test_update_contract_terms (const struct InstanceData *instance, 1928 const struct OrderData *order, 1929 enum GNUNET_DB_QueryStatus expected_result) 1930 { 1931 TEST_COND_RET_ON_FAIL (expected_result == 1932 TALER_MERCHANTDB_update_contract_terms (pg, 1933 instance->instance.id, 1934 order->id, 1935 order->contract), 1936 "Update contract terms failed\n"); 1937 return 0; 1938 } 1939 1940 1941 /** 1942 * Tests lookup of contract terms 1943 * 1944 * @param instance the instance to lookup from. 1945 * @param order the order to lookup for. 1946 * @return 0 on success, 1 otherwise. 1947 */ 1948 static int 1949 test_lookup_contract_terms (const struct InstanceData *instance, 1950 const struct OrderData *order) 1951 { 1952 json_t *contract = NULL; 1953 uint64_t order_serial; 1954 1955 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 1956 TALER_MERCHANTDB_lookup_contract_terms (pg, 1957 instance->instance.id, 1958 order->id, 1959 &contract, 1960 &order_serial, 1961 NULL)) 1962 { 1963 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1964 "Lookup contract terms failed\n"); 1965 GNUNET_assert (NULL == contract); 1966 return 1; 1967 } 1968 if (1 != json_equal (order->contract, 1969 contract)) 1970 { 1971 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1972 "Lookup contract terms failed: mismatched data\n"); 1973 json_decref (contract); 1974 return 1; 1975 } 1976 json_decref (contract); 1977 return 0; 1978 } 1979 1980 1981 /** 1982 * Tests deleting contract terms for an order. 1983 * 1984 * @param instance the instance to delete from. 1985 * @param order the order whose contract terms we should delete. 1986 * @param legal_expiration how long we must wait after creating an order to delete it 1987 * @param expected_result the result we expect to receive. 1988 * @return 0 on success, 1 otherwise. 1989 */ 1990 static int 1991 test_delete_contract_terms (const struct InstanceData *instance, 1992 const struct OrderData *order, 1993 struct GNUNET_TIME_Relative legal_expiration, 1994 enum GNUNET_DB_QueryStatus expected_result) 1995 { 1996 TEST_COND_RET_ON_FAIL (expected_result == 1997 TALER_MERCHANTDB_delete_contract_terms (pg, 1998 instance->instance.id, 1999 order->id, 2000 legal_expiration), 2001 "Delete contract terms failed\n"); 2002 return 0; 2003 } 2004 2005 2006 /** 2007 * Test marking a contract as paid in the database. 2008 * 2009 * @param instance the instance to use. 2010 * @param order the order whose contract to use. 2011 * @param expected_result the result we expect to receive. 2012 * @return 0 on success, 1 otherwise. 2013 */ 2014 static int 2015 test_mark_contract_paid (const struct InstanceData *instance, 2016 const struct OrderData *order, 2017 enum GNUNET_DB_QueryStatus expected_result) 2018 { 2019 struct TALER_PrivateContractHashP h_contract_terms; 2020 2021 GNUNET_assert (GNUNET_OK == 2022 TALER_JSON_contract_hash (order->contract, 2023 &h_contract_terms)); 2024 TEST_COND_RET_ON_FAIL (expected_result == 2025 TALER_MERCHANTDB_mark_contract_paid (pg, 2026 instance->instance.id, 2027 &h_contract_terms, 2028 "test_orders_session", 2029 -1), 2030 "Mark contract paid failed\n"); 2031 return 0; 2032 } 2033 2034 2035 /** 2036 * Tests looking up the status of an order. 2037 * 2038 * @param instance the instance to lookup from. 2039 * @param order the order to lookup. 2040 * @param expected_paid whether the order was paid or not. 2041 * @return 0 on success, 1 otherwise. 2042 */ 2043 static int 2044 test_lookup_order_status (const struct InstanceData *instance, 2045 const struct OrderData *order, 2046 bool expected_paid) 2047 { 2048 struct TALER_PrivateContractHashP h_contract_terms_expected; 2049 struct TALER_PrivateContractHashP h_contract_terms; 2050 bool order_paid = false; 2051 2052 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 2053 TALER_MERCHANTDB_lookup_order_status (pg, 2054 instance->instance.id, 2055 order->id, 2056 &h_contract_terms, 2057 &order_paid)) 2058 { 2059 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2060 "Lookup order status failed\n"); 2061 return 1; 2062 } 2063 GNUNET_assert (GNUNET_OK == 2064 TALER_JSON_contract_hash (order->contract, 2065 &h_contract_terms_expected)); 2066 if ((expected_paid != order_paid) || 2067 (0 != GNUNET_memcmp (&h_contract_terms, 2068 &h_contract_terms_expected))) 2069 { 2070 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2071 "Lookup order status/deposit failed: mismatched data\n"); 2072 return 1; 2073 } 2074 return 0; 2075 } 2076 2077 2078 /** 2079 * Test looking up an order by its fulfillment. 2080 * 2081 * @param instance the instance to lookup from. 2082 * @param order the order to lookup. 2083 * @param the session id associated with the payment. 2084 * @return 0 on success, 1 otherwise. 2085 */ 2086 static int 2087 test_lookup_order_by_fulfillment (const struct InstanceData *instance, 2088 const struct OrderData *order, 2089 const char *session_id) 2090 { 2091 char *order_id; 2092 const char *fulfillment_url = 2093 json_string_value (json_object_get (order->contract, 2094 "fulfillment_url")); 2095 GNUNET_assert (NULL != fulfillment_url); 2096 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 2097 TALER_MERCHANTDB_lookup_order_by_fulfillment (pg, 2098 instance->instance.id, 2099 fulfillment_url, 2100 session_id, 2101 false, 2102 &order_id)) 2103 { 2104 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2105 "Lookup order by fulfillment failed\n"); 2106 GNUNET_free (order_id); 2107 return 1; 2108 } 2109 if (0 != strcmp (order->id, 2110 order_id)) 2111 { 2112 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2113 "Lookup order by fulfillment failed\n"); 2114 GNUNET_free (order_id); 2115 return 1; 2116 } 2117 GNUNET_free (order_id); 2118 return 0; 2119 } 2120 2121 2122 /** 2123 * Test looking up the status of an order. 2124 * 2125 * @param order_id the row of the order in the database. 2126 * @param session_id the session id associated with the payment. 2127 * @param expected_paid whether the order was paid or not. 2128 * @param expected_wired whether the order was wired or not. 2129 * @return 0 on success, 1 otherwise. 2130 */ 2131 static int 2132 test_lookup_payment_status (const char *instance_id, 2133 const char *order_id, 2134 const char *session_id, 2135 bool expected_paid, 2136 bool expected_wired) 2137 { 2138 bool paid; 2139 bool wired; 2140 bool matches; 2141 uint64_t os; 2142 int16_t choice_index; 2143 2144 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 2145 TALER_MERCHANTDB_lookup_contract_terms3 (pg, 2146 instance_id, 2147 order_id, 2148 session_id, 2149 NULL, 2150 &os, 2151 &paid, 2152 &wired, 2153 &matches, 2154 NULL, 2155 &choice_index), 2156 "Lookup payment status failed\n"); 2157 if ( (NULL != session_id) && (! matches) ) 2158 { 2159 paid = false; 2160 wired = false; 2161 } 2162 if (expected_wired != wired) 2163 { 2164 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2165 "Lookup payment status for %s/%s failed: wired status is wrong (expected %d got %d)\n", 2166 instance_id, 2167 order_id, 2168 expected_wired, 2169 wired); 2170 return 1; 2171 } 2172 if (expected_paid != paid) 2173 { 2174 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2175 "Lookup payment status failed: paid status is wrong\n"); 2176 return 1; 2177 } 2178 return 0; 2179 } 2180 2181 2182 /** 2183 * Test marking an order as being wired. 2184 * 2185 * @param order_id the row of the order in the database. 2186 * @param expected_result the result we expect the DB to return. 2187 * @return 0 on success, 1 otherwise. 2188 */ 2189 static int 2190 test_mark_order_wired (uint64_t order_id, 2191 enum GNUNET_DB_QueryStatus expected_result) 2192 { 2193 TEST_COND_RET_ON_FAIL (expected_result == 2194 TALER_MERCHANTDB_mark_order_wired (pg, 2195 order_id), 2196 "Mark order wired failed\n"); 2197 return 0; 2198 } 2199 2200 2201 /** 2202 * Closure for order tests. 2203 */ 2204 struct TestOrders_Closure 2205 { 2206 /** 2207 * The instance to use for the order tests. 2208 */ 2209 struct InstanceData instance; 2210 2211 /** 2212 * A product to use for the order tests. 2213 */ 2214 struct ProductData product; 2215 2216 /** 2217 * The array of orders 2218 */ 2219 struct OrderData orders[3]; 2220 }; 2221 2222 2223 /** 2224 * Initializes order test data. 2225 * 2226 * @param cls the order test closure. 2227 */ 2228 static void 2229 pre_test_orders (struct TestOrders_Closure *cls) 2230 { 2231 /* Instance */ 2232 make_instance ("test_inst_orders", 2233 &cls->instance); 2234 2235 /* Product */ 2236 make_product ("test_orders_pd_0", 2237 &cls->product); 2238 2239 /* Orders */ 2240 make_order ("test_orders_od_0", 2241 &cls->orders[0]); 2242 make_order ("test_orders_od_1", 2243 &cls->orders[1]); 2244 make_order ("test_orders_od_2", 2245 &cls->orders[2]); 2246 2247 GNUNET_assert (0 == 2248 json_object_set_new (cls->orders[1].contract, 2249 "other_field", 2250 json_string ("Second contract"))); 2251 2252 cls->orders[2].pay_deadline = GNUNET_TIME_UNIT_ZERO_TS; 2253 GNUNET_assert (0 == 2254 json_object_set_new ( 2255 cls->orders[2].contract, 2256 "pay_deadline", 2257 GNUNET_JSON_from_timestamp (cls->orders[2].pay_deadline))); 2258 } 2259 2260 2261 /** 2262 * Frees memory after order tests. 2263 * 2264 * @param cls the order test closure. 2265 */ 2266 static void 2267 post_test_orders (struct TestOrders_Closure *cls) 2268 { 2269 free_instance_data (&cls->instance); 2270 free_product_data (&cls->product); 2271 free_order_data (&cls->orders[0]); 2272 free_order_data (&cls->orders[1]); 2273 free_order_data (&cls->orders[2]); 2274 } 2275 2276 2277 /** 2278 * Run the tests for orders. 2279 * 2280 * @param cls the order test closure. 2281 * @return 0 on success, 1 on failure. 2282 */ 2283 static int 2284 run_test_orders (struct TestOrders_Closure *cls) 2285 { 2286 struct TALER_MERCHANTDB_OrderFilter filter = { 2287 .paid = TALER_EXCHANGE_YNA_ALL, 2288 .refunded = TALER_EXCHANGE_YNA_ALL, 2289 .wired = TALER_EXCHANGE_YNA_ALL, 2290 .date = GNUNET_TIME_UNIT_ZERO_TS, 2291 .start_row = 0, 2292 .delta = 8 2293 }; 2294 uint64_t serial; 2295 2296 /* Insert the instance */ 2297 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 2298 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2299 /* Test inserting an order */ 2300 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 2301 &cls->orders[0], 2302 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2303 /* Test double insert fails */ 2304 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 2305 &cls->orders[0], 2306 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 2307 /* Test lookup order */ 2308 TEST_RET_ON_FAIL (test_lookup_order (&cls->instance, 2309 &cls->orders[0])); 2310 /* Make sure it fails correctly for nonexistent orders */ 2311 { 2312 struct TALER_MerchantPostDataHashP unused; 2313 2314 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 2315 TALER_MERCHANTDB_lookup_order (pg, 2316 cls->instance.instance.id, 2317 cls->orders[1].id, 2318 NULL, 2319 &unused, 2320 NULL)) 2321 { 2322 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2323 "Lookup order failed\n"); 2324 return 1; 2325 } 2326 } 2327 /* Test lookups on multiple orders */ 2328 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 2329 &cls->orders[1], 2330 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2331 serial = get_order_serial (&cls->instance, 2332 &cls->orders[0]); 2333 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 2334 &filter, 2335 2, 2336 cls->orders)); 2337 /* Test inserting contract terms */ 2338 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 2339 &cls->orders[0], 2340 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2341 /* Test double insert fails */ 2342 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 2343 &cls->orders[0], 2344 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 2345 /* Test order lock */ 2346 TEST_RET_ON_FAIL (test_insert_product (&cls->instance, 2347 &cls->product, 2348 0, 2349 NULL, 2350 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 2351 false, 2352 false, 2353 -1)); 2354 if (1 != TALER_MERCHANTDB_insert_order_lock (pg, 2355 cls->instance.instance.id, 2356 cls->orders[0].id, 2357 cls->product.id, 2358 5, 2359 0)) 2360 { 2361 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2362 "Insert order lock failed\n"); 2363 return 1; 2364 } 2365 /* Test lookup contract terms */ 2366 TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance, 2367 &cls->orders[0])); 2368 /* Test lookup fails for nonexistent contract terms */ 2369 { 2370 json_t *lookup_contract = NULL; 2371 uint64_t lookup_order_serial; 2372 2373 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 2374 TALER_MERCHANTDB_lookup_contract_terms (pg, 2375 cls->instance.instance.id, 2376 cls->orders[1].id, 2377 &lookup_contract, 2378 &lookup_order_serial, 2379 NULL)) 2380 { 2381 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2382 "Lookup contract terms failed\n"); 2383 GNUNET_assert (NULL == lookup_contract); 2384 return 1; 2385 } 2386 } 2387 /* Test update contract terms */ 2388 GNUNET_assert (0 == 2389 json_object_set_new (cls->orders[0].contract, 2390 "some_new_field", 2391 json_string ("another value"))); 2392 TEST_RET_ON_FAIL (test_update_contract_terms (&cls->instance, 2393 &cls->orders[0], 2394 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2395 TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance, 2396 &cls->orders[0])); 2397 /* Test lookup order status */ 2398 TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance, 2399 &cls->orders[0], 2400 false)); 2401 { 2402 struct TALER_PrivateContractHashP h_contract_terms; 2403 bool order_paid = false; 2404 2405 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 2406 TALER_MERCHANTDB_lookup_order_status (pg, 2407 cls->instance.instance.id, 2408 cls->orders[1].id, 2409 &h_contract_terms, 2410 &order_paid)) 2411 { 2412 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2413 "Lookup order status failed\n"); 2414 return 1; 2415 } 2416 } 2417 /* Test lookup payment status */ 2418 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 2419 cls->orders[0].id, 2420 NULL, 2421 false, 2422 false)); 2423 /* Test lookup order status fails for nonexistent order */ 2424 { 2425 struct TALER_PrivateContractHashP h_contract_terms; 2426 bool order_paid; 2427 2428 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 2429 TALER_MERCHANTDB_lookup_order_status (pg, 2430 cls->instance.instance.id, 2431 cls->orders[1].id, 2432 &h_contract_terms, 2433 &order_paid)) 2434 { 2435 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2436 "Lookup order status failed\n"); 2437 return 1; 2438 } 2439 } 2440 /* Test marking contracts as paid */ 2441 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 2442 &cls->orders[0], 2443 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2444 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 2445 cls->orders[0].id, 2446 NULL, 2447 true, 2448 false)); 2449 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 2450 cls->orders[0].id, 2451 "test_orders_session", 2452 true, 2453 false)); 2454 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 2455 cls->orders[0].id, 2456 "bad_session", 2457 false, 2458 false)); 2459 /* Test lookup order by fulfillment */ 2460 TEST_RET_ON_FAIL (test_lookup_order_by_fulfillment (&cls->instance, 2461 &cls->orders[0], 2462 "test_orders_session")); 2463 { 2464 char *order_id; 2465 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 2466 TALER_MERCHANTDB_lookup_order_by_fulfillment (pg, 2467 cls->instance.instance.id, 2468 "fulfillment_url", 2469 "test_orders_session", 2470 false, 2471 &order_id)) 2472 { 2473 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2474 "Lookup order by fulfillment failed\n"); 2475 GNUNET_free (order_id); 2476 return 1; 2477 } 2478 } 2479 /* Test mark as paid fails for nonexistent order */ 2480 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 2481 &cls->orders[1], 2482 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 2483 TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance, 2484 &cls->orders[0], 2485 true)); 2486 filter.paid = TALER_EXCHANGE_YNA_YES; 2487 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 2488 &filter, 2489 1, 2490 cls->orders)); 2491 /* Test marking orders as wired */ 2492 TEST_RET_ON_FAIL (test_mark_order_wired (serial, 2493 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)) 2494 ; 2495 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 2496 cls->orders[0].id, 2497 NULL, 2498 true, 2499 true)); 2500 TEST_RET_ON_FAIL (test_mark_order_wired (1007, 2501 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)) 2502 ; 2503 /* If an order has been claimed and we aren't past 2504 the pay deadline, we can't delete it. */ 2505 TEST_RET_ON_FAIL (test_delete_order (&cls->instance, 2506 &cls->orders[0], 2507 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 2508 /* Test we can't delete before the legal expiration */ 2509 TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance, 2510 &cls->orders[0], 2511 GNUNET_TIME_UNIT_MONTHS, 2512 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 2513 /* Test deleting contract terms */ 2514 TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance, 2515 &cls->orders[0], 2516 GNUNET_TIME_UNIT_ZERO, 2517 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2518 /* Test we can't delete something that doesn't exist */ 2519 TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance, 2520 &cls->orders[0], 2521 GNUNET_TIME_UNIT_ZERO, 2522 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 2523 /* Test delete order where we aren't past 2524 the deadline, but the order is unclaimed. */ 2525 TEST_RET_ON_FAIL (test_delete_order (&cls->instance, 2526 &cls->orders[1], 2527 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2528 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 2529 &filter, 2530 0, 2531 NULL)); 2532 /* Test we can't delete something that doesn't exist */ 2533 TEST_RET_ON_FAIL (test_delete_order (&cls->instance, 2534 &cls->orders[1], 2535 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 2536 2537 /* Test we can also delete a claimed order that's past the pay deadline. */ 2538 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 2539 &cls->orders[2], 2540 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2541 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 2542 &cls->orders[2], 2543 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2544 TEST_RET_ON_FAIL (test_delete_order (&cls->instance, 2545 &cls->orders[2], 2546 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 2547 return 0; 2548 } 2549 2550 2551 /** 2552 * Does all tasks for testing orders. 2553 * 2554 * @return 0 when successful, 1 otherwise. 2555 */ 2556 static int 2557 test_orders (void) 2558 { 2559 struct TestOrders_Closure test_cls; 2560 int test_result; 2561 2562 pre_test_orders (&test_cls); 2563 test_result = run_test_orders (&test_cls); 2564 post_test_orders (&test_cls); 2565 return test_result; 2566 } 2567 2568 2569 /* ********** Deposits ********** */ 2570 2571 2572 /** 2573 * A container for exchange signing key data. 2574 */ 2575 struct ExchangeSignkeyData 2576 { 2577 /** 2578 * The master private key of the exchange. 2579 */ 2580 struct TALER_MasterPrivateKeyP master_priv; 2581 2582 /** 2583 * The master public key of the exchange. 2584 */ 2585 struct TALER_MasterPublicKeyP master_pub; 2586 2587 /** 2588 * A signature made with the master keys. 2589 */ 2590 struct TALER_MasterSignatureP master_sig; 2591 2592 /** 2593 * The private key of the exchange. 2594 */ 2595 struct TALER_ExchangePrivateKeyP exchange_priv; 2596 2597 /** 2598 * The public key of the exchange. 2599 */ 2600 struct TALER_ExchangePublicKeyP exchange_pub; 2601 2602 /** 2603 * When the signing key becomes valid. 2604 */ 2605 struct GNUNET_TIME_Timestamp start_date; 2606 2607 /** 2608 * When the signing key stops being used. 2609 */ 2610 struct GNUNET_TIME_Timestamp expire_date; 2611 2612 /** 2613 * When the signing key becomes invalid for proof. 2614 */ 2615 struct GNUNET_TIME_Timestamp end_date; 2616 }; 2617 2618 2619 /** 2620 * Creates an exchange signing key. 2621 * 2622 * @param signkey the signing key data to fill. 2623 */ 2624 static void 2625 make_exchange_signkey (struct ExchangeSignkeyData *signkey) 2626 { 2627 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 2628 2629 GNUNET_CRYPTO_eddsa_key_create (&signkey->exchange_priv.eddsa_priv); 2630 GNUNET_CRYPTO_eddsa_key_get_public (&signkey->exchange_priv.eddsa_priv, 2631 &signkey->exchange_pub.eddsa_pub); 2632 GNUNET_CRYPTO_eddsa_key_create (&signkey->master_priv.eddsa_priv); 2633 GNUNET_CRYPTO_eddsa_key_get_public (&signkey->master_priv.eddsa_priv, 2634 &signkey->master_pub.eddsa_pub); 2635 signkey->start_date = now; 2636 signkey->expire_date = now; 2637 signkey->end_date = now; 2638 TALER_exchange_offline_signkey_validity_sign ( 2639 &signkey->exchange_pub, 2640 signkey->start_date, 2641 signkey->expire_date, 2642 signkey->end_date, 2643 &signkey->master_priv, 2644 &signkey->master_sig); 2645 } 2646 2647 2648 /** 2649 * A container for deposit data. 2650 */ 2651 struct DepositData 2652 { 2653 /** 2654 * When the deposit was made. 2655 */ 2656 struct GNUNET_TIME_Timestamp timestamp; 2657 2658 /** 2659 * Hash of the associated order's contract terms. 2660 */ 2661 struct TALER_PrivateContractHashP h_contract_terms; 2662 2663 /** 2664 * Public key of the coin that has been deposited. 2665 */ 2666 struct TALER_CoinSpendPublicKeyP coin_pub; 2667 2668 /** 2669 * Signature of the coin that has been deposited. 2670 */ 2671 struct TALER_CoinSpendSignatureP coin_sig; 2672 2673 /** 2674 * URL of the exchange. 2675 */ 2676 const char *exchange_url; 2677 2678 /** 2679 * Value of the coin with fees applied. 2680 */ 2681 struct TALER_Amount amount_with_fee; 2682 2683 /** 2684 * Fee charged for deposit. 2685 */ 2686 struct TALER_Amount deposit_fee; 2687 2688 /** 2689 * Fee to be charged in case of a refund. 2690 */ 2691 struct TALER_Amount refund_fee; 2692 2693 /** 2694 * Fee charged after the money is wired. 2695 */ 2696 struct TALER_Amount wire_fee; 2697 2698 /** 2699 * Hash of the wire details. 2700 */ 2701 struct TALER_MerchantWireHashP h_wire; 2702 2703 /** 2704 * Signature the exchange made on this deposit. 2705 */ 2706 struct TALER_ExchangeSignatureP exchange_sig; 2707 2708 }; 2709 2710 2711 /** 2712 * Generates deposit data for an order. 2713 * 2714 * @param instance the instance to make the deposit to. 2715 * @param account the merchant account to use. 2716 * @param order the order this deposit is for. 2717 * @param signkey the signing key to use. 2718 * @param deposit the deposit data to fill. 2719 */ 2720 static void 2721 make_deposit (const struct InstanceData *instance, 2722 const struct TALER_MERCHANTDB_AccountDetails *account, 2723 const struct OrderData *order, 2724 const struct ExchangeSignkeyData *signkey, 2725 struct DepositData *deposit) 2726 { 2727 struct TALER_CoinSpendPrivateKeyP coin_priv; 2728 struct GNUNET_TIME_Timestamp now; 2729 struct TALER_Amount amount_without_fee; 2730 2731 now = GNUNET_TIME_timestamp_get (); 2732 deposit->timestamp = now; 2733 GNUNET_assert (GNUNET_OK == 2734 TALER_JSON_contract_hash (order->contract, 2735 &deposit->h_contract_terms)); 2736 GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv); 2737 GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, 2738 &deposit->coin_pub.eddsa_pub); 2739 deposit->exchange_url = "https://test-exchange/"; 2740 GNUNET_assert (GNUNET_OK == 2741 TALER_string_to_amount ("EUR:50.00", 2742 &deposit->amount_with_fee)); 2743 GNUNET_assert (GNUNET_OK == 2744 TALER_string_to_amount ("EUR:1.00", 2745 &deposit->deposit_fee)); 2746 GNUNET_assert (GNUNET_OK == 2747 TALER_string_to_amount ("EUR:1.50", 2748 &deposit->refund_fee)); 2749 GNUNET_assert (GNUNET_OK == 2750 TALER_string_to_amount ("EUR:2.00", 2751 &deposit->wire_fee)); 2752 GNUNET_assert (0 <= 2753 TALER_amount_subtract (&amount_without_fee, 2754 &deposit->amount_with_fee, 2755 &deposit->deposit_fee)); 2756 deposit->h_wire = account->h_wire; 2757 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, 2758 &deposit->exchange_sig, 2759 sizeof (deposit->exchange_sig)); 2760 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, 2761 &deposit->coin_sig, 2762 sizeof (deposit->coin_sig)); 2763 } 2764 2765 2766 /** 2767 * Tests inserting an exchange signing key into the database. 2768 * 2769 * @param signkey the signing key to insert. 2770 * @param expected_result the result we expect the database to return. 2771 * @return 0 on success, 1 otherwise. 2772 */ 2773 static int 2774 test_insert_exchange_signkey (const struct ExchangeSignkeyData *signkey, 2775 enum GNUNET_DB_QueryStatus expected_result) 2776 { 2777 TEST_COND_RET_ON_FAIL (expected_result == 2778 TALER_MERCHANTDB_insert_exchange_signkey (pg, 2779 &signkey->master_pub, 2780 &signkey->exchange_pub 2781 , 2782 signkey->start_date, 2783 signkey->expire_date, 2784 signkey->end_date, 2785 &signkey->master_sig), 2786 "Insert exchange signkey failed\n"); 2787 return 0; 2788 } 2789 2790 2791 /** 2792 * Tests inserting a deposit into the database. 2793 * 2794 * @param instance the instance the deposit was made to. 2795 * @param signkey the signing key used. 2796 * @param deposit the deposit information to insert. 2797 * @param expected_result the result we expect the database to return. 2798 * @return 0 on success, 1 otherwise. 2799 */ 2800 static int 2801 test_insert_deposit (const struct InstanceData *instance, 2802 const struct ExchangeSignkeyData *signkey, 2803 const struct DepositData *deposit, 2804 enum GNUNET_DB_QueryStatus expected_result) 2805 { 2806 uint64_t row; 2807 struct TALER_Amount awf; 2808 2809 GNUNET_assert (0 <= 2810 TALER_amount_subtract (&awf, 2811 &deposit->amount_with_fee, 2812 &deposit->deposit_fee)); 2813 TEST_COND_RET_ON_FAIL ( 2814 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 2815 TALER_MERCHANTDB_insert_deposit_confirmation (pg, 2816 instance->instance.id, 2817 deposit->timestamp, 2818 &deposit->h_contract_terms, 2819 deposit->exchange_url, 2820 deposit->timestamp, 2821 &awf, 2822 &deposit->wire_fee, 2823 &deposit->h_wire, 2824 &deposit->exchange_sig, 2825 &signkey->exchange_pub, 2826 &row), 2827 "Insert deposit confirmation failed\n"); 2828 TEST_COND_RET_ON_FAIL ( 2829 expected_result == 2830 TALER_MERCHANTDB_insert_deposit (pg, 2831 0, 2832 row, 2833 &deposit->coin_pub, 2834 &deposit->coin_sig, 2835 &deposit->amount_with_fee, 2836 &deposit->deposit_fee, 2837 &deposit->refund_fee, 2838 GNUNET_TIME_absolute_get ()), 2839 "Insert deposit failed\n"); 2840 return 0; 2841 } 2842 2843 2844 /** 2845 * Closure for testing deposit lookup 2846 */ 2847 struct TestLookupDeposits_Closure 2848 { 2849 /** 2850 * Number of deposits to compare to 2851 */ 2852 unsigned int deposits_to_cmp_length; 2853 2854 /** 2855 * Pointer to array of deposit data 2856 */ 2857 const struct DepositData *deposits_to_cmp; 2858 2859 /** 2860 * Pointer to array of number of matches per deposit 2861 */ 2862 unsigned int *results_matching; 2863 2864 /** 2865 * Total number of results returned 2866 */ 2867 unsigned int results_length; 2868 }; 2869 2870 2871 /** 2872 * Called after 'test_lookup_deposits'. 2873 * 2874 * @param cls pointer to the test lookup closure. 2875 * @param coin_pub public key of the coin deposited. 2876 * @param amount_with_fee amount of the deposit with fees. 2877 * @param deposit_fee fee charged for the deposit. 2878 * @param refund_fee fee charged in case of a refund. 2879 */ 2880 static void 2881 lookup_deposits_cb (void *cls, 2882 const char *exchange_url, 2883 const struct TALER_CoinSpendPublicKeyP *coin_pub, 2884 const struct TALER_Amount *amount_with_fee, 2885 const struct TALER_Amount *deposit_fee, 2886 const struct TALER_Amount *refund_fee) 2887 { 2888 struct TestLookupDeposits_Closure *cmp = cls; 2889 if (NULL == cmp) 2890 return; 2891 cmp->results_length += 1; 2892 for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i) 2893 { 2894 if ((GNUNET_OK == 2895 TALER_amount_cmp_currency ( 2896 &cmp->deposits_to_cmp[i].amount_with_fee, 2897 amount_with_fee)) && 2898 (0 == 2899 TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee, 2900 amount_with_fee)) && 2901 (GNUNET_OK == 2902 TALER_amount_cmp_currency ( 2903 &cmp->deposits_to_cmp[i].deposit_fee, 2904 deposit_fee)) && 2905 (0 == 2906 TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee, 2907 deposit_fee)) && 2908 (GNUNET_OK == 2909 TALER_amount_cmp_currency ( 2910 &cmp->deposits_to_cmp[i].refund_fee, 2911 refund_fee)) && 2912 (0 == 2913 TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee, 2914 refund_fee))) 2915 { 2916 cmp->results_matching[i] += 1; 2917 } 2918 2919 } 2920 } 2921 2922 2923 /** 2924 * Tests looking up deposits from the database. 2925 * 2926 * @param instance the instance to lookup deposits from. 2927 * @param h_contract_terms the contract terms that the deposits should have. 2928 * @param deposits_length length of @e deposits. 2929 * @param deposits the deposits we expect to be found. 2930 * @return 0 on success, 1 otherwise. 2931 */ 2932 static int 2933 test_lookup_deposits (const struct InstanceData *instance, 2934 const struct TALER_PrivateContractHashP *h_contract_terms, 2935 unsigned int deposits_length, 2936 const struct DepositData *deposits) 2937 { 2938 unsigned int results_matching[GNUNET_NZL (deposits_length)]; 2939 struct TestLookupDeposits_Closure cmp = { 2940 .deposits_to_cmp_length = deposits_length, 2941 .deposits_to_cmp = deposits, 2942 .results_matching = results_matching, 2943 .results_length = 0 2944 }; 2945 memset (results_matching, 2946 0, 2947 sizeof (unsigned int) * deposits_length); 2948 TEST_COND_RET_ON_FAIL (0 <= 2949 TALER_MERCHANTDB_lookup_deposits (pg, 2950 instance->instance.id, 2951 h_contract_terms, 2952 &lookup_deposits_cb, 2953 &cmp), 2954 "Lookup deposits failed\n"); 2955 if (deposits_length != cmp.results_length) 2956 { 2957 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2958 "Lookup deposits failed: incorrect number of results returned (%d)\n", 2959 cmp.results_length); 2960 return 1; 2961 } 2962 for (unsigned int i = 0; deposits_length > i; ++i) 2963 { 2964 if (cmp.results_matching[i] != 1) 2965 { 2966 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 2967 "Lookup deposits failed: mismatched data\n"); 2968 return 1; 2969 } 2970 } 2971 return 0; 2972 } 2973 2974 2975 /** 2976 * Called after 'test_lookup_deposits_contract_and_coin'. 2977 * 2978 * @param cls pointer to the test lookup closure. 2979 * @param exchange_url URL to the exchange 2980 * @param amount_with_fee amount of the deposit with fees. 2981 * @param deposit_fee fee charged for the deposit. 2982 * @param refund_fee fee charged in case of a refund. 2983 * @param wire_fee fee charged when the money is wired. 2984 * @param h_wire hash of the wire transfer details. 2985 * @param deposit_timestamp when the deposit was made. 2986 * @param refund_deadline deadline for refunding the deposit. 2987 * @param exchange_sig signature the exchange made on the deposit. 2988 * @param exchange_pub public key of the exchange. 2989 */ 2990 static void 2991 lookup_deposits_contract_coin_cb ( 2992 void *cls, 2993 const char *exchange_url, 2994 const struct TALER_Amount *amount_with_fee, 2995 const struct TALER_Amount *deposit_fee, 2996 const struct TALER_Amount *refund_fee, 2997 const struct TALER_Amount *wire_fee, 2998 const struct TALER_MerchantWireHashP *h_wire, 2999 struct GNUNET_TIME_Timestamp deposit_timestamp, 3000 struct GNUNET_TIME_Timestamp refund_deadline, 3001 const struct TALER_ExchangeSignatureP *exchange_sig, 3002 const struct TALER_ExchangePublicKeyP *exchange_pub) 3003 { 3004 struct TestLookupDeposits_Closure *cmp = cls; 3005 3006 if (NULL == cmp) 3007 return; 3008 cmp->results_length++; 3009 for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i) 3010 { 3011 if ((GNUNET_TIME_timestamp_cmp (cmp->deposits_to_cmp[i].timestamp, 3012 ==, 3013 deposit_timestamp)) && 3014 (0 == strcmp (cmp->deposits_to_cmp[i].exchange_url, 3015 exchange_url)) && 3016 (GNUNET_OK == TALER_amount_cmp_currency ( 3017 &cmp->deposits_to_cmp[i].amount_with_fee, 3018 amount_with_fee)) && 3019 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee, 3020 amount_with_fee)) && 3021 (GNUNET_OK == TALER_amount_cmp_currency ( 3022 &cmp->deposits_to_cmp[i].deposit_fee, 3023 deposit_fee)) && 3024 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee, 3025 deposit_fee)) && 3026 (GNUNET_OK == TALER_amount_cmp_currency ( 3027 &cmp->deposits_to_cmp[i].refund_fee, 3028 refund_fee)) && 3029 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee, 3030 refund_fee)) && 3031 (GNUNET_OK == TALER_amount_cmp_currency ( 3032 &cmp->deposits_to_cmp[i].wire_fee, 3033 wire_fee)) && 3034 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].wire_fee, 3035 wire_fee)) && 3036 (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire, 3037 h_wire)) && 3038 (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].exchange_sig, 3039 exchange_sig))) 3040 { 3041 cmp->results_matching[i]++; 3042 } 3043 } 3044 } 3045 3046 3047 /** 3048 * Tests lookup of deposits by contract and coin. 3049 * 3050 * @param instance the instance to lookup from. 3051 * @param h_contract the contract terms the deposits should have. 3052 * @param coin_pub the public key of the coin the deposits should have. 3053 * @param deposits_length length of @e deposits. 3054 * @param deposits the deposits the db is expected to find. 3055 * @return 0 on success, 1 otherwise. 3056 */ 3057 static int 3058 test_lookup_deposits_contract_and_coin ( 3059 const struct InstanceData *instance, 3060 const struct TALER_PrivateContractHashP *h_contract, 3061 const struct TALER_CoinSpendPublicKeyP *coin_pub, 3062 unsigned int deposits_length, 3063 const struct DepositData *deposits) 3064 { 3065 unsigned int results_matching[deposits_length]; 3066 struct TestLookupDeposits_Closure cmp = { 3067 .deposits_to_cmp_length = deposits_length, 3068 .deposits_to_cmp = deposits, 3069 .results_matching = results_matching, 3070 .results_length = 0 3071 }; 3072 memset (results_matching, 3073 0, 3074 sizeof (unsigned int) * deposits_length); 3075 TEST_COND_RET_ON_FAIL ( 3076 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 3077 TALER_MERCHANTDB_lookup_deposits_by_contract_and_coin ( 3078 pg, 3079 instance->instance.id, 3080 h_contract, 3081 coin_pub, 3082 &lookup_deposits_contract_coin_cb, 3083 &cmp), 3084 "Lookup deposits by contract and coin failed\n"); 3085 if (deposits_length != cmp.results_length) 3086 { 3087 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3088 "Lookup deposits failed: incorrect number of results returned\n"); 3089 return 1; 3090 } 3091 for (unsigned int i = 0; deposits_length > i; ++i) 3092 { 3093 if (cmp.results_matching[i] != 1) 3094 { 3095 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3096 "Lookup deposits failed: mismatched data\n"); 3097 return 1; 3098 } 3099 } 3100 return 0; 3101 } 3102 3103 3104 /** 3105 * Called after 'test_lookup_deposits_by_order'. 3106 * 3107 * @param cls pointer to the test lookup closure. 3108 * @param deposit_serial row number of the deposit in the database. 3109 * @param exchange_url URL to the exchange 3110 * @param h_wire hash of the wire transfer details. 3111 * @param deposit_timestamp when was the deposit made 3112 * @param amount_with_fee amount of the deposit with fees. 3113 * @param deposit_fee fee charged for the deposit. 3114 * @param coin_pub public key of the coin deposited. 3115 */ 3116 static void 3117 lookup_deposits_order_cb (void *cls, 3118 uint64_t deposit_serial, 3119 const char *exchange_url, 3120 const struct TALER_MerchantWireHashP *h_wire, 3121 struct GNUNET_TIME_Timestamp deposit_timestamp, 3122 const struct TALER_Amount *amount_with_fee, 3123 const struct TALER_Amount *deposit_fee, 3124 const struct TALER_CoinSpendPublicKeyP *coin_pub) 3125 { 3126 struct TestLookupDeposits_Closure *cmp = cls; 3127 3128 if (NULL == cmp) 3129 return; 3130 cmp->results_length += 1; 3131 for (unsigned int i = 0; i < cmp->deposits_to_cmp_length; ++i) 3132 { 3133 if ((0 == strcmp (cmp->deposits_to_cmp[i].exchange_url, 3134 exchange_url)) && 3135 (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire, 3136 h_wire)) && 3137 (GNUNET_OK == TALER_amount_cmp_currency ( 3138 &cmp->deposits_to_cmp[i].amount_with_fee, 3139 amount_with_fee)) && 3140 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee, 3141 amount_with_fee)) && 3142 (GNUNET_OK == TALER_amount_cmp_currency ( 3143 &cmp->deposits_to_cmp[i].deposit_fee, 3144 deposit_fee)) && 3145 (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee, 3146 deposit_fee)) && 3147 (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].coin_pub, 3148 coin_pub))) 3149 cmp->results_matching[i] += 1; 3150 } 3151 } 3152 3153 3154 /** 3155 * Tests looking up deposits by associated order. 3156 * 3157 * @param order_serial row number of the order to lookup for. 3158 * @param deposits_length length of @e deposits_length. 3159 * @param deposits the deposits we expect to be found. 3160 * @return 0 on success, 1 otherwise. 3161 */ 3162 static int 3163 test_lookup_deposits_by_order (uint64_t order_serial, 3164 unsigned int deposits_length, 3165 const struct DepositData *deposits) 3166 { 3167 unsigned int results_matching[deposits_length]; 3168 struct TestLookupDeposits_Closure cmp = { 3169 .deposits_to_cmp_length = deposits_length, 3170 .deposits_to_cmp = deposits, 3171 .results_matching = results_matching, 3172 .results_length = 0 3173 }; 3174 memset (results_matching, 3175 0, 3176 sizeof (unsigned int) * deposits_length); 3177 if (deposits_length != 3178 TALER_MERCHANTDB_lookup_deposits_by_order (pg, 3179 order_serial, 3180 &lookup_deposits_order_cb, 3181 &cmp)) 3182 { 3183 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3184 "Lookup deposits by order failed\n"); 3185 return 1; 3186 } 3187 if (deposits_length != cmp.results_length) 3188 { 3189 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3190 "Lookup deposits by order failed: incorrect number of results\n"); 3191 return 1; 3192 } 3193 for (unsigned int i = 0; i < deposits_length; ++i) 3194 { 3195 if (1 != cmp.results_matching[i]) 3196 { 3197 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3198 "Lookup deposits by order failed: mismatched data\n"); 3199 return 1; 3200 } 3201 } 3202 return 0; 3203 } 3204 3205 3206 /** 3207 * Container for information for looking up the row number of a deposit. 3208 */ 3209 struct LookupDepositSerial_Closure 3210 { 3211 /** 3212 * The deposit we're looking for. 3213 */ 3214 const struct DepositData *deposit; 3215 3216 /** 3217 * The serial found. 3218 */ 3219 uint64_t serial; 3220 }; 3221 3222 3223 /** 3224 * Called after 'get_deposit_serial'. 3225 * 3226 * @param cls pointer to the test lookup closure. 3227 * @param deposit_serial row number of the deposit in the database. 3228 * @param exchange_url URL to the exchange 3229 * @param h_wire hash of the wire transfer details. 3230 * @param deposit_timestamp when was the deposit made. 3231 * @param amount_with_fee amount of the deposit with fees. 3232 * @param deposit_fee fee charged for the deposit. 3233 * @param coin_pub public key of the coin deposited. 3234 */ 3235 static void 3236 get_deposit_serial_cb (void *cls, 3237 uint64_t deposit_serial, 3238 const char *exchange_url, 3239 const struct TALER_MerchantWireHashP *h_wire, 3240 struct GNUNET_TIME_Timestamp deposit_timestamp, 3241 const struct TALER_Amount *amount_with_fee, 3242 const struct TALER_Amount *deposit_fee, 3243 const struct TALER_CoinSpendPublicKeyP *coin_pub) 3244 { 3245 struct LookupDepositSerial_Closure *lookup_cls = cls; 3246 3247 (void) deposit_timestamp; 3248 if (NULL == lookup_cls) 3249 return; 3250 if ((0 == strcmp (lookup_cls->deposit->exchange_url, 3251 exchange_url)) && 3252 (0 == GNUNET_memcmp (&lookup_cls->deposit->h_wire, 3253 h_wire)) && 3254 (GNUNET_OK == TALER_amount_cmp_currency ( 3255 &lookup_cls->deposit->amount_with_fee, 3256 amount_with_fee)) && 3257 (0 == TALER_amount_cmp (&lookup_cls->deposit->amount_with_fee, 3258 amount_with_fee)) && 3259 (GNUNET_OK == TALER_amount_cmp_currency ( 3260 &lookup_cls->deposit->deposit_fee, 3261 deposit_fee)) && 3262 (0 == TALER_amount_cmp (&lookup_cls->deposit->deposit_fee, 3263 deposit_fee)) && 3264 (0 == GNUNET_memcmp (&lookup_cls->deposit->coin_pub, 3265 coin_pub))) 3266 lookup_cls->serial = deposit_serial; 3267 } 3268 3269 3270 /** 3271 * Convenience function to retrieve the row number of a deposit in the database. 3272 * 3273 * @param instance the instance to get deposits from. 3274 * @param order the order associated with the deposit. 3275 * @param deposit the deposit to lookup the serial for. 3276 * @return the row number of the deposit. 3277 */ 3278 static uint64_t 3279 get_deposit_serial (const struct InstanceData *instance, 3280 const struct OrderData *order, 3281 const struct DepositData *deposit) 3282 { 3283 uint64_t order_serial = get_order_serial (instance, 3284 order); 3285 struct LookupDepositSerial_Closure lookup_cls = { 3286 .deposit = deposit, 3287 .serial = 0 3288 }; 3289 3290 GNUNET_assert (0 < 3291 TALER_MERCHANTDB_lookup_deposits_by_order (pg, 3292 order_serial, 3293 &get_deposit_serial_cb, 3294 &lookup_cls)); 3295 GNUNET_assert (0 != lookup_cls.serial); 3296 3297 return lookup_cls.serial; 3298 } 3299 3300 3301 /** 3302 * Closure for deposit tests. 3303 */ 3304 struct TestDeposits_Closure 3305 { 3306 /** 3307 * The instance settings 3308 */ 3309 struct InstanceData instance; 3310 3311 /** 3312 * The merchant account 3313 */ 3314 struct TALER_MERCHANTDB_AccountDetails account; 3315 3316 /** 3317 * The exchange signing key 3318 */ 3319 struct ExchangeSignkeyData signkey; 3320 3321 /** 3322 * The order data 3323 */ 3324 struct OrderData orders[2]; 3325 3326 /** 3327 * The array of deposits 3328 */ 3329 struct DepositData deposits[3]; 3330 }; 3331 3332 3333 /** 3334 * Initializes data for testing deposits. 3335 * 3336 * @param cls the test closure to initialize. 3337 */ 3338 static void 3339 pre_test_deposits (struct TestDeposits_Closure *cls) 3340 { 3341 /* Instance */ 3342 make_instance ("test_inst_deposits", 3343 &cls->instance); 3344 3345 /* Account */ 3346 make_account (&cls->account); 3347 cls->account.instance_id = cls->instance.instance.id; 3348 /* Signing key */ 3349 make_exchange_signkey (&cls->signkey); 3350 3351 /* Order */ 3352 make_order ("test_deposits_od_1", 3353 &cls->orders[0]); 3354 make_order ("test_deposits_od_2", 3355 &cls->orders[1]); 3356 3357 /* Deposit */ 3358 make_deposit (&cls->instance, 3359 &cls->account, 3360 &cls->orders[0], 3361 &cls->signkey, 3362 &cls->deposits[0]); 3363 make_deposit (&cls->instance, 3364 &cls->account, 3365 &cls->orders[0], 3366 &cls->signkey, 3367 &cls->deposits[1]); 3368 GNUNET_assert (GNUNET_OK == 3369 TALER_string_to_amount ("EUR:29.00", 3370 &cls->deposits[1].amount_with_fee)); 3371 make_deposit (&cls->instance, 3372 &cls->account, 3373 &cls->orders[1], 3374 &cls->signkey, 3375 &cls->deposits[2]); 3376 } 3377 3378 3379 /** 3380 * Cleans up memory after testing deposits. 3381 * 3382 * @param cls the closure containing memory to free. 3383 */ 3384 static void 3385 post_test_deposits (struct TestDeposits_Closure *cls) 3386 { 3387 free_instance_data (&cls->instance); 3388 json_decref (cls->orders[0].contract); 3389 json_decref (cls->orders[1].contract); 3390 } 3391 3392 3393 /** 3394 * Runs tests for deposits. 3395 * 3396 * @param cls the closure containing test data. 3397 * @return 0 on success, 1 otherwise. 3398 */ 3399 static int 3400 run_test_deposits (struct TestDeposits_Closure *cls) 3401 { 3402 /* Insert the instance */ 3403 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 3404 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3405 /* Insert an account */ 3406 TEST_RET_ON_FAIL (test_insert_account (&cls->instance, 3407 &cls->account, 3408 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3409 /* Insert a signing key */ 3410 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 3411 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3412 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 3413 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 3414 /* Insert an order */ 3415 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 3416 &cls->orders[0], 3417 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3418 /* Insert contract terms */ 3419 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 3420 &cls->orders[0], 3421 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3422 /* Test inserting a deposit */ 3423 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 3424 &cls->signkey, 3425 &cls->deposits[0], 3426 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3427 /* Test double inserts are idempotent */ 3428 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 3429 &cls->signkey, 3430 &cls->deposits[0], 3431 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 3432 /* Test lookup deposits */ 3433 TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance, 3434 &cls->deposits[0].h_contract_terms, 3435 1, 3436 cls->deposits)); 3437 TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance, 3438 &cls->deposits[2].h_contract_terms, 3439 0, 3440 NULL)); 3441 /* Test lookup deposits by contract and coins */ 3442 TEST_RET_ON_FAIL (test_lookup_deposits_contract_and_coin ( 3443 &cls->instance, 3444 &cls->deposits[0].h_contract_terms, 3445 &cls->deposits[0].coin_pub, 3446 1, 3447 cls->deposits)); 3448 /* Test multiple deposits */ 3449 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 3450 &cls->signkey, 3451 &cls->deposits[1], 3452 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3453 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 3454 &cls->orders[1], 3455 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3456 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 3457 &cls->orders[1], 3458 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3459 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 3460 &cls->signkey, 3461 &cls->deposits[2], 3462 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 3463 TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance, 3464 &cls->deposits[0].h_contract_terms, 3465 2, 3466 cls->deposits)); 3467 TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance, 3468 &cls->deposits[2].h_contract_terms, 3469 1, 3470 &cls->deposits[2])); 3471 /* Test lookup deposits by order */ 3472 { 3473 uint64_t order_serial = get_order_serial (&cls->instance, 3474 &cls->orders[0]); 3475 TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial, 3476 2, 3477 cls->deposits)); 3478 order_serial = get_order_serial (&cls->instance, 3479 &cls->orders[1]); 3480 TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial, 3481 1, 3482 &cls->deposits[2])); 3483 } 3484 return 0; 3485 } 3486 3487 3488 /** 3489 * Handles functionality for testing deposits. 3490 * 3491 * @return 0 on success, 1 otherwise. 3492 */ 3493 static int 3494 test_deposits (void) 3495 { 3496 struct TestDeposits_Closure test_cls; 3497 int test_result; 3498 3499 pre_test_deposits (&test_cls); 3500 test_result = run_test_deposits (&test_cls); 3501 post_test_deposits (&test_cls); 3502 return test_result; 3503 } 3504 3505 3506 /* *********** Transfers ********** */ 3507 3508 3509 /** 3510 * Container for wire fee data for an exchange. 3511 */ 3512 struct WireFeeData 3513 { 3514 /** 3515 * The method used. 3516 */ 3517 const char *wire_method; 3518 3519 /** 3520 * Hash of the wire method. 3521 */ 3522 struct GNUNET_HashCode h_wire_method; 3523 3524 /** 3525 * Wire fees charged. 3526 */ 3527 struct TALER_WireFeeSet fees; 3528 3529 /** 3530 * Start date of the wire fee. 3531 */ 3532 struct GNUNET_TIME_Timestamp wire_fee_start; 3533 3534 /** 3535 * End date of the wire fee. 3536 */ 3537 struct GNUNET_TIME_Timestamp wire_fee_end; 3538 3539 /** 3540 * Signature on the wire fee. 3541 */ 3542 struct TALER_MasterSignatureP fee_sig; 3543 }; 3544 3545 3546 /** 3547 * Creates data for an exchange wire fee. 3548 * 3549 * @param signkey the exchange signing key data. 3550 * @param wire_fee where to store the wire fee data. 3551 */ 3552 static void 3553 make_wire_fee (const struct ExchangeSignkeyData *signkey, 3554 struct WireFeeData *wire_fee) 3555 { 3556 wire_fee->wire_method = "wire-method"; 3557 GNUNET_CRYPTO_hash (wire_fee->wire_method, 3558 strlen (wire_fee->wire_method) + 1, 3559 &wire_fee->h_wire_method); 3560 GNUNET_assert (GNUNET_OK == 3561 TALER_string_to_amount ("EUR:0.49", 3562 &wire_fee->fees.wire)); 3563 GNUNET_assert (GNUNET_OK == 3564 TALER_string_to_amount ("EUR:0.49", 3565 &wire_fee->fees.closing)); 3566 wire_fee->wire_fee_start = GNUNET_TIME_timestamp_get (); 3567 wire_fee->wire_fee_end = GNUNET_TIME_relative_to_timestamp ( 3568 GNUNET_TIME_UNIT_MONTHS); 3569 TALER_exchange_offline_wire_fee_sign ( 3570 wire_fee->wire_method, 3571 wire_fee->wire_fee_start, 3572 wire_fee->wire_fee_end, 3573 &wire_fee->fees, 3574 &signkey->master_priv, 3575 &wire_fee->fee_sig); 3576 } 3577 3578 3579 /** 3580 * Container for wire transfer data. 3581 */ 3582 struct TransferData 3583 { 3584 /** 3585 * Id of the transfer. 3586 */ 3587 struct TALER_WireTransferIdentifierRawP wtid; 3588 3589 /** 3590 * The main data for the transfer. 3591 */ 3592 struct TALER_EXCHANGE_TransferData data; 3593 3594 /** 3595 * URL to the exchange the transfer was made through. 3596 */ 3597 const char *exchange_url; 3598 3599 /** 3600 * How much the fee for the deposit was. 3601 */ 3602 struct TALER_Amount deposit_fee; 3603 3604 /** 3605 * Whether the transfer has been confirmed. 3606 */ 3607 bool confirmed; 3608 3609 /** 3610 * Whether the transfer has been verified. 3611 */ 3612 bool verified; 3613 }; 3614 3615 3616 /** 3617 * Creates a transfer for use with testing. 3618 * 3619 * @param deposits_length length of @e deposits. 3620 * @param deposits list of deposits to combine into one transfer. 3621 * @param transfer where to write the transfer data. 3622 */ 3623 static void 3624 make_transfer (const struct ExchangeSignkeyData *signkey, 3625 unsigned int deposits_length, 3626 const struct DepositData deposits[static deposits_length], 3627 struct TransferData *transfer) 3628 { 3629 struct TALER_TrackTransferDetails *details = NULL; 3630 3631 GNUNET_CRYPTO_seed_weak_random (585); 3632 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, 3633 &transfer->wtid, 3634 sizeof (struct TALER_WireTransferIdentifierRawP)); 3635 transfer->exchange_url = deposits[0].exchange_url; 3636 transfer->verified = false; 3637 transfer->confirmed = false; 3638 transfer->data.details_length = 0; 3639 GNUNET_assert (GNUNET_OK == 3640 TALER_amount_set_zero (deposits[0].amount_with_fee.currency, 3641 &transfer->data.total_amount)); 3642 GNUNET_assert (GNUNET_OK == 3643 TALER_amount_set_zero (deposits[0].amount_with_fee.currency, 3644 &transfer->deposit_fee)); 3645 for (unsigned int i = 0; i < deposits_length; ++i) 3646 { 3647 GNUNET_array_grow (details, 3648 transfer->data.details_length, 3649 i + 1); 3650 details[i].h_contract_terms = deposits[i].h_contract_terms; 3651 details[i].coin_pub = deposits[i].coin_pub; 3652 details[i].coin_value = deposits[i].amount_with_fee; 3653 details[i].coin_fee = deposits[i].deposit_fee; 3654 GNUNET_assert (0 <= 3655 TALER_amount_add (&transfer->data.total_amount, 3656 &transfer->data.total_amount, 3657 &deposits[i].amount_with_fee)); 3658 GNUNET_assert (0 <= 3659 TALER_amount_add (&transfer->deposit_fee, 3660 &transfer->deposit_fee, 3661 &deposits[i].deposit_fee)); 3662 } 3663 transfer->data.exchange_pub = signkey->exchange_pub; 3664 transfer->data.execution_time = GNUNET_TIME_timestamp_get (); 3665 transfer->data.details = details; 3666 GNUNET_assert (GNUNET_OK == 3667 TALER_string_to_amount ("EUR:0.50", 3668 &transfer->data.wire_fee)); 3669 } 3670 3671 3672 /** 3673 * Closure for testing 'lookup_transfer_summary' 3674 */ 3675 struct TestLookupTransferSummary_Closure 3676 { 3677 /** 3678 * Id of the order the transfer was made for. 3679 */ 3680 const char *order_id; 3681 3682 /** 3683 * The value of the deposit made. 3684 */ 3685 const struct TALER_Amount *deposit_value; 3686 3687 /** 3688 * The fee on the deposit made. 3689 */ 3690 const struct TALER_Amount *deposit_fee; 3691 3692 /** 3693 * 0 if the comparison is true, 1 if false. 3694 */ 3695 int result; 3696 }; 3697 3698 3699 /** 3700 * Called after 'test_lookup_transfer_summary'. 3701 * 3702 * @param cls pointer to 'TestLookupTransferSummary_Closure'. 3703 * @param order_id id of the order the transfer was made for. 3704 * @param deposit_value the value of the deposit made. 3705 * @param deposit_fee the fee on the deposit made. 3706 */ 3707 static void 3708 lookup_transfer_summary_cb (void *cls, 3709 const char *order_id, 3710 const struct TALER_Amount *deposit_value, 3711 const struct TALER_Amount *deposit_fee) 3712 { 3713 struct TestLookupTransferSummary_Closure *cmp = cls; 3714 if (NULL == cmp) 3715 return; 3716 if ((0 == strcmp (cmp->order_id, 3717 order_id)) && 3718 (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_value, 3719 deposit_value)) && 3720 (0 == TALER_amount_cmp (cmp->deposit_value, 3721 deposit_value)) && 3722 (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_fee, 3723 deposit_fee)) && 3724 (0 == TALER_amount_cmp (cmp->deposit_fee, 3725 deposit_fee))) 3726 cmp->result = 0; 3727 else 3728 cmp->result = 1; 3729 } 3730 3731 3732 /** 3733 * Tests looking up a transfer's summary. 3734 * 3735 * @param exchange_url url to the exchange for the transfer. 3736 * @param wtid identifier of the transfer. 3737 * @param expected_order_id the id of the order associated with the transfer. 3738 * @param expected_deposit_value the amount of the deposit made for the transfer. 3739 * @param expected_deposit_fee the fee on the deposit made for the transfer. 3740 * @return 1 on success, 0 otherwise. 3741 */ 3742 static int 3743 test_lookup_transfer_summary ( 3744 const char *exchange_url, 3745 const struct TALER_WireTransferIdentifierRawP *wtid, 3746 const char *expected_order_id, 3747 const struct TALER_Amount *expected_deposit_value, 3748 const struct TALER_Amount *expected_deposit_fee) 3749 { 3750 struct TestLookupTransferSummary_Closure cmp = { 3751 .order_id = expected_order_id, 3752 .deposit_value = expected_deposit_value, 3753 .deposit_fee = expected_deposit_fee, 3754 .result = 0 3755 }; 3756 if (1 != TALER_MERCHANTDB_lookup_transfer_summary (pg, 3757 exchange_url, 3758 wtid, 3759 &lookup_transfer_summary_cb, 3760 &cmp)) 3761 { 3762 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3763 "Lookup transfer summary failed\n"); 3764 return 1; 3765 } 3766 if (0 != cmp.result) 3767 { 3768 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3769 "Lookup transfer summary failed: mismatched data\n"); 3770 return 1; 3771 } 3772 return 0; 3773 } 3774 3775 3776 /** 3777 * Closure for testing 'lookup_transfer_details'. 3778 */ 3779 struct TestLookupTransferDetails_Closure 3780 { 3781 /** 3782 * Length of @e details_to_cmp. 3783 */ 3784 unsigned int details_to_cmp_length; 3785 3786 /** 3787 * The details we expect to find. 3788 */ 3789 const struct TALER_TrackTransferDetails *details_to_cmp; 3790 3791 /** 3792 * Number of results matching each detail in @e details_to_cmp. 3793 */ 3794 unsigned int *results_matching; 3795 3796 /** 3797 * Total number of results found. 3798 */ 3799 unsigned int results_length; 3800 }; 3801 3802 3803 /** 3804 * Called after 'test_lookup_transfer_details'. 3805 * 3806 * @param cls pointer to 'TestLookupTransferDetails_Closure'. 3807 * @param current_offset offset within transfer details. 3808 * @param details the details that were found. 3809 */ 3810 static void 3811 lookup_transfer_details_cb (void *cls, 3812 unsigned int current_offset, 3813 const struct TALER_TrackTransferDetails *details) 3814 { 3815 struct TestLookupTransferDetails_Closure *cmp = cls; 3816 if (NULL == cmp) 3817 return; 3818 for (unsigned int i = 0; cmp->details_to_cmp_length > i; ++i) 3819 { 3820 if ((0 == GNUNET_memcmp (&cmp->details_to_cmp[i].h_contract_terms, 3821 &details->h_contract_terms)) && 3822 (0 == GNUNET_memcmp (&cmp->details_to_cmp[i].coin_pub, 3823 &details->coin_pub)) && 3824 (GNUNET_OK == TALER_amount_cmp_currency ( 3825 &cmp->details_to_cmp[i].coin_value, 3826 &details->coin_value)) && 3827 (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_value, 3828 &details->coin_value)) && 3829 (GNUNET_OK == TALER_amount_cmp_currency ( 3830 &cmp->details_to_cmp[i].coin_fee, 3831 &details->coin_fee)) && 3832 (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_fee, 3833 &details->coin_fee))) 3834 { 3835 cmp->results_matching[i] += 1; 3836 } 3837 } 3838 cmp->results_length += 1; 3839 } 3840 3841 3842 /** 3843 * Tests looking up details for a wire transfer. 3844 * 3845 * @param exchange_url url to the exchange. 3846 * @param wtid id of the transfer. 3847 * @param details_length the length of @e details. 3848 * @param details the details we expect to be returned. 3849 * @return 1 on success, 0 otherwise. 3850 */ 3851 static int 3852 test_lookup_transfer_details ( 3853 const char *exchange_url, 3854 const struct TALER_WireTransferIdentifierRawP *wtid, 3855 unsigned int details_length, 3856 const struct TALER_TrackTransferDetails *details) 3857 { 3858 unsigned int results_matching[details_length]; 3859 struct TestLookupTransferDetails_Closure cmp = { 3860 .details_to_cmp_length = details_length, 3861 .details_to_cmp = details, 3862 .results_matching = results_matching, 3863 .results_length = 0 3864 }; 3865 memset (results_matching, 3866 0, 3867 sizeof (unsigned int) * details_length); 3868 if (1 != TALER_MERCHANTDB_lookup_transfer_details (pg, 3869 exchange_url, 3870 wtid, 3871 &lookup_transfer_details_cb, 3872 &cmp)) 3873 { 3874 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3875 "Lookup transfer details failed\n"); 3876 return 1; 3877 } 3878 if (details_length != cmp.results_length) 3879 { 3880 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3881 "Lookup transfer details failed: incorrect number of results (%d)\n", 3882 cmp.results_length); 3883 return 1; 3884 } 3885 for (unsigned int i = 0; details_length > i; ++i) 3886 { 3887 if (1 != cmp.results_matching[i]) 3888 { 3889 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 3890 "Lookup transfer details failed: mismatched data\n"); 3891 return 1; 3892 } 3893 } 3894 return 0; 3895 } 3896 3897 3898 /** 3899 * Closure for 'lookup_transfer_details_by_order'. 3900 */ 3901 struct TestLookupTransferDetailsByOrder_Closure 3902 { 3903 /** 3904 * Length of @e transfers_to_cmp. 3905 */ 3906 unsigned int transfers_to_cmp_length; 3907 3908 /** 3909 * List of transfers that we expect to find. 3910 */ 3911 const struct TransferData *transfers_to_cmp; 3912 3913 /** 3914 * How many results match the corresponding element of @e transfers_to_cmp. 3915 */ 3916 unsigned int *results_matching; 3917 3918 /** 3919 * Total number of results found. 3920 */ 3921 unsigned int results_length; 3922 }; 3923 3924 3925 /** 3926 * Called after 'test_lookup_transfer_details_by_order'. 3927 * 3928 * @param cls pointer to 'TestLookupTransferDetailsByOrder_Closure'. 3929 * @param wtid identifier of the transfer found. 3930 * @param exchange_url exchange url of the transfer found. 3931 * @param execution_time when the transfer found occurred. 3932 * @param deposit_value amount of the deposit for the transfer found. 3933 * @param deposit_fee amount of the fee for the deposit of the transfer. 3934 * @param transfer_confirmed did the merchant confirm that a wire transfer with 3935 * @a wtid over the total amount happened? 3936 */ 3937 static void 3938 lookup_transfer_details_order_cb ( 3939 void *cls, 3940 const struct TALER_WireTransferIdentifierRawP *wtid, 3941 const char *exchange_url, 3942 struct GNUNET_TIME_Timestamp execution_time, 3943 const struct TALER_Amount *deposit_value, 3944 const struct TALER_Amount *deposit_fee, 3945 bool transfer_confirmed, 3946 uint64_t expected_transfer_serial_id) 3947 { 3948 struct TestLookupTransferDetailsByOrder_Closure *cmp = cls; 3949 3950 if (NULL == cmp) 3951 return; 3952 cmp->results_length += 1; 3953 for (unsigned int i = 0; i < cmp->transfers_to_cmp_length; ++i) 3954 { 3955 /* Right now lookup_transfer_details_by_order leaves execution_time 3956 uninitialized */ 3957 if ((0 == GNUNET_memcmp (&cmp->transfers_to_cmp[i].wtid, 3958 wtid)) && 3959 (0 == strcmp (cmp->transfers_to_cmp[i].exchange_url, 3960 exchange_url)) && 3961 (GNUNET_OK == 3962 TALER_amount_cmp_currency ( 3963 &cmp->transfers_to_cmp[i].data.total_amount, 3964 deposit_value)) && 3965 (0 == 3966 TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount, 3967 deposit_value)) && 3968 (GNUNET_OK == 3969 TALER_amount_cmp_currency ( 3970 &cmp->transfers_to_cmp[i].deposit_fee, 3971 deposit_fee)) && 3972 (0 == 3973 TALER_amount_cmp (&cmp->transfers_to_cmp[i].deposit_fee, 3974 deposit_fee)) ) 3975 cmp->results_matching[i] += 1; 3976 } 3977 } 3978 3979 3980 /** 3981 * Tests looking up wire transfers associated with an order. 3982 * 3983 * @param order_serial the order to be queried. 3984 * @param transfers_length length of @e transfers. 3985 * @param transfers the transfers we expect to be found. 3986 * @return 0 on success, 1 otherwise. 3987 */ 3988 static int 3989 test_lookup_transfer_details_by_order ( 3990 uint64_t order_serial, 3991 unsigned int transfers_length, 3992 const struct TransferData *transfers) 3993 { 3994 unsigned int results_matching[transfers_length]; 3995 struct TestLookupTransferDetailsByOrder_Closure cmp = { 3996 .transfers_to_cmp_length = transfers_length, 3997 .transfers_to_cmp = transfers, 3998 .results_matching = results_matching, 3999 .results_length = 0 4000 }; 4001 memset (results_matching, 4002 0, 4003 sizeof (unsigned int) * transfers_length); 4004 if (transfers_length != 4005 TALER_MERCHANTDB_lookup_transfer_details_by_order ( 4006 pg, 4007 order_serial, 4008 &lookup_transfer_details_order_cb, 4009 &cmp)) 4010 { 4011 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4012 "Lookup transfer details by order failed\n"); 4013 return 1; 4014 } 4015 if (transfers_length != cmp.results_length) 4016 { 4017 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4018 "Lookup transfer details by order failed: incorrect number of results\n"); 4019 return 1; 4020 } 4021 for (unsigned int i = 0; i < transfers_length; ++i) 4022 { 4023 if (1 != cmp.results_matching[i]) 4024 { 4025 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4026 "Lookup transfer details by order failed: mismatched data\n"); 4027 return 1; 4028 } 4029 } 4030 return 0; 4031 } 4032 4033 4034 /** 4035 * Tests inserting wire fee data for an exchange. 4036 * 4037 * @param signkey the signing key for the exchange. 4038 * @param wire_fee the wire fee data. 4039 * @param expected_result what the database should return. 4040 * @return 0 on success, 1 otherwise. 4041 */ 4042 static int 4043 test_insert_wire_fee (const struct ExchangeSignkeyData *signkey, 4044 const struct WireFeeData *wire_fee, 4045 enum GNUNET_DB_QueryStatus expected_result) 4046 { 4047 TEST_COND_RET_ON_FAIL (expected_result == 4048 TALER_MERCHANTDB_store_wire_fee_by_exchange ( 4049 pg, 4050 &signkey->master_pub, 4051 &wire_fee->h_wire_method, 4052 &wire_fee->fees, 4053 wire_fee->wire_fee_start, 4054 wire_fee->wire_fee_end, 4055 &wire_fee->fee_sig), 4056 "Store wire fee by exchange failed\n"); 4057 return 0; 4058 } 4059 4060 4061 /** 4062 * Tests looking up wire fee data for an exchange. 4063 * 4064 * @param signkey the signing key to use for lookup. 4065 * @param wire_fee_data the wire fee data we expect to find. 4066 * @return 0 on success, 1 otherwise. 4067 */ 4068 static int 4069 test_lookup_wire_fee (const struct ExchangeSignkeyData *signkey, 4070 const struct WireFeeData *wire_fee_data) 4071 { 4072 struct TALER_WireFeeSet fees; 4073 struct GNUNET_TIME_Timestamp start_date; 4074 struct GNUNET_TIME_Timestamp end_date; 4075 struct TALER_MasterSignatureP master_sig; 4076 if (1 != TALER_MERCHANTDB_lookup_wire_fee (pg, 4077 &signkey->master_pub, 4078 wire_fee_data->wire_method, 4079 GNUNET_TIME_timestamp_get (), 4080 &fees, 4081 &start_date, 4082 &end_date, 4083 &master_sig)) 4084 { 4085 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4086 "Lookup wire fee failed\n"); 4087 return 1; 4088 } 4089 if ((0 != 4090 TALER_wire_fee_set_cmp (&wire_fee_data->fees, 4091 &fees)) || 4092 (GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_start, 4093 !=, 4094 start_date)) || 4095 (GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_end, 4096 !=, 4097 end_date)) || 4098 (0 != GNUNET_memcmp (&wire_fee_data->fee_sig, 4099 &master_sig))) 4100 { 4101 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4102 "Lookup wire fee failed: mismatched data\n"); 4103 return 1; 4104 } 4105 return 0; 4106 } 4107 4108 4109 /** 4110 * Closure for 'lookup_transfers'. 4111 */ 4112 struct TestLookupTransfers_Closure 4113 { 4114 /** 4115 * Length of @e transfers_to_cmp. 4116 */ 4117 unsigned int transfers_to_cmp_length; 4118 4119 /** 4120 * The transfers we expect to find. 4121 */ 4122 const struct TransferData *transfers_to_cmp; 4123 4124 /** 4125 * Number of results matching each transfer. 4126 */ 4127 unsigned int *results_matching; 4128 4129 /** 4130 * Total number of results found. 4131 */ 4132 unsigned int results_length; 4133 }; 4134 4135 4136 /** 4137 * Function called after 'test_lookup_transfers'. 4138 * 4139 * @param cls pointer to 'TestLookupTransfers_Closure'. 4140 * @param credit_amount how much was wired to the merchant (minus fees) 4141 * @param wtid wire transfer identifier 4142 * @param payto_uri target account that received the wire transfer 4143 * @param exchange_url base URL of the exchange that made the wire transfer 4144 * @param transfer_serial_id serial number identifying the transfer in the backend 4145 * @param expected_transfer_serial_id serial number identifying the expected transfer in the backend, 0 if not @a expected 4146 * @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_TS 4147 * if it did not yet happen 4148 * @param expected true if the merchant acknowledged the wire transfer reception 4149 */ 4150 static void 4151 lookup_transfers_cb (void *cls, 4152 const struct TALER_Amount *credit_amount, 4153 const struct TALER_WireTransferIdentifierRawP *wtid, 4154 struct TALER_FullPayto payto_uri, 4155 const char *exchange_url, 4156 uint64_t transfer_serial_id, 4157 uint64_t expected_transfer_serial_id, 4158 struct GNUNET_TIME_Absolute execution_time, 4159 bool expected) 4160 { 4161 struct TestLookupTransfers_Closure *cmp = cls; 4162 if (NULL == cmp) 4163 return; 4164 for (unsigned int i = 0; cmp->transfers_to_cmp_length > i; ++i) 4165 { 4166 if ( (GNUNET_OK == 4167 TALER_amount_cmp_currency ( 4168 &cmp->transfers_to_cmp[i].data.total_amount, 4169 credit_amount)) && 4170 (0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount, 4171 credit_amount)) ) 4172 { 4173 cmp->results_matching[i]++; 4174 } 4175 } 4176 cmp->results_length++; 4177 } 4178 4179 4180 /** 4181 * Tests looking up transfers from the database. 4182 * 4183 * @param instance the instance to lookup from. 4184 * @param account the account the transfer was made to. 4185 * @param before do not return transfers before this time. 4186 * @param after do not return transfers after this time. 4187 * @param limit maximum number of transfers to return. 4188 * @param offset row in the database to start with. 4189 * @param filter_verified how to filter verified transfers. 4190 * @param transfers_length length of @e transfers. 4191 * @param transfers the transfers we expect to find. 4192 * @return 0 on success, 1 otherwise. 4193 */ 4194 static int 4195 test_lookup_transfers (const struct InstanceData *instance, 4196 const struct TALER_MERCHANTDB_AccountDetails *account, 4197 struct GNUNET_TIME_Timestamp before, 4198 struct GNUNET_TIME_Timestamp after, 4199 int64_t limit, 4200 uint64_t offset, 4201 unsigned int transfers_length, 4202 const struct TransferData *transfers) 4203 { 4204 unsigned int results_matching[transfers_length]; 4205 struct TestLookupTransfers_Closure cmp = { 4206 .transfers_to_cmp_length = transfers_length, 4207 .transfers_to_cmp = transfers, 4208 .results_matching = results_matching, 4209 .results_length = 0 4210 }; 4211 memset (results_matching, 4212 0, 4213 sizeof (unsigned int) * transfers_length); 4214 if (1 != TALER_MERCHANTDB_lookup_transfers (pg, 4215 instance->instance.id, 4216 account->payto_uri, 4217 before, 4218 after, 4219 limit, 4220 offset, 4221 TALER_EXCHANGE_YNA_ALL, 4222 &lookup_transfers_cb, 4223 &cmp)) 4224 { 4225 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4226 "Lookup transfers failed\n"); 4227 return 1; 4228 } 4229 if (transfers_length != cmp.results_length) 4230 { 4231 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4232 "Lookup transfers failed: incorrect number of results\n"); 4233 return 1; 4234 } 4235 for (unsigned int i = 0; transfers_length > i; ++i) 4236 { 4237 if (1 != cmp.results_matching[i]) 4238 { 4239 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4240 "Lookup transfers failed: mismatched data\n"); 4241 return 1; 4242 } 4243 } 4244 return 0; 4245 } 4246 4247 4248 /** 4249 * Tests inserting a transfer into the database. 4250 * 4251 * @param instance the instance to use. 4252 * @param account the account to transfer to. 4253 * @param transfer the transfer to insert. 4254 * @param expected_result the result we expect the db to return. 4255 * @return 0 on success, 1 otherwise. 4256 */ 4257 static int 4258 test_insert_transfer (const struct InstanceData *instance, 4259 const struct TALER_MERCHANTDB_AccountDetails *account, 4260 const struct TransferData *transfer, 4261 enum GNUNET_DB_QueryStatus expected_result) 4262 { 4263 bool no_instance; 4264 bool no_account; 4265 bool conflict; 4266 4267 TEST_COND_RET_ON_FAIL (expected_result == 4268 TALER_MERCHANTDB_insert_transfer (pg, 4269 instance->instance.id, 4270 transfer->exchange_url, 4271 &transfer->wtid, 4272 &transfer->data.total_amount, 4273 account->payto_uri, 4274 transfer->confirmed, 4275 &no_instance, 4276 &no_account, 4277 &conflict), 4278 "Insert transfer failed\n"); 4279 return 0; 4280 } 4281 4282 4283 /** 4284 * Tests linking a deposit to a transfer. 4285 * 4286 * @param instance the instance that the deposit and transfer are for. 4287 * @param signkey the signing used on the deposit. 4288 * @param order the order the deposit and transfer were made for. 4289 * @param transfer the transfer. 4290 * @param expected_result the result the database should return. 4291 * @param expected_cleared clearance status the database should return. 4292 * @return 0 on success, 1 otherwise. 4293 */ 4294 static int 4295 test_insert_deposit_to_transfer (const struct InstanceData *instance, 4296 const struct ExchangeSignkeyData *signkey, 4297 const struct OrderData *order, 4298 const struct DepositData *deposit, 4299 const struct TransferData *transfer, 4300 enum GNUNET_DB_QueryStatus expected_result, 4301 bool expect_cleared) 4302 { 4303 const struct TALER_EXCHANGE_DepositData deposit_data = { 4304 .exchange_pub = signkey->exchange_pub, 4305 .exchange_sig = deposit->exchange_sig, 4306 .wtid = transfer->wtid, 4307 .execution_time = transfer->data.execution_time, 4308 .coin_contribution = deposit->amount_with_fee 4309 }; 4310 uint64_t deposit_serial = get_deposit_serial (instance, 4311 order, 4312 deposit); 4313 4314 TEST_COND_RET_ON_FAIL (expected_result == 4315 TALER_MERCHANTDB_insert_deposit_to_transfer ( 4316 pg, 4317 deposit_serial, 4318 &deposit->h_wire, 4319 deposit->exchange_url, 4320 &deposit_data), 4321 "insert deposit to transfer failed\n"); 4322 return 0; 4323 } 4324 4325 4326 /** 4327 * Inserts details for a transfer into the database. 4328 * 4329 * @param instance the instance the transfer is in. 4330 * @param account the destination account for the transfer. 4331 * @param transfer the transfer we are adding details to. 4332 * @param expected_result the result expected from the db. 4333 * @return 0 on success, 1 otherwise. 4334 */ 4335 static int 4336 test_insert_transfer_details ( 4337 const struct InstanceData *instance, 4338 const struct TALER_MERCHANTDB_AccountDetails *account, 4339 const struct TransferData *transfer, 4340 enum GNUNET_DB_QueryStatus expected_result) 4341 { 4342 TEST_COND_RET_ON_FAIL (expected_result == 4343 TALER_MERCHANTDB_insert_transfer_details ( 4344 pg, 4345 instance->instance.id, 4346 transfer->exchange_url, 4347 account->payto_uri, 4348 &transfer->wtid, 4349 &transfer->data), 4350 "Insert transfer details failed\n"); 4351 return 0; 4352 } 4353 4354 4355 /** 4356 * Container for data used when testing transfers. 4357 */ 4358 struct TestTransfers_Closure 4359 { 4360 /** 4361 * The instance. 4362 */ 4363 struct InstanceData instance; 4364 4365 /** 4366 * The account. 4367 */ 4368 struct TALER_MERCHANTDB_AccountDetails account; 4369 4370 /** 4371 * The exchange signing key. 4372 */ 4373 struct ExchangeSignkeyData signkey; 4374 4375 /** 4376 * The order data. 4377 */ 4378 struct OrderData order; 4379 4380 /** 4381 * The deposit data. 4382 */ 4383 struct DepositData deposit; 4384 4385 /** 4386 * Wire fee data. 4387 */ 4388 struct WireFeeData wire_fee[2]; 4389 4390 /** 4391 * The transfers. 4392 */ 4393 struct TransferData transfers[1]; 4394 }; 4395 4396 4397 /** 4398 * Prepares for testing transfers. 4399 * 4400 * @param cls the test data. 4401 */ 4402 static void 4403 pre_test_transfers (struct TestTransfers_Closure *cls) 4404 { 4405 /* Instance */ 4406 make_instance ("test_inst_transfers", 4407 &cls->instance); 4408 4409 /* Account */ 4410 make_account (&cls->account); 4411 cls->account.instance_id = cls->instance.instance.id; 4412 /* Order */ 4413 make_order ("test_transfers_od_1", 4414 &cls->order); 4415 4416 /* Signing key */ 4417 make_exchange_signkey (&cls->signkey); 4418 4419 /* Deposit */ 4420 make_deposit (&cls->instance, 4421 &cls->account, 4422 &cls->order, 4423 &cls->signkey, 4424 &cls->deposit); 4425 4426 /* Wire fee */ 4427 make_wire_fee (&cls->signkey, 4428 &cls->wire_fee[0]); 4429 make_wire_fee (&cls->signkey, 4430 &cls->wire_fee[1]); 4431 cls->wire_fee[1].wire_method = "wire-method-2"; 4432 GNUNET_CRYPTO_hash (cls->wire_fee[1].wire_method, 4433 strlen (cls->wire_fee[1].wire_method) + 1, 4434 &cls->wire_fee[1].h_wire_method); 4435 4436 /* Transfers */ 4437 make_transfer (&cls->signkey, 4438 1, 4439 &cls->deposit, 4440 &cls->transfers[0]); 4441 cls->transfers[0].confirmed = true; 4442 } 4443 4444 4445 /** 4446 * Cleans up after testing transfers. 4447 * 4448 * @param cls the test data. 4449 */ 4450 static void 4451 post_test_transfers (struct TestTransfers_Closure *cls) 4452 { 4453 GNUNET_array_grow (cls->transfers->data.details, 4454 cls->transfers->data.details_length, 4455 0); 4456 free_instance_data (&cls->instance); 4457 free_order_data (&cls->order); 4458 } 4459 4460 4461 /** 4462 * Runs the tests for transfers. 4463 * 4464 * @param cls the test data. 4465 * @return 0 on success, 1 otherwise. 4466 */ 4467 static int 4468 run_test_transfers (struct TestTransfers_Closure *cls) 4469 { 4470 uint64_t order_serial; 4471 struct TALER_WireFeeSet fees; 4472 4473 /* Test lookup wire fee fails when it isn't in the db */ 4474 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 4475 TALER_MERCHANTDB_lookup_wire_fee (pg, 4476 &cls->signkey.master_pub, 4477 cls->wire_fee[0].wire_method, 4478 GNUNET_TIME_timestamp_get (), 4479 &fees, 4480 NULL, 4481 NULL, 4482 NULL), 4483 "Lookup wire fee failed\n"); 4484 /* Test store wire fee by exchange */ 4485 TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey, 4486 &cls->wire_fee[0], 4487 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4488 /* Test double insertion fails */ 4489 TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey, 4490 &cls->wire_fee[0], 4491 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 4492 /* Test lookup wire fee by exchange */ 4493 TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey, 4494 &cls->wire_fee[0])); 4495 /* Test different wire fees for different methods. */ 4496 TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey, 4497 &cls->wire_fee[1], 4498 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4499 TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey, 4500 &cls->wire_fee[1])); 4501 /* Insert the instance */ 4502 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 4503 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4504 /* Insert the account */ 4505 TEST_RET_ON_FAIL (test_insert_account (&cls->instance, 4506 &cls->account, 4507 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4508 /* Insert a signing key */ 4509 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 4510 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4511 /* Insert an order */ 4512 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 4513 &cls->order, 4514 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4515 /* Insert contract terms */ 4516 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 4517 &cls->order, 4518 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4519 order_serial = get_order_serial (&cls->instance, 4520 &cls->order); 4521 /* Insert the deposit */ 4522 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 4523 &cls->signkey, 4524 &cls->deposit, 4525 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4526 /* Mark as paid, otherwise wiring doesn't make sense. */ 4527 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 4528 &cls->order, 4529 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4530 /* Insert the transfer */ 4531 TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance, 4532 &cls->account, 4533 &cls->transfers[0], 4534 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4535 TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance, 4536 &cls->account, 4537 &cls->transfers[0], 4538 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4539 TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, 4540 &cls->signkey, 4541 &cls->order, 4542 &cls->deposit, 4543 &cls->transfers[0], 4544 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 4545 false)); 4546 TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, 4547 &cls->signkey, 4548 &cls->order, 4549 &cls->deposit, 4550 &cls->transfers[0], 4551 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 4552 false)); 4553 TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance, 4554 &cls->account, 4555 &cls->transfers[0], 4556 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 4557 /* Inserting deposits/transfers will automatically mark as wired. */ 4558 TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id, 4559 cls->order.id, 4560 NULL, 4561 true, 4562 true)); 4563 TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance, 4564 &cls->signkey, 4565 &cls->order, 4566 &cls->deposit, 4567 &cls->transfers[0], 4568 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT, 4569 true)); 4570 4571 TEST_RET_ON_FAIL (test_lookup_transfer_summary (cls->deposit.exchange_url, 4572 &cls->transfers[0].wtid, 4573 cls->order.id, 4574 &cls->deposit.amount_with_fee, 4575 &cls->deposit.deposit_fee)); 4576 TEST_RET_ON_FAIL (test_lookup_transfer_details (cls->deposit.exchange_url, 4577 &cls->transfers[0].wtid, 4578 1, 4579 &cls->transfers[0].data. 4580 details[0])); 4581 TEST_RET_ON_FAIL (test_lookup_transfer_details_by_order (order_serial, 4582 1, 4583 &cls->transfers[0])); 4584 TEST_RET_ON_FAIL (test_lookup_transfers (&cls->instance, 4585 &cls->account, 4586 GNUNET_TIME_UNIT_FOREVER_TS, 4587 GNUNET_TIME_UNIT_ZERO_TS, 4588 8, 4589 0, 4590 1, 4591 &cls->transfers[0])); 4592 return 0; 4593 } 4594 4595 4596 /** 4597 * Takes care of all work for testing transfers. 4598 * 4599 * @return 0 on success, 1 otherwise. 4600 */ 4601 static int 4602 test_transfers (void) 4603 { 4604 struct TestTransfers_Closure test_cls; 4605 int test_result; 4606 4607 pre_test_transfers (&test_cls); 4608 test_result = run_test_transfers (&test_cls); 4609 post_test_transfers (&test_cls); 4610 return test_result; 4611 } 4612 4613 4614 /** 4615 * Closure for testing lookup_refunds. 4616 */ 4617 struct TestLookupRefunds_Closure 4618 { 4619 /** 4620 * Length of @e coin_pub_to_cmp and @e refund_amount_to_cmp. 4621 */ 4622 unsigned int refunds_to_cmp_length; 4623 4624 /** 4625 * Public keys of the refunded coins. 4626 */ 4627 const struct TALER_CoinSpendPublicKeyP *coin_pub_to_cmp; 4628 4629 /** 4630 * Amount of each refund. 4631 */ 4632 const struct TALER_Amount *refund_amount_to_cmp; 4633 4634 /** 4635 * Number of results matching each refund provided. 4636 */ 4637 unsigned int *results_matching; 4638 4639 /** 4640 * Total number of results returned; 4641 */ 4642 unsigned int results_length; 4643 }; 4644 4645 4646 /** 4647 * Called after test_lookup_refunds. 4648 * @param cls pointer to a TestLookupRefunds_Closure. 4649 * @param coin_pub the public key of the coin for the refund found. 4650 * @param refund_amount the amount of the refund found. 4651 */ 4652 static void 4653 lookup_refunds_cb (void *cls, 4654 const struct TALER_CoinSpendPublicKeyP *coin_pub, 4655 const struct TALER_Amount *refund_amount) 4656 { 4657 struct TestLookupRefunds_Closure *cmp = cls; 4658 if (NULL == cmp) 4659 return; 4660 cmp->results_length += 1; 4661 for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i) 4662 { 4663 if ((0 == GNUNET_memcmp (&cmp->coin_pub_to_cmp[i], 4664 coin_pub)) && 4665 (GNUNET_OK == TALER_amount_cmp_currency (&cmp->refund_amount_to_cmp[i], 4666 refund_amount)) && 4667 (0 == TALER_amount_cmp (&cmp->refund_amount_to_cmp[i], 4668 refund_amount))) 4669 { 4670 cmp->results_matching[i] += 1; 4671 } 4672 } 4673 } 4674 4675 4676 /** 4677 * Tests looking up refunds from the database. 4678 * @param instance the instance to look up refunds for. 4679 * @param h_contract_terms hash of the contract terms the refunds are for. 4680 * @param refunds_length length of @e coin_pubs and @e refund_amounts. 4681 * @param coin_pubs the public keys of the coins that were refunded. 4682 * @param refund_amounts the amounts of the coins that were refunded. 4683 * 4684 * @return 0 on success, 1 otherwise. 4685 */ 4686 static int 4687 test_lookup_refunds (const struct InstanceData *instance, 4688 const struct TALER_PrivateContractHashP *h_contract_terms, 4689 unsigned int refunds_length, 4690 const struct TALER_CoinSpendPublicKeyP *coin_pubs, 4691 const struct TALER_Amount *refund_amounts) 4692 { 4693 unsigned int results_matching[refunds_length]; 4694 struct TestLookupRefunds_Closure cmp = { 4695 .refunds_to_cmp_length = refunds_length, 4696 .coin_pub_to_cmp = coin_pubs, 4697 .refund_amount_to_cmp = refund_amounts, 4698 .results_matching = results_matching, 4699 .results_length = 0 4700 }; 4701 memset (results_matching, 0, sizeof (unsigned int) * refunds_length); 4702 if (1 != TALER_MERCHANTDB_lookup_refunds (pg, 4703 instance->instance.id, 4704 h_contract_terms, 4705 &lookup_refunds_cb, 4706 &cmp)) 4707 { 4708 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4709 "Lookup refunds failed\n"); 4710 return 1; 4711 } 4712 if (refunds_length != cmp.results_length) 4713 { 4714 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4715 "Lookup refunds failed: incorrect number of results returned\n") 4716 ; 4717 return 1; 4718 } 4719 for (unsigned int i = 0; refunds_length > i; ++i) 4720 { 4721 if (1 != cmp.results_matching[i]) 4722 { 4723 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4724 "Lookup refunds failed: mismatched data\n"); 4725 return 1; 4726 } 4727 } 4728 return 0; 4729 } 4730 4731 4732 /** 4733 * Container for refund data. 4734 */ 4735 struct RefundData 4736 { 4737 /** 4738 * When the refund occurred. 4739 */ 4740 struct GNUNET_TIME_Timestamp timestamp; 4741 4742 /** 4743 * Reason for the refund. 4744 */ 4745 const char *reason; 4746 4747 /** 4748 * Amount of the refund. 4749 */ 4750 struct TALER_Amount refund_amount; 4751 4752 /** 4753 * Public key of the coin that was refunded. 4754 */ 4755 const struct TALER_CoinSpendPublicKeyP *coin_pub; 4756 4757 /** 4758 * URL to the exchange that did the refund. 4759 */ 4760 const char *exchange_url; 4761 }; 4762 4763 4764 /** 4765 * Creates a refund for testing with. 4766 * @param deposit the deposit being refunded. 4767 * @param refund the data to set. 4768 */ 4769 static void 4770 make_refund (const struct DepositData *deposit, 4771 struct RefundData *refund) 4772 { 4773 refund->timestamp = GNUNET_TIME_timestamp_get (); 4774 refund->reason = "some reason"; 4775 refund->refund_amount = deposit->amount_with_fee; 4776 refund->coin_pub = &deposit->coin_pub; 4777 refund->exchange_url = deposit->exchange_url; 4778 } 4779 4780 4781 /** 4782 * Container for proof of a refund. 4783 */ 4784 struct RefundProofData 4785 { 4786 /** 4787 * Fee charged for the refund. 4788 */ 4789 struct TALER_Amount refund_fee; 4790 4791 /** 4792 * The exchange's signature on the refund. 4793 */ 4794 struct TALER_ExchangeSignatureP exchange_sig; 4795 }; 4796 4797 4798 /** 4799 * Closure for testing lookup_refunds_detailed. 4800 */ 4801 struct TestLookupRefundsDetailed_Closure 4802 { 4803 /** 4804 * Length of @e refunds_to_cmp. 4805 */ 4806 unsigned int refunds_to_cmp_length; 4807 4808 /** 4809 * The refunds we expect to find. 4810 */ 4811 const struct RefundData *refunds_to_cmp; 4812 4813 /** 4814 * Whether to compare the timestamps or not (if we don't have direct control 4815 * of the timestamps, there will be differences on the order of microseconds 4816 * that can be ignored). 4817 */ 4818 bool cmp_timestamps; 4819 4820 /** 4821 * The number of results matching each refund. 4822 */ 4823 unsigned int *results_matching; 4824 4825 /** 4826 * The total number of results from the db. 4827 */ 4828 unsigned int results_length; 4829 }; 4830 4831 4832 /** 4833 * Called after test_lookup_refunds_detailed. 4834 * @param cls pointer to a TestLookupRefundsDetailed_Closure. 4835 * @param refund_serial unique serial number of the refund 4836 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 4837 * @param coin_pub public coin from which the refund comes from 4838 * @param exchange_url URL of the exchange that issued @a coin_pub 4839 * @param rtransaction_id identificator of the refund 4840 * @param reason human-readable explanation of the refund 4841 * @param refund_amount refund amount which is being taken from @a coin_pub 4842 * @param pending true if this refund has not been processed by the wallet/exchange 4843 */ 4844 static void 4845 lookup_refunds_detailed_cb (void *cls, 4846 uint64_t refund_serial, 4847 struct GNUNET_TIME_Timestamp timestamp, 4848 const struct TALER_CoinSpendPublicKeyP *coin_pub, 4849 const char *exchange_url, 4850 uint64_t rtransaction_id, 4851 const char *reason, 4852 const struct TALER_Amount *refund_amount, 4853 bool pending) 4854 { 4855 struct TestLookupRefundsDetailed_Closure *cmp = cls; 4856 if (NULL == cmp) 4857 return; 4858 cmp->results_length += 1; 4859 for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i) 4860 { 4861 if (((GNUNET_TIME_timestamp_cmp (cmp->refunds_to_cmp[i].timestamp, 4862 ==, 4863 timestamp)) || 4864 ! cmp->cmp_timestamps) && 4865 (0 == GNUNET_memcmp (cmp->refunds_to_cmp[i].coin_pub, 4866 coin_pub)) && 4867 (0 == strcmp (cmp->refunds_to_cmp[i].exchange_url, 4868 exchange_url)) && 4869 (0 == strcmp (cmp->refunds_to_cmp[i].reason, 4870 reason)) && 4871 (GNUNET_OK == 4872 TALER_amount_cmp_currency ( 4873 &cmp->refunds_to_cmp[i].refund_amount, 4874 refund_amount)) && 4875 (0 == TALER_amount_cmp (&cmp->refunds_to_cmp[i].refund_amount, 4876 refund_amount))) 4877 { 4878 cmp->results_matching[i] += 1; 4879 } 4880 } 4881 } 4882 4883 4884 /** 4885 * Tests looking up refunds with details from the database. 4886 * @param instance the instance to lookup from. 4887 * @param h_contract_terms the contract terms the refunds were made for. 4888 * @param cmp_timestamps whether to compare timestamps or not. 4889 * @param refunds_length length of @e refunds. 4890 * @param refunds the refunds we expect to be returned. 4891 * 4892 * @return 0 on success, 1 otherwise. 4893 */ 4894 static int 4895 test_lookup_refunds_detailed ( 4896 const struct InstanceData *instance, 4897 const struct TALER_PrivateContractHashP *h_contract_terms, 4898 bool cmp_timestamps, 4899 unsigned int refunds_length, 4900 const struct RefundData *refunds) 4901 { 4902 unsigned int results_matching[refunds_length]; 4903 struct TestLookupRefundsDetailed_Closure cmp = { 4904 .refunds_to_cmp_length = refunds_length, 4905 .refunds_to_cmp = refunds, 4906 .cmp_timestamps = cmp_timestamps, 4907 .results_matching = results_matching, 4908 .results_length = 0 4909 }; 4910 memset (results_matching, 0, sizeof (unsigned int) * refunds_length); 4911 if (0 > TALER_MERCHANTDB_lookup_refunds_detailed (pg, 4912 instance->instance.id, 4913 h_contract_terms, 4914 &lookup_refunds_detailed_cb, 4915 &cmp)) 4916 { 4917 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4918 "Lookup refunds detailed failed\n"); 4919 return 1; 4920 } 4921 if (refunds_length != cmp.results_length) 4922 { 4923 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4924 "Lookup refunds detailed failed: incorrect number of results\n") 4925 ; 4926 return 1; 4927 } 4928 for (unsigned int i = 0; refunds_length > i; ++i) 4929 { 4930 if (1 != cmp.results_matching[i]) 4931 { 4932 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 4933 "Lookup refunds detailed failed: mismatched data\n"); 4934 return 1; 4935 } 4936 } 4937 return 0; 4938 } 4939 4940 4941 /** 4942 * Closure for get_refund_serial. 4943 */ 4944 struct LookupRefundSerial_Closure 4945 { 4946 /** 4947 * The refund we are looking up the id for. 4948 */ 4949 const struct RefundData *refund; 4950 4951 /** 4952 * The row number found. 4953 */ 4954 uint64_t serial; 4955 }; 4956 4957 4958 /** 4959 * Called after get_refund_serial. 4960 * @param cls pointer to a LookupRefundSerial_Closure. 4961 * @param refund_serial unique serial number of the refund 4962 * @param timestamp time of the refund (for grouping of refunds in the wallet UI) 4963 * @param coin_pub public coin from which the refund comes from 4964 * @param exchange_url URL of the exchange that issued @a coin_pub 4965 * @param rtransaction_id identificator of the refund 4966 * @param reason human-readable explanation of the refund 4967 * @param refund_amount refund amount which is being taken from @a coin_pub 4968 * @param pending true if this refund has not been processed by the wallet/exchange 4969 */ 4970 static void 4971 get_refund_serial_cb (void *cls, 4972 uint64_t refund_serial, 4973 struct GNUNET_TIME_Timestamp timestamp, 4974 const struct TALER_CoinSpendPublicKeyP *coin_pub, 4975 const char *exchange_url, 4976 uint64_t rtransaction_id, 4977 const char *reason, 4978 const struct TALER_Amount *refund_amount, 4979 bool pending) 4980 { 4981 struct LookupRefundSerial_Closure *lookup_cls = cls; 4982 if (NULL == lookup_cls) 4983 return; 4984 if ((GNUNET_TIME_timestamp_cmp (lookup_cls->refund->timestamp, 4985 ==, 4986 timestamp)) && 4987 (0 == GNUNET_memcmp (lookup_cls->refund->coin_pub, 4988 coin_pub)) && 4989 (0 == strcmp (lookup_cls->refund->exchange_url, 4990 exchange_url)) && 4991 (0 == strcmp (lookup_cls->refund->reason, 4992 reason)) && 4993 (GNUNET_OK == 4994 TALER_amount_cmp_currency ( 4995 &lookup_cls->refund->refund_amount, 4996 refund_amount)) && 4997 (0 == TALER_amount_cmp (&lookup_cls->refund->refund_amount, 4998 refund_amount))) 4999 lookup_cls->serial = refund_serial; 5000 } 5001 5002 5003 /** 5004 * Utility function for getting the database row number of a refund. 5005 * @param instance the instance associated with the refund. 5006 * @param h_contract_terms the contract terms the refund was made with. 5007 * @param refund the refund we are querying the row number of. 5008 * 5009 * @return the row number of the refund. 5010 */ 5011 static uint64_t 5012 get_refund_serial (const struct InstanceData *instance, 5013 const struct TALER_PrivateContractHashP *h_contract_terms, 5014 const struct RefundData *refund) 5015 { 5016 struct LookupRefundSerial_Closure lookup_cls = { 5017 .refund = refund, 5018 .serial = 0 5019 }; 5020 5021 GNUNET_assert (0 < TALER_MERCHANTDB_lookup_refunds_detailed (pg, 5022 instance->instance.id, 5023 h_contract_terms, 5024 &get_refund_serial_cb, 5025 &lookup_cls)); 5026 GNUNET_assert (0 != lookup_cls.serial); 5027 5028 return lookup_cls.serial; 5029 } 5030 5031 5032 /** 5033 * Tests looking up proof of a refund. 5034 * @param refund_serial the row number of the refund. 5035 * @param expected_exchange_sig the exchange signature we are expecting. 5036 * @param expected_exchange_pub the exchange public key we are expecting. 5037 * 5038 * @return 0 on success, 1 otherwise. 5039 */ 5040 static int 5041 test_lookup_refund_proof (uint64_t refund_serial, 5042 const struct 5043 TALER_ExchangeSignatureP *expected_exchange_sig, 5044 const struct 5045 TALER_ExchangePublicKeyP *expected_exchange_pub) 5046 { 5047 struct TALER_ExchangeSignatureP exchange_sig; 5048 struct TALER_ExchangePublicKeyP exchange_pub; 5049 if (1 != TALER_MERCHANTDB_lookup_refund_proof (pg, 5050 refund_serial, 5051 &exchange_sig, 5052 &exchange_pub)) 5053 { 5054 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5055 "Lookup refund proof failed\n"); 5056 return 1; 5057 } 5058 if ((0 != GNUNET_memcmp (expected_exchange_sig, 5059 &exchange_sig)) || 5060 (0 != GNUNET_memcmp (expected_exchange_pub, 5061 &exchange_pub))) 5062 { 5063 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5064 "Lookup refund proof failed: mismatched data\n"); 5065 return 1; 5066 } 5067 return 0; 5068 } 5069 5070 5071 /** 5072 * Closure for testing refund functionality. 5073 */ 5074 struct TestRefunds_Closure 5075 { 5076 /** 5077 * The instance. 5078 */ 5079 struct InstanceData instance; 5080 5081 /** 5082 * The merchant account. 5083 */ 5084 struct TALER_MERCHANTDB_AccountDetails account; 5085 5086 /** 5087 * The exchange signing key. 5088 */ 5089 struct ExchangeSignkeyData signkey; 5090 5091 /** 5092 * The order data. 5093 */ 5094 struct OrderData orders[2]; 5095 5096 /** 5097 * The deposit data. 5098 */ 5099 struct DepositData deposits[3]; 5100 5101 /** 5102 * The refund data. 5103 */ 5104 struct RefundData refunds[3]; 5105 5106 /** 5107 * The refund proof data. 5108 */ 5109 struct RefundProofData refund_proof; 5110 }; 5111 5112 5113 /** 5114 * Prepares for testing refunds. 5115 * @param cls the closure to initialize. 5116 */ 5117 static void 5118 pre_test_refunds (struct TestRefunds_Closure *cls) 5119 { 5120 /* Instance */ 5121 make_instance ("test_inst_refunds", 5122 &cls->instance); 5123 5124 /* Account */ 5125 make_account (&cls->account); 5126 cls->account.instance_id = cls->instance.instance.id; 5127 /* Signing key */ 5128 make_exchange_signkey (&cls->signkey); 5129 5130 /* Order */ 5131 make_order ("test_refunds_od_0", 5132 &cls->orders[0]); 5133 make_order ("test_refunds_od_1", 5134 &cls->orders[1]); 5135 5136 /* Deposit */ 5137 make_deposit (&cls->instance, 5138 &cls->account, 5139 &cls->orders[0], 5140 &cls->signkey, 5141 &cls->deposits[0]); 5142 make_deposit (&cls->instance, 5143 &cls->account, 5144 &cls->orders[0], 5145 &cls->signkey, 5146 &cls->deposits[1]); 5147 make_deposit (&cls->instance, 5148 &cls->account, 5149 &cls->orders[1], 5150 &cls->signkey, 5151 &cls->deposits[2]); 5152 5153 /* Refund */ 5154 make_refund (&cls->deposits[0], 5155 &cls->refunds[0]); 5156 make_refund (&cls->deposits[2], 5157 &cls->refunds[1]); 5158 make_refund (&cls->deposits[2], 5159 &cls->refunds[2]); 5160 GNUNET_assert (GNUNET_OK == 5161 TALER_string_to_amount ("EUR:10.00", 5162 &cls->refunds[1].refund_amount)); 5163 cls->refunds[1].reason = "refund 1"; 5164 GNUNET_assert (GNUNET_OK == 5165 TALER_string_to_amount ("EUR:10.00", 5166 &cls->refunds[2].refund_amount)); 5167 cls->refunds[2].reason = "refund 2"; 5168 5169 /* Refund proof */ 5170 GNUNET_assert (GNUNET_OK == 5171 TALER_string_to_amount ("EUR:0.02", 5172 &cls->refund_proof.refund_fee)); 5173 memset (&cls->refund_proof.exchange_sig, 5174 42, 5175 sizeof (cls->refund_proof.exchange_sig)); 5176 } 5177 5178 5179 /** 5180 * Cleans up after testing refunds. 5181 * @param cls the closure. 5182 */ 5183 static void 5184 post_test_refunds (struct TestRefunds_Closure *cls) 5185 { 5186 free_instance_data (&cls->instance); 5187 free_order_data (&cls->orders[0]); 5188 free_order_data (&cls->orders[1]); 5189 } 5190 5191 5192 /** 5193 * Runs the refund tests. 5194 * @param cls the closure. 5195 * 5196 * @return 0 on success, 1 otherwise. 5197 */ 5198 static int 5199 run_test_refunds (struct TestRefunds_Closure *cls) 5200 { 5201 struct TALER_Amount inc; 5202 uint64_t refund_serial; 5203 5204 /* Insert an instance */ 5205 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 5206 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5207 /* Insert an account */ 5208 TEST_RET_ON_FAIL (test_insert_account (&cls->instance, 5209 &cls->account, 5210 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5211 /* Insert an order */ 5212 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 5213 &cls->orders[0], 5214 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5215 /* Insert contract terms */ 5216 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 5217 &cls->orders[0], 5218 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5219 /* Insert a signing key */ 5220 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 5221 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5222 /* Insert a deposit */ 5223 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 5224 &cls->signkey, 5225 &cls->deposits[0], 5226 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5227 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 5228 &cls->signkey, 5229 &cls->deposits[1], 5230 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5231 /* Mark as paid */ 5232 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 5233 &cls->orders[0], 5234 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5235 /* Test refund coin */ 5236 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 5237 TALER_MERCHANTDB_refund_coin (pg, 5238 cls->instance.instance.id, 5239 &cls->deposits[0].h_contract_terms 5240 , 5241 cls->refunds[0].timestamp, 5242 cls->refunds[0].coin_pub, 5243 cls->refunds[0].reason), 5244 "Refund coin failed\n"); 5245 refund_serial = get_refund_serial (&cls->instance, 5246 &cls->deposits[0].h_contract_terms, 5247 &cls->refunds[0]); 5248 /* Test double refund fails */ 5249 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 5250 TALER_MERCHANTDB_refund_coin (pg, 5251 cls->instance.instance.id, 5252 &cls->deposits[0].h_contract_terms 5253 , 5254 cls->refunds[0].timestamp, 5255 cls->refunds[0].coin_pub, 5256 cls->refunds[0].reason), 5257 "Refund coin failed\n"); 5258 /* Test lookup refunds */ 5259 TEST_RET_ON_FAIL (test_lookup_refunds (&cls->instance, 5260 &cls->deposits[0].h_contract_terms, 5261 1, 5262 cls->refunds[0].coin_pub, 5263 &cls->refunds[0].refund_amount)); 5264 /* Test lookup refunds detailed */ 5265 TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance, 5266 &cls->deposits[0]. 5267 h_contract_terms, 5268 true, 5269 1, 5270 &cls->refunds[0])); 5271 /* Test insert refund proof */ 5272 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 5273 TALER_MERCHANTDB_insert_refund_proof (pg, 5274 refund_serial, 5275 &cls->refund_proof. 5276 exchange_sig, 5277 &cls->signkey.exchange_pub 5278 ), 5279 "Insert refund proof failed\n"); 5280 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == 5281 TALER_MERCHANTDB_insert_refund_proof (pg, 5282 refund_serial, 5283 &cls->refund_proof. 5284 exchange_sig, 5285 &cls->signkey.exchange_pub 5286 ), 5287 "Insert refund proof failed\n"); 5288 /* Test that we can't give too much in refunds */ 5289 GNUNET_assert (GNUNET_OK == 5290 TALER_string_to_amount ("EUR:1000.00", 5291 &inc)); 5292 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_TOO_HIGH == 5293 TALER_MERCHANTDB_increase_refund (pg, 5294 cls->instance.instance.id, 5295 cls->orders[0].id, 5296 &inc, 5297 NULL, 5298 NULL, 5299 "more"), 5300 "Increase refund failed\n"); 5301 /* Test increase refund */ 5302 GNUNET_assert (GNUNET_OK == 5303 TALER_string_to_amount ("EUR:1.00", 5304 &inc)); 5305 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS == 5306 TALER_MERCHANTDB_increase_refund (pg, 5307 cls->instance.instance.id, 5308 cls->orders[0].id, 5309 &inc, 5310 NULL, 5311 NULL, 5312 "more"), 5313 "Increase refund failed\n"); 5314 /* Test lookup refund proof */ 5315 TEST_RET_ON_FAIL (test_lookup_refund_proof (1, 5316 &cls->refund_proof.exchange_sig, 5317 &cls->signkey.exchange_pub)); 5318 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 5319 &cls->orders[1], 5320 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5321 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 5322 &cls->orders[1], 5323 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5324 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 5325 &cls->signkey, 5326 &cls->deposits[2], 5327 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5328 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 5329 &cls->orders[1], 5330 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5331 /* Test refunding a small amount of the coin, then increasing it */ 5332 GNUNET_assert (GNUNET_OK == 5333 TALER_string_to_amount ("EUR:10.00", 5334 &inc)); 5335 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS == 5336 TALER_MERCHANTDB_increase_refund (pg, 5337 cls->instance.instance.id, 5338 cls->orders[1].id, 5339 &inc, 5340 NULL, 5341 NULL, 5342 cls->refunds[1].reason), 5343 "Increase refund failed\n"); 5344 GNUNET_assert (GNUNET_OK == 5345 TALER_string_to_amount ("EUR:20.00", 5346 &inc)); 5347 TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS == 5348 TALER_MERCHANTDB_increase_refund (pg, 5349 cls->instance.instance.id, 5350 cls->orders[1].id, 5351 &inc, 5352 NULL, 5353 NULL, 5354 cls->refunds[2].reason), 5355 "Increase refund failed\n"); 5356 TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance, 5357 &cls->deposits[2]. 5358 h_contract_terms, 5359 false, 5360 2, 5361 &cls->refunds[1])); 5362 return 0; 5363 } 5364 5365 5366 /** 5367 * All logic for testing refunds. 5368 * 5369 * @return 0 on success, 1 otherwise. 5370 */ 5371 static int 5372 test_refunds (void) 5373 { 5374 struct TestRefunds_Closure test_cls; 5375 int test_result; 5376 5377 pre_test_refunds (&test_cls); 5378 test_result = run_test_refunds (&test_cls); 5379 post_test_refunds (&test_cls); 5380 return test_result; 5381 } 5382 5383 5384 /** 5385 * Convenience function that reverses an array of orders. 5386 * @param orders_length length of @e orders. 5387 * @param orders the array to reverse. 5388 */ 5389 static void 5390 reverse_order_data_array (unsigned int orders_length, 5391 struct OrderData *orders) 5392 { 5393 struct OrderData tmp[orders_length]; 5394 for (unsigned int i = 0; i < orders_length; ++i) 5395 tmp[i] = orders[orders_length - 1 - i]; 5396 for (unsigned int i = 0; i < orders_length; ++i) 5397 orders[i] = tmp[i]; 5398 } 5399 5400 5401 /** 5402 * Closure for testing all the filters for looking up orders. 5403 */ 5404 struct TestLookupOrdersAllFilters_Closure 5405 { 5406 /** 5407 * The instance. 5408 */ 5409 struct InstanceData instance; 5410 5411 /** 5412 * The merchant account. 5413 */ 5414 struct TALER_MERCHANTDB_AccountDetails account; 5415 5416 /** 5417 * The exchange signing key. 5418 */ 5419 struct ExchangeSignkeyData signkey; 5420 5421 /** 5422 * The array of order ids. 5423 */ 5424 char *order_ids[64]; 5425 5426 /** 5427 * The array of orders. 5428 */ 5429 struct OrderData orders[64]; 5430 5431 /** 5432 * The array of deposits. 5433 */ 5434 struct DepositData deposits[64]; 5435 5436 /** 5437 * The array of refunds. 5438 */ 5439 struct RefundData refunds[64]; 5440 }; 5441 5442 5443 /** 5444 * Sets up for testing lookup order filters. 5445 * @param cls the closure. 5446 */ 5447 static void 5448 pre_test_lookup_orders_all_filters ( 5449 struct TestLookupOrdersAllFilters_Closure *cls) 5450 { 5451 make_instance ("test_inst_lookup_orders_all_filters", 5452 &cls->instance); 5453 make_account (&cls->account); 5454 cls->account.instance_id = cls->instance.instance.id; 5455 make_exchange_signkey (&cls->signkey); 5456 for (unsigned int i = 0; i < 64; ++i) 5457 { 5458 (void) GNUNET_asprintf (&cls->order_ids[i], 5459 "test_orders_filter_od_%u", 5460 i); 5461 make_order (cls->order_ids[i], 5462 &cls->orders[i]); 5463 GNUNET_assert (0 == 5464 json_object_set_new (cls->orders[i].contract, 5465 "order_id", 5466 json_string (cls->order_ids[i]))); 5467 make_deposit (&cls->instance, 5468 &cls->account, 5469 &cls->orders[i], 5470 &cls->signkey, 5471 &cls->deposits[i]); 5472 make_refund (&cls->deposits[i], 5473 &cls->refunds[i]); 5474 } 5475 } 5476 5477 5478 /** 5479 * Cleans up after testing lookup order filters. 5480 * @param cls the closure. 5481 */ 5482 static void 5483 post_test_lookup_orders_all_filters ( 5484 struct TestLookupOrdersAllFilters_Closure *cls) 5485 { 5486 free_instance_data (&cls->instance); 5487 for (unsigned int i = 0; i < 64; ++i) 5488 { 5489 free_order_data (&cls->orders[i]); 5490 GNUNET_free (cls->order_ids[i]); 5491 } 5492 } 5493 5494 5495 /** 5496 * Runs the tests for lookup order filters. 5497 * @param cls the closure. 5498 * 5499 * @return 0 on success, 1 otherwise. 5500 */ 5501 static int 5502 run_test_lookup_orders_all_filters ( 5503 struct TestLookupOrdersAllFilters_Closure *cls) 5504 { 5505 /* Order filter extravaganza */ 5506 struct 5507 { 5508 bool claimed; 5509 bool paid; 5510 bool refunded; 5511 bool wired; 5512 } order_status[64]; 5513 unsigned int *permutation; 5514 5515 /* Pseudorandomly generate variations for the filter to differentiate */ 5516 GNUNET_CRYPTO_seed_weak_random (1); 5517 permutation = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_WEAK, 5518 64); 5519 for (unsigned int i = 0; i < 64; ++i) 5520 { 5521 unsigned int dest = permutation[i]; 5522 order_status[dest].claimed = (i & 1) ? true : false; 5523 order_status[dest].paid = (3 == (i & 3)) ? true : false; 5524 order_status[dest].refunded = (5 == (i & 5)) ? true : false; 5525 order_status[dest].wired = (9 == (i & 9)) ? true : false; 5526 } 5527 GNUNET_free (permutation); 5528 5529 5530 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 5531 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5532 TEST_RET_ON_FAIL (test_insert_account (&cls->instance, 5533 &cls->account, 5534 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5535 TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey, 5536 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5537 for (unsigned int i = 0; i < 64; ++i) 5538 { 5539 uint64_t order_serial; 5540 5541 TEST_RET_ON_FAIL (test_insert_order (&cls->instance, 5542 &cls->orders[i], 5543 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5544 order_serial = get_order_serial (&cls->instance, 5545 &cls->orders[i]); 5546 5547 if (order_status[i].claimed) 5548 { 5549 TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance, 5550 &cls->orders[i], 5551 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5552 } 5553 else 5554 { 5555 continue; 5556 } 5557 5558 if (order_status[i].paid) 5559 TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance, 5560 &cls->orders[i], 5561 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5562 if (order_status[i].refunded) 5563 { 5564 TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance, 5565 &cls->signkey, 5566 &cls->deposits[i], 5567 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5568 TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 5569 TALER_MERCHANTDB_refund_coin (pg, 5570 cls->instance.instance.id, 5571 &cls->deposits[i]. 5572 h_contract_terms, 5573 cls->refunds[i].timestamp, 5574 cls->refunds[i].coin_pub, 5575 cls->refunds[i].reason), 5576 "Refund coin failed\n"); 5577 } 5578 5579 if (order_status[i].wired) 5580 TEST_RET_ON_FAIL (test_mark_order_wired (order_serial, 5581 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5582 } 5583 5584 /* There are 3^3 = 27 possibilities here, not counting inc/dec and start row */ 5585 for (unsigned int i = 0; i < 27; ++i) 5586 { 5587 struct TALER_MERCHANTDB_OrderFilter filter = { 5588 .paid = (i % 3) + 1, 5589 .refunded = ((i / 3) % 3) + 1, 5590 .wired = ((i / 9) % 3) + 1, 5591 .date = GNUNET_TIME_UNIT_ZERO_TS, 5592 .start_row = 0, 5593 .delta = 64 5594 }; 5595 unsigned int orders_length = 0; 5596 struct OrderData orders[64]; 5597 5598 /* Now figure out which orders should make it through the filter */ 5599 for (unsigned int j = 0; j < 64; ++j) 5600 { 5601 if (((TALER_EXCHANGE_YNA_YES == filter.paid) && 5602 (true != order_status[j].paid)) || 5603 ((TALER_EXCHANGE_YNA_NO == filter.paid) && 5604 (false != order_status[j].paid)) || 5605 ((TALER_EXCHANGE_YNA_YES == filter.refunded) && 5606 (true != order_status[j].refunded)) || 5607 ((TALER_EXCHANGE_YNA_NO == filter.refunded) && 5608 (false != order_status[j].refunded)) || 5609 ((TALER_EXCHANGE_YNA_YES == filter.wired) && 5610 (true != order_status[j].wired)) || 5611 ((TALER_EXCHANGE_YNA_NO == filter.wired) && 5612 (false != order_status[j].wired))) 5613 continue; 5614 orders[orders_length] = cls->orders[j]; 5615 orders_length += 1; 5616 } 5617 5618 /* Test the lookup */ 5619 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 5620 &filter, 5621 orders_length, 5622 orders)); 5623 5624 /* Now test decreasing */ 5625 filter.start_row = 256; 5626 filter.date = GNUNET_TIME_UNIT_FOREVER_TS; 5627 filter.delta = -64; 5628 5629 reverse_order_data_array (orders_length, 5630 orders); 5631 5632 TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance, 5633 &filter, 5634 orders_length, 5635 orders)); 5636 } 5637 5638 return 0; 5639 } 5640 5641 5642 /** 5643 * Handles all logic for testing lookup order filters. 5644 * 5645 * @return 0 on success, 1 otherwise. 5646 */ 5647 static int 5648 test_lookup_orders_all_filters (void) 5649 { 5650 struct TestLookupOrdersAllFilters_Closure test_cls; 5651 int test_result; 5652 5653 memset (&test_cls, 5654 0, 5655 sizeof (test_cls)); 5656 pre_test_lookup_orders_all_filters (&test_cls); 5657 test_result = run_test_lookup_orders_all_filters (&test_cls); 5658 post_test_lookup_orders_all_filters (&test_cls); 5659 return test_result; 5660 } 5661 5662 5663 static void 5664 kyc_status_ok ( 5665 void *cls, 5666 const struct TALER_MerchantWireHashP *h_wire, 5667 struct TALER_FullPayto payto_uri, 5668 const char *exchange_url, 5669 struct GNUNET_TIME_Timestamp last_check, 5670 bool kyc_ok, 5671 const struct TALER_AccountAccessTokenP *access_token, 5672 unsigned int last_http_status, 5673 enum TALER_ErrorCode last_ec, 5674 bool in_aml_review, 5675 const json_t *jlimits) 5676 { 5677 bool *fail = cls; 5678 5679 if (kyc_ok) 5680 *fail = false; 5681 } 5682 5683 5684 static void 5685 kyc_status_fail ( 5686 void *cls, 5687 const struct TALER_MerchantWireHashP *h_wire, 5688 struct TALER_FullPayto payto_uri, 5689 const char *exchange_url, 5690 struct GNUNET_TIME_Timestamp last_check, 5691 bool kyc_ok, 5692 const struct TALER_AccountAccessTokenP *access_token, 5693 unsigned int last_http_status, 5694 enum TALER_ErrorCode last_ec, 5695 bool in_aml_review, 5696 const json_t *jlimits) 5697 { 5698 bool *fail = cls; 5699 5700 if (! kyc_ok) 5701 *fail = false; 5702 } 5703 5704 5705 /** 5706 * Function that tests the KYC table. 5707 * 5708 * @return 0 on success, 1 otherwise. 5709 */ 5710 static int 5711 test_kyc (void) 5712 { 5713 struct InstanceData instance; 5714 struct TALER_MERCHANTDB_AccountDetails account; 5715 bool fail; 5716 struct GNUNET_TIME_Timestamp now; 5717 5718 make_instance ("test_kyc", 5719 &instance); 5720 make_account (&account); 5721 account.instance_id = instance.instance.id; 5722 TEST_RET_ON_FAIL (test_insert_instance (&instance, 5723 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5724 TEST_RET_ON_FAIL (test_insert_account (&instance, 5725 &account, 5726 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 5727 now = GNUNET_TIME_timestamp_get (); 5728 TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 5729 TALER_MERCHANTDB_account_kyc_set_status (pg, 5730 instance.instance.id, 5731 &account.h_wire, 5732 "https://exchange.net/", 5733 now, 5734 GNUNET_TIME_UNIT_FOREVER_ABS, 5735 GNUNET_TIME_UNIT_HOURS, 5736 MHD_HTTP_OK, 5737 TALER_EC_NONE, 5738 42, 5739 NULL, 5740 NULL, 5741 false, 5742 false)); 5743 TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 5744 TALER_MERCHANTDB_account_kyc_set_status (pg, 5745 instance.instance.id, 5746 &account.h_wire, 5747 "https://exchange2.com/", 5748 now, 5749 GNUNET_TIME_UNIT_FOREVER_ABS, 5750 GNUNET_TIME_UNIT_HOURS, 5751 MHD_HTTP_OK, 5752 TALER_EC_NONE, 5753 42, 5754 NULL, 5755 NULL, 5756 false, 5757 false)); 5758 TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 5759 TALER_MERCHANTDB_account_kyc_set_status (pg, 5760 instance.instance.id, 5761 &account.h_wire, 5762 "https://exchange.net/", 5763 now, 5764 GNUNET_TIME_UNIT_FOREVER_ABS, 5765 GNUNET_TIME_UNIT_HOURS, 5766 MHD_HTTP_OK, 5767 TALER_EC_NONE, 5768 42, 5769 NULL, 5770 NULL, 5771 false, 5772 true)); 5773 fail = true; 5774 TEST_RET_ON_FAIL (1 != 5775 TALER_MERCHANTDB_account_kyc_get_status (pg, 5776 instance.instance.id, 5777 &account.h_wire, 5778 "https://exchange.net/", 5779 &kyc_status_ok, 5780 &fail)); 5781 TEST_RET_ON_FAIL (fail); 5782 fail = true; 5783 TEST_RET_ON_FAIL (1 != 5784 TALER_MERCHANTDB_account_kyc_get_status (pg, 5785 instance.instance.id, 5786 NULL, 5787 "https://exchange2.com/", 5788 &kyc_status_fail, 5789 &fail)); 5790 TEST_RET_ON_FAIL (fail); 5791 fail = true; 5792 TEST_RET_ON_FAIL (2 != 5793 TALER_MERCHANTDB_account_kyc_get_status (pg, 5794 instance.instance.id, 5795 NULL, 5796 NULL, 5797 &kyc_status_fail, 5798 &fail)); 5799 TEST_RET_ON_FAIL (fail); 5800 fail = true; 5801 TEST_RET_ON_FAIL (2 != 5802 TALER_MERCHANTDB_account_kyc_get_status (pg, 5803 instance.instance.id, 5804 NULL, 5805 NULL, 5806 &kyc_status_ok, 5807 &fail)); 5808 TEST_RET_ON_FAIL (fail); 5809 json_decref (instance.instance.address); 5810 json_decref (instance.instance.jurisdiction); 5811 return 0; 5812 } 5813 5814 5815 /* *********** Templates ********** */ 5816 5817 /** 5818 * A container for data relevant to a template. 5819 */ 5820 struct TemplateData 5821 { 5822 /** 5823 * The identifier of the template. 5824 */ 5825 const char *id; 5826 5827 /** 5828 * The details of the template. 5829 */ 5830 struct TALER_MERCHANTDB_TemplateDetails template; 5831 }; 5832 5833 5834 /** 5835 * Creates a template for testing with. 5836 * 5837 * @param id the id of the template. 5838 * @param template the template data to fill. 5839 */ 5840 static void 5841 make_template (const char *id, 5842 struct TemplateData *template) 5843 { 5844 template->id = id; 5845 template->template.template_description = "This is a test template"; 5846 template->template.otp_id = NULL; 5847 template->template.template_contract = json_array (); 5848 GNUNET_assert (NULL != template->template.template_contract); 5849 } 5850 5851 5852 /** 5853 * Frees memory associated with @e TemplateData. 5854 * 5855 * @param template the container to free. 5856 */ 5857 static void 5858 free_template_data (struct TemplateData *template) 5859 { 5860 GNUNET_free (template->template.otp_id); 5861 json_decref (template->template.template_contract); 5862 } 5863 5864 5865 /** 5866 * Compare two templates for equality. 5867 * 5868 * @param a the first template. 5869 * @param b the second template. 5870 * @return 0 on equality, 1 otherwise. 5871 */ 5872 static int 5873 check_templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *a, 5874 const struct TALER_MERCHANTDB_TemplateDetails *b) 5875 { 5876 if ((0 != strcmp (a->template_description, 5877 b->template_description)) || 5878 ( (NULL == a->otp_id) && (NULL != b->otp_id)) || 5879 ( (NULL != a->otp_id) && (NULL == b->otp_id)) || 5880 ( (NULL != a->otp_id) && (0 != strcmp (a->otp_id, 5881 b->otp_id))) || 5882 (1 != json_equal (a->template_contract, 5883 b->template_contract))) 5884 return 1; 5885 return 0; 5886 } 5887 5888 5889 /** 5890 * Tests inserting template data into the database. 5891 * 5892 * @param instance the instance to insert the template for. 5893 * @param template the template data to insert. 5894 * @param expected_result the result we expect the db to return. 5895 * @return 0 when successful, 1 otherwise. 5896 */ 5897 static int 5898 test_insert_template (const struct InstanceData *instance, 5899 const struct TemplateData *template, 5900 enum GNUNET_DB_QueryStatus expected_result) 5901 { 5902 TEST_COND_RET_ON_FAIL (expected_result == 5903 TALER_MERCHANTDB_insert_template (pg, 5904 instance->instance.id, 5905 template->id, 5906 0, 5907 &template->template), 5908 "Insert template failed\n"); 5909 return 0; 5910 } 5911 5912 5913 /** 5914 * Tests updating template data in the database. 5915 * 5916 * @param instance the instance to update the template for. 5917 * @param template the template data to update. 5918 * @param expected_result the result we expect the db to return. 5919 * @return 0 when successful, 1 otherwise. 5920 */ 5921 static int 5922 test_update_template (const struct InstanceData *instance, 5923 const struct TemplateData *template, 5924 enum GNUNET_DB_QueryStatus expected_result) 5925 { 5926 TEST_COND_RET_ON_FAIL (expected_result == 5927 TALER_MERCHANTDB_update_template (pg, 5928 instance->instance.id, 5929 template->id, 5930 &template->template), 5931 "Update template failed\n"); 5932 return 0; 5933 } 5934 5935 5936 /** 5937 * Tests looking up a template from the db. 5938 * 5939 * @param instance the instance to query from. 5940 * @param template the template to query and compare to. 5941 * @return 0 when successful, 1 otherwise. 5942 */ 5943 static int 5944 test_lookup_template (const struct InstanceData *instance, 5945 const struct TemplateData *template) 5946 { 5947 const struct TALER_MERCHANTDB_TemplateDetails *to_cmp 5948 = &template->template; 5949 struct TALER_MERCHANTDB_TemplateDetails lookup_result; 5950 5951 if (0 > TALER_MERCHANTDB_lookup_template (pg, 5952 instance->instance.id, 5953 template->id, 5954 &lookup_result)) 5955 { 5956 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5957 "Lookup template failed\n"); 5958 TALER_MERCHANTDB_template_details_free (&lookup_result); 5959 return 1; 5960 } 5961 if (0 != check_templates_equal (&lookup_result, 5962 to_cmp)) 5963 { 5964 GNUNET_break (0); 5965 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 5966 "Lookup template failed: incorrect template returned\n"); 5967 TALER_MERCHANTDB_template_details_free (&lookup_result); 5968 return 1; 5969 } 5970 TALER_MERCHANTDB_template_details_free (&lookup_result); 5971 return 0; 5972 } 5973 5974 5975 /** 5976 * Closure for testing template lookup 5977 */ 5978 struct TestLookupTemplates_Closure 5979 { 5980 /** 5981 * Number of template ids to compare to 5982 */ 5983 unsigned int templates_to_cmp_length; 5984 5985 /** 5986 * Pointer to array of template ids 5987 */ 5988 const struct TemplateData *templates_to_cmp; 5989 5990 /** 5991 * Pointer to array of number of matches for each template 5992 */ 5993 unsigned int *results_matching; 5994 5995 /** 5996 * Total number of results returned 5997 */ 5998 unsigned int results_length; 5999 }; 6000 6001 6002 /** 6003 * Function called after calling @e test_lookup_templates 6004 * 6005 * @param cls a pointer to the lookup closure. 6006 * @param template_id the identifier of the template found. 6007 */ 6008 static void 6009 lookup_templates_cb (void *cls, 6010 const char *template_id, 6011 const char *template_description) 6012 { 6013 struct TestLookupTemplates_Closure *cmp = cls; 6014 6015 if (NULL == cmp) 6016 return; 6017 cmp->results_length += 1; 6018 for (unsigned int i = 0; cmp->templates_to_cmp_length > i; ++i) 6019 { 6020 if ( (0 == strcmp (cmp->templates_to_cmp[i].id, 6021 template_id)) && 6022 (0 == strcmp (cmp->templates_to_cmp[i].template.template_description, 6023 template_description)) ) 6024 cmp->results_matching[i] += 1; 6025 } 6026 } 6027 6028 6029 /** 6030 * Tests looking up all templates for an instance. 6031 * 6032 * @param instance the instance to query from. 6033 * @param templates_length the number of templates we are expecting. 6034 * @param templates the list of templates that we expect to be found. 6035 * @return 0 when successful, 1 otherwise. 6036 */ 6037 static int 6038 test_lookup_templates (const struct InstanceData *instance, 6039 unsigned int templates_length, 6040 const struct TemplateData *templates) 6041 { 6042 unsigned int results_matching[templates_length]; 6043 struct TestLookupTemplates_Closure cls = { 6044 .templates_to_cmp_length = templates_length, 6045 .templates_to_cmp = templates, 6046 .results_matching = results_matching, 6047 .results_length = 0 6048 }; 6049 6050 memset (results_matching, 6051 0, 6052 sizeof (unsigned int) * templates_length); 6053 if (0 > TALER_MERCHANTDB_lookup_templates (pg, 6054 instance->instance.id, 6055 &lookup_templates_cb, 6056 &cls)) 6057 { 6058 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6059 "Lookup templates failed\n"); 6060 return 1; 6061 } 6062 if (templates_length != cls.results_length) 6063 { 6064 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6065 "Lookup templates failed: incorrect number of results\n"); 6066 return 1; 6067 } 6068 for (unsigned int i = 0; templates_length > i; ++i) 6069 { 6070 if (1 != cls.results_matching[i]) 6071 { 6072 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6073 "Lookup templates failed: mismatched data\n"); 6074 return 1; 6075 } 6076 } 6077 return 0; 6078 } 6079 6080 6081 /** 6082 * Tests deleting a template. 6083 * 6084 * @param instance the instance to delete the template from. 6085 * @param template the template that should be deleted. 6086 * @param expected_result the result that we expect the DB to return. 6087 * @return 0 when successful, 1 otherwise. 6088 */ 6089 static int 6090 test_delete_template (const struct InstanceData *instance, 6091 const struct TemplateData *template, 6092 enum GNUNET_DB_QueryStatus expected_result) 6093 { 6094 TEST_COND_RET_ON_FAIL (expected_result == 6095 TALER_MERCHANTDB_delete_template (pg, 6096 instance->instance.id, 6097 template->id), 6098 "Delete template failed\n"); 6099 return 0; 6100 } 6101 6102 6103 /** 6104 * Closure for template tests. 6105 */ 6106 struct TestTemplates_Closure 6107 { 6108 /** 6109 * The instance to use for this test. 6110 */ 6111 struct InstanceData instance; 6112 6113 /** 6114 * The array of templates. 6115 */ 6116 struct TemplateData templates[2]; 6117 }; 6118 6119 6120 /** 6121 * Sets up the data structures used in the template tests. 6122 * 6123 * @param cls the closure to fill with test data. 6124 */ 6125 static void 6126 pre_test_templates (struct TestTemplates_Closure *cls) 6127 { 6128 /* Instance */ 6129 make_instance ("test_inst_templates", 6130 &cls->instance); 6131 6132 /* Templates */ 6133 make_template ("test_templates_pd_0", 6134 &cls->templates[0]); 6135 6136 make_template ("test_templates_pd_1", 6137 &cls->templates[1]); 6138 cls->templates[1].template.template_description = 6139 "This is a another test template"; 6140 } 6141 6142 6143 /** 6144 * Handles all teardown after testing. 6145 * 6146 * @param cls the closure containing memory to be freed. 6147 */ 6148 static void 6149 post_test_templates (struct TestTemplates_Closure *cls) 6150 { 6151 free_instance_data (&cls->instance); 6152 free_template_data (&cls->templates[0]); 6153 free_template_data (&cls->templates[1]); 6154 } 6155 6156 6157 /** 6158 * Runs the tests for templates. 6159 * 6160 * @param cls the container of the test data. 6161 * @return 0 on success, 1 otherwise. 6162 */ 6163 static int 6164 run_test_templates (struct TestTemplates_Closure *cls) 6165 { 6166 6167 /* Test that insert without an instance fails */ 6168 TEST_RET_ON_FAIL (test_insert_template (&cls->instance, 6169 &cls->templates[0], 6170 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 6171 /* Insert the instance */ 6172 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 6173 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6174 /* Test inserting a template */ 6175 TEST_RET_ON_FAIL (test_insert_template (&cls->instance, 6176 &cls->templates[0], 6177 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6178 /* Test that double insert fails */ 6179 TEST_RET_ON_FAIL (test_insert_template (&cls->instance, 6180 &cls->templates[0], 6181 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 6182 /* Test lookup of individual templates */ 6183 TEST_RET_ON_FAIL (test_lookup_template (&cls->instance, 6184 &cls->templates[0])); 6185 /* Make sure it fails correctly for templates that don't exist */ 6186 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 6187 TALER_MERCHANTDB_lookup_template (pg, 6188 cls->instance.instance.id, 6189 "nonexistent_template", 6190 NULL)) 6191 { 6192 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6193 "Lookup template failed\n"); 6194 return 1; 6195 } 6196 /* Test template update */ 6197 cls->templates[0].template.template_description = 6198 "This is a test template that has been updated!"; 6199 GNUNET_free (cls->templates[0].template.otp_id); 6200 cls->templates[0].template.otp_id = GNUNET_strdup ("otp_id"); 6201 { 6202 /* ensure OTP device exists */ 6203 struct TALER_MERCHANTDB_OtpDeviceDetails td = { 6204 .otp_description = "my otp", 6205 .otp_key = "my key", 6206 .otp_algorithm = 1, 6207 .otp_ctr = 42 6208 }; 6209 GNUNET_assert (0 <= 6210 TALER_MERCHANTDB_insert_otp (pg, 6211 cls->instance.instance.id, 6212 "otp_id", 6213 &td)); 6214 } 6215 GNUNET_assert (0 == 6216 json_array_append_new ( 6217 cls->templates[0].template.template_contract, 6218 json_string ("This is a test. 3CH."))); 6219 TEST_RET_ON_FAIL (test_update_template (&cls->instance, 6220 &cls->templates[0], 6221 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6222 TEST_RET_ON_FAIL (test_lookup_template (&cls->instance, 6223 &cls->templates[0])); 6224 TEST_RET_ON_FAIL (test_update_template (&cls->instance, 6225 &cls->templates[1], 6226 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 6227 /* Test collective template lookup */ 6228 TEST_RET_ON_FAIL (test_insert_template (&cls->instance, 6229 &cls->templates[1], 6230 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6231 TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance, 6232 2, 6233 cls->templates)); 6234 6235 /* Test template deletion */ 6236 TEST_RET_ON_FAIL (test_delete_template (&cls->instance, 6237 &cls->templates[1], 6238 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6239 /* Test double deletion fails */ 6240 TEST_RET_ON_FAIL (test_delete_template (&cls->instance, 6241 &cls->templates[1], 6242 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 6243 TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance, 6244 1, 6245 cls->templates)); 6246 return 0; 6247 } 6248 6249 6250 /** 6251 * Takes care of template testing. 6252 * 6253 * @return 0 on success, 1 otherwise. 6254 */ 6255 static int 6256 test_templates (void) 6257 { 6258 struct TestTemplates_Closure test_cls; 6259 int test_result; 6260 6261 memset (&test_cls, 6262 0, 6263 sizeof (test_cls)); 6264 pre_test_templates (&test_cls); 6265 test_result = run_test_templates (&test_cls); 6266 post_test_templates (&test_cls); 6267 return test_result; 6268 } 6269 6270 6271 /* *********** Webhooks ********** */ 6272 6273 /** 6274 * A container for data relevant to a webhook. 6275 */ 6276 struct WebhookData 6277 { 6278 6279 /** 6280 * The identifier of the webhook. 6281 */ 6282 const char *id; 6283 6284 /** 6285 * The details of the webhook. 6286 */ 6287 struct TALER_MERCHANTDB_WebhookDetails webhook; 6288 }; 6289 6290 6291 /** 6292 * Creates a webhook for testing with. 6293 * 6294 * @param id the id of the webhook. 6295 * @param webhook the webhook data to fill. 6296 */ 6297 static void 6298 make_webhook (const char *id, 6299 struct WebhookData *webhook) 6300 { 6301 webhook->id = id; 6302 webhook->webhook.event_type = "Paid"; 6303 webhook->webhook.url = "https://exampletest.com"; 6304 webhook->webhook.http_method = "POST"; 6305 webhook->webhook.header_template = "Authorization:XYJAORKJEO"; 6306 webhook->webhook.body_template = "$Amount"; 6307 } 6308 6309 6310 /** 6311 * Compare two webhooks for equality. 6312 * 6313 * @param a the first webhook. 6314 * @param b the second webhook. 6315 * @return 0 on equality, 1 otherwise. 6316 */ 6317 static int 6318 check_webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *a, 6319 const struct TALER_MERCHANTDB_WebhookDetails *b) 6320 { 6321 if ((0 != strcmp (a->event_type, 6322 b->event_type)) || 6323 (0 != strcmp (a->url, 6324 b->url)) || 6325 (0 != strcmp (a->http_method, 6326 b->http_method)) || 6327 (0 != strcmp (a->header_template, 6328 b->header_template)) || 6329 (0 != strcmp (a->body_template, 6330 b->body_template))) 6331 return 1; 6332 return 0; 6333 } 6334 6335 6336 /** 6337 * Tests inserting webhook data into the database. 6338 * 6339 * @param instance the instance to insert the webhook for. 6340 * @param webhook the webhook data to insert. 6341 * @param expected_result the result we expect the db to return. 6342 * @return 0 when successful, 1 otherwise. 6343 */ 6344 static int 6345 test_insert_webhook (const struct InstanceData *instance, 6346 const struct WebhookData *webhook, 6347 enum GNUNET_DB_QueryStatus expected_result) 6348 { 6349 TEST_COND_RET_ON_FAIL (expected_result == 6350 TALER_MERCHANTDB_insert_webhook (pg, 6351 instance->instance.id, 6352 webhook->id, 6353 &webhook->webhook), 6354 "Insert webhook failed\n"); 6355 return 0; 6356 } 6357 6358 6359 /** 6360 * Tests updating webhook data in the database. 6361 * 6362 * @param instance the instance to update the webhook for. 6363 * @param webhook the webhook data to update. 6364 * @param expected_result the result we expect the db to return. 6365 * @return 0 when successful, 1 otherwise. 6366 */ 6367 static int 6368 test_update_webhook (const struct InstanceData *instance, 6369 const struct WebhookData *webhook, 6370 enum GNUNET_DB_QueryStatus expected_result) 6371 { 6372 TEST_COND_RET_ON_FAIL (expected_result == 6373 TALER_MERCHANTDB_update_webhook (pg, 6374 instance->instance.id, 6375 webhook->id, 6376 &webhook->webhook), 6377 "Update webhook failed\n"); 6378 return 0; 6379 } 6380 6381 6382 /** 6383 * Tests looking up a webhook from the db. 6384 * 6385 * @param instance the instance to query from. 6386 * @param webhook the webhook to query and compare to. 6387 * @return 0 when successful, 1 otherwise. 6388 */ 6389 static int 6390 test_lookup_webhook (const struct InstanceData *instance, 6391 const struct WebhookData *webhook) 6392 { 6393 const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook; 6394 struct TALER_MERCHANTDB_WebhookDetails lookup_result; 6395 6396 if (0 > TALER_MERCHANTDB_lookup_webhook (pg, 6397 instance->instance.id, 6398 webhook->id, 6399 &lookup_result)) 6400 { 6401 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6402 "Lookup webhook failed\n"); 6403 TALER_MERCHANTDB_webhook_details_free (&lookup_result); 6404 return 1; 6405 } 6406 if (0 != check_webhooks_equal (&lookup_result, 6407 to_cmp)) 6408 { 6409 GNUNET_break (0); 6410 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6411 "Lookup webhook failed: incorrect webhook returned\n"); 6412 TALER_MERCHANTDB_webhook_details_free (&lookup_result); 6413 return 1; 6414 } 6415 TALER_MERCHANTDB_webhook_details_free (&lookup_result); 6416 return 0; 6417 } 6418 6419 6420 /** 6421 * Closure for testing webhook lookup 6422 */ 6423 struct TestLookupWebhooks_Closure 6424 { 6425 /** 6426 * Number of webhook ids to compare to 6427 */ 6428 unsigned int webhooks_to_cmp_length; 6429 6430 /** 6431 * Pointer to array of webhook ids 6432 */ 6433 const struct WebhookData *webhooks_to_cmp; 6434 6435 /** 6436 * Pointer to array of number of matches for each webhook 6437 */ 6438 unsigned int *results_matching; 6439 6440 /** 6441 * Total number of results returned 6442 */ 6443 unsigned int results_length; 6444 }; 6445 6446 6447 /** 6448 * Function called after calling @e test_lookup_webhooks 6449 * 6450 * @param cls a pointer to the lookup closure. 6451 * @param webhook_id the identifier of the webhook found. 6452 */ 6453 static void 6454 lookup_webhooks_cb (void *cls, 6455 const char *webhook_id, 6456 const char *event_type) 6457 { 6458 struct TestLookupWebhooks_Closure *cmp = cls; 6459 if (NULL == cmp) 6460 return; 6461 cmp->results_length += 1; 6462 for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i) 6463 { 6464 if ((0 == strcmp (cmp->webhooks_to_cmp[i].id, 6465 webhook_id)) && 6466 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type, 6467 event_type)) ) 6468 cmp->results_matching[i] += 1; 6469 } 6470 } 6471 6472 6473 /** 6474 * Tests looking up all webhooks for an instance. 6475 * 6476 * @param instance the instance to query from. 6477 * @param webhooks_length the number of webhooks we are expecting. 6478 * @param webhooks the list of webhooks that we expect to be found. 6479 * @return 0 when successful, 1 otherwise. 6480 */ 6481 static int 6482 test_lookup_webhooks (const struct InstanceData *instance, 6483 unsigned int webhooks_length, 6484 const struct WebhookData *webhooks) 6485 { 6486 unsigned int results_matching[webhooks_length]; 6487 struct TestLookupWebhooks_Closure cls = { 6488 .webhooks_to_cmp_length = webhooks_length, 6489 .webhooks_to_cmp = webhooks, 6490 .results_matching = results_matching, 6491 .results_length = 0 6492 }; 6493 memset (results_matching, 0, sizeof (unsigned int) * webhooks_length); 6494 if (0 > TALER_MERCHANTDB_lookup_webhooks (pg, 6495 instance->instance.id, 6496 &lookup_webhooks_cb, 6497 &cls)) 6498 { 6499 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6500 "Lookup webhooks failed\n"); 6501 return 1; 6502 } 6503 if (webhooks_length != cls.results_length) 6504 { 6505 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6506 "Lookup webhooks failed: incorrect number of results\n"); 6507 return 1; 6508 } 6509 for (unsigned int i = 0; webhooks_length > i; ++i) 6510 { 6511 if (1 != cls.results_matching[i]) 6512 { 6513 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6514 "Lookup webhooks failed: mismatched data\n"); 6515 return 1; 6516 } 6517 } 6518 return 0; 6519 } 6520 6521 6522 /** 6523 * Function called after calling @e test_lookup_webhooks 6524 * 6525 * @param cls a pointer to the lookup closure. 6526 * @param webhook_id the identifier of the webhook found. 6527 */ 6528 static void 6529 lookup_webhook_by_event_cb (void *cls, 6530 uint64_t webhook_serial, 6531 const char *event_type, 6532 const char *url, 6533 const char *http_method, 6534 const char *header_template, 6535 const char *body_template) 6536 { 6537 struct TestLookupWebhooks_Closure *cmp = cls; 6538 if (NULL == cmp) 6539 return; 6540 cmp->results_length += 1; 6541 for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i) 6542 { 6543 if ((0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type, 6544 event_type)) && 6545 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.url, 6546 url)) && 6547 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.http_method, 6548 http_method)) && 6549 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.header_template, 6550 header_template)) && 6551 (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.body_template, 6552 body_template)) ) 6553 cmp->results_matching[i] += 1; 6554 } 6555 } 6556 6557 6558 /** 6559 * Tests looking up webhooks by event for an instance. 6560 * 6561 * @param instance the instance to query from. 6562 * @param webhooks_length the number of webhooks we are expecting. 6563 * @param webhooks the list of webhooks that we expect to be found. 6564 * @return 0 when successful, 1 otherwise. 6565 */ 6566 static int 6567 test_lookup_webhook_by_event (const struct InstanceData *instance, 6568 unsigned int webhooks_length, 6569 const struct WebhookData *webhooks) 6570 { 6571 unsigned int results_matching[webhooks_length]; 6572 struct TestLookupWebhooks_Closure cls = { 6573 .webhooks_to_cmp_length = webhooks_length, 6574 .webhooks_to_cmp = webhooks, 6575 .results_matching = results_matching, 6576 .results_length = 0 6577 }; 6578 memset (results_matching, 0, sizeof (unsigned int) * webhooks_length); 6579 if (0 > TALER_MERCHANTDB_lookup_webhook_by_event (pg, 6580 instance->instance.id, 6581 webhooks->webhook.event_type, 6582 &lookup_webhook_by_event_cb, 6583 &cls)) 6584 { 6585 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6586 "Lookup webhooks by event failed\n"); 6587 return 1; 6588 } 6589 6590 if (webhooks_length != cls.results_length) 6591 { 6592 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6593 "Lookup webhooks by event failed: incorrect number of results\n"); 6594 return 1; 6595 } 6596 for (unsigned int i = 0; webhooks_length > i; ++i) 6597 { 6598 if (1 != cls.results_matching[i]) 6599 { 6600 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6601 "Lookup webhooks by event failed: mismatched data\n"); 6602 return 1; 6603 } 6604 } 6605 return 0; 6606 } 6607 6608 6609 /** 6610 * Tests deleting a webhook. 6611 * 6612 * @param instance the instance to delete the webhook from. 6613 * @param webhook the webhook that should be deleted. 6614 * @param expected_result the result that we expect the DB to return. 6615 * @return 0 when successful, 1 otherwise. 6616 */ 6617 static int 6618 test_delete_webhook (const struct InstanceData *instance, 6619 const struct WebhookData *webhook, 6620 enum GNUNET_DB_QueryStatus expected_result) 6621 { 6622 TEST_COND_RET_ON_FAIL (expected_result == 6623 TALER_MERCHANTDB_delete_webhook (pg, 6624 instance->instance.id, 6625 webhook->id), 6626 "Delete webhook failed\n"); 6627 return 0; 6628 } 6629 6630 6631 /** 6632 * Closure for webhook tests. 6633 */ 6634 struct TestWebhooks_Closure 6635 { 6636 /** 6637 * The instance to use for this test. 6638 */ 6639 struct InstanceData instance; 6640 6641 /** 6642 * The array of webhooks. 6643 */ 6644 struct WebhookData webhooks[3]; 6645 }; 6646 6647 6648 /** 6649 * Sets up the data structures used in the webhook tests. 6650 * 6651 * @param cls the closure to fill with test data. 6652 */ 6653 static void 6654 pre_test_webhooks (struct TestWebhooks_Closure *cls) 6655 { 6656 /* Instance */ 6657 make_instance ("test_inst_webhooks", 6658 &cls->instance); 6659 6660 /* Webhooks */ 6661 make_webhook ("test_webhooks_wb_0", 6662 &cls->webhooks[0]); 6663 6664 make_webhook ("test_webhooks_wb_1", 6665 &cls->webhooks[1]); 6666 cls->webhooks[1].webhook.event_type = "Test paid"; 6667 cls->webhooks[1].webhook.url = "https://example.com"; 6668 cls->webhooks[1].webhook.http_method = "POST"; 6669 cls->webhooks[1].webhook.header_template = "Authorization:1XYJAOR493O"; 6670 cls->webhooks[1].webhook.body_template = "$Amount"; 6671 6672 make_webhook ("test_webhooks_wb_2", 6673 &cls->webhooks[2]); 6674 cls->webhooks[2].webhook.event_type = "Test paid"; 6675 cls->webhooks[2].webhook.url = "https://examplerefund.com"; 6676 cls->webhooks[2].webhook.http_method = "POST"; 6677 cls->webhooks[2].webhook.header_template = "Authorization:XY6ORK52JEO"; 6678 cls->webhooks[2].webhook.body_template = "$Amount"; 6679 } 6680 6681 6682 /** 6683 * Handles all teardown after testing. 6684 * 6685 * @param cls the closure containing memory to be freed. 6686 */ 6687 static void 6688 post_test_webhooks (struct TestWebhooks_Closure *cls) 6689 { 6690 free_instance_data (&cls->instance); 6691 } 6692 6693 6694 /** 6695 * Runs the tests for webhooks. 6696 * 6697 * @param cls the container of the test data. 6698 * @return 0 on success, 1 otherwise. 6699 */ 6700 static int 6701 run_test_webhooks (struct TestWebhooks_Closure *cls) 6702 { 6703 6704 /* Test that insert without an instance fails */ 6705 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 6706 &cls->webhooks[0], 6707 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 6708 /* Insert the instance */ 6709 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 6710 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6711 /* Test inserting a webhook */ 6712 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 6713 &cls->webhooks[0], 6714 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6715 /* Test that double insert fails */ 6716 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 6717 &cls->webhooks[0], 6718 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 6719 /* Test lookup of individual webhooks */ 6720 TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance, 6721 &cls->webhooks[0])); 6722 /* Make sure it fails correctly for webhooks that don't exist */ 6723 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 6724 TALER_MERCHANTDB_lookup_webhook (pg, 6725 cls->instance.instance.id, 6726 "nonexistent_webhook", 6727 NULL)) 6728 { 6729 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 6730 "Lookup webhook failed\n"); 6731 return 1; 6732 } 6733 /* Test webhook update */ 6734 cls->webhooks[0].webhook.event_type = 6735 "Test paid"; 6736 cls->webhooks[0].webhook.url = 6737 "example.com"; 6738 cls->webhooks[0].webhook.http_method = 6739 "POST"; 6740 cls->webhooks[0].webhook.header_template = 6741 "Authorization:WEKFOEKEXZ"; 6742 cls->webhooks[0].webhook.body_template = 6743 "$Amount"; 6744 TEST_RET_ON_FAIL (test_update_webhook (&cls->instance, 6745 &cls->webhooks[0], 6746 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6747 6748 TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance, 6749 &cls->webhooks[0])); 6750 TEST_RET_ON_FAIL (test_update_webhook (&cls->instance, 6751 &cls->webhooks[1], 6752 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 6753 /* Test collective webhook lookup */ 6754 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 6755 &cls->webhooks[1], 6756 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6757 TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance, 6758 2, 6759 cls->webhooks)); 6760 TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance, 6761 2, 6762 cls->webhooks)); 6763 TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance, 6764 &cls->webhooks[2], 6765 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6766 6767 /* Test webhook deletion */ 6768 TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance, 6769 &cls->webhooks[1], 6770 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 6771 /* Test double deletion fails */ 6772 TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance, 6773 &cls->webhooks[1], 6774 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 6775 cls->webhooks[1] = cls->webhooks[2]; 6776 TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance, 6777 2, 6778 cls->webhooks)); 6779 TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance, 6780 2, 6781 cls->webhooks)); 6782 return 0; 6783 } 6784 6785 6786 /** 6787 * Takes care of webhook testing. 6788 * 6789 * @return 0 on success, 1 otherwise. 6790 */ 6791 static int 6792 test_webhooks (void) 6793 { 6794 struct TestWebhooks_Closure test_cls; 6795 int test_result; 6796 6797 pre_test_webhooks (&test_cls); 6798 test_result = run_test_webhooks (&test_cls); 6799 post_test_webhooks (&test_cls); 6800 return test_result; 6801 } 6802 6803 6804 /* *********** Pending Webhooks ********** */ 6805 6806 /** 6807 * A container for data relevant to a pending webhook. 6808 */ 6809 struct PendingWebhookData 6810 { 6811 /** 6812 * Reference to the configured webhook template. 6813 */ 6814 uint64_t webhook_serial; 6815 6816 /** 6817 * The details of the pending webhook. 6818 */ 6819 struct TALER_MERCHANTDB_PendingWebhookDetails pwebhook; 6820 }; 6821 6822 6823 /** 6824 * Creates a pending webhook for testing with. 6825 * 6826 * @param serial reference to the configured webhook template. 6827 * @param pwebhook the pending webhook data to fill. 6828 */ 6829 static void 6830 make_pending_webhook (uint64_t webhook_serial, 6831 struct PendingWebhookData *pwebhook) 6832 { 6833 pwebhook->webhook_serial = webhook_serial; 6834 pwebhook->pwebhook.next_attempt = GNUNET_TIME_UNIT_ZERO_ABS; 6835 pwebhook->pwebhook.retries = 0; 6836 pwebhook->pwebhook.url = "https://exampletest.com"; 6837 pwebhook->pwebhook.http_method = "POST"; 6838 pwebhook->pwebhook.header = "Authorization:XYJAORKJEO"; 6839 pwebhook->pwebhook.body = "$Amount"; 6840 } 6841 6842 6843 /** 6844 * Tests inserting pending webhook data into the database. 6845 * 6846 * @param instance the instance to insert the pending webhook for. 6847 * @param pending webhook the pending webhook data to insert. 6848 * @param expected_result the result we expect the db to return. 6849 * @return 0 when successful, 1 otherwise. 6850 */ 6851 static int 6852 test_insert_pending_webhook (const struct InstanceData *instance, 6853 struct PendingWebhookData *pwebhook, 6854 enum GNUNET_DB_QueryStatus expected_result) 6855 { 6856 6857 TEST_COND_RET_ON_FAIL (expected_result == 6858 TALER_MERCHANTDB_insert_pending_webhook (pg, 6859 instance->instance.id, 6860 pwebhook-> 6861 webhook_serial, 6862 pwebhook->pwebhook.url, 6863 pwebhook->pwebhook. 6864 http_method, 6865 pwebhook->pwebhook. 6866 header, 6867 pwebhook->pwebhook.body 6868 ), 6869 "Insert pending webhook failed\n"); 6870 return 0; 6871 } 6872 6873 6874 /** 6875 * Tests updating pending webhook data in the database. 6876 * 6877 * @param instance the instance to update the pending webhook for. 6878 * @param pending webhook the pending webhook data to update. 6879 * @param expected_result the result we expect the db to return. 6880 * @return 0 when successful, 1 otherwise. 6881 */ 6882 static int 6883 test_update_pending_webhook (const struct InstanceData *instance, 6884 struct PendingWebhookData *pwebhook, 6885 enum GNUNET_DB_QueryStatus expected_result) 6886 { 6887 pwebhook->pwebhook.next_attempt = GNUNET_TIME_relative_to_absolute ( 6888 GNUNET_TIME_UNIT_HOURS); 6889 pwebhook->pwebhook.retries++; 6890 TEST_COND_RET_ON_FAIL (expected_result == 6891 TALER_MERCHANTDB_update_pending_webhook (pg, 6892 pwebhook-> 6893 webhook_serial, 6894 pwebhook->pwebhook. 6895 next_attempt), 6896 "Update pending webhook failed\n"); 6897 return 0; 6898 } 6899 6900 6901 /** 6902 * Container for information for looking up the row number of a deposit. 6903 */ 6904 struct LookupPendingWebhookSerial_Closure 6905 { 6906 /** 6907 * The pending webhook we're looking for. 6908 */ 6909 const struct PendingWebhookData *pwebhook; 6910 6911 /** 6912 * The serial found. 6913 */ 6914 uint64_t webhook_pending_serial; 6915 }; 6916 6917 6918 /** 6919 * Function called after calling @e test_lookup_all_webhook, 6920 * test_lookup_future_webhook and test_lookup_pending_webhook 6921 * 6922 * @param cls a pointer to the lookup closure. 6923 * @param webhook_serial reference to the configured webhook template. 6924 */ 6925 static void 6926 get_pending_serial_cb (void *cls, 6927 uint64_t webhook_pending_serial, 6928 struct GNUNET_TIME_Absolute next_attempt, 6929 uint32_t retries, 6930 const char *url, 6931 const char *http_method, 6932 const char *header, 6933 const char *body) 6934 { 6935 struct LookupPendingWebhookSerial_Closure *lpw = cls; 6936 6937 if ((0 == strcmp (lpw->pwebhook->pwebhook.url, 6938 url)) && 6939 (0 == strcmp (lpw->pwebhook->pwebhook.http_method, 6940 http_method)) && 6941 (0 == strcmp (lpw->pwebhook->pwebhook.header, 6942 header)) && 6943 (0 == strcmp (lpw->pwebhook->pwebhook.body, 6944 body)) ) 6945 { 6946 lpw->webhook_pending_serial = webhook_pending_serial; 6947 } 6948 /* else 6949 { 6950 fprintf(stdout, "next_attempt: %lu vs %lu\n", lpw->pwebhook->pwebhook.next_attempt.abs_value_us, next_attempt.abs_value_us); 6951 fprintf(stdout, "retries: %d vs %d\n", lpw->pwebhook->pwebhook.retries, retries); 6952 fprintf(stdout, "url: %s vs %s\n", lpw->pwebhook->pwebhook.url, url); 6953 fprintf(stdout, "http_method: %s vs %s\n", lpw->pwebhook->pwebhook.http_method, http_method); 6954 fprintf(stdout, "header: %s vs %s\n", lpw->pwebhook->pwebhook.header, header); 6955 fprintf(stdout, "body: %s vs %s\n", lpw->pwebhook->pwebhook.body, body); 6956 }*/ 6957 } 6958 6959 6960 /** 6961 * Convenience function to retrieve the row number of a webhook pending in the database. 6962 * 6963 * @param instance the instance to get webhook pending(wp) from. 6964 * @param webhook pending the wp to lookup the serial for. 6965 * @return the row number of the deposit. 6966 */ 6967 static uint64_t 6968 get_pending_serial (const struct InstanceData *instance, 6969 const struct PendingWebhookData *pwebhook) 6970 { 6971 struct LookupPendingWebhookSerial_Closure lpw = { 6972 .pwebhook = pwebhook, 6973 .webhook_pending_serial = 0 6974 }; 6975 6976 GNUNET_assert (0 < 6977 TALER_MERCHANTDB_lookup_all_webhooks (pg, 6978 instance->instance.id, 6979 0, 6980 INT_MAX, 6981 &get_pending_serial_cb, 6982 &lpw)); 6983 GNUNET_assert (0 != lpw.webhook_pending_serial); 6984 6985 return lpw.webhook_pending_serial; 6986 } 6987 6988 6989 /** 6990 * Closure for testing pending webhook lookup 6991 */ 6992 struct TestLookupPendingWebhooks_Closure 6993 { 6994 /** 6995 * Number of webhook serial to compare to 6996 */ 6997 unsigned int webhooks_to_cmp_length; 6998 6999 /** 7000 * Pointer to array of webhook serials 7001 */ 7002 const struct PendingWebhookData *webhooks_to_cmp; 7003 7004 /** 7005 * Pointer to array of number of matches for each pending webhook 7006 */ 7007 unsigned int *results_matching; 7008 7009 /** 7010 * Total number of results returned 7011 */ 7012 unsigned int results_length; 7013 }; 7014 7015 7016 /** 7017 * Function called after calling @e test_lookup_all_webhook, 7018 * test_lookup_future_webhook and test_lookup_pending_webhook 7019 * 7020 * @param cls a pointer to the lookup closure. 7021 * @param webhook_serial reference to the configured webhook template. 7022 */ 7023 static void 7024 lookup_pending_webhooks_cb (void *cls, 7025 uint64_t webhook_pending_serial, 7026 struct GNUNET_TIME_Absolute next_attempt, 7027 uint32_t retries, 7028 const char *url, 7029 const char *http_method, 7030 const char *header, 7031 const char *body) 7032 { 7033 struct TestLookupPendingWebhooks_Closure *cmp = cls; 7034 7035 cmp->results_length++; 7036 for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i) 7037 { 7038 if ((0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.url, 7039 url)) && 7040 (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.http_method, 7041 http_method)) && 7042 (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.header, 7043 header)) && 7044 (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.body, 7045 body)) ) 7046 { 7047 cmp->results_matching[i]++; 7048 } 7049 } 7050 } 7051 7052 7053 /** 7054 * Tests looking up the pending webhook for an instance. 7055 * 7056 * @param instance the instance to query from. 7057 * @param pwebhooks_length the number of pending webhook we are expecting. 7058 * @param pwebhooks the list of pending webhooks that we expect to be found. 7059 * @return 0 when successful, 1 otherwise. 7060 */ 7061 static int 7062 test_lookup_pending_webhooks (const struct InstanceData *instance, 7063 unsigned int pwebhooks_length, 7064 const struct PendingWebhookData *pwebhooks) 7065 { 7066 unsigned int results_matching[pwebhooks_length]; 7067 struct TestLookupPendingWebhooks_Closure cls = { 7068 .webhooks_to_cmp_length = pwebhooks_length, 7069 .webhooks_to_cmp = pwebhooks, 7070 .results_matching = results_matching, 7071 .results_length = 0 7072 }; 7073 7074 memset (results_matching, 0, sizeof (results_matching)); 7075 if (0 > TALER_MERCHANTDB_lookup_pending_webhooks (pg, 7076 &lookup_pending_webhooks_cb, 7077 &cls)) 7078 { 7079 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7080 "Lookup pending webhook failed\n"); 7081 return 1; 7082 } 7083 if (pwebhooks_length != cls.results_length) 7084 { 7085 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7086 "Lookup pending webhook failed: incorrect number of results\n"); 7087 return 1; 7088 } 7089 for (unsigned int i = 0; i < pwebhooks_length; i++) 7090 { 7091 if (1 != cls.results_matching[i]) 7092 { 7093 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7094 "Lookup pending webhook failed: mismatched data\n"); 7095 return 1; 7096 } 7097 } 7098 return 0; 7099 } 7100 7101 7102 /** 7103 * Tests looking up the future webhook to send for an instance. 7104 * 7105 * @param instance the instance to query from. 7106 * @param pwebhooks_length the number of pending webhook we are expecting. 7107 * @param pwebhooks the list of pending webhooks that we expect to be found. 7108 * @return 0 when successful, 1 otherwise. 7109 */ 7110 static int 7111 test_lookup_future_webhook (const struct InstanceData *instance, 7112 unsigned int pwebhooks_length, 7113 const struct PendingWebhookData *pwebhooks) 7114 { 7115 unsigned int results_matching[pwebhooks_length]; 7116 struct TestLookupPendingWebhooks_Closure cls = { 7117 .webhooks_to_cmp_length = pwebhooks_length, 7118 .webhooks_to_cmp = pwebhooks, 7119 .results_matching = results_matching, 7120 .results_length = 0 7121 }; 7122 7123 memset (results_matching, 0, sizeof (results_matching)); 7124 if (0 > TALER_MERCHANTDB_lookup_future_webhook (pg, 7125 &lookup_pending_webhooks_cb, 7126 &cls)) 7127 { 7128 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7129 "Lookup future webhook failed\n"); 7130 return 1; 7131 } 7132 if (pwebhooks_length != cls.results_length) 7133 { 7134 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7135 "Lookup future webhook failed: incorrect number of results\n"); 7136 return 1; 7137 } 7138 for (unsigned int i = 0; pwebhooks_length > i; ++i) 7139 { 7140 if (1 != cls.results_matching[i]) 7141 { 7142 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7143 "Lookup future webhook failed: mismatched data\n"); 7144 return 1; 7145 } 7146 } 7147 return 0; 7148 } 7149 7150 7151 /** 7152 * Tests looking up all the pending webhook for an instance. 7153 * 7154 * @param instance the instance to query from. 7155 * @param pwebhooks_length the number of pending webhook we are expecting. 7156 * @param pwebhooks the list of pending webhooks that we expect to be found. 7157 * @return 0 when successful, 1 otherwise. 7158 */ 7159 static int 7160 test_lookup_all_webhooks (const struct InstanceData *instance, 7161 unsigned int pwebhooks_length, 7162 const struct PendingWebhookData *pwebhooks) 7163 { 7164 uint64_t max_results = 2; 7165 uint64_t min_row = 0; 7166 unsigned int results_matching[GNUNET_NZL (pwebhooks_length)]; 7167 struct TestLookupPendingWebhooks_Closure cls = { 7168 .webhooks_to_cmp_length = pwebhooks_length, 7169 .webhooks_to_cmp = pwebhooks, 7170 .results_matching = results_matching, 7171 .results_length = 0 7172 }; 7173 7174 memset (results_matching, 7175 0, 7176 sizeof (results_matching)); 7177 if (0 > TALER_MERCHANTDB_lookup_all_webhooks (pg, 7178 instance->instance.id, 7179 min_row, 7180 max_results, 7181 &lookup_pending_webhooks_cb, 7182 &cls)) 7183 { 7184 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7185 "Lookup all webhooks failed\n"); 7186 return 1; 7187 } 7188 if (pwebhooks_length != cls.results_length) 7189 { 7190 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7191 "Lookup all webhooks failed: incorrect number of results\n"); 7192 return 1; 7193 } 7194 for (unsigned int i = 0; pwebhooks_length > i; ++i) 7195 { 7196 if (1 != cls.results_matching[i]) 7197 { 7198 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7199 "Lookup all webhooks failed: mismatched data\n"); 7200 return 1; 7201 } 7202 } 7203 return 0; 7204 } 7205 7206 7207 /** 7208 * Tests deleting a pending webhook. 7209 * 7210 * @param instance the instance to delete the pending webhook from. 7211 * @param pwebhook the pending webhook that should be deleted. 7212 * @param expected_result the result that we expect the DB to return. 7213 * @return 0 when successful, 1 otherwise. 7214 */ 7215 static int 7216 test_delete_pending_webhook (uint64_t webhooks_pending_serial, 7217 enum GNUNET_DB_QueryStatus expected_result) 7218 { 7219 7220 TEST_COND_RET_ON_FAIL (expected_result == 7221 TALER_MERCHANTDB_delete_pending_webhook (pg, 7222 webhooks_pending_serial), 7223 "Delete webhook failed\n"); 7224 return 0; 7225 } 7226 7227 7228 /** 7229 * Closure for pending webhook tests. 7230 */ 7231 struct TestPendingWebhooks_Closure 7232 { 7233 /** 7234 * The instance to use for this test. 7235 */ 7236 struct InstanceData instance; 7237 7238 /** 7239 * The array of pending webhooks. 7240 */ 7241 struct PendingWebhookData pwebhooks[2]; 7242 }; 7243 7244 7245 /** 7246 * Sets up the data structures used in the pending webhook tests. 7247 * 7248 * @param cls the closure to fill with test data. 7249 */ 7250 static void 7251 pre_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) 7252 { 7253 /* Instance */ 7254 make_instance ("test_inst_pending_webhooks", 7255 &cls->instance); 7256 7257 /* Webhooks */ 7258 make_pending_webhook (1, 7259 &cls->pwebhooks[0]); 7260 7261 make_pending_webhook (4, 7262 &cls->pwebhooks[1]); 7263 cls->pwebhooks[1].pwebhook.url = "https://test.com"; 7264 cls->pwebhooks[1].pwebhook.http_method = "POST"; 7265 cls->pwebhooks[1].pwebhook.header = "Authorization:XYJAO5R06EO"; 7266 cls->pwebhooks[1].pwebhook.body = "$Amount"; 7267 } 7268 7269 7270 /** 7271 * Handles all teardown after testing. 7272 * 7273 * @param cls the closure containing memory to be freed. 7274 */ 7275 static void 7276 post_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) 7277 { 7278 free_instance_data (&cls->instance); 7279 } 7280 7281 7282 /** 7283 * Runs the tests for pending webhooks. 7284 * 7285 * @param cls the container of the test data. 7286 * @return 0 on success, 1 otherwise. 7287 */ 7288 static int 7289 run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls) 7290 { 7291 uint64_t webhook_pending_serial0; 7292 uint64_t webhook_pending_serial1; 7293 7294 /* Test that insert without an instance fails */ 7295 TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance, 7296 &cls->pwebhooks[0], 7297 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 7298 7299 /* Insert the instance */ 7300 TEST_RET_ON_FAIL (test_insert_instance (&cls->instance, 7301 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7302 7303 /* Test inserting a pending webhook */ 7304 TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance, 7305 &cls->pwebhooks[0], 7306 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7307 TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance, 7308 &cls->pwebhooks[1], 7309 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7310 /* Test collective pending webhook lookup */ 7311 TEST_RET_ON_FAIL (test_lookup_pending_webhooks (&cls->instance, 7312 2, 7313 cls->pwebhooks)); 7314 /* Test pending webhook update */ 7315 TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance, 7316 &cls->pwebhooks[0], 7317 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7318 TEST_RET_ON_FAIL (test_lookup_future_webhook (&cls->instance, 7319 1, 7320 &cls->pwebhooks[1])); 7321 TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance, 7322 &cls->pwebhooks[1], 7323 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 7324 // ??? 7325 TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance, 7326 2, 7327 cls->pwebhooks)); 7328 7329 webhook_pending_serial0 = get_pending_serial (&cls->instance, 7330 &cls->pwebhooks[0]); 7331 webhook_pending_serial1 = get_pending_serial (&cls->instance, 7332 &cls->pwebhooks[1]); 7333 7334 /* Test webhook deletion */ 7335 TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1, 7336 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7337 /* Test double deletion fails */ 7338 TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1, 7339 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS)); 7340 TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial0, 7341 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT)); 7342 TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance, 7343 0, 7344 NULL)); 7345 return 0; 7346 } 7347 7348 7349 /** 7350 * Takes care of pending webhook testing. 7351 * 7352 * @return 0 on success, 1 otherwise. 7353 */ 7354 static int 7355 test_pending_webhooks (void) 7356 { 7357 struct TestPendingWebhooks_Closure test_cls; 7358 int test_result; 7359 7360 pre_test_pending_webhooks (&test_cls); 7361 test_result = run_test_pending_webhooks (&test_cls); 7362 post_test_pending_webhooks (&test_cls); 7363 return test_result; 7364 } 7365 7366 7367 /** 7368 * Function that runs all tests. 7369 * 7370 * @return 0 on success, 1 otherwise. 7371 */ 7372 static int 7373 run_tests (void) 7374 { 7375 TEST_RET_ON_FAIL (test_instances ()); 7376 TEST_RET_ON_FAIL (test_products ()); 7377 TEST_RET_ON_FAIL (test_orders ()); 7378 TEST_RET_ON_FAIL (test_deposits ()); 7379 TEST_RET_ON_FAIL (test_transfers ()); 7380 TEST_RET_ON_FAIL (test_refunds ()); 7381 TEST_RET_ON_FAIL (test_lookup_orders_all_filters ()); 7382 TEST_RET_ON_FAIL (test_kyc ()); 7383 TEST_RET_ON_FAIL (test_templates ()); 7384 TEST_RET_ON_FAIL (test_webhooks ()); 7385 TEST_RET_ON_FAIL (test_pending_webhooks ()); 7386 return 0; 7387 } 7388 7389 7390 /** 7391 * Main function that will be run by the scheduler. 7392 * 7393 * @param cls closure with config 7394 */ 7395 static void 7396 run (void *cls) 7397 { 7398 struct GNUNET_CONFIGURATION_Handle *cfg = cls; 7399 7400 /* Drop the tables to cleanup anything that might cause issues */ 7401 (void) TALER_MERCHANTDB_drop_tables (cfg); 7402 if (GNUNET_OK != 7403 TALER_MERCHANTDB_create_tables (cfg)) 7404 { 7405 result = 77; 7406 return; 7407 } 7408 if (NULL == (pg = TALER_MERCHANTDB_connect (cfg))) 7409 { 7410 result = 77; 7411 return; 7412 } 7413 /* Run the preflight */ 7414 TALER_MERCHANTDB_preflight (pg); 7415 7416 result = run_tests (); 7417 /* if (0 == result) result = run_test_templates (); */ 7418 7419 TALER_MERCHANTDB_disconnect (pg); 7420 pg = NULL; 7421 if (0 != result) 7422 return; 7423 if (GNUNET_OK != 7424 TALER_MERCHANTDB_drop_tables (cfg)) 7425 { 7426 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 7427 "Dropping tables failed\n"); 7428 result = 1; 7429 return; 7430 } 7431 } 7432 7433 7434 /** 7435 * Entry point for the tests. 7436 */ 7437 int 7438 main (int argc, 7439 char *const argv[]) 7440 { 7441 struct GNUNET_CONFIGURATION_Handle *cfg; 7442 7443 GNUNET_log_setup (argv[0], 7444 "DEBUG", 7445 NULL); 7446 cfg = GNUNET_CONFIGURATION_create (TALER_MERCHANT_project_data ()); 7447 if (GNUNET_OK != 7448 GNUNET_CONFIGURATION_parse (cfg, 7449 "test-merchantdb-postgres.conf")) 7450 { 7451 GNUNET_break (0); 7452 return 2; 7453 } 7454 GNUNET_SCHEDULER_run (&run, 7455 cfg); 7456 GNUNET_CONFIGURATION_destroy (cfg); 7457 return result; 7458 } 7459 7460 7461 /* end of test_merchantdb.c */