/* This file is part of TALER (C) 2014-2017, 2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. TALER is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file merchant/test_merchantdb.c * @brief testcase for merchant's postgres db plugin * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler_merchantdb_lib.h" /** * Global return value for the test. Initially -1, set to 0 upon * completion. Other values indicate some kind of error. */ static int result; /** * Handle to the plugin we are testing. */ static struct TALER_MERCHANTDB_Plugin *plugin; #define TEST_RET_ON_FAIL(test) if (0 != test) \ { \ return 1; \ } /** * Instance settings along with corresponding accounts */ struct InstanceWithAccounts { /** * Pointer to the instance settings */ const struct TALER_MERCHANTDB_InstanceSettings *instance; /** * Length of the array of accounts */ unsigned int accounts_length; /** * Pointer to the array of accounts */ const struct TALER_MERCHANTDB_AccountDetails *accounts; }; /** * Closure for testing instance lookup */ struct TestLookupInstances_Closure { /** * Number of instances to compare to */ unsigned int instances_to_cmp_length; /** * Pointer to array of instances */ const struct InstanceWithAccounts *instances_to_cmp; /** * Pointer to array of number of matches for each instance */ unsigned int *results_matching; /** * Total number of results returned */ unsigned int results_length; }; static int check_instances_equal (const struct TALER_MERCHANTDB_InstanceSettings *a, const struct TALER_MERCHANTDB_InstanceSettings *b) { if ((0 != strcmp (a->id, b->id)) || (0 != strcmp (a->name, b->name)) || (1 != json_equal (a->address, b->address)) || (1 != json_equal (a->jurisdiction, b->jurisdiction)) || (GNUNET_OK != TALER_amount_cmp_currency (&a->default_max_deposit_fee, &b->default_max_deposit_fee)) || (0 != TALER_amount_cmp (&a->default_max_deposit_fee, &b->default_max_deposit_fee)) || (GNUNET_OK != TALER_amount_cmp_currency (&a->default_max_wire_fee, &b->default_max_wire_fee)) || (0 != TALER_amount_cmp (&a->default_max_wire_fee, &b->default_max_wire_fee)) || (a->default_wire_fee_amortization != b->default_wire_fee_amortization) || (a->default_wire_transfer_delay.rel_value_us != b->default_wire_transfer_delay.rel_value_us) || (a->default_pay_delay.rel_value_us != b->default_pay_delay.rel_value_us)) return 1; return 0; } static int check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a, const struct TALER_MERCHANTDB_AccountDetails *b) { if ((0 != GNUNET_CRYPTO_hash_cmp (&a->h_wire, &b->h_wire)) || (0 != GNUNET_CRYPTO_hash_cmp (&a->salt, &b->salt)) || (0 != strcmp (a->payto_uri, b->payto_uri)) || (a->active != b->active)) return 1; return 0; } static void lookup_instances_cb (void *cls, const struct TALER_MerchantPublicKeyP *merchant_pub, const struct TALER_MerchantPrivateKeyP *merchant_priv, const struct TALER_MERCHANTDB_InstanceSettings *is, unsigned int accounts_length, const struct TALER_MERCHANTDB_AccountDetails accounts[]) { struct TestLookupInstances_Closure *cmp = cls; if (NULL == cmp) return; cmp->results_length += 1; /* Look through the closure and test each instance for equality */ for (unsigned int i = 0; cmp->instances_to_cmp_length > i; ++i) { int accounts_matching[accounts_length]; bool accounts_match = true; if (0 != check_instances_equal (cmp->instances_to_cmp[i].instance, is)) continue; if (accounts_length != cmp->instances_to_cmp[i].accounts_length) continue; /* Count matches between the accounts found and accounts in cls */ for (unsigned int j = 0; accounts_length > j; ++j) accounts_matching[j] = 0; for (unsigned int j = 0; accounts_length > j; ++j) { for (unsigned int k = 0; accounts_length > k; ++k) { if (0 == check_accounts_equal (&cmp->instances_to_cmp[i].accounts[j], &accounts[k])) accounts_matching[j] += 1; } } /* Each account from the lookup should match with one and only one from cls */ for (unsigned int j = 0; accounts_length > j; ++j) if (1 != accounts_matching[j]) accounts_match = false; if (true == accounts_match) cmp->results_matching[i] += 1; } } static int test_insert_instance (const struct TALER_MerchantPublicKeyP *merchant_pub, const struct TALER_MerchantPrivateKeyP *merchant_priv, const struct TALER_MERCHANTDB_InstanceSettings *is) { if (1 != plugin->insert_instance (plugin->cls, merchant_pub, merchant_priv, is)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Instance insertion failed\n"); return 1; } return 0; } static int test_lookup_instances (bool active_only, unsigned int instances_length, struct InstanceWithAccounts instances[]) { unsigned int results_matching[instances_length]; struct TestLookupInstances_Closure cmp = { .instances_to_cmp_length = instances_length, .instances_to_cmp = instances, .results_matching = results_matching, .results_length = 0 }; memset (results_matching, 0, sizeof (unsigned int) * instances_length); if (0 > plugin->lookup_instances (plugin->cls, active_only, &lookup_instances_cb, &cmp)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup instances failed\n"); return 1; } if (instances_length != cmp.results_length) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup instances failed: incorrect number of results\n"); return 1; } for (unsigned int i = 0; instances_length > i; ++i) { if (1 != cmp.results_matching[i]) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup instances failed: mismatched data\n"); return 1; } } return 0; } static int test_insert_account (const char *instance_id, const struct TALER_MERCHANTDB_AccountDetails *account) { if (1 != plugin->insert_account (plugin->cls, instance_id, account)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert account failed\n"); return 1; } return 0; } /** * Closure for instance tests */ struct TestInstances_Closure { /** * The instance settings */ struct TALER_MERCHANTDB_InstanceSettings is; /** * The instance public key */ struct TALER_MerchantPublicKeyP merchant_pub; /** * The instance private key */ struct TALER_MerchantPrivateKeyP merchant_priv; /** * The accounts array */ struct TALER_MERCHANTDB_AccountDetails accounts[2]; }; /** * Sets up the data structures used in the instance tests */ static void pre_test_instances (struct TestInstances_Closure *cls) { GNUNET_CRYPTO_eddsa_key_create (&cls->merchant_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&cls->merchant_priv.eddsa_priv, &cls->merchant_pub.eddsa_pub); cls->is.id = "test_inst"; cls->is.name = "Test"; cls->is.address = json_array (); GNUNET_assert (NULL != cls->is.address); GNUNET_assert (0 == json_array_append (cls->is.address, json_string ("123 Example St"))); cls->is.jurisdiction = json_array (); GNUNET_assert (NULL != cls->is.jurisdiction); GNUNET_assert (0 == json_array_append (cls->is.jurisdiction, json_string ("Ohio"))); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_deposit_fee)); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_wire_fee)); cls->is.default_wire_fee_amortization = 1; cls->is.default_wire_transfer_delay = GNUNET_TIME_relative_get_minute_ (); cls->is.default_pay_delay = GNUNET_TIME_relative_get_second_ (); GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, &cls->accounts[0].h_wire); GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, &cls->accounts[0].salt); cls->accounts[0].payto_uri = "payto://x-taler-bank/bank.demo.taler.net/4"; cls->accounts[0].active = true; GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, &cls->accounts[1].h_wire); GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, &cls->accounts[1].salt); cls->accounts[1].payto_uri = "payto://other-bank/bank.demo.other.net/4"; cls->accounts[1].active = true; } /** * Handles all teardown after testing */ static void post_test_instances (struct TestInstances_Closure *cls) { json_decref (cls->is.address); json_decref (cls->is.jurisdiction); } /** * Function that tests instances. * * @param cls closure with config */ static int run_test_instances (struct TestInstances_Closure *cls) { struct InstanceWithAccounts instances[1] = { { .accounts_length = 0, .accounts = NULL, .instance = &cls->is } }; uint64_t account_serial; /* Test inserting an instance */ TEST_RET_ON_FAIL (test_insert_instance (&cls->merchant_pub, &cls->merchant_priv, &cls->is)); /* Test lookup instances- is our new instance there? */ TEST_RET_ON_FAIL (test_lookup_instances (false, 1, instances)); /* Test update instance */ cls->is.name = "Test - updated"; if (1 != plugin->update_instance (plugin->cls, &cls->is)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Update instance failed\n"); return 1; } TEST_RET_ON_FAIL (test_lookup_instances (false, 1, instances)); /* Test account creation */ TEST_RET_ON_FAIL (test_insert_account (cls->is.id, &cls->accounts[0])); /* Test accounts from instance lookup */ instances[0].accounts_length = 1; instances[0].accounts = cls->accounts; TEST_RET_ON_FAIL (test_lookup_instances (false, 1, instances)); /* Test account inactivation */ if (0 > plugin->inactivate_account (plugin->cls, &cls->accounts[0].h_wire)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Inactivate account failed\n"); return 1; } cls->accounts[0].active = false; TEST_RET_ON_FAIL (test_lookup_instances (false, 1, instances)); /* Test multiple accounts */ /* Test lookup account */ if (1 != plugin->lookup_account (plugin->cls, cls->is.id, cls->accounts[0].payto_uri, &account_serial)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup account failed\n"); return 1; } if (1 != account_serial) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup account failed: incorrect serial number found\n"); return 1; } if (1 == plugin->lookup_account (plugin->cls, cls->is.id, "payto://other-uri", &account_serial)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup account failed: account found where there is none\n"); return 1; } /* Test instance private key deletion */ if (0 > plugin->delete_instance_private_key (plugin->cls, cls->is.id)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Delete instance private key failed\n"); return 1; } TEST_RET_ON_FAIL (test_lookup_instances (true, 0, NULL)); TEST_RET_ON_FAIL (test_lookup_instances (false, 1, instances)); /* Test instance deletion */ return 0; } /** * Function that tests instances. * * @param cls closure with config */ static int test_instances (void *cls) { struct TestInstances_Closure test_cls; pre_test_instances (&test_cls); int test_result = run_test_instances (&test_cls); post_test_instances (&test_cls); return test_result; } static void free_product (struct TALER_MERCHANTDB_ProductDetails *pd) { if (NULL != pd->description) GNUNET_free (pd->description); if (NULL != pd->description_i18n) json_decref (pd->description_i18n); if (NULL != pd->unit) GNUNET_free (pd->unit); if (NULL != pd->image) json_decref (pd->image); if (NULL != pd->address) json_decref (pd->address); } /** * Closure for testing product lookup */ struct TestLookupProducts_Closure { /** * Number of product ids to compare to */ unsigned int products_to_cmp_length; /** * Pointer to array of product ids */ const char **product_ids_to_cmp; /** * Pointer to array of number of matches for each product */ unsigned int *results_matching; /** * Total number of results returned */ unsigned int results_length; }; static void lookup_products_cb (void *cls, const char *product_id) { struct TestLookupProducts_Closure *cmp = cls; if (NULL == cmp) return; cmp->results_length += 1; for (unsigned int i = 0; cmp->products_to_cmp_length > i; ++i) { if (0 == strcmp (cmp->product_ids_to_cmp[i], product_id)) cmp->results_matching[i] += 1; } } static int check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a, const struct TALER_MERCHANTDB_ProductDetails *b) { if ((0 != strcmp (a->description, b->description)) || (1 != json_equal (a->description_i18n, b->description_i18n)) || (0 != strcmp (a->unit, b->unit)) || (GNUNET_OK != TALER_amount_cmp_currency (&a->price, &b->price)) || (0 != TALER_amount_cmp (&a->price, &b->price)) || (1 != json_equal (a->taxes, b->taxes)) || (a->total_stock != b->total_stock) || (a->total_sold != b->total_sold) || (a->total_lost != b->total_lost) || (1 != json_equal (a->image, b->image)) || (1 != json_equal (a->address, b->address)) || (a->next_restock.abs_value_us != b->next_restock.abs_value_us)) return 1; return 0; } static int test_insert_product (const char *is, const char *pd_id, const struct TALER_MERCHANTDB_ProductDetails *pd) { if (1 != plugin->insert_product (plugin->cls, is, pd_id, pd)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert product failed\n"); return 1; } return 0; } static int test_lookup_product (const char *is, const char *pd_id, const struct TALER_MERCHANTDB_ProductDetails *to_cmp) { struct TALER_MERCHANTDB_ProductDetails lookup_result; if (0 > plugin->lookup_product (plugin->cls, is, pd_id, &lookup_result)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup product failed\n"); free_product (&lookup_result); return 1; } if (0 != check_products_equal (&lookup_result, to_cmp)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup product failed: incorrect product returned\n"); free_product (&lookup_result); return 1; } free_product (&lookup_result); return 0; } static int test_lookup_products (const char *is, unsigned int products_length, const char **product_ids) { unsigned int results_matching[products_length]; struct TestLookupProducts_Closure cls = { .products_to_cmp_length = products_length, .product_ids_to_cmp = product_ids, .results_matching = results_matching, .results_length = 0 }; memset (results_matching, 0, sizeof (unsigned int) * products_length); if (0 > plugin->lookup_products (plugin->cls, is, &lookup_products_cb, &cls)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup products failed\n"); return 1; } if (products_length != cls.results_length) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup products failed: incorrect number of results\n"); return 1; } for (unsigned int i = 0; products_length > i; ++i) { if (1 != cls.results_matching[i]) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup products failed: mismatched data\n"); return 1; } } return 0; } static int test_delete_product (const char *is, const char *pd) { if (0 > plugin->delete_product (plugin->cls, is, pd)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Delete product failed\n"); plugin->drop_tables (plugin->cls); return 1; } return 0; } /** * Closure for product tests. */ struct TestProducts_Closure { /** * The instance settings */ struct TALER_MERCHANTDB_InstanceSettings is; /** * The instance public key */ struct TALER_MerchantPublicKeyP merchant_pub; /** * The instance private key */ struct TALER_MerchantPrivateKeyP merchant_priv; /** * The array of products */ struct TALER_MERCHANTDB_ProductDetails products[2]; /** * The array of product ids */ const char *product_ids[2]; }; /** * Sets up the data structures used in the product tests */ static void pre_test_products (struct TestProducts_Closure *cls) { /* Instance */ GNUNET_CRYPTO_eddsa_key_create (&cls->merchant_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&cls->merchant_priv.eddsa_priv, &cls->merchant_pub.eddsa_pub); cls->is.id = "test_inst_products"; cls->is.name = "Test"; cls->is.address = json_array (); GNUNET_assert (NULL != cls->is.address); GNUNET_assert (0 == json_array_append (cls->is.address, json_string ("123 Example St"))); cls->is.jurisdiction = json_array (); GNUNET_assert (NULL != cls->is.jurisdiction); GNUNET_assert (0 == json_array_append (cls->is.jurisdiction, json_string ("Ohio"))); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_deposit_fee)); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_wire_fee)); cls->is.default_wire_fee_amortization = 1; cls->is.default_wire_transfer_delay = GNUNET_TIME_relative_get_minute_ (); cls->is.default_pay_delay = GNUNET_TIME_relative_get_second_ (); /* Products */ cls->product_ids[0] = "test_products_pd_0"; cls->products[0].description = "This is a test product"; cls->products[0].description_i18n = json_array (); GNUNET_assert (NULL != cls->products[0].description_i18n); cls->products[0].unit = "boxes"; GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:120.40", &cls->products[0].price)); cls->products[0].taxes = json_array (); GNUNET_assert (NULL != cls->products[0].taxes); cls->products[0].total_stock = 55; cls->products[0].total_sold = 0; cls->products[0].total_lost = 0; cls->products[0].image = json_array (); GNUNET_assert (NULL != cls->products[0].image); cls->products[0].address = json_array (); GNUNET_assert (NULL != cls->products[0].address); cls->products[0].next_restock = GNUNET_TIME_absolute_get_zero_ (); cls->product_ids[1] = "test_products_pd_1"; cls->products[1].description = "This is a another test product"; cls->products[1].description_i18n = json_array (); GNUNET_assert (NULL != cls->products[1].description_i18n); cls->products[1].unit = "cans"; GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:4.95", &cls->products[1].price)); cls->products[1].taxes = json_array (); GNUNET_assert (NULL != cls->products[1].taxes); cls->products[1].total_stock = 5001; cls->products[1].total_sold = 0; cls->products[1].total_lost = 0; cls->products[1].image = json_array (); GNUNET_assert (NULL != cls->products[1].image); cls->products[1].address = json_array (); GNUNET_assert (NULL != cls->products[1].address); cls->products[1].next_restock = GNUNET_TIME_absolute_get_zero_ (); } /** * Handles all teardown after testing */ static void post_test_products (struct TestProducts_Closure *cls) { /* Cleanup instance */ json_decref (cls->is.address); json_decref (cls->is.jurisdiction); /* Cleanup products */ json_decref (cls->products[0].description_i18n); json_decref (cls->products[0].taxes); json_decref (cls->products[0].image); json_decref (cls->products[0].address); json_decref (cls->products[1].description_i18n); json_decref (cls->products[1].taxes); json_decref (cls->products[1].image); json_decref (cls->products[1].address); } static int run_test_products (struct TestProducts_Closure *cls) { /* Insert the instance */ TEST_RET_ON_FAIL (test_insert_instance (&cls->merchant_pub, &cls->merchant_priv, &cls->is)); /* Test inserting a product */ TEST_RET_ON_FAIL (test_insert_product (cls->is.id, cls->product_ids[0], &cls->products[0])); /* Test lookup of individual products */ TEST_RET_ON_FAIL (test_lookup_product (cls->is.id, cls->product_ids[0], &cls->products[0])); /* Make sure it fails correctly for products that don't exist */ /* Test product update */ cls->products[0].description = "This is a test product that has been updated!"; if (1 != plugin->update_product (plugin->cls, cls->is.id, cls->product_ids[0], &cls->products[0])) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Update product failed\n"); return 1; } TEST_RET_ON_FAIL (test_lookup_product (cls->is.id, cls->product_ids[0], &cls->products[0])); /* Test collective product lookup */ TEST_RET_ON_FAIL (test_insert_product (cls->is.id, cls->product_ids[1], &cls->products[1])); TEST_RET_ON_FAIL (test_lookup_products (cls->is.id, 2, cls->product_ids)); /* Test locking */ struct GNUNET_Uuid uuid; uuid.value[0] = 0x1287346a; struct GNUNET_TIME_Absolute refund_deadline = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_WEEKS); if (1 != plugin->lock_product (plugin->cls, cls->is.id, cls->product_ids[0], &uuid, 1, refund_deadline)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lock product failed\n"); return 1; } /* Test product deletion */ TEST_RET_ON_FAIL (test_delete_product (cls->is.id, cls->product_ids[1])); TEST_RET_ON_FAIL (test_lookup_products (cls->is.id, 1, cls->product_ids)); /* Test unlocking */ if (1 != plugin->unlock_inventory (plugin->cls, &uuid)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unlock inventory failed\n"); return 1; } TEST_RET_ON_FAIL (test_delete_product (cls->is.id, cls->product_ids[0])); TEST_RET_ON_FAIL (test_lookup_products (cls->is.id, 0, NULL)); return 0; } static int test_products (void *cls) { struct TestProducts_Closure test_cls; pre_test_products (&test_cls); int test_result = run_test_products (&test_cls); post_test_products (&test_cls); return test_result; } /** * Closure for testing order lookup */ struct TestLookupOrders_Closure { /** * Number of orders to compare to */ unsigned int orders_to_cmp_length; /** * Pointer to (ordered) array of order ids */ const char **order_ids_to_cmp; /** * Pointer to array of bools indicating matches in the correct index */ bool *results_match; /** * Total number of results returned */ unsigned int results_length; }; static void lookup_orders_cb (void *cls, const char *order_id, uint64_t order_serial, struct GNUNET_TIME_Absolute timestamp) { struct TestLookupOrders_Closure *cmp = cls; if (NULL == cmp) return; unsigned int i = cmp->results_length; cmp->results_length += 1; if (cmp->orders_to_cmp_length > i) { /* Compare the orders */ if (0 == strcmp (cmp->order_ids_to_cmp[i], order_id)) cmp->results_match[i] = true; else cmp->results_match[i] = false; } } static int test_insert_order (const char *instance_id, const char *order_id, struct GNUNET_TIME_Absolute pay_deadline, const json_t *contract_terms) { if (1 != plugin->insert_order (plugin->cls, instance_id, order_id, pay_deadline, contract_terms)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert order failed\n"); return 1; } return 0; } static int test_lookup_order (const char *is, const char *od, const json_t *contract_to_cmp) { json_t *lookup_terms = NULL; if (0 > plugin->lookup_order (plugin->cls, is, od, &lookup_terms)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup order failed\n"); if (NULL != lookup_terms) json_decref (lookup_terms); return 1; } if (1 != json_equal (contract_to_cmp, lookup_terms)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup order failed: incorrect order returned\n"); if (NULL != lookup_terms) json_decref (lookup_terms); return 1; } json_decref (lookup_terms); return 0; } static int test_lookup_orders (const char *is, const struct TALER_MERCHANTDB_OrderFilter *of, unsigned int orders_length, const char **order_ids_to_cmp) { bool results_match[orders_length]; struct TestLookupOrders_Closure cls = { .orders_to_cmp_length = orders_length, .order_ids_to_cmp = order_ids_to_cmp, .results_match = results_match, .results_length = 0 }; memset (results_match, 0, sizeof (bool) * orders_length); if (0 > plugin->lookup_orders (plugin->cls, is, of, &lookup_orders_cb, &cls)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup orders failed\n"); return 1; } if (orders_length != cls.results_length) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup orders failed: incorrect number of results (%d)\n", cls.results_length); return 1; } for (unsigned int i = 0; orders_length > i; ++i) { if (false == cls.results_match[i]) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup orders failed: mismatched data\n"); return 1; } } return 0; } static int test_delete_order (const char *is, const char *od) { if (0 > plugin->delete_order (plugin->cls, is, od)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Delete order failed\n"); return 1; } return 0; } static int test_insert_contract_terms (const char *is, const char *order_id, json_t *contract) { if (1 != plugin->insert_contract_terms (plugin->cls, is, order_id, contract)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert contract terms failed\n"); return 1; } return 0; } static int test_lookup_contract_terms (const char *instance_id, const char *order_id, const json_t *expected_contract) { json_t *contract = NULL; if (1 != plugin->lookup_contract_terms (plugin->cls, instance_id, order_id, &contract)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup contract terms failed\n"); if (NULL != contract) json_decref (contract); return 1; } if (1 != json_equal (expected_contract, contract)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup contract terms failed: mismatched data\n"); if (NULL != contract) json_decref (contract); return 1; } if (NULL != contract) json_decref (contract); return 0; } static int test_lookup_order_status (const char *instance_id, const char *order_id, const struct GNUNET_HashCode *expected_contract_terms, bool expected_paid) { struct GNUNET_HashCode h_contract_terms; bool order_paid = false; if (1 != plugin->lookup_order_status (plugin->cls, instance_id, order_id, &h_contract_terms, &order_paid)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup order status failed\n"); return 1; } if ((expected_paid != order_paid) || (0 != GNUNET_memcmp (&h_contract_terms, expected_contract_terms))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup order status/deposit failed: mismatched data\n"); return 1; } return 0; } /** * Container for order data */ struct OrderData { /** * The id of the order */ const char *id; /** * The pay deadline for the order */ struct GNUNET_TIME_Absolute pay_deadline; /** * The contract of the order */ json_t *contract; }; /** * Closure for order tests. */ struct TestOrders_Closure { /** * The instance settings */ struct TALER_MERCHANTDB_InstanceSettings is; /** * The instance public key */ struct TALER_MerchantPublicKeyP merchant_pub; /** * The instance private key */ struct TALER_MerchantPrivateKeyP merchant_priv; /** * The array of orders */ struct OrderData orders[2]; }; static void pre_test_orders (struct TestOrders_Closure *cls) { struct GNUNET_TIME_Absolute pay_deadline = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_DAYS); struct GNUNET_TIME_Absolute refund_deadline = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_WEEKS); /* Instance */ GNUNET_CRYPTO_eddsa_key_create (&cls->merchant_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&cls->merchant_priv.eddsa_priv, &cls->merchant_pub.eddsa_pub); cls->is.id = "test_inst_orders"; cls->is.name = "Test"; cls->is.address = json_array (); GNUNET_assert (NULL != cls->is.address); GNUNET_assert (0 == json_array_append (cls->is.address, json_string ("123 Example St"))); cls->is.jurisdiction = json_array (); GNUNET_assert (NULL != cls->is.jurisdiction); GNUNET_assert (0 == json_array_append (cls->is.jurisdiction, json_string ("Ohio"))); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_deposit_fee)); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_wire_fee)); cls->is.default_wire_fee_amortization = 1; cls->is.default_wire_transfer_delay = GNUNET_TIME_relative_get_minute_ (); cls->is.default_pay_delay = GNUNET_TIME_relative_get_second_ (); /* Orders */ cls->orders[0].id = "test_orders_od_0"; cls->orders[0].pay_deadline = GNUNET_TIME_absolute_get_zero_ (); cls->orders[0].contract = json_object (); GNUNET_assert (NULL != cls->orders[0].contract); pay_deadline = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_DAYS); refund_deadline = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_WEEKS); GNUNET_TIME_round_abs (&pay_deadline); GNUNET_TIME_round_abs (&refund_deadline); json_object_set (cls->orders[0].contract, "fulfillment_url", json_string ("a")); json_object_set (cls->orders[0].contract, "pay_deadline", GNUNET_JSON_from_time_abs (pay_deadline)); json_object_set (cls->orders[0].contract, "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline)); cls->orders[1].id = "test_orders_od_1"; cls->orders[1].pay_deadline = GNUNET_TIME_absolute_get_zero_ (); cls->orders[1].contract = json_array (); GNUNET_assert (NULL != cls->orders[1].contract); GNUNET_assert (0 == json_array_append (cls->orders[1].contract, json_string ("Second contract"))); } static void post_test_orders (struct TestOrders_Closure *cls) { json_decref (cls->is.address); json_decref (cls->is.jurisdiction); json_decref (cls->orders[0].contract); json_decref (cls->orders[1].contract); } static int run_test_orders (struct TestOrders_Closure *cls) { struct TALER_MERCHANTDB_OrderFilter filter = { .paid = TALER_MERCHANTDB_YNA_ALL, .refunded = TALER_MERCHANTDB_YNA_ALL, .wired = TALER_MERCHANTDB_YNA_ALL, .date = GNUNET_TIME_absolute_get_zero_ (), .start_row = 0, .delta = 8 }; const char *order_id_list[2] = { cls->orders[0].id, cls->orders[1].id }; struct GNUNET_HashCode h_contract_terms; GNUNET_assert (GNUNET_OK == TALER_JSON_hash (cls->orders[0].contract, &h_contract_terms)); struct GNUNET_TIME_Relative expiration = GNUNET_TIME_UNIT_MONTHS; /* Insert the instance */ TEST_RET_ON_FAIL (test_insert_instance (&cls->merchant_pub, &cls->merchant_priv, &cls->is)); /* Test inserting an order */ TEST_RET_ON_FAIL (test_insert_order (cls->is.id, cls->orders[0].id, cls->orders[0].pay_deadline, cls->orders[0].contract)); /* Test lookup order */ TEST_RET_ON_FAIL (test_lookup_order (cls->is.id, cls->orders[0].id, cls->orders[0].contract)); /* Make sure it fails correctly for nonexistent orders */ /* Test lookups on multiple orders */ TEST_RET_ON_FAIL (test_insert_order (cls->is.id, cls->orders[1].id, cls->orders[1].pay_deadline, cls->orders[1].contract)); TEST_RET_ON_FAIL (test_lookup_orders (cls->is.id, &filter, 2, order_id_list)); /* Test inserting contract terms */ TEST_RET_ON_FAIL (test_insert_contract_terms (cls->is.id, cls->orders[0].id, cls->orders[0].contract)); /* Test lookup contract terms */ TEST_RET_ON_FAIL (test_lookup_contract_terms (cls->is.id, cls->orders[0].id, cls->orders[0].contract)); /* Test lookup order status */ TEST_RET_ON_FAIL (test_lookup_order_status (cls->is.id, cls->orders[0].id, &h_contract_terms, false)); /* Test marking contracts as paid */ if (1 != plugin->mark_contract_paid (plugin->cls, cls->is.id, &h_contract_terms, "test_orders_session")) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Mark contract as paid failed\n"); return 1; } TEST_RET_ON_FAIL (test_lookup_order_status (cls->is.id, cls->orders[0].id, &h_contract_terms, true)); filter.paid = TALER_MERCHANTDB_YNA_YES; TEST_RET_ON_FAIL (test_lookup_orders (cls->is.id, &filter, 1, order_id_list)); /* Test deleting contract terms */ if (1 != plugin->delete_contract_terms (plugin->cls, cls->is.id, cls->orders[0].id, expiration)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Delete contract terms failed\n"); return 1; } /* Test delete order */ TEST_RET_ON_FAIL (test_delete_order (cls->is.id, cls->orders[1].id)); TEST_RET_ON_FAIL (test_lookup_orders (cls->is.id, &filter, 0, NULL)); return 0; } static int test_orders (void *cls) { struct TestOrders_Closure test_cls; pre_test_orders (&test_cls); int test_result = run_test_orders (&test_cls); post_test_orders (&test_cls); return test_result; } /** * A container for deposit data. */ struct DepositData { struct GNUNET_TIME_Absolute timestamp; struct GNUNET_HashCode h_contract_terms; struct TALER_CoinSpendPublicKeyP coin_pub; const char *exchange_url; struct TALER_Amount amount_with_fee; struct TALER_Amount deposit_fee; struct TALER_Amount refund_fee; struct TALER_Amount wire_fee; struct GNUNET_HashCode h_wire; struct TALER_ExchangeSignatureP exchange_sig; }; /** * Closure for testing deposit lookup */ struct TestLookupDeposits_Closure { /** * Number of deposits to compare to */ unsigned int deposits_to_cmp_length; /** * Pointer to array of deposit data */ const struct DepositData *deposits_to_cmp; /** * Pointer to array of number of matches per deposit */ unsigned int *results_matching; /** * Total number of results returned */ unsigned int results_length; }; static void lookup_deposits_cb (void *cls, const char *exchange_url, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_Amount *amount_with_fee, const struct TALER_Amount *deposit_fee, const struct TALER_Amount *refund_fee, const struct TALER_Amount *wire_fee) { struct TestLookupDeposits_Closure *cmp = cls; if (NULL == cmp) return; cmp->results_length += 1; for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i) { if ((GNUNET_OK == TALER_amount_cmp_currency ( &cmp->deposits_to_cmp[i].amount_with_fee, amount_with_fee)) && (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee, amount_with_fee)) && (GNUNET_OK == TALER_amount_cmp_currency ( &cmp->deposits_to_cmp[i].deposit_fee, deposit_fee)) && (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee, deposit_fee)) && (GNUNET_OK == TALER_amount_cmp_currency ( &cmp->deposits_to_cmp[i].refund_fee, refund_fee)) && (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee, refund_fee)) && (GNUNET_OK == TALER_amount_cmp_currency ( &cmp->deposits_to_cmp[i].wire_fee, wire_fee)) && (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].wire_fee, wire_fee))) { cmp->results_matching[i] += 1; } } } static void lookup_deposits_contract_coin_cb (void *cls, const char *exchange_url, const struct TALER_Amount *amount_with_fee, const struct TALER_Amount *deposit_fee, const struct TALER_Amount *refund_fee, const struct TALER_Amount *wire_fee, const struct GNUNET_HashCode *h_wire, struct GNUNET_TIME_Absolute deposit_timestamp, struct GNUNET_TIME_Absolute refund_deadline, const struct TALER_ExchangeSignatureP *exchange_sig, const struct TALER_ExchangePublicKeyP *exchange_pub) { struct TestLookupDeposits_Closure *cmp = cls; if (NULL == cmp) return; cmp->results_length += 1; for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i) { if ((cmp->deposits_to_cmp[i].timestamp.abs_value_us == deposit_timestamp.abs_value_us) && (0 == strcmp (cmp->deposits_to_cmp[i].exchange_url, exchange_url)) && (GNUNET_OK == TALER_amount_cmp_currency ( &cmp->deposits_to_cmp[i].amount_with_fee, amount_with_fee)) && (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee, amount_with_fee)) && (GNUNET_OK == TALER_amount_cmp_currency ( &cmp->deposits_to_cmp[i].deposit_fee, deposit_fee)) && (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee, deposit_fee)) && (GNUNET_OK == TALER_amount_cmp_currency ( &cmp->deposits_to_cmp[i].refund_fee, refund_fee)) && (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee, refund_fee)) && (GNUNET_OK == TALER_amount_cmp_currency ( &cmp->deposits_to_cmp[i].wire_fee, wire_fee)) && (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].wire_fee, wire_fee)) && (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire, h_wire)) && (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].exchange_sig, exchange_sig))) { cmp->results_matching[i] += 1; } } } static int test_insert_deposit (const char *is, const struct TALER_ExchangePublicKeyP *exchange_pub, const struct DepositData *deposit) { if (1 != plugin->insert_deposit (plugin->cls, is, deposit->timestamp, &deposit->h_contract_terms, &deposit->coin_pub, deposit->exchange_url, &deposit->amount_with_fee, &deposit->deposit_fee, &deposit->refund_fee, &deposit->wire_fee, &deposit->h_wire, &deposit->exchange_sig, exchange_pub)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert deposit failed\n"); return 1; } return 0; } static int test_lookup_deposits (const char *is, const struct GNUNET_HashCode *h_contract_terms, unsigned int deposits_length, const struct DepositData *deposits) { unsigned int results_matching[deposits_length]; struct TestLookupDeposits_Closure cmp = { .deposits_to_cmp_length = deposits_length, .deposits_to_cmp = deposits, .results_matching = results_matching, .results_length = 0 }; memset (results_matching, 0, sizeof (unsigned int) * deposits_length); if (0 > plugin->lookup_deposits (plugin->cls, is, h_contract_terms, &lookup_deposits_cb, &cmp)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup deposits failed\n"); return 1; } if (deposits_length != cmp.results_length) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup deposits failed: incorrect number of results returned\n"); return 1; } for (unsigned int i = 0; deposits_length > i; ++i) { if (cmp.results_matching[i] != 1) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup deposits failed: mismatched data\n"); return 1; } } return 0; } static int test_lookup_deposits_contract_and_coin (const char *instance_id, const struct GNUNET_HashCode *h_contract, const struct TALER_CoinSpendPublicKeyP *coin_pub, unsigned int deposits_length, const struct DepositData *deposits) { unsigned int results_matching[deposits_length]; struct TestLookupDeposits_Closure cmp = { .deposits_to_cmp_length = deposits_length, .deposits_to_cmp = deposits, .results_matching = results_matching, .results_length = 0 }; memset (results_matching, 0, sizeof (unsigned int) * deposits_length); if (0 > plugin->lookup_deposits_by_contract_and_coin (plugin->cls, instance_id, h_contract, coin_pub, & lookup_deposits_contract_coin_cb, &cmp)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup deposits by contract and coin failed\n"); return 1; } if (deposits_length != cmp.results_length) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup deposits failed: incorrect number of results returned\n"); return 1; } for (unsigned int i = 0; deposits_length > i; ++i) { if (cmp.results_matching[i] != 1) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup deposits failed: mismatched data\n"); return 1; } } return 0; } /** * Closure for deposit tests. */ struct TestDeposits_Closure { /** * The instance settings */ struct TALER_MERCHANTDB_InstanceSettings is; /** * The instance public key */ struct TALER_MerchantPublicKeyP merchant_pub; /** * The instance private key */ struct TALER_MerchantPrivateKeyP merchant_priv; /** * The merchant account */ struct TALER_MERCHANTDB_AccountDetails account; /** * The exchange public key */ struct TALER_ExchangePublicKeyP exchange_pub; /** * The exchange private key */ struct TALER_ExchangePrivateKeyP exchange_priv; /** * The master public key */ struct TALER_MasterPublicKeyP master_pub; /** * The master private key */ struct TALER_MasterPrivateKeyP master_priv; /** * The master signature */ struct TALER_MasterSignatureP master_sig; /** * The exchange signkey start date */ struct GNUNET_TIME_Absolute signkey_start; /** * The exchange signkey expire date */ struct GNUNET_TIME_Absolute signkey_expire; /** * The exchange signkey end date */ struct GNUNET_TIME_Absolute signkey_end; /** * The order data */ struct OrderData order; /** * The array of deposits */ struct DepositData deposits[2]; }; static void pre_test_deposits (struct TestDeposits_Closure *cls) { struct GNUNET_TIME_Absolute pay_deadline; struct GNUNET_TIME_Absolute refund_deadline; struct TALER_ExchangeSigningKeyValidityPS exch_sign = { .purpose = { .size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)), .purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY) } }; struct TALER_CoinSpendPrivateKeyP coin_priv; struct TALER_DepositRequestPS deposit_sign = { .purpose = { .size = htonl (sizeof (struct TALER_DepositRequestPS)), .purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT) } }; /* Instance */ GNUNET_CRYPTO_eddsa_key_create (&cls->merchant_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&cls->merchant_priv.eddsa_priv, &cls->merchant_pub.eddsa_pub); cls->is.id = "test_inst_deposits"; cls->is.name = "Test"; cls->is.address = json_array (); GNUNET_assert (NULL != cls->is.address); GNUNET_assert (0 == json_array_append (cls->is.address, json_string ("123 Example St"))); cls->is.jurisdiction = json_array (); GNUNET_assert (NULL != cls->is.jurisdiction); GNUNET_assert (0 == json_array_append (cls->is.jurisdiction, json_string ("Ohio"))); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_deposit_fee)); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_wire_fee)); cls->is.default_wire_fee_amortization = 1; cls->is.default_wire_transfer_delay = GNUNET_TIME_relative_get_minute_ (); cls->is.default_pay_delay = GNUNET_TIME_relative_get_second_ (); /* Account */ GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, &cls->account.h_wire); GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, &cls->account.salt); cls->account.payto_uri = "payto://x-taler-bank/bank.demo.taler.net/4"; cls->account.active = true; /* Signing key */ GNUNET_CRYPTO_eddsa_key_create (&cls->exchange_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&cls->exchange_priv.eddsa_priv, &cls->exchange_pub.eddsa_pub); GNUNET_CRYPTO_eddsa_key_create (&cls->master_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&cls->master_priv.eddsa_priv, &cls->master_pub.eddsa_pub); GNUNET_CRYPTO_eddsa_sign (&cls->master_priv.eddsa_priv, &exch_sign, &cls->master_sig.eddsa_signature); cls->signkey_start = GNUNET_TIME_absolute_get (); cls->signkey_expire = GNUNET_TIME_absolute_get (); cls->signkey_end = GNUNET_TIME_absolute_get (); /* Order */ pay_deadline = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_DAYS); refund_deadline = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), GNUNET_TIME_UNIT_WEEKS); GNUNET_TIME_round_abs (&pay_deadline); GNUNET_TIME_round_abs (&refund_deadline); cls->order.id = "test_orders_od_1"; cls->order.pay_deadline = pay_deadline; cls->order.contract = json_object (); json_object_set (cls->order.contract, "fulfillment_url", json_string ("a")); json_object_set (cls->order.contract, "pay_deadline", GNUNET_JSON_from_time_abs (pay_deadline)); json_object_set (cls->order.contract, "refund_deadline", GNUNET_JSON_from_time_abs (refund_deadline)); /* Deposit */ cls->deposits[0].timestamp = GNUNET_TIME_absolute_get (); GNUNET_assert (GNUNET_OK == TALER_JSON_hash (cls->order.contract, &cls->deposits[0].h_contract_terms)); GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv, &cls->deposits[0].coin_pub.eddsa_pub); cls->deposits[0].exchange_url = "test-exchange"; GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:50.00", &cls->deposits[0].amount_with_fee)); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1.00", &cls->deposits[0].deposit_fee)); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1.50", &cls->deposits[0].refund_fee)); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:2.00", &cls->deposits[0].wire_fee)); cls->deposits[0].h_wire = cls->account.h_wire; deposit_sign.h_contract_terms = cls->deposits[0].h_contract_terms; deposit_sign.h_wire = cls->deposits[0].h_wire; deposit_sign.wallet_timestamp = GNUNET_TIME_absolute_hton ( GNUNET_TIME_absolute_get ()); deposit_sign.refund_deadline = GNUNET_TIME_absolute_hton ( GNUNET_TIME_absolute_get ()); TALER_amount_hton (&deposit_sign.amount_with_fee, &cls->deposits[0].amount_with_fee); TALER_amount_hton (&deposit_sign.deposit_fee, &cls->deposits[0].deposit_fee); deposit_sign.merchant = cls->merchant_pub; deposit_sign.coin_pub = cls->deposits[0].coin_pub; GNUNET_CRYPTO_eddsa_sign (&cls->exchange_priv.eddsa_priv, &deposit_sign, &cls->deposits[0].exchange_sig.eddsa_signature); } static void post_test_deposits (struct TestDeposits_Closure *cls) { /* Instance */ json_decref (cls->is.address); json_decref (cls->is.jurisdiction); /* Order */ json_decref (cls->order.contract); } static int run_test_deposits (struct TestDeposits_Closure *cls) { /* Insert the instance */ TEST_RET_ON_FAIL (test_insert_instance (&cls->merchant_pub, &cls->merchant_priv, &cls->is)); /* Insert an account */ TEST_RET_ON_FAIL (test_insert_account (cls->is.id, &cls->account)); /* Insert a signing key */ if (1 != plugin->insert_exchange_signkey (plugin->cls, &cls->master_pub, &cls->exchange_pub, cls->signkey_start, cls->signkey_expire, cls->signkey_end, &cls->master_sig)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert signing key failed\n"); return 1; } /* Insert an order */ TEST_RET_ON_FAIL (test_insert_order (cls->is.id, cls->order.id, cls->order.pay_deadline, cls->order.contract)); /* Insert contract terms */ TEST_RET_ON_FAIL (test_insert_contract_terms (cls->is.id, cls->order.id, cls->order.contract)); /* Test inserting a deposit */ TEST_RET_ON_FAIL (test_insert_deposit (cls->is.id, &cls->exchange_pub, &cls->deposits[0])); /* Test lookup deposits */ TEST_RET_ON_FAIL (test_lookup_deposits (cls->is.id, &cls->deposits[0].h_contract_terms, 1, cls->deposits)); /* Test lookup deposits by contract and coins */ TEST_RET_ON_FAIL (test_lookup_deposits_contract_and_coin (cls->is.id, &cls->deposits[0]. h_contract_terms, &cls->deposits[0]. coin_pub, 1, cls->deposits)); return 0; } static int test_deposits (void *cls) { struct TestDeposits_Closure test_cls; pre_test_deposits (&test_cls); int test_result = run_test_deposits (&test_cls); post_test_deposits (&test_cls); return test_result; } static int test_lookup_transfer (const char *exchange_url, const struct TALER_WireTransferIdentifierRawP *wtid, const struct TALER_Amount *total_expected, const struct TALER_Amount *fee_expected, const struct GNUNET_TIME_Absolute *time_expected, bool verified_expected) { struct TALER_Amount total; struct TALER_Amount fee; struct GNUNET_TIME_Absolute time; bool verified; if (1 != plugin->lookup_transfer (plugin->cls, exchange_url, wtid, &total, &fee, &time, &verified)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup transfer failed\n"); return 1; } if ((GNUNET_OK != TALER_amount_cmp_currency (total_expected, &total)) || (0 != TALER_amount_cmp (total_expected, &total)) || (GNUNET_OK != TALER_amount_cmp_currency (fee_expected, &fee)) || (0 != TALER_amount_cmp (fee_expected, &fee)) || (time_expected->abs_value_us != time.abs_value_us) || (verified_expected != verified)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup transfer failed: mismatched data\n"); return 1; } return 0; } struct TestLookupTransferSummary_Closure { const char *order_id; const struct TALER_Amount *deposit_value; const struct TALER_Amount *deposit_fee; int result; }; static void lookup_transfer_summary_cb (void *cls, const char *order_id, const struct TALER_Amount *deposit_value, const struct TALER_Amount *deposit_fee) { struct TestLookupTransferSummary_Closure *cmp = cls; if (NULL == cmp) return; if ((0 == strcmp (cmp->order_id, order_id)) && (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_value, deposit_value)) && (0 == TALER_amount_cmp (cmp->deposit_value, deposit_value)) && (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_fee, deposit_fee)) && (0 == TALER_amount_cmp (cmp->deposit_fee, deposit_fee))) cmp->result = 1; else cmp->result = 0; } static int test_lookup_transfer_summary (const char *exchange_url, const struct TALER_WireTransferIdentifierRawP *wtid, const char *expected_order_id, const struct TALER_Amount *expected_deposit_value, const struct TALER_Amount *expected_deposit_fee) { struct TestLookupTransferSummary_Closure cmp = { .order_id = expected_order_id, .deposit_value = expected_deposit_value, .deposit_fee = expected_deposit_fee }; if (1 != plugin->lookup_transfer_summary (plugin->cls, exchange_url, wtid, &lookup_transfer_summary_cb, &cmp)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup transfer summary failed\n"); return 1; } if (1 != cmp.result) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup transfer summary failed: mismatched data\n"); return 1; } return 0; } struct TestLookupTransferDetails_Closure { unsigned int details_to_cmp_length; const struct TALER_TrackTransferDetails *details_to_cmp; unsigned int *results_matching; unsigned int results_length; }; static void lookup_transfer_details_cb (void *cls, unsigned int current_offset, const struct TALER_TrackTransferDetails *details) { if (NULL == cls) return; struct TestLookupTransferDetails_Closure *cmp = cls; cmp->results_length += 1; } static int test_lookup_transfer_details (const char *exchange_url, const struct TALER_WireTransferIdentifierRawP *wtid, unsigned int details_length, const struct TALER_TrackTransferDetails *details) { unsigned int results_matching[details_length]; memset (results_matching, 0, sizeof (unsigned int) * details_length); struct TestLookupTransferDetails_Closure cmp = { .details_to_cmp_length = details_length, .details_to_cmp = details, .results_matching = results_matching, .results_length = 0 }; if (1 != plugin->lookup_transfer_details (plugin->cls, exchange_url, wtid, &lookup_transfer_details_cb, &cmp)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup transfer details failed\n"); return 1; } if (details_length != cmp.results_length) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup transfer details failed: incorrect number of results (%d)\n", cmp.results_length); return 1; } for (unsigned int i = 0; details_length > i; ++i) { if (1 != cmp.results_matching[i]) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Lookup transfer details failed: mismatched data\n"); return 1; } } return 0; } struct TestTransfers_Closure { /** * The instance settings */ struct TALER_MERCHANTDB_InstanceSettings is; /** * The instance public key */ struct TALER_MerchantPublicKeyP merchant_pub; /** * The instance private key */ struct TALER_MerchantPrivateKeyP merchant_priv; /** * The account */ struct TALER_MERCHANTDB_AccountDetails account; /** * The exchange public key */ struct TALER_ExchangePublicKeyP exchange_pub; /** * The exchange private key */ struct TALER_ExchangePrivateKeyP exchange_priv; /** * The exchange signature */ struct TALER_ExchangeSignatureP exchange_sig; /** * The master public key */ struct TALER_MasterPublicKeyP master_pub; /** * The master private key */ struct TALER_MasterPrivateKeyP master_priv; /** * The master signature */ struct TALER_MasterSignatureP master_sig; /** * The exchange signkey start date */ struct GNUNET_TIME_Absolute signkey_start; /** * The exchange signkey expire date */ struct GNUNET_TIME_Absolute signkey_expire; /** * The exchange signkey end date */ struct GNUNET_TIME_Absolute signkey_end; }; static void pre_test_transfers (struct TestTransfers_Closure *cls) { struct TALER_ExchangeSigningKeyValidityPS exch_sign = { .purpose = { .size = htonl (sizeof (struct TALER_ExchangeSigningKeyValidityPS)), .purpose = htonl (TALER_SIGNATURE_MASTER_SIGNING_KEY_VALIDITY) } }; /* Instance */ GNUNET_CRYPTO_eddsa_key_create (&cls->merchant_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&cls->merchant_priv.eddsa_priv, &cls->merchant_pub.eddsa_pub); cls->is.id = "test_inst_transfers"; cls->is.name = "Test"; cls->is.address = json_array (); GNUNET_assert (NULL != cls->is.address); GNUNET_assert (0 == json_array_append (cls->is.address, json_string ("123 Example St"))); cls->is.jurisdiction = json_array (); GNUNET_assert (NULL != cls->is.jurisdiction); GNUNET_assert (0 == json_array_append (cls->is.jurisdiction, json_string ("Ohio"))); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_deposit_fee)); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:1200.40", &cls->is.default_max_wire_fee)); cls->is.default_wire_fee_amortization = 1; cls->is.default_wire_transfer_delay = GNUNET_TIME_relative_get_minute_ (); cls->is.default_pay_delay = GNUNET_TIME_relative_get_second_ (); /* Account */ GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, &cls->account.h_wire); GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG, &cls->account.salt); cls->account.payto_uri = "payto://x-taler-bank/bank.demo.taler.net/4"; cls->account.active = true; /* Signing key */ GNUNET_CRYPTO_eddsa_key_create (&cls->exchange_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&cls->exchange_priv.eddsa_priv, &cls->exchange_pub.eddsa_pub); GNUNET_CRYPTO_eddsa_key_create (&cls->master_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&cls->master_priv.eddsa_priv, &cls->master_pub.eddsa_pub); GNUNET_CRYPTO_eddsa_sign (&cls->master_priv.eddsa_priv, &exch_sign, &cls->master_sig.eddsa_signature); cls->signkey_start = GNUNET_TIME_absolute_get (); cls->signkey_expire = GNUNET_TIME_absolute_get (); cls->signkey_end = GNUNET_TIME_absolute_get (); } static void post_test_transfers (struct TestTransfers_Closure *cls) { json_decref (cls->is.address); json_decref (cls->is.jurisdiction); } static int run_test_transfers (struct TestTransfers_Closure *cls) { const char *exchange_url = "exch-url"; struct TALER_WireTransferIdentifierRawP wtid = { .raw = {0} }; struct TALER_Amount amount; struct TALER_Amount total_with_fee; struct TALER_EXCHANGE_TransferData transfer_data = { .exchange_pub = cls->exchange_pub, .execution_time = GNUNET_TIME_absolute_get (), }; /* Insert the instance */ TEST_RET_ON_FAIL (test_insert_instance (&cls->merchant_pub, &cls->merchant_priv, &cls->is)); /* Insert the account */ TEST_RET_ON_FAIL (test_insert_account (cls->is.id, &cls->account)); /* Insert the transfer */ GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:13", &amount)); if (1 != plugin->insert_transfer (plugin->cls, cls->is.id, exchange_url, &wtid, &amount, cls->account.payto_uri, false)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert transfer failed\n"); return 1; } /* Insert a signing key */ if (1 != plugin->insert_exchange_signkey (plugin->cls, &cls->master_pub, &cls->exchange_pub, cls->signkey_start, cls->signkey_expire, cls->signkey_end, &cls->master_sig)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert signing key failed\n"); return 1; } /* Test transfer details */ GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:13", &transfer_data.total_amount)); GNUNET_assert (GNUNET_OK == TALER_string_to_amount ("EUR:0.49", &transfer_data.wire_fee)); if (1 != plugin->insert_transfer_details (plugin->cls, cls->is.id, "exch-url", cls->account.payto_uri, &wtid, &transfer_data)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Insert transfer details failed\n"); return 1; } /* Test lookup transfer */ GNUNET_assert (0 <= TALER_amount_add (&total_with_fee, &transfer_data.total_amount, &transfer_data.wire_fee)); TEST_RET_ON_FAIL (test_lookup_transfer (exchange_url, &wtid, &total_with_fee, &transfer_data.wire_fee, &transfer_data.execution_time, false)); /* Test set status to verified */ if (1 != plugin->set_transfer_status_to_verified (plugin->cls, exchange_url, &wtid)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Set transfer to verified failed\n"); return 1; } TEST_RET_ON_FAIL (test_lookup_transfer (exchange_url, &wtid, &total_with_fee, &transfer_data.wire_fee, &transfer_data.execution_time, true)); /* Test lookup transfer summary */ /* TEST_RET_ON_FAIL (test_lookup_transfer_summary (exchange_url, &wtid, "", &amount, &transfer_data.wire_fee)); */ /* Test lookup transfer details */ /* TEST_RET_ON_FAIL (test_lookup_transfer_details (exchange_url, &wtid, 0, NULL)); */ return 0; } static int test_transfers (void *cls) { struct TestTransfers_Closure test_cls; pre_test_transfers (&test_cls); int test_result = run_test_transfers (&test_cls); post_test_transfers (&test_cls); return test_result; } /** * Function that runs all tests and returns 1 upon error, 0 on success. */ static int run_tests (void *cls) { TEST_RET_ON_FAIL (test_instances (cls)); TEST_RET_ON_FAIL (test_products (cls)); TEST_RET_ON_FAIL (test_orders (cls)); TEST_RET_ON_FAIL (test_deposits (cls)); TEST_RET_ON_FAIL (test_transfers (cls)); return 0; } /** * Main function that will be run by the scheduler. * * @param cls closure with config */ static void run (void *cls) { struct GNUNET_CONFIGURATION_Handle *cfg = cls; /* Data for 'store_payment()' */ /* Load the plugin */ if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg))) { result = 77; return; } /* Run the preflight */ plugin->preflight (plugin->cls); result = run_tests (cls); /* Test dropping tables */ if (GNUNET_OK != plugin->drop_tables (plugin->cls)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Dropping tables failed\n"); result = 77; return; } /* Unload the plugin */ TALER_MERCHANTDB_plugin_unload (plugin); if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Plugin unload failed\n"); result = 77; return; } GNUNET_break (GNUNET_OK == plugin->drop_tables (plugin->cls)); TALER_MERCHANTDB_plugin_unload (plugin); plugin = NULL; } int main (int argc, char *const argv[]) { const char *plugin_name; char *config_filename; char *testname; struct GNUNET_CONFIGURATION_Handle *cfg; result = -1; if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) { GNUNET_break (0); return -1; } GNUNET_log_setup (argv[0], "DEBUG", NULL); plugin_name++; (void) GNUNET_asprintf (&testname, "test-merchantdb-%s", plugin_name); (void) GNUNET_asprintf (&config_filename, "%s.conf", testname); cfg = GNUNET_CONFIGURATION_create (); if (GNUNET_OK != GNUNET_CONFIGURATION_parse (cfg, config_filename)) { GNUNET_break (0); GNUNET_free (config_filename); GNUNET_free (testname); return 2; } GNUNET_SCHEDULER_run (&run, cfg); GNUNET_CONFIGURATION_destroy (cfg); GNUNET_free (config_filename); GNUNET_free (testname); return result; } /* end of test_merchantdb.c */