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