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