/*
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 */