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