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