/*
This file is part of TALER
(C) 2014-2024 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 test_merchantdb.c
* @brief testcase for merchant's postgres db plugin
* @author Marcello Stanisci
* @author Christian Grothoff
* @author Pricilla Huang
*/
#include "platform.h"
#include
#include
#include
#include "taler_merchant_util.h"
#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;
/**
* @param test 0 on success, non-zero on failure
*/
#define TEST_WITH_FAIL_CLAUSE(test, on_fail) \
if ((test)) \
{ \
GNUNET_break (0); \
on_fail \
}
#define TEST_COND_RET_ON_FAIL(cond, msg) \
if (! (cond)) \
{ \
GNUNET_break (0); \
GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
msg); \
return 1; \
}
/**
* @param __test 0 on success, non-zero on failure
*/
#define TEST_RET_ON_FAIL(__test) \
TEST_WITH_FAIL_CLAUSE (__test, \
return 1; \
)
/* ********** Instances ********** */
/**
* Container for instance settings along with keys.
*/
struct InstanceData
{
/**
* The instance settings.
*/
struct TALER_MERCHANTDB_InstanceSettings instance;
/**
* The public key for the instance.
*/
struct TALER_MerchantPublicKeyP merchant_pub;
/**
* The private key for the instance.
*/
struct TALER_MerchantPrivateKeyP merchant_priv;
};
/**
* Creates data for an instance to use with testing.
*
* @param instance_id the identifier for this instance.
* @param instance the instance data to be filled.
*/
static void
make_instance (const char *instance_id,
struct InstanceData *instance)
{
memset (instance,
0,
sizeof (*instance));
GNUNET_CRYPTO_eddsa_key_create (&instance->merchant_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&instance->merchant_priv.eddsa_priv,
&instance->merchant_pub.eddsa_pub);
instance->instance.id = (char *) instance_id;
instance->instance.name = (char *) "Test";
instance->instance.address = json_array ();
GNUNET_assert (NULL != instance->instance.address);
GNUNET_assert (0 == json_array_append_new (instance->instance.address,
json_string ("123 Example St")));
instance->instance.jurisdiction = json_array ();
GNUNET_assert (NULL != instance->instance.jurisdiction);
GNUNET_assert (0 == json_array_append_new (instance->instance.jurisdiction,
json_string ("Ohio")));
instance->instance.use_stefan = true;
instance->instance.default_wire_transfer_delay =
GNUNET_TIME_relative_get_minute_ ();
instance->instance.default_pay_delay = GNUNET_TIME_relative_get_second_ ();
}
/**
* Frees memory allocated when creating an instance for testing.
*
* @param instance the instance containing the memory to be freed.
*/
static void
free_instance_data (struct InstanceData *instance)
{
json_decref (instance->instance.address);
json_decref (instance->instance.jurisdiction);
}
/**
* Creates an account with test data for an instance.
*
* @param account the account to initialize.
*/
static void
make_account (struct TALER_MERCHANTDB_AccountDetails *account)
{
memset (account,
0,
sizeof (*account));
GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG,
&account->h_wire.hash);
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
&account->salt,
sizeof (account->salt));
account->payto_uri.full_payto
= (char *) "payto://x-taler-bank/bank.demo.taler.net/4";
account->active = true;
}
/**
* 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;
};
/**
* Compares two instances for equality.
*
* @param a the first instance.
* @param b the second instance.
* @return 0 on equality, 1 otherwise.
*/
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)) ||
(a->use_stefan != b->use_stefan) ||
(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;
}
#if 0
/**
* Compares two accounts for equality.
*
* @param a the first account.
* @param b the second account.
* @return 0 on equality, 1 otherwise.
*/
static int
check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a,
const struct TALER_MERCHANTDB_AccountDetails *b)
{
if ((0 != GNUNET_memcmp (&a->h_wire,
&b->h_wire)) ||
(0 != GNUNET_memcmp (&a->salt,
&b->salt)) ||
(0 != TALER_full_payto_cmp (a->payto_uri,
b->payto_uri)) ||
(a->active != b->active))
return 1;
return 0;
}
#endif
/**
* Called after testing 'lookup_instances'.
*
* @param cls pointer to 'struct TestLookupInstances_Closure'.
* @param merchant_pub public key of the instance
* @param merchant_priv private key of the instance, NULL if not available
* @param is general instance settings
* @param ias instance authentication settings
*/
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,
const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
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)
{
if (0 != check_instances_equal (cmp->instances_to_cmp[i].instance,
is))
continue;
cmp->results_matching[i] += 1;
}
}
/**
* Tests @e insert_instance.
*
* @param instance the instance data to insert.
* @param expected_result the result that should be returned from the plugin.
* @return 0 on success, 1 on failure.
*/
static int
test_insert_instance (const struct InstanceData *instance,
enum GNUNET_DB_QueryStatus expected_result)
{
struct TALER_MERCHANTDB_InstanceAuthSettings ias = { 0 };
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_instance (plugin->cls,
&instance->merchant_pub,
&instance->merchant_priv,
&instance->instance,
&ias),
"Insert instance failed\n");
return 0;
}
/**
* Tests @e update_instance.
*
* @param updated_data the instance data to update the row in the database to.
* @param expected_result the result that should be returned from the plugin.
* @return 0 on success, 1 on failure.
*/
static int
test_update_instance (const struct InstanceData *updated_data,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->update_instance (plugin->cls,
&updated_data->instance),
"Update instance failed\n");
return 0;
}
/**
* Tests @e lookup_instances.
*
* @param active_only whether to lookup all instance, or only active ones.
* @param instances_length number of instances to compare to in @e instances.
* @param instances a list of instances that will be compared with the results
* found.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_instances (bool active_only,
unsigned int instances_length,
struct InstanceWithAccounts instances[])
{
unsigned int results_matching[GNUNET_NZL (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;
}
/**
* Tests removing the private key of the given instance from the database.
*
* @param instance the instance whose private key to delete.
* @param expected_result the result we expect the db to return.
* @return 0 on success, 1 otherwise.
*/
static int
test_delete_instance_private_key (const struct InstanceData *instance,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->delete_instance_private_key (
plugin->cls,
instance->instance.id),
"Delete instance private key failed\n");
return 0;
}
/**
* Tests purging all data for an instance from the database.
*
* @param instance the instance to purge.
* @param expected_result the result we expect the db to return.
* @return 0 on success, 1 otherwise.
*/
static int
test_purge_instance (const struct InstanceData *instance,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->purge_instance (plugin->cls,
instance->instance.id),
"Purge instance failed\n");
return 0;
}
/**
* Tests inserting an account for a merchant instance.
*
* @param instance the instance to associate the account with.
* @param account the account to insert.
* @param expected_result the result expected from the db.
* @return 0 on success, 1 otherwise.
*/
static int
test_insert_account (const struct InstanceData *instance,
const struct TALER_MERCHANTDB_AccountDetails *account,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_account (plugin->cls,
account),
"Insert account failed\n");
return 0;
}
/**
* Tests deactivating an account.
*
* @param account the account to deactivate.
* @param expected_result the result expected from the plugin.
* @return 0 on success, 1 otherwise.
*/
static int
test_inactivate_account (const struct InstanceData *instance,
const struct TALER_MERCHANTDB_AccountDetails *account,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->inactivate_account (plugin->cls,
instance->instance.id,
&account->h_wire),
"Deactivate account failed\n");
return 0;
}
/**
* Closure for instance tests
*/
struct TestInstances_Closure
{
/**
* The list of instances that we use for testing instances.
*/
struct InstanceData instances[2];
/**
* The list of accounts to use with the instances.
*/
struct TALER_MERCHANTDB_AccountDetails accounts[2];
};
/**
* Sets up the data structures used in the instance tests
*
* @cls the closure to initialize with test data.
*/
static void
pre_test_instances (struct TestInstances_Closure *cls)
{
/* Instance */
make_instance ("test_instances_inst0",
&cls->instances[0]);
make_instance ("test_instances_inst1",
&cls->instances[1]);
/* Accounts */
make_account (&cls->accounts[0]);
cls->accounts[0].instance_id
= cls->instances[0].instance.id;
make_account (&cls->accounts[1]);
cls->accounts[1].instance_id
= cls->instances[1].instance.id;
}
/**
* Handles all teardown after testing
*
* @cls the closure to free data from
*/
static void
post_test_instances (struct TestInstances_Closure *cls)
{
free_instance_data (&cls->instances[0]);
free_instance_data (&cls->instances[1]);
}
/**
* Function that tests instances.
*
* @param cls closure with config
* @return 0 on success, 1 if failure.
*/
static int
run_test_instances (struct TestInstances_Closure *cls)
{
struct InstanceWithAccounts instances[2] = {
{
.accounts_length = 0,
.accounts = cls->accounts,
.instance = &cls->instances[0].instance
},
{
.accounts_length = 0,
.accounts = cls->accounts,
.instance = &cls->instances[1].instance
}
};
uint64_t account_serial;
/* Test inserting an instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double insertion fails */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test lookup instances- is our new instance there? */
TEST_RET_ON_FAIL (test_lookup_instances (false,
1,
instances));
/* Test update instance */
cls->instances[0].instance.name = "Test - updated";
json_array_append_new (cls->instances[0].instance.address,
json_pack ("{s:s, s:I}",
"this", "is",
"more data", 47));
json_array_append_new (cls->instances[0].instance.jurisdiction,
json_pack ("{s:s}",
"vegetables", "bad"));
cls->instances[0].instance.use_stefan = false;
cls->instances[0].instance.default_wire_transfer_delay =
GNUNET_TIME_UNIT_HOURS;
cls->instances[0].instance.default_pay_delay = GNUNET_TIME_UNIT_MINUTES;
TEST_RET_ON_FAIL (test_update_instance (&cls->instances[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_instances (false,
1,
instances));
TEST_RET_ON_FAIL (test_update_instance (&cls->instances[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test account creation */
TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0],
&cls->accounts[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double account insertion fails */
TEST_RET_ON_FAIL (test_insert_account (&cls->instances[1],
&cls->accounts[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0],
&cls->accounts[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
instances[0].accounts_length = 1;
TEST_RET_ON_FAIL (test_lookup_instances (false,
1,
instances));
/* Test deactivate account */
TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[0],
&cls->accounts[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[1],
&cls->accounts[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
cls->accounts[0].active = false;
TEST_RET_ON_FAIL (test_lookup_instances (false,
1,
instances));
/* Test lookup account */
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->lookup_account (plugin->cls,
cls->instances[0].instance.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 (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_account (plugin->cls,
cls->instances[0].instance.id,
(struct TALER_FullPayto) {
(char *) "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 */
TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
TEST_RET_ON_FAIL (test_lookup_instances (true,
0,
NULL));
TEST_RET_ON_FAIL (test_lookup_instances (false,
1,
instances));
TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_instances (false,
2,
instances));
TEST_RET_ON_FAIL (test_lookup_instances (true,
1,
&instances[1]));
TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test that the instance is gone. */
TEST_RET_ON_FAIL (test_lookup_instances (false,
1,
instances));
return 0;
}
/**
* Function that tests instances.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_instances (void)
{
struct TestInstances_Closure test_cls;
int test_result;
pre_test_instances (&test_cls);
test_result = run_test_instances (&test_cls);
post_test_instances (&test_cls);
return test_result;
}
/* *********** Products ********** */
/**
* A container for data relevant to a product.
*/
struct ProductData
{
/**
* The identifier of the product.
*/
const char *id;
/**
* The details of the product.
*/
struct TALER_MERCHANTDB_ProductDetails product;
};
/**
* Creates a product for testing with.
*
* @param id the id of the product.
* @param product the product data to fill.
*/
static void
make_product (const char *id,
struct ProductData *product)
{
product->id = id;
product->product.description = "This is a test product";
product->product.description_i18n = json_array ();
GNUNET_assert (NULL != product->product.description_i18n);
product->product.unit = "boxes";
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:120.40",
&product->product.price));
product->product.taxes = json_array ();
GNUNET_assert (NULL != product->product.taxes);
product->product.total_stock = 55;
product->product.total_sold = 0;
product->product.total_lost = 0;
product->product.image = GNUNET_strdup ("");
GNUNET_assert (NULL != product->product.image);
product->product.address = json_array ();
GNUNET_assert (NULL != product->product.address);
product->product.next_restock = GNUNET_TIME_UNIT_ZERO_TS;
}
/**
* Frees memory associated with @e ProductData.
*
* @param product the container to free.
*/
static void
free_product_data (struct ProductData *product)
{
json_decref (product->product.description_i18n);
json_decref (product->product.taxes);
GNUNET_free (product->product.image);
json_decref (product->product.address);
}
/**
* Compare two products for equality.
*
* @param a the first product.
* @param b the second product.
* @return 0 on equality, 1 otherwise.
*/
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) ||
(0 != strcmp (a->image,
b->image)) ||
(1 != json_equal (a->address,
b->address)) ||
(GNUNET_TIME_timestamp_cmp (a->next_restock,
!=,
b->next_restock)))
return 1;
return 0;
}
/**
* Tests inserting product data into the database.
*
* @param instance the instance to insert the product for.
* @param product the product data to insert.
* @param num_cats length of the @a cats array
* @param cats array of categories for the product
* @param expected_result the result we expect the db to return.
* @param expect_conflict expected conflict status
* @param expect_no_instance expected instance missing status
* @param expected_no_cat expected category missing index
* @return 0 when successful, 1 otherwise.
*/
static int
test_insert_product (const struct InstanceData *instance,
const struct ProductData *product,
unsigned int num_cats,
const uint64_t *cats,
enum GNUNET_DB_QueryStatus expected_result,
bool expect_conflict,
bool expect_no_instance,
ssize_t expected_no_cat)
{
bool conflict;
bool no_instance;
ssize_t no_cat;
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_product (plugin->cls,
instance->instance.id,
product->id,
&product->product,
num_cats,
cats,
&no_instance,
&conflict,
&no_cat),
"Insert product failed\n");
if (expected_result > 0)
{
TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance,
"No instance wrong");
TEST_COND_RET_ON_FAIL (conflict == expect_conflict,
"Conflict wrong");
TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat,
"Wrong category missing returned");
}
return 0;
}
/**
* Tests updating product data in the database.
*
* @param instance the instance to update the product for.
* @param product the product data to update.
* @param expected_result the result we expect the db to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_update_product (const struct InstanceData *instance,
const struct ProductData *product,
unsigned int num_cats,
const uint64_t *cats,
enum GNUNET_DB_QueryStatus expected_result,
bool expect_no_instance,
bool expect_no_product,
bool expect_lost_reduced,
bool expect_sold_reduced,
bool expect_stocked_reduced,
ssize_t expected_no_cat)
{
bool no_instance;
ssize_t no_cat;
bool no_product;
bool lost_reduced;
bool sold_reduced;
bool stocked_reduced;
TEST_COND_RET_ON_FAIL (
expected_result ==
plugin->update_product (plugin->cls,
instance->instance.id,
product->id,
&product->product,
num_cats,
cats,
&no_instance,
&no_cat,
&no_product,
&lost_reduced,
&sold_reduced,
&stocked_reduced),
"Update product failed\n");
if (expected_result > 0)
{
TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance,
"No instance wrong");
TEST_COND_RET_ON_FAIL (no_product == expect_no_product,
"No product wrong");
TEST_COND_RET_ON_FAIL (lost_reduced == expect_lost_reduced,
"No product wrong");
TEST_COND_RET_ON_FAIL (stocked_reduced == expect_stocked_reduced,
"Stocked reduced wrong");
TEST_COND_RET_ON_FAIL (sold_reduced == expect_sold_reduced,
"Sold reduced wrong");
TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat,
"Wrong category missing returned");
}
return 0;
}
/**
* Tests looking up a product from the db.
*
* @param instance the instance to query from.
* @param product the product to query and compare to.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_product (const struct InstanceData *instance,
const struct ProductData *product)
{
struct TALER_MERCHANTDB_ProductDetails lookup_result;
size_t num_categories = 0;
uint64_t *categories = NULL;
if (0 > plugin->lookup_product (plugin->cls,
instance->instance.id,
product->id,
&lookup_result,
&num_categories,
&categories))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup product failed\n");
TALER_MERCHANTDB_product_details_free (&lookup_result);
return 1;
}
GNUNET_free (categories);
{
const struct TALER_MERCHANTDB_ProductDetails *to_cmp = &product->product;
if (0 != check_products_equal (&lookup_result,
to_cmp))
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup product failed: incorrect product returned\n");
TALER_MERCHANTDB_product_details_free (&lookup_result);
return 1;
}
}
TALER_MERCHANTDB_product_details_free (&lookup_result);
return 0;
}
/**
* 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 struct ProductData *products_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;
};
/**
* Function called after calling @e test_lookup_products
*
* @param cls a pointer to the lookup closure.
* @param product_serial DB row ID
* @param product_id the identifier of the product found.
*/
static void
lookup_products_cb (void *cls,
uint64_t product_serial,
const char *product_id)
{
struct TestLookupProducts_Closure *cmp = cls;
GNUNET_assert (product_serial > 0);
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; cmp->products_to_cmp_length > i; ++i)
{
if (0 == strcmp (cmp->products_to_cmp[i].id,
product_id))
cmp->results_matching[i] += 1;
}
}
/**
* Tests looking up all products for an instance.
*
* @param instance the instance to query from.
* @param products_length the number of products we are expecting.
* @param products the list of products that we expect to be found.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_products (const struct InstanceData *instance,
unsigned int products_length,
const struct ProductData *products)
{
unsigned int results_matching[GNUNET_NZL (products_length)];
struct TestLookupProducts_Closure cls = {
.products_to_cmp_length = products_length,
.products_to_cmp = products,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * products_length);
if (0 > plugin->lookup_products (plugin->cls,
instance->instance.id,
0,
20,
&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;
}
/**
* Tests deleting a product.
*
* @param instance the instance to delete the product from.
* @param product the product that should be deleted.
* @param expected_result the result that we expect the plugin to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_delete_product (const struct InstanceData *instance,
const struct ProductData *product,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->delete_product (plugin->cls,
instance->instance.id,
product->id),
"Delete product failed\n");
return 0;
}
/**
* Closure for product tests.
*/
struct TestProducts_Closure
{
/**
* The instance to use for this test.
*/
struct InstanceData instance;
/**
* The array of products.
*/
struct ProductData products[2];
};
/**
* Sets up the data structures used in the product tests.
*
* @param cls the closure to fill with test data.
*/
static void
pre_test_products (struct TestProducts_Closure *cls)
{
/* Instance */
make_instance ("test_inst_products",
&cls->instance);
/* Products */
make_product ("test_products_pd_0",
&cls->products[0]);
make_product ("test_products_pd_1",
&cls->products[1]);
cls->products[1].product.description = "This is a another test product";
cls->products[1].product.unit = "cans";
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:4.95",
&cls->products[1].product.price));
cls->products[1].product.total_stock = 5001;
}
/**
* Handles all teardown after testing.
*
* @param cls the closure containing memory to be freed.
*/
static void
post_test_products (struct TestProducts_Closure *cls)
{
free_instance_data (&cls->instance);
free_product_data (&cls->products[0]);
free_product_data (&cls->products[1]);
}
/**
* Runs the tests for products.
*
* @param cls the container of the test data.
* @return 0 on success, 1 otherwise.
*/
static int
run_test_products (struct TestProducts_Closure *cls)
{
struct GNUNET_Uuid uuid;
struct GNUNET_TIME_Timestamp refund_deadline =
GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS);
/* Test that insert without an instance fails */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->products[0],
0,
NULL,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
false,
true,
-1));
/* Insert the instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test inserting a product */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->products[0],
0,
NULL,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
false,
false,
-1));
/* Test that double insert succeeds */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->products[0],
0,
NULL,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
false,
false,
-1));
/* Test that conflicting insert fails */
{
uint64_t cat = 42;
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->products[0],
1,
&cat,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
true,
false,
-1));
}
/* Test lookup of individual products */
TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
&cls->products[0]));
/* Make sure it fails correctly for products that don't exist */
{
size_t num_categories = 0;
uint64_t *categories = NULL;
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_product (plugin->cls,
cls->instance.instance.id,
"nonexistent_product",
NULL,
&num_categories,
&categories))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup product failed\n");
return 1;
}
GNUNET_free (categories);
}
/* Test product update */
cls->products[0].product.description =
"This is a test product that has been updated!";
GNUNET_assert (0 ==
json_array_append_new (
cls->products[0].product.description_i18n,
json_string (
"description in another language")));
cls->products[0].product.unit = "barrels";
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:7.68",
&cls->products[0].product.price));
GNUNET_assert (0 ==
json_array_append_new (cls->products[0].product.taxes,
json_string ("2% sales tax")));
cls->products[0].product.total_stock = 100;
cls->products[0].product.total_sold = 0; /* will be ignored! */
cls->products[0].product.total_lost = 7;
GNUNET_free (cls->products[0].product.image);
cls->products[0].product.image = GNUNET_strdup ("image");
GNUNET_assert (0 ==
json_array_append_new (cls->products[0].product.address,
json_string ("444 Some Street")));
cls->products[0].product.next_restock = GNUNET_TIME_timestamp_get ();
TEST_RET_ON_FAIL (test_update_product (
&cls->instance,
&cls->products[0],
0,
NULL,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
false,
false,
false,
false,
false,
-1));
{
struct ProductData stock_dec = cls->products[0];
stock_dec.product.total_stock = 40;
TEST_RET_ON_FAIL (test_update_product (
&cls->instance,
&stock_dec,
0,
NULL,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
false,
false,
false,
false,
true,
-1));
}
{
struct ProductData lost_dec = cls->products[0];
lost_dec.product.total_lost = 1;
TEST_RET_ON_FAIL (test_update_product (
&cls->instance,
&lost_dec,
0,
NULL,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
false,
false,
true,
false,
false,
-1));
}
TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
&cls->products[0]));
TEST_RET_ON_FAIL (test_update_product (
&cls->instance,
&cls->products[1],
0,
NULL,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
false,
true,
false,
false,
false,
-1));
/* Test collective product lookup */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->products[1],
0,
NULL,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
false,
false,
-1));
TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
2,
cls->products));
/* Test locking */
uuid.value[0] = 0x1287346a;
if (0 != plugin->lock_product (plugin->cls,
cls->instance.instance.id,
cls->products[0].id,
&uuid,
256,
refund_deadline))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lock product failed\n");
return 1;
}
if (1 != plugin->lock_product (plugin->cls,
cls->instance.instance.id,
cls->products[0].id,
&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->instance,
&cls->products[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double deletion fails */
TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
&cls->products[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
1,
cls->products));
/* Test unlocking */
if (1 != plugin->unlock_inventory (plugin->cls,
&uuid))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unlock inventory failed\n");
return 1;
}
if (0 != 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->instance,
&cls->products[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
0,
NULL));
return 0;
}
/**
* Takes care of product testing.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_products (void)
{
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;
}
/* ********** Orders ********** */
/**
* Container for order data
*/
struct OrderData
{
/**
* The id of the order
*/
const char *id;
/**
* The pay deadline for the order
*/
struct GNUNET_TIME_Timestamp pay_deadline;
/**
* The contract of the order
*/
json_t *contract;
/**
* The claim token for the order.
*/
struct TALER_ClaimTokenP claim_token;
};
/**
* Builds an order for testing.
*
* @param order_id the identifier to use for the order.
* @param order the container to fill with data.
*/
static void
make_order (const char *order_id,
struct OrderData *order)
{
struct GNUNET_TIME_Timestamp refund_deadline;
order->id = order_id;
order->contract = json_object ();
GNUNET_assert (NULL != order->contract);
order->pay_deadline = GNUNET_TIME_relative_to_timestamp (
GNUNET_TIME_UNIT_DAYS);
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
&order->claim_token,
sizeof (order->claim_token));
refund_deadline = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS);
json_object_set_new (order->contract,
"fulfillment_url",
json_string ("a"));
json_object_set_new (order->contract,
"order_id",
json_string (order_id));
json_object_set_new (order->contract,
"pay_deadline",
GNUNET_JSON_from_timestamp (order->pay_deadline));
json_object_set_new (order->contract,
"refund_deadline",
GNUNET_JSON_from_timestamp (refund_deadline));
}
/**
* Frees memory associated with an order.
*
* @param the order to free.
*/
static void
free_order_data (struct OrderData *order)
{
json_decref (order->contract);
}
/**
* Tests inserting an order into the database.
*
* @param instance the instance to insert the order for.
* @param order the order to insert.
* @param expected_result the value we expect the db to return.
* @return 0 on success, 1 otherwise.
*/
static int
test_insert_order (const struct InstanceData *instance,
const struct OrderData *order,
enum GNUNET_DB_QueryStatus expected_result)
{
struct TALER_MerchantPostDataHashP h_post;
memset (&h_post,
42,
sizeof (h_post));
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_order (plugin->cls,
instance->instance.id,
order->id,
NULL, /* session_id */
&h_post,
order->pay_deadline,
&order->claim_token,
order->contract,
NULL,
0),
"Insert order failed\n");
return 0;
}
/**
* Tests looking up an order in the database.
*
* @param instance the instance to lookup from.
* @param order the order that should be looked up.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_order (const struct InstanceData *instance,
const struct OrderData *order)
{
struct TALER_ClaimTokenP ct;
json_t *lookup_terms = NULL;
struct TALER_MerchantPostDataHashP oh;
struct TALER_MerchantPostDataHashP wh;
memset (&wh,
42,
sizeof (wh));
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->lookup_order (plugin->cls,
instance->instance.id,
order->id,
&ct,
&oh,
&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 (order->contract,
lookup_terms)) ||
(0 != GNUNET_memcmp (&order->claim_token,
&ct)) ||
(0 != GNUNET_memcmp (&oh,
&wh)) )
{
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;
}
/**
* 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 struct OrderData *orders_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;
};
/**
* Called after @e test_lookup_orders.
*
* @param cls the lookup closure.
* @param order_id the identifier of the order found.
* @param order_serial the row number of the order found.
* @param timestamp when the order was added to the database.
*/
static void
lookup_orders_cb (void *cls,
const char *order_id,
uint64_t order_serial,
struct GNUNET_TIME_Timestamp 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->orders_to_cmp[i].id,
order_id))
cmp->results_match[i] = true;
else
cmp->results_match[i] = false;
}
}
/**
* Tests looking up orders for an instance.
*
* @param instance the instance.
* @param filter the filters applied on the lookup.
* @param orders_length the number of orders we expect to find.
* @param orders the orders we expect to find.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_orders (const struct InstanceData *instance,
const struct TALER_MERCHANTDB_OrderFilter *filter,
unsigned int orders_length,
const struct OrderData *orders)
{
bool results_match[GNUNET_NZL (orders_length)];
struct TestLookupOrders_Closure cls = {
.orders_to_cmp_length = orders_length,
.orders_to_cmp = orders,
.results_match = results_match,
.results_length = 0
};
memset (results_match,
0,
sizeof (bool) * orders_length);
if (0 > plugin->lookup_orders (plugin->cls,
instance->instance.id,
filter,
&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 (index %d)\n",
i);
return 1;
}
}
return 0;
}
/**
* Container for data used for looking up the row number of an order.
*/
struct LookupOrderSerial_Closure
{
/**
* The order that is being looked up.
*/
const struct OrderData *order;
/**
* The serial of the order that was found.
*/
uint64_t serial;
};
/**
* Called after @e test_lookup_orders.
*
* @param cls the lookup closure.
* @param order_id the identifier of the order found.
* @param order_serial the row number of the order found.
* @param timestamp when the order was added to the database.
*/
static void
get_order_serial_cb (void *cls,
const char *order_id,
uint64_t order_serial,
struct GNUNET_TIME_Timestamp timestamp)
{
struct LookupOrderSerial_Closure *lookup_cls = cls;
if (NULL == lookup_cls)
return;
if (0 == strcmp (lookup_cls->order->id,
order_id))
lookup_cls->serial = order_serial;
}
/**
* Convenience function for getting the row number of an order.
*
* @param instance the instance to look up from.
* @param order the order to lookup the serial for.
* @return the row number of the order.
*/
static uint64_t
get_order_serial (const struct InstanceData *instance,
const struct OrderData *order)
{
struct LookupOrderSerial_Closure lookup_cls = {
.order = order,
.serial = 0
};
struct TALER_MERCHANTDB_OrderFilter filter = {
.paid = TALER_EXCHANGE_YNA_ALL,
.refunded = TALER_EXCHANGE_YNA_ALL,
.wired = TALER_EXCHANGE_YNA_ALL,
.date = GNUNET_TIME_UNIT_ZERO_TS,
.start_row = 0,
.delta = 256
};
GNUNET_assert (0 < plugin->lookup_orders (plugin->cls,
instance->instance.id,
&filter,
&get_order_serial_cb,
&lookup_cls));
GNUNET_assert (0 != lookup_cls.serial);
return lookup_cls.serial;
}
/**
* Tests deleting an order from the database.
*
* @param instance the instance to delete the order from.
* @param order the order to delete.
* @param expected_result the result we expect to receive.
* @return 0 on success, 1 otherwise.
*/
static int
test_delete_order (const struct InstanceData *instance,
const struct OrderData *order,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->delete_order (plugin->cls,
instance->instance.id,
order->id,
false),
"Delete order failed\n");
return 0;
}
/**
* Test inserting contract terms for an order.
*
* @param instance the instance.
* @param order the order containing the contract terms.
* @param expected_result the result we expect to receive.
* @return 0 on success, 1 otherwise.
*/
static int
test_insert_contract_terms (const struct InstanceData *instance,
const struct OrderData *order,
enum GNUNET_DB_QueryStatus expected_result)
{
uint64_t os;
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_contract_terms (plugin->cls,
instance->instance.id,
order->id,
order->contract,
&os),
"Insert contract terms failed\n");
return 0;
}
/**
* Test updating contract terms for an order.
*
* @param instance the instance.
* @param order the order containing the contract terms.
* @param expected_result the result we expect to receive.
* @return 0 on success, 1 otherwise.
*/
static int
test_update_contract_terms (const struct InstanceData *instance,
const struct OrderData *order,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->update_contract_terms (plugin->cls,
instance->instance.id,
order->id,
order->contract),
"Update contract terms failed\n");
return 0;
}
/**
* Tests lookup of contract terms
*
* @param instance the instance to lookup from.
* @param order the order to lookup for.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_contract_terms (const struct InstanceData *instance,
const struct OrderData *order)
{
json_t *contract = NULL;
uint64_t order_serial;
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->lookup_contract_terms (plugin->cls,
instance->instance.id,
order->id,
&contract,
&order_serial,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup contract terms failed\n");
GNUNET_assert (NULL == contract);
return 1;
}
if (1 != json_equal (order->contract,
contract))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup contract terms failed: mismatched data\n");
json_decref (contract);
return 1;
}
json_decref (contract);
return 0;
}
/**
* Tests deleting contract terms for an order.
*
* @param instance the instance to delete from.
* @param order the order whose contract terms we should delete.
* @param legal_expiration how long we must wait after creating an order to delete it
* @param expected_result the result we expect to receive.
* @return 0 on success, 1 otherwise.
*/
static int
test_delete_contract_terms (const struct InstanceData *instance,
const struct OrderData *order,
struct GNUNET_TIME_Relative legal_expiration,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->delete_contract_terms (plugin->cls,
instance->instance.id,
order->id,
legal_expiration),
"Delete contract terms failed\n");
return 0;
}
/**
* Test marking a contract as paid in the database.
*
* @param instance the instance to use.
* @param order the order whose contract to use.
* @param expected_result the result we expect to receive.
* @return 0 on success, 1 otherwise.
*/
static int
test_mark_contract_paid (const struct InstanceData *instance,
const struct OrderData *order,
enum GNUNET_DB_QueryStatus expected_result)
{
struct TALER_PrivateContractHashP h_contract_terms;
GNUNET_assert (GNUNET_OK ==
TALER_JSON_contract_hash (order->contract,
&h_contract_terms));
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->mark_contract_paid (plugin->cls,
instance->instance.id,
&h_contract_terms,
"test_orders_session"),
"Mark contract paid failed\n");
return 0;
}
/**
* Tests looking up the status of an order.
*
* @param instance the instance to lookup from.
* @param order the order to lookup.
* @param expected_paid whether the order was paid or not.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_order_status (const struct InstanceData *instance,
const struct OrderData *order,
bool expected_paid)
{
struct TALER_PrivateContractHashP h_contract_terms_expected;
struct TALER_PrivateContractHashP h_contract_terms;
bool order_paid = false;
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->lookup_order_status (plugin->cls,
instance->instance.id,
order->id,
&h_contract_terms,
&order_paid))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup order status failed\n");
return 1;
}
GNUNET_assert (GNUNET_OK ==
TALER_JSON_contract_hash (order->contract,
&h_contract_terms_expected));
if ((expected_paid != order_paid) ||
(0 != GNUNET_memcmp (&h_contract_terms,
&h_contract_terms_expected)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup order status/deposit failed: mismatched data\n");
return 1;
}
return 0;
}
/**
* Test looking up an order by its fulfillment.
*
* @param instance the instance to lookup from.
* @param order the order to lookup.
* @param the session id associated with the payment.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_order_by_fulfillment (const struct InstanceData *instance,
const struct OrderData *order,
const char *session_id)
{
char *order_id;
const char *fulfillment_url =
json_string_value (json_object_get (order->contract,
"fulfillment_url"));
GNUNET_assert (NULL != fulfillment_url);
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->lookup_order_by_fulfillment (plugin->cls,
instance->instance.id,
fulfillment_url,
session_id,
false,
&order_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup order by fulfillment failed\n");
GNUNET_free (order_id);
return 1;
}
if (0 != strcmp (order->id,
order_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup order by fulfillment failed\n");
GNUNET_free (order_id);
return 1;
}
GNUNET_free (order_id);
return 0;
}
/**
* Test looking up the status of an order.
*
* @param order_id the row of the order in the database.
* @param session_id the session id associated with the payment.
* @param expected_paid whether the order was paid or not.
* @param expected_wired whether the order was wired or not.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_payment_status (const char *instance_id,
const char *order_id,
const char *session_id,
bool expected_paid,
bool expected_wired)
{
bool paid;
bool wired;
bool matches;
uint64_t os;
int16_t choice_index;
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
plugin->lookup_contract_terms3 (plugin->cls,
instance_id,
order_id,
session_id,
NULL,
&os,
&paid,
&wired,
&matches,
NULL,
&choice_index),
"Lookup payment status failed\n");
if ( (NULL != session_id) && (! matches) )
{
paid = false;
wired = false;
}
if (expected_wired != wired)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup payment status failed: wired status is wrong\n");
return 1;
}
if (expected_paid != paid)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup payment status failed: paid status is wrong\n");
return 1;
}
return 0;
}
/**
* Test marking an order as being wired.
*
* @param order_id the row of the order in the database.
* @param expected_result the result we expect the plugin to return.
* @return 0 on success, 1 otherwise.
*/
static int
test_mark_order_wired (uint64_t order_id,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->mark_order_wired (plugin->cls,
order_id),
"Mark order wired failed\n");
return 0;
}
/**
* Closure for order tests.
*/
struct TestOrders_Closure
{
/**
* The instance to use for the order tests.
*/
struct InstanceData instance;
/**
* A product to use for the order tests.
*/
struct ProductData product;
/**
* The array of orders
*/
struct OrderData orders[3];
};
/**
* Initializes order test data.
*
* @param cls the order test closure.
*/
static void
pre_test_orders (struct TestOrders_Closure *cls)
{
/* Instance */
make_instance ("test_inst_orders",
&cls->instance);
/* Product */
make_product ("test_orders_pd_0",
&cls->product);
/* Orders */
make_order ("test_orders_od_0",
&cls->orders[0]);
make_order ("test_orders_od_1",
&cls->orders[1]);
make_order ("test_orders_od_2",
&cls->orders[2]);
GNUNET_assert (0 == json_object_set_new (cls->orders[1].contract,
"other_field",
json_string ("Second contract")));
cls->orders[2].pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
GNUNET_assert (0 ==
json_object_set_new (
cls->orders[2].contract,
"pay_deadline",
GNUNET_JSON_from_timestamp (cls->orders[2].pay_deadline)));
}
/**
* Frees memory after order tests.
*
* @param cls the order test closure.
*/
static void
post_test_orders (struct TestOrders_Closure *cls)
{
free_instance_data (&cls->instance);
free_product_data (&cls->product);
free_order_data (&cls->orders[0]);
free_order_data (&cls->orders[1]);
free_order_data (&cls->orders[2]);
}
/**
* Run the tests for orders.
*
* @param cls the order test closure.
* @return 0 on success, 1 on failure.
*/
static int
run_test_orders (struct TestOrders_Closure *cls)
{
struct TALER_MERCHANTDB_OrderFilter filter = {
.paid = TALER_EXCHANGE_YNA_ALL,
.refunded = TALER_EXCHANGE_YNA_ALL,
.wired = TALER_EXCHANGE_YNA_ALL,
.date = GNUNET_TIME_UNIT_ZERO_TS,
.start_row = 0,
.delta = 8
};
uint64_t serial;
/* Insert the instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test inserting an order */
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double insert fails */
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test lookup order */
TEST_RET_ON_FAIL (test_lookup_order (&cls->instance,
&cls->orders[0]));
/* Make sure it fails correctly for nonexistent orders */
{
struct TALER_MerchantPostDataHashP unused;
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_order (plugin->cls,
cls->instance.instance.id,
cls->orders[1].id,
NULL,
&unused,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup order failed\n");
return 1;
}
}
/* Test lookups on multiple orders */
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->orders[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
serial = get_order_serial (&cls->instance,
&cls->orders[0]);
TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
&filter,
2,
cls->orders));
/* Test inserting contract terms */
TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double insert fails */
TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test order lock */
TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
&cls->product,
0,
NULL,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
false,
false,
-1));
if (1 != plugin->insert_order_lock (plugin->cls,
cls->instance.instance.id,
cls->orders[0].id,
cls->product.id,
5))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Insert order lock failed\n");
return 1;
}
/* Test lookup contract terms */
TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance,
&cls->orders[0]));
/* Test lookup fails for nonexistent contract terms */
{
json_t *lookup_contract = NULL;
uint64_t lookup_order_serial;
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_contract_terms (plugin->cls,
cls->instance.instance.id,
cls->orders[1].id,
&lookup_contract,
&lookup_order_serial,
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup contract terms failed\n");
GNUNET_assert (NULL == lookup_contract);
return 1;
}
}
/* Test update contract terms */
json_object_set_new (cls->orders[0].contract,
"some_new_field",
json_string ("another value"));
TEST_RET_ON_FAIL (test_update_contract_terms (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance,
&cls->orders[0]));
/* Test lookup order status */
TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance,
&cls->orders[0],
false));
{
struct TALER_PrivateContractHashP h_contract_terms;
bool order_paid = false;
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_order_status (plugin->cls,
cls->instance.instance.id,
cls->orders[1].id,
&h_contract_terms,
&order_paid))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup order status failed\n");
return 1;
}
}
/* Test lookup payment status */
TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
cls->orders[0].id,
NULL,
false,
false));
/* Test lookup order status fails for nonexistent order */
{
struct TALER_PrivateContractHashP h_contract_terms;
bool order_paid;
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_order_status (plugin->cls,
cls->instance.instance.id,
cls->orders[1].id,
&h_contract_terms,
&order_paid))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup order status failed\n");
return 1;
}
}
/* Test marking contracts as paid */
TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
cls->orders[0].id,
NULL,
true,
false));
TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
cls->orders[0].id,
"test_orders_session",
true,
false));
TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
cls->orders[0].id,
"bad_session",
false,
false));
/* Test lookup order by fulfillment */
TEST_RET_ON_FAIL (test_lookup_order_by_fulfillment (&cls->instance,
&cls->orders[0],
"test_orders_session"));
{
char *order_id;
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_order_by_fulfillment (plugin->cls,
cls->instance.instance.id,
"fulfillment_url",
"test_orders_session",
false,
&order_id))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup order by fulfillment failed\n");
GNUNET_free (order_id);
return 1;
}
}
/* Test mark as paid fails for nonexistent order */
TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
&cls->orders[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance,
&cls->orders[0],
true));
filter.paid = TALER_EXCHANGE_YNA_YES;
TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
&filter,
1,
cls->orders));
/* Test marking orders as wired */
TEST_RET_ON_FAIL (test_mark_order_wired (serial,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT))
;
TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
cls->orders[0].id,
NULL,
true,
true));
TEST_RET_ON_FAIL (test_mark_order_wired (1007,
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS))
;
/* If an order has been claimed and we aren't past
the pay deadline, we can't delete it. */
TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test we can't delete before the legal expiration */
TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
&cls->orders[0],
GNUNET_TIME_UNIT_MONTHS,
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test deleting contract terms */
TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
&cls->orders[0],
GNUNET_TIME_UNIT_ZERO,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test we can't delete something that doesn't exist */
TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
&cls->orders[0],
GNUNET_TIME_UNIT_ZERO,
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test delete order where we aren't past
the deadline, but the order is unclaimed. */
TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
&cls->orders[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
&filter,
0,
NULL));
/* Test we can't delete something that doesn't exist */
TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
&cls->orders[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test we can also delete a claimed order that's past the pay deadline. */
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->orders[2],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
&cls->orders[2],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
&cls->orders[2],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
return 0;
}
/**
* Does all tasks for testing orders.
*
* @return 0 when successful, 1 otherwise.
*/
static int
test_orders (void)
{
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;
}
/* ********** Deposits ********** */
/**
* A container for exchange signing key data.
*/
struct ExchangeSignkeyData
{
/**
* The master private key of the exchange.
*/
struct TALER_MasterPrivateKeyP master_priv;
/**
* The master public key of the exchange.
*/
struct TALER_MasterPublicKeyP master_pub;
/**
* A signature made with the master keys.
*/
struct TALER_MasterSignatureP master_sig;
/**
* The private key of the exchange.
*/
struct TALER_ExchangePrivateKeyP exchange_priv;
/**
* The public key of the exchange.
*/
struct TALER_ExchangePublicKeyP exchange_pub;
/**
* When the signing key becomes valid.
*/
struct GNUNET_TIME_Timestamp start_date;
/**
* When the signing key stops being used.
*/
struct GNUNET_TIME_Timestamp expire_date;
/**
* When the signing key becomes invalid for proof.
*/
struct GNUNET_TIME_Timestamp end_date;
};
/**
* Creates an exchange signing key.
*
* @param signkey the signing key data to fill.
*/
static void
make_exchange_signkey (struct ExchangeSignkeyData *signkey)
{
struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
GNUNET_CRYPTO_eddsa_key_create (&signkey->exchange_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&signkey->exchange_priv.eddsa_priv,
&signkey->exchange_pub.eddsa_pub);
GNUNET_CRYPTO_eddsa_key_create (&signkey->master_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&signkey->master_priv.eddsa_priv,
&signkey->master_pub.eddsa_pub);
signkey->start_date = now;
signkey->expire_date = now;
signkey->end_date = now;
TALER_exchange_offline_signkey_validity_sign (
&signkey->exchange_pub,
signkey->start_date,
signkey->expire_date,
signkey->end_date,
&signkey->master_priv,
&signkey->master_sig);
}
/**
* A container for deposit data.
*/
struct DepositData
{
/**
* When the deposit was made.
*/
struct GNUNET_TIME_Timestamp timestamp;
/**
* Hash of the associated order's contract terms.
*/
struct TALER_PrivateContractHashP h_contract_terms;
/**
* Public key of the coin that has been deposited.
*/
struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* Signature of the coin that has been deposited.
*/
struct TALER_CoinSpendSignatureP coin_sig;
/**
* URL of the exchange.
*/
const char *exchange_url;
/**
* Value of the coin with fees applied.
*/
struct TALER_Amount amount_with_fee;
/**
* Fee charged for deposit.
*/
struct TALER_Amount deposit_fee;
/**
* Fee to be charged in case of a refund.
*/
struct TALER_Amount refund_fee;
/**
* Fee charged after the money is wired.
*/
struct TALER_Amount wire_fee;
/**
* Hash of the wire details.
*/
struct TALER_MerchantWireHashP h_wire;
/**
* Signature the exchange made on this deposit.
*/
struct TALER_ExchangeSignatureP exchange_sig;
};
/**
* Generates deposit data for an order.
*
* @param instance the instance to make the deposit to.
* @param account the merchant account to use.
* @param order the order this deposit is for.
* @param signkey the signing key to use.
* @param deposit the deposit data to fill.
*/
static void
make_deposit (const struct InstanceData *instance,
const struct TALER_MERCHANTDB_AccountDetails *account,
const struct OrderData *order,
const struct ExchangeSignkeyData *signkey,
struct DepositData *deposit)
{
struct TALER_CoinSpendPrivateKeyP coin_priv;
struct GNUNET_TIME_Timestamp now;
struct TALER_Amount amount_without_fee;
now = GNUNET_TIME_timestamp_get ();
deposit->timestamp = now;
GNUNET_assert (GNUNET_OK ==
TALER_JSON_contract_hash (order->contract,
&deposit->h_contract_terms));
GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv);
GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
&deposit->coin_pub.eddsa_pub);
deposit->exchange_url = "https://test-exchange/";
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:50.00",
&deposit->amount_with_fee));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:1.00",
&deposit->deposit_fee));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:1.50",
&deposit->refund_fee));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:2.00",
&deposit->wire_fee));
GNUNET_assert (0 <=
TALER_amount_subtract (&amount_without_fee,
&deposit->amount_with_fee,
&deposit->deposit_fee));
deposit->h_wire = account->h_wire;
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&deposit->exchange_sig,
sizeof (deposit->exchange_sig));
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&deposit->coin_sig,
sizeof (deposit->coin_sig));
}
/**
* Tests inserting an exchange signing key into the database.
*
* @param signkey the signing key to insert.
* @param expected_result the result we expect the database to return.
* @return 0 on success, 1 otherwise.
*/
static int
test_insert_exchange_signkey (const struct ExchangeSignkeyData *signkey,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_exchange_signkey (plugin->cls,
&signkey->master_pub,
&signkey->exchange_pub
,
signkey->start_date,
signkey->expire_date,
signkey->end_date,
&signkey->master_sig),
"Insert exchange signkey failed\n");
return 0;
}
/**
* Tests inserting a deposit into the database.
*
* @param instance the instance the deposit was made to.
* @param signkey the signing key used.
* @param deposit the deposit information to insert.
* @param expected_result the result we expect the database to return.
* @return 0 on success, 1 otherwise.
*/
static int
test_insert_deposit (const struct InstanceData *instance,
const struct ExchangeSignkeyData *signkey,
const struct DepositData *deposit,
enum GNUNET_DB_QueryStatus expected_result)
{
uint64_t row;
struct TALER_Amount awf;
GNUNET_assert (0 <=
TALER_amount_subtract (&awf,
&deposit->amount_with_fee,
&deposit->deposit_fee));
TEST_COND_RET_ON_FAIL (
expected_result ==
plugin->insert_deposit_confirmation (plugin->cls,
instance->instance.id,
deposit->timestamp,
&deposit->h_contract_terms,
deposit->exchange_url,
deposit->timestamp,
&awf,
&deposit->wire_fee,
&deposit->h_wire,
&deposit->exchange_sig,
&signkey->exchange_pub,
&row),
"Insert deposit confirmation failed\n");
TEST_COND_RET_ON_FAIL (
expected_result ==
plugin->insert_deposit (plugin->cls,
0,
row,
&deposit->coin_pub,
&deposit->coin_sig,
&deposit->amount_with_fee,
&deposit->deposit_fee,
&deposit->refund_fee),
"Insert deposit failed\n");
return 0;
}
/**
* 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;
};
/**
* Called after 'test_lookup_deposits'.
*
* @param cls pointer to the test lookup closure.
* @param coin_pub public key of the coin deposited.
* @param amount_with_fee amount of the deposit with fees.
* @param deposit_fee fee charged for the deposit.
* @param refund_fee fee charged in case of a refund.
*/
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)
{
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)))
{
cmp->results_matching[i] += 1;
}
}
}
/**
* Tests looking up deposits from the database.
*
* @param instance the instance to lookup deposits from.
* @param h_contract_terms the contract terms that the deposits should have.
* @param deposits_length length of @e deposits.
* @param deposits the deposits we expect to be found.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_deposits (const struct InstanceData *instance,
const struct TALER_PrivateContractHashP *h_contract_terms,
unsigned int deposits_length,
const struct DepositData *deposits)
{
unsigned int results_matching[GNUNET_NZL (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);
TEST_COND_RET_ON_FAIL (0 <=
plugin->lookup_deposits (plugin->cls,
instance->instance.id,
h_contract_terms,
&lookup_deposits_cb,
&cmp),
"Lookup deposits failed\n");
if (deposits_length != cmp.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup deposits failed: incorrect number of results returned (%d)\n",
cmp.results_length);
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;
}
/**
* Called after 'test_lookup_deposits_contract_and_coin'.
*
* @param cls pointer to the test lookup closure.
* @param exchange_url URL to the exchange
* @param amount_with_fee amount of the deposit with fees.
* @param deposit_fee fee charged for the deposit.
* @param refund_fee fee charged in case of a refund.
* @param wire_fee fee charged when the money is wired.
* @param h_wire hash of the wire transfer details.
* @param deposit_timestamp when the deposit was made.
* @param refund_deadline deadline for refunding the deposit.
* @param exchange_sig signature the exchange made on the deposit.
* @param exchange_pub public key of the exchange.
*/
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 TALER_MerchantWireHashP *h_wire,
struct GNUNET_TIME_Timestamp deposit_timestamp,
struct GNUNET_TIME_Timestamp 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 ((GNUNET_TIME_timestamp_cmp (cmp->deposits_to_cmp[i].timestamp,
==,
deposit_timestamp)) &&
(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;
}
}
}
/**
* Tests lookup of deposits by contract and coin.
*
* @param instance the instance to lookup from.
* @param h_contract the contract terms the deposits should have.
* @param coin_pub the public key of the coin the deposits should have.
* @param deposits_length length of @e deposits.
* @param deposits the deposits the db is expected to find.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_deposits_contract_and_coin (
const struct InstanceData *instance,
const struct TALER_PrivateContractHashP *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);
TEST_COND_RET_ON_FAIL (
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
plugin->lookup_deposits_by_contract_and_coin (
plugin->cls,
instance->instance.id,
h_contract,
coin_pub,
&lookup_deposits_contract_coin_cb,
&cmp),
"Lookup deposits by contract and coin failed\n");
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;
}
/**
* Called after 'test_lookup_deposits_by_order'.
*
* @param cls pointer to the test lookup closure.
* @param deposit_serial row number of the deposit in the database.
* @param exchange_url URL to the exchange
* @param h_wire hash of the wire transfer details.
* @param deposit_timestamp when was the deposit made
* @param amount_with_fee amount of the deposit with fees.
* @param deposit_fee fee charged for the deposit.
* @param coin_pub public key of the coin deposited.
*/
static void
lookup_deposits_order_cb (void *cls,
uint64_t deposit_serial,
const char *exchange_url,
const struct TALER_MerchantWireHashP *h_wire,
struct GNUNET_TIME_Timestamp deposit_timestamp,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
struct TestLookupDeposits_Closure *cmp = cls;
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; i < cmp->deposits_to_cmp_length; ++i)
{
if ((0 == strcmp (cmp->deposits_to_cmp[i].exchange_url,
exchange_url)) &&
(0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire,
h_wire)) &&
(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)) &&
(0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].coin_pub,
coin_pub)))
cmp->results_matching[i] += 1;
}
}
/**
* Tests looking up deposits by associated order.
*
* @param order_serial row number of the order to lookup for.
* @param deposits_length length of @e deposits_length.
* @param deposits the deposits we expect to be found.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_deposits_by_order (uint64_t order_serial,
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 (deposits_length !=
plugin->lookup_deposits_by_order (plugin->cls,
order_serial,
&lookup_deposits_order_cb,
&cmp))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup deposits by order failed\n");
return 1;
}
if (deposits_length != cmp.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup deposits by order failed: incorrect number of results\n");
return 1;
}
for (unsigned int i = 0; i < deposits_length; ++i)
{
if (1 != cmp.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup deposits by order failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Container for information for looking up the row number of a deposit.
*/
struct LookupDepositSerial_Closure
{
/**
* The deposit we're looking for.
*/
const struct DepositData *deposit;
/**
* The serial found.
*/
uint64_t serial;
};
/**
* Called after 'get_deposit_serial'.
*
* @param cls pointer to the test lookup closure.
* @param deposit_serial row number of the deposit in the database.
* @param exchange_url URL to the exchange
* @param h_wire hash of the wire transfer details.
* @param deposit_timestamp when was the deposit made.
* @param amount_with_fee amount of the deposit with fees.
* @param deposit_fee fee charged for the deposit.
* @param coin_pub public key of the coin deposited.
*/
static void
get_deposit_serial_cb (void *cls,
uint64_t deposit_serial,
const char *exchange_url,
const struct TALER_MerchantWireHashP *h_wire,
struct GNUNET_TIME_Timestamp deposit_timestamp,
const struct TALER_Amount *amount_with_fee,
const struct TALER_Amount *deposit_fee,
const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
struct LookupDepositSerial_Closure *lookup_cls = cls;
(void) deposit_timestamp;
if (NULL == lookup_cls)
return;
if ((0 == strcmp (lookup_cls->deposit->exchange_url,
exchange_url)) &&
(0 == GNUNET_memcmp (&lookup_cls->deposit->h_wire,
h_wire)) &&
(GNUNET_OK == TALER_amount_cmp_currency (
&lookup_cls->deposit->amount_with_fee,
amount_with_fee)) &&
(0 == TALER_amount_cmp (&lookup_cls->deposit->amount_with_fee,
amount_with_fee)) &&
(GNUNET_OK == TALER_amount_cmp_currency (
&lookup_cls->deposit->deposit_fee,
deposit_fee)) &&
(0 == TALER_amount_cmp (&lookup_cls->deposit->deposit_fee,
deposit_fee)) &&
(0 == GNUNET_memcmp (&lookup_cls->deposit->coin_pub,
coin_pub)))
lookup_cls->serial = deposit_serial;
}
/**
* Convenience function to retrieve the row number of a deposit in the database.
*
* @param instance the instance to get deposits from.
* @param order the order associated with the deposit.
* @param deposit the deposit to lookup the serial for.
* @return the row number of the deposit.
*/
static uint64_t
get_deposit_serial (const struct InstanceData *instance,
const struct OrderData *order,
const struct DepositData *deposit)
{
uint64_t order_serial = get_order_serial (instance,
order);
struct LookupDepositSerial_Closure lookup_cls = {
.deposit = deposit,
.serial = 0
};
GNUNET_assert (0 <
plugin->lookup_deposits_by_order (plugin->cls,
order_serial,
&get_deposit_serial_cb,
&lookup_cls));
GNUNET_assert (0 != lookup_cls.serial);
return lookup_cls.serial;
}
/**
* Closure for deposit tests.
*/
struct TestDeposits_Closure
{
/**
* The instance settings
*/
struct InstanceData instance;
/**
* The merchant account
*/
struct TALER_MERCHANTDB_AccountDetails account;
/**
* The exchange signing key
*/
struct ExchangeSignkeyData signkey;
/**
* The order data
*/
struct OrderData orders[2];
/**
* The array of deposits
*/
struct DepositData deposits[3];
};
/**
* Initializes data for testing deposits.
*
* @param cls the test closure to initialize.
*/
static void
pre_test_deposits (struct TestDeposits_Closure *cls)
{
/* Instance */
make_instance ("test_inst_deposits",
&cls->instance);
/* Account */
make_account (&cls->account);
cls->account.instance_id = cls->instance.instance.id;
/* Signing key */
make_exchange_signkey (&cls->signkey);
/* Order */
make_order ("test_deposits_od_1",
&cls->orders[0]);
make_order ("test_deposits_od_2",
&cls->orders[1]);
/* Deposit */
make_deposit (&cls->instance,
&cls->account,
&cls->orders[0],
&cls->signkey,
&cls->deposits[0]);
make_deposit (&cls->instance,
&cls->account,
&cls->orders[0],
&cls->signkey,
&cls->deposits[1]);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:29.00",
&cls->deposits[1].amount_with_fee));
make_deposit (&cls->instance,
&cls->account,
&cls->orders[1],
&cls->signkey,
&cls->deposits[2]);
}
/**
* Cleans up memory after testing deposits.
*
* @param cls the closure containing memory to free.
*/
static void
post_test_deposits (struct TestDeposits_Closure *cls)
{
free_instance_data (&cls->instance);
json_decref (cls->orders[0].contract);
json_decref (cls->orders[1].contract);
}
/**
* Runs tests for deposits.
*
* @param cls the closure containing test data.
* @return 0 on success, 1 otherwise.
*/
static int
run_test_deposits (struct TestDeposits_Closure *cls)
{
/* Insert the instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert an account */
TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
&cls->account,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert a signing key */
TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Insert an order */
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert contract terms */
TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test inserting a deposit */
TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
&cls->signkey,
&cls->deposits[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double inserts fail */
TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
&cls->signkey,
&cls->deposits[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test lookup deposits */
TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
&cls->deposits[0].h_contract_terms,
1,
cls->deposits));
TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
&cls->deposits[2].h_contract_terms,
0,
NULL));
/* Test lookup deposits by contract and coins */
TEST_RET_ON_FAIL (test_lookup_deposits_contract_and_coin (
&cls->instance,
&cls->deposits[0].h_contract_terms,
&cls->deposits[0].coin_pub,
1,
cls->deposits));
/* Test multiple deposits */
TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
&cls->signkey,
&cls->deposits[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->orders[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
&cls->orders[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
&cls->signkey,
&cls->deposits[2],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
&cls->deposits[0].h_contract_terms,
2,
cls->deposits));
TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
&cls->deposits[2].h_contract_terms,
1,
&cls->deposits[2]));
/* Test lookup deposits by order */
{
uint64_t order_serial = get_order_serial (&cls->instance,
&cls->orders[0]);
TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial,
2,
cls->deposits));
order_serial = get_order_serial (&cls->instance,
&cls->orders[1]);
TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial,
1,
&cls->deposits[2]));
}
return 0;
}
/**
* Handles functionality for testing deposits.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_deposits (void)
{
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;
}
/* *********** Transfers ********** */
/**
* Container for wire fee data for an exchange.
*/
struct WireFeeData
{
/**
* The method used.
*/
const char *wire_method;
/**
* Hash of the wire method.
*/
struct GNUNET_HashCode h_wire_method;
/**
* Wire fees charged.
*/
struct TALER_WireFeeSet fees;
/**
* Start date of the wire fee.
*/
struct GNUNET_TIME_Timestamp wire_fee_start;
/**
* End date of the wire fee.
*/
struct GNUNET_TIME_Timestamp wire_fee_end;
/**
* Signature on the wire fee.
*/
struct TALER_MasterSignatureP fee_sig;
};
/**
* Creates data for an exchange wire fee.
*
* @param signkey the exchange signing key data.
* @param wire_fee where to store the wire fee data.
*/
static void
make_wire_fee (const struct ExchangeSignkeyData *signkey,
struct WireFeeData *wire_fee)
{
wire_fee->wire_method = "wire-method";
GNUNET_CRYPTO_hash (wire_fee->wire_method,
strlen (wire_fee->wire_method) + 1,
&wire_fee->h_wire_method);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:0.49",
&wire_fee->fees.wire));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:0.49",
&wire_fee->fees.closing));
wire_fee->wire_fee_start = GNUNET_TIME_timestamp_get ();
wire_fee->wire_fee_end = GNUNET_TIME_relative_to_timestamp (
GNUNET_TIME_UNIT_MONTHS);
TALER_exchange_offline_wire_fee_sign (
wire_fee->wire_method,
wire_fee->wire_fee_start,
wire_fee->wire_fee_end,
&wire_fee->fees,
&signkey->master_priv,
&wire_fee->fee_sig);
}
/**
* Container for wire transfer data.
*/
struct TransferData
{
/**
* Id of the transfer.
*/
struct TALER_WireTransferIdentifierRawP wtid;
/**
* The main data for the transfer.
*/
struct TALER_EXCHANGE_TransferData data;
/**
* URL to the exchange the transfer was made through.
*/
const char *exchange_url;
/**
* How much the fee for the deposit was.
*/
struct TALER_Amount deposit_fee;
/**
* Whether the transfer has been confirmed.
*/
bool confirmed;
/**
* Whether the transfer has been verified.
*/
bool verified;
};
/**
* Creates a transfer for use with testing.
*
* @param deposits_length length of @e deposits.
* @param deposits list of deposits to combine into one transfer.
* @param transfer where to write the transfer data.
*/
static void
make_transfer (const struct ExchangeSignkeyData *signkey,
unsigned int deposits_length,
const struct DepositData deposits[static deposits_length],
struct TransferData *transfer)
{
struct TALER_TrackTransferDetails *details = NULL;
GNUNET_CRYPTO_seed_weak_random (585);
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&transfer->wtid,
sizeof (struct TALER_WireTransferIdentifierRawP));
transfer->exchange_url = deposits[0].exchange_url;
transfer->verified = false;
transfer->confirmed = false;
transfer->data.details_length = 0;
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (deposits[0].amount_with_fee.currency,
&transfer->data.total_amount));
GNUNET_assert (GNUNET_OK ==
TALER_amount_set_zero (deposits[0].amount_with_fee.currency,
&transfer->deposit_fee));
for (unsigned int i = 0; i < deposits_length; ++i)
{
GNUNET_array_grow (details,
transfer->data.details_length,
i + 1);
details[i].h_contract_terms = deposits[i].h_contract_terms;
details[i].coin_pub = deposits[i].coin_pub;
details[i].coin_value = deposits[i].amount_with_fee;
details[i].coin_fee = deposits[i].deposit_fee;
GNUNET_assert (0 <=
TALER_amount_add (&transfer->data.total_amount,
&transfer->data.total_amount,
&deposits[i].amount_with_fee));
GNUNET_assert (0 <=
TALER_amount_add (&transfer->deposit_fee,
&transfer->deposit_fee,
&deposits[i].deposit_fee));
}
transfer->data.exchange_pub = signkey->exchange_pub;
transfer->data.execution_time = GNUNET_TIME_timestamp_get ();
transfer->data.details = details;
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:0.50",
&transfer->data.wire_fee));
}
/**
* Tests looking up a transfer from the database.
*
* @param exchange_url url to the exchange of the transfer.
* @param wtid id of the transfer.
* @param total_expected the total amount of the transfer.
* @param fee_expected the fee on the transfer.
* @param time_expected when the transfer was made.
* @param verified_expected whether the transfer was verified.
* @return 1 on success, 0 otherwise.
*/
static int
test_lookup_transfer (
const struct InstanceData *instance,
const struct TransferData *transfer)
{
struct TALER_Amount total_with_fee;
struct TALER_Amount total;
struct TALER_Amount fee;
struct TALER_Amount exchange_amount;
struct GNUNET_TIME_Timestamp time;
bool esig;
bool verified;
if (1 != plugin->lookup_transfer (plugin->cls,
instance->instance.id,
transfer->exchange_url,
&transfer->wtid,
&total,
&fee,
&exchange_amount,
&time,
&esig,
&verified))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfer failed\n");
return 1;
}
GNUNET_assert (0 <= TALER_amount_add (&total_with_fee,
&transfer->data.total_amount,
&transfer->data.wire_fee));
if ((GNUNET_OK != TALER_amount_cmp_currency (&total_with_fee,
&total)) ||
(0 != TALER_amount_cmp (&total_with_fee,
&total)) ||
(GNUNET_OK != TALER_amount_cmp_currency (&transfer->data.wire_fee,
&fee)) ||
(0 != TALER_amount_cmp (&transfer->data.wire_fee,
&fee)) ||
(GNUNET_TIME_timestamp_cmp (transfer->data.execution_time,
!=,
time)) ||
(transfer->verified != verified))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfer failed: mismatched data\n");
return 1;
}
return 0;
}
/**
* Closure for testing 'lookup_transfer_summary'
*/
struct TestLookupTransferSummary_Closure
{
/**
* Id of the order the transfer was made for.
*/
const char *order_id;
/**
* The value of the deposit made.
*/
const struct TALER_Amount *deposit_value;
/**
* The fee on the deposit made.
*/
const struct TALER_Amount *deposit_fee;
/**
* 0 if the comparison is true, 1 if false.
*/
int result;
};
/**
* Called after 'test_lookup_transfer_summary'.
*
* @param cls pointer to 'TestLookupTransferSummary_Closure'.
* @param order_id id of the order the transfer was made for.
* @param deposit_value the value of the deposit made.
* @param deposit_fee the fee on the deposit made.
*/
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 = 0;
else
cmp->result = 1;
}
/**
* Tests looking up a transfer's summary.
*
* @param exchange_url url to the exchange for the transfer.
* @param wtid identifier of the transfer.
* @param expected_order_id the id of the order associated with the transfer.
* @param expected_deposit_value the amount of the deposit made for the transfer.
* @param expected_deposit_fee the fee on the deposit made for the transfer.
* @return 1 on success, 0 otherwise.
*/
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,
.result = 0
};
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 (0 != cmp.result)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfer summary failed: mismatched data\n");
return 1;
}
return 0;
}
/**
* Closure for testing 'lookup_transfer_details'.
*/
struct TestLookupTransferDetails_Closure
{
/**
* Length of @e details_to_cmp.
*/
unsigned int details_to_cmp_length;
/**
* The details we expect to find.
*/
const struct TALER_TrackTransferDetails *details_to_cmp;
/**
* Number of results matching each detail in @e details_to_cmp.
*/
unsigned int *results_matching;
/**
* Total number of results found.
*/
unsigned int results_length;
};
/**
* Called after 'test_lookup_transfer_details'.
*
* @param cls pointer to 'TestLookupTransferDetails_Closure'.
* @param current_offset offset within transfer details.
* @param details the details that were found.
*/
static void
lookup_transfer_details_cb (void *cls,
unsigned int current_offset,
const struct TALER_TrackTransferDetails *details)
{
struct TestLookupTransferDetails_Closure *cmp = cls;
if (NULL == cmp)
return;
for (unsigned int i = 0; cmp->details_to_cmp_length > i; ++i)
{
if ((0 == GNUNET_memcmp (&cmp->details_to_cmp[i].h_contract_terms,
&details->h_contract_terms)) &&
(0 == GNUNET_memcmp (&cmp->details_to_cmp[i].coin_pub,
&details->coin_pub)) &&
(GNUNET_OK == TALER_amount_cmp_currency (
&cmp->details_to_cmp[i].coin_value,
&details->coin_value)) &&
(0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_value,
&details->coin_value)) &&
(GNUNET_OK == TALER_amount_cmp_currency (
&cmp->details_to_cmp[i].coin_fee,
&details->coin_fee)) &&
(0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_fee,
&details->coin_fee)))
{
cmp->results_matching[i] += 1;
}
}
cmp->results_length += 1;
}
/**
* Tests looking up details for a wire transfer.
*
* @param exchange_url url to the exchange.
* @param wtid id of the transfer.
* @param details_length the length of @e details.
* @param details the details we expect to be returned.
* @return 1 on success, 0 otherwise.
*/
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];
struct TestLookupTransferDetails_Closure cmp = {
.details_to_cmp_length = details_length,
.details_to_cmp = details,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching,
0,
sizeof (unsigned int) * details_length);
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;
}
/**
* Closure for 'lookup_transfer_details_by_order'.
*/
struct TestLookupTransferDetailsByOrder_Closure
{
/**
* Length of @e transfers_to_cmp.
*/
unsigned int transfers_to_cmp_length;
/**
* List of transfers that we expect to find.
*/
const struct TransferData *transfers_to_cmp;
/**
* How many results match the corresponding element of @e transfers_to_cmp.
*/
unsigned int *results_matching;
/**
* Total number of results found.
*/
unsigned int results_length;
};
/**
* Called after 'test_lookup_transfer_details_by_order'.
*
* @param cls pointer to 'TestLookupTransferDetailsByOrder_Closure'.
* @param wtid identifier of the transfer found.
* @param exchange_url exchange url of the transfer found.
* @param execution_time when the transfer found occurred.
* @param deposit_value amount of the deposit for the transfer found.
* @param deposit_fee amount of the fee for the deposit of the transfer.
* @param transfer_confirmed whether the transfer was confirmed.
*/
static void
lookup_transfer_details_order_cb (
void *cls,
const struct TALER_WireTransferIdentifierRawP *wtid,
const char *exchange_url,
struct GNUNET_TIME_Timestamp execution_time,
const struct TALER_Amount *deposit_value,
const struct TALER_Amount *deposit_fee,
bool transfer_confirmed)
{
struct TestLookupTransferDetailsByOrder_Closure *cmp = cls;
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; i < cmp->transfers_to_cmp_length; ++i)
{
/* Right now lookup_transfer_details_by_order leaves execution_time
uninitialized and transfer_confirmed always false. */
if ((0 == GNUNET_memcmp (&cmp->transfers_to_cmp[i].wtid,
wtid)) &&
(0 == strcmp (cmp->transfers_to_cmp[i].exchange_url,
exchange_url)) &&
(GNUNET_OK ==
TALER_amount_cmp_currency (
&cmp->transfers_to_cmp[i].data.total_amount,
deposit_value)) &&
(0 ==
TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
deposit_value)) &&
(GNUNET_OK ==
TALER_amount_cmp_currency (
&cmp->transfers_to_cmp[i].deposit_fee,
deposit_fee)) &&
(0 ==
TALER_amount_cmp (&cmp->transfers_to_cmp[i].deposit_fee,
deposit_fee)) /* &&
(cmp->transfers_to_cmp[i].confirmed == transfer_confirmed)*/)
cmp->results_matching[i] += 1;
}
}
/**
* Tests looking up wire transfers associated with an order.
*
* @param order_serial the order to be queried.
* @param transfers_length length of @e transfers.
* @param transfers the transfers we expect to be found.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_transfer_details_by_order (
uint64_t order_serial,
unsigned int transfers_length,
const struct TransferData *transfers)
{
unsigned int results_matching[transfers_length];
struct TestLookupTransferDetailsByOrder_Closure cmp = {
.transfers_to_cmp_length = transfers_length,
.transfers_to_cmp = transfers,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching,
0,
sizeof (unsigned int) * transfers_length);
if (transfers_length !=
plugin->lookup_transfer_details_by_order (
plugin->cls,
order_serial,
&lookup_transfer_details_order_cb,
&cmp))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfer details by order failed\n");
return 1;
}
if (transfers_length != cmp.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfer details by order failed: incorrect number of results\n");
return 1;
}
for (unsigned int i = 0; i < transfers_length; ++i)
{
if (1 != cmp.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfer details by order failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Tests inserting wire fee data for an exchange.
*
* @param signkey the signing key for the exchange.
* @param wire_fee the wire fee data.
* @param expected_result what the database should return.
* @return 0 on success, 1 otherwise.
*/
static int
test_insert_wire_fee (const struct ExchangeSignkeyData *signkey,
const struct WireFeeData *wire_fee,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->store_wire_fee_by_exchange (
plugin->cls,
&signkey->master_pub,
&wire_fee->h_wire_method,
&wire_fee->fees,
wire_fee->wire_fee_start,
wire_fee->wire_fee_end,
&wire_fee->fee_sig),
"Store wire fee by exchange failed\n");
return 0;
}
/**
* Tests looking up wire fee data for an exchange.
*
* @param signkey the signing key to use for lookup.
* @param wire_fee_data the wire fee data we expect to find.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_wire_fee (const struct ExchangeSignkeyData *signkey,
const struct WireFeeData *wire_fee_data)
{
struct TALER_WireFeeSet fees;
struct GNUNET_TIME_Timestamp start_date;
struct GNUNET_TIME_Timestamp end_date;
struct TALER_MasterSignatureP master_sig;
if (1 != plugin->lookup_wire_fee (plugin->cls,
&signkey->master_pub,
wire_fee_data->wire_method,
GNUNET_TIME_timestamp_get (),
&fees,
&start_date,
&end_date,
&master_sig))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup wire fee failed\n");
return 1;
}
if ((0 !=
TALER_wire_fee_set_cmp (&wire_fee_data->fees,
&fees)) ||
(GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_start,
!=,
start_date)) ||
(GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_end,
!=,
end_date)) ||
(0 != GNUNET_memcmp (&wire_fee_data->fee_sig,
&master_sig)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup wire fee failed: mismatched data\n");
return 1;
}
return 0;
}
/**
* Closure for 'lookup_transfers'.
*/
struct TestLookupTransfers_Closure
{
/**
* Length of @e transfers_to_cmp.
*/
unsigned int transfers_to_cmp_length;
/**
* The transfers we expect to find.
*/
const struct TransferData *transfers_to_cmp;
/**
* Number of results matching each transfer.
*/
unsigned int *results_matching;
/**
* Total number of results found.
*/
unsigned int results_length;
};
/**
* Function called after 'test_lookup_transfers'.
*
* @param cls pointer to 'TestLookupTransfers_Closure'.
* @param credit_amount how much was wired to the merchant (minus fees)
* @param wtid wire transfer identifier
* @param payto_uri target account that received the wire transfer
* @param exchange_url base URL of the exchange that made the wire transfer
* @param transfer_serial_id serial number identifying the transfer in the backend
* @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_TS
* if it did not yet happen
* @param verified true if we checked the exchange's answer and liked it,
* false there is a problem (verification failed or did not yet happen)
* @param confirmed true if the merchant confirmed this wire transfer
* false if it is so far only claimed to have been made by the exchange
*/
static void
lookup_transfers_cb (void *cls,
const struct TALER_Amount *credit_amount,
const struct TALER_WireTransferIdentifierRawP *wtid,
struct TALER_FullPayto payto_uri,
const char *exchange_url,
uint64_t transfer_serial_id,
struct GNUNET_TIME_Timestamp execution_time,
bool verified,
bool confirmed)
{
struct TestLookupTransfers_Closure *cmp = cls;
if (NULL == cmp)
return;
for (unsigned int i = 0; cmp->transfers_to_cmp_length > i; ++i)
{
if ((GNUNET_OK ==
TALER_amount_cmp_currency (
&cmp->transfers_to_cmp[i].data.total_amount,
credit_amount)) &&
(0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
credit_amount)) &&
(GNUNET_TIME_timestamp_cmp (
cmp->transfers_to_cmp[i].data.execution_time,
==,
execution_time)))
{
cmp->results_matching[i] += 1;
}
}
cmp->results_length += 1;
}
/**
* Tests looking up transfers from the database.
*
* @param instance the instance to lookup from.
* @param account the account the transfer was made to.
* @param before do not return transfers before this time.
* @param after do not return transfers after this time.
* @param limit maximum number of transfers to return.
* @param offset row in the database to start with.
* @param filter_verified how to filter verified transfers.
* @param transfers_length length of @e transfers.
* @param transfers the transfers we expect to find.
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_transfers (const struct InstanceData *instance,
const struct TALER_MERCHANTDB_AccountDetails *account,
struct GNUNET_TIME_Timestamp before,
struct GNUNET_TIME_Timestamp after,
int64_t limit,
uint64_t offset,
enum TALER_EXCHANGE_YesNoAll filter_verified,
unsigned int transfers_length,
const struct TransferData *transfers)
{
unsigned int results_matching[transfers_length];
struct TestLookupTransfers_Closure cmp = {
.transfers_to_cmp_length = transfers_length,
.transfers_to_cmp = transfers,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching,
0,
sizeof (unsigned int) * transfers_length);
if (1 != plugin->lookup_transfers (plugin->cls,
instance->instance.id,
account->payto_uri,
before,
after,
limit,
offset,
filter_verified,
&lookup_transfers_cb,
&cmp))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfers failed\n");
return 1;
}
if (transfers_length != cmp.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfers failed: incorrect number of results\n");
return 1;
}
for (unsigned int i = 0; transfers_length > i; ++i)
{
if (1 != cmp.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup transfers failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Tests inserting a transfer into the database.
*
* @param instance the instance to use.
* @param account the account to transfer to.
* @param transfer the transfer to insert.
* @param expected_result the result we expect the db to return.
* @return 0 on success, 1 otherwise.
*/
static int
test_insert_transfer (const struct InstanceData *instance,
const struct TALER_MERCHANTDB_AccountDetails *account,
const struct TransferData *transfer,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_transfer (plugin->cls,
instance->instance.id,
transfer->exchange_url,
&transfer->wtid,
&transfer->data.total_amount,
account->payto_uri,
transfer->confirmed),
"Insert transfer failed\n");
return 0;
}
/**
* Tests linking a deposit to a transfer.
*
* @param instance the instance that the deposit and transfer are for.
* @param signkey the signing used on the deposit.
* @param order the order the deposit and transfer were made for.
* @param transfer the transfer.
* @param expected_result the result the database should return.
* @return 0 on success, 1 otherwise.
*/
static int
test_insert_deposit_to_transfer (const struct InstanceData *instance,
const struct ExchangeSignkeyData *signkey,
const struct OrderData *order,
const struct DepositData *deposit,
const struct TransferData *transfer,
enum GNUNET_DB_QueryStatus expected_result)
{
const struct TALER_EXCHANGE_DepositData deposit_data = {
.exchange_pub = signkey->exchange_pub,
.exchange_sig = deposit->exchange_sig,
.wtid = transfer->wtid,
.execution_time = transfer->data.execution_time,
.coin_contribution = deposit->amount_with_fee
};
uint64_t deposit_serial = get_deposit_serial (instance,
order,
deposit);
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_deposit_to_transfer (plugin->cls,
deposit_serial,
&deposit_data),
"insert deposit to transfer failed\n");
return 0;
}
/**
* Inserts details for a transfer into the database.
*
* @param instance the instance the transfer is in.
* @param account the destination account for the transfer.
* @param transfer the transfer we are adding details to.
* @param expected_result the result expected from the db.
* @return 0 on success, 1 otherwise.
*/
static int
test_insert_transfer_details (
const struct InstanceData *instance,
const struct TALER_MERCHANTDB_AccountDetails *account,
const struct TransferData *transfer,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_transfer_details (plugin->cls,
instance->instance.id,
transfer->exchange_url
,
account->payto_uri,
&transfer->wtid,
&transfer->data),
"Insert transfer details failed\n");
return 0;
}
/**
* Container for data used when testing transfers.
*/
struct TestTransfers_Closure
{
/**
* The instance.
*/
struct InstanceData instance;
/**
* The account.
*/
struct TALER_MERCHANTDB_AccountDetails account;
/**
* The exchange signing key.
*/
struct ExchangeSignkeyData signkey;
/**
* The order data.
*/
struct OrderData order;
/**
* The deposit data.
*/
struct DepositData deposit;
/**
* Wire fee data.
*/
struct WireFeeData wire_fee[2];
/**
* The transfers.
*/
struct TransferData transfers[1];
};
/**
* Prepares for testing transfers.
*
* @param cls the test data.
*/
static void
pre_test_transfers (struct TestTransfers_Closure *cls)
{
/* Instance */
make_instance ("test_inst_transfers",
&cls->instance);
/* Account */
make_account (&cls->account);
cls->account.instance_id = cls->instance.instance.id;
/* Order */
make_order ("test_transfers_od_1",
&cls->order);
/* Signing key */
make_exchange_signkey (&cls->signkey);
/* Deposit */
make_deposit (&cls->instance,
&cls->account,
&cls->order,
&cls->signkey,
&cls->deposit);
/* Wire fee */
make_wire_fee (&cls->signkey,
&cls->wire_fee[0]);
make_wire_fee (&cls->signkey,
&cls->wire_fee[1]);
cls->wire_fee[1].wire_method = "wire-method-2";
GNUNET_CRYPTO_hash (cls->wire_fee[1].wire_method,
strlen (cls->wire_fee[1].wire_method) + 1,
&cls->wire_fee[1].h_wire_method);
/* Transfers */
make_transfer (&cls->signkey,
1,
&cls->deposit,
&cls->transfers[0]);
cls->transfers[0].confirmed = true;
}
/**
* Cleans up after testing transfers.
*
* @param cls the test data.
*/
static void
post_test_transfers (struct TestTransfers_Closure *cls)
{
GNUNET_array_grow (cls->transfers->data.details,
cls->transfers->data.details_length,
0);
free_instance_data (&cls->instance);
free_order_data (&cls->order);
}
/**
* Runs the tests for transfers.
*
* @param cls the test data.
* @return 0 on success, 1 otherwise.
*/
static int
run_test_transfers (struct TestTransfers_Closure *cls)
{
uint64_t order_serial;
struct TALER_WireFeeSet fees;
/* Test lookup wire fee fails when it isn't in the db */
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
plugin->lookup_wire_fee (plugin->cls,
&cls->signkey.master_pub,
cls->wire_fee[0].wire_method,
GNUNET_TIME_timestamp_get (),
&fees,
NULL,
NULL,
NULL),
"Lookup wire fee failed\n");
/* Test store wire fee by exchange */
TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
&cls->wire_fee[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double insertion fails */
TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
&cls->wire_fee[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test lookup wire fee by exchange */
TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey,
&cls->wire_fee[0]));
/* Test different wire fees for different methods. */
TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
&cls->wire_fee[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey,
&cls->wire_fee[1]));
/* Insert the instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert the account */
TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
&cls->account,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert a signing key */
TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert an order */
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->order,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert contract terms */
TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
&cls->order,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
order_serial = get_order_serial (&cls->instance,
&cls->order);
/* Insert the deposit */
TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
&cls->signkey,
&cls->deposit,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert the transfer */
TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance,
&cls->account,
&cls->transfers[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance,
&cls->account,
&cls->transfers[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
&cls->signkey,
&cls->order,
&cls->deposit,
&cls->transfers[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
&cls->signkey,
&cls->order,
&cls->deposit,
&cls->transfers[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance,
&cls->account,
&cls->transfers[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
cls->order.id,
NULL,
false,
true));
TEST_RET_ON_FAIL (test_lookup_transfer (&cls->instance,
&cls->transfers[0]));
TEST_COND_RET_ON_FAIL (
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
plugin->set_transfer_status_to_confirmed (
plugin->cls,
cls->instance.instance.id,
cls->deposit.exchange_url,
&cls->transfers[0].wtid,
&cls->deposit.amount_with_fee),
"Set transfer status to confirmed failed\n");
cls->transfers[0].confirmed = true;
TEST_RET_ON_FAIL (test_lookup_transfer (&cls->instance,
&cls->transfers[0]));
TEST_RET_ON_FAIL (test_lookup_transfer_summary (cls->deposit.exchange_url,
&cls->transfers[0].wtid,
cls->order.id,
&cls->deposit.amount_with_fee,
&cls->deposit.deposit_fee));
TEST_RET_ON_FAIL (test_lookup_transfer_details (cls->deposit.exchange_url,
&cls->transfers[0].wtid,
1,
&cls->transfers[0].data.
details[0]));
TEST_RET_ON_FAIL (test_lookup_transfer_details_by_order (order_serial,
1,
&cls->transfers[0]));
TEST_RET_ON_FAIL (test_lookup_transfers (&cls->instance,
&cls->account,
GNUNET_TIME_UNIT_FOREVER_TS,
GNUNET_TIME_UNIT_ZERO_TS,
8,
0,
TALER_EXCHANGE_YNA_ALL,
1,
&cls->transfers[0]));
return 0;
}
/**
* Takes care of all work for testing transfers.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_transfers (void)
{
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;
}
/**
* Closure for testing lookup_refunds.
*/
struct TestLookupRefunds_Closure
{
/**
* Length of @e coin_pub_to_cmp and @e refund_amount_to_cmp.
*/
unsigned int refunds_to_cmp_length;
/**
* Public keys of the refunded coins.
*/
const struct TALER_CoinSpendPublicKeyP *coin_pub_to_cmp;
/**
* Amount of each refund.
*/
const struct TALER_Amount *refund_amount_to_cmp;
/**
* Number of results matching each refund provided.
*/
unsigned int *results_matching;
/**
* Total number of results returned;
*/
unsigned int results_length;
};
/**
* Called after test_lookup_refunds.
* @param cls pointer to a TestLookupRefunds_Closure.
* @param coin_pub the public key of the coin for the refund found.
* @param refund_amount the amount of the refund found.
*/
static void
lookup_refunds_cb (void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const struct TALER_Amount *refund_amount)
{
struct TestLookupRefunds_Closure *cmp = cls;
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i)
{
if ((0 == GNUNET_memcmp (&cmp->coin_pub_to_cmp[i],
coin_pub)) &&
(GNUNET_OK == TALER_amount_cmp_currency (&cmp->refund_amount_to_cmp[i],
refund_amount)) &&
(0 == TALER_amount_cmp (&cmp->refund_amount_to_cmp[i],
refund_amount)))
{
cmp->results_matching[i] += 1;
}
}
}
/**
* Tests looking up refunds from the database.
* @param instance the instance to look up refunds for.
* @param h_contract_terms hash of the contract terms the refunds are for.
* @param refunds_length length of @e coin_pubs and @e refund_amounts.
* @param coin_pubs the public keys of the coins that were refunded.
* @param refund_amounts the amounts of the coins that were refunded.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_refunds (const struct InstanceData *instance,
const struct TALER_PrivateContractHashP *h_contract_terms,
unsigned int refunds_length,
const struct TALER_CoinSpendPublicKeyP *coin_pubs,
const struct TALER_Amount *refund_amounts)
{
unsigned int results_matching[refunds_length];
struct TestLookupRefunds_Closure cmp = {
.refunds_to_cmp_length = refunds_length,
.coin_pub_to_cmp = coin_pubs,
.refund_amount_to_cmp = refund_amounts,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
if (1 != plugin->lookup_refunds (plugin->cls,
instance->instance.id,
h_contract_terms,
&lookup_refunds_cb,
&cmp))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup refunds failed\n");
return 1;
}
if (refunds_length != cmp.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup refunds failed: incorrect number of results returned\n")
;
return 1;
}
for (unsigned int i = 0; refunds_length > i; ++i)
{
if (1 != cmp.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup refunds failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Container for refund data.
*/
struct RefundData
{
/**
* When the refund occurred.
*/
struct GNUNET_TIME_Timestamp timestamp;
/**
* Reason for the refund.
*/
const char *reason;
/**
* Amount of the refund.
*/
struct TALER_Amount refund_amount;
/**
* Public key of the coin that was refunded.
*/
const struct TALER_CoinSpendPublicKeyP *coin_pub;
/**
* URL to the exchange that did the refund.
*/
const char *exchange_url;
};
/**
* Creates a refund for testing with.
* @param deposit the deposit being refunded.
* @param refund the data to set.
*/
static void
make_refund (const struct DepositData *deposit,
struct RefundData *refund)
{
refund->timestamp = GNUNET_TIME_timestamp_get ();
refund->reason = "some reason";
refund->refund_amount = deposit->amount_with_fee;
refund->coin_pub = &deposit->coin_pub;
refund->exchange_url = deposit->exchange_url;
}
/**
* Container for proof of a refund.
*/
struct RefundProofData
{
/**
* Fee charged for the refund.
*/
struct TALER_Amount refund_fee;
/**
* The exchange's signature on the refund.
*/
struct TALER_ExchangeSignatureP exchange_sig;
};
/**
* Closure for testing lookup_refunds_detailed.
*/
struct TestLookupRefundsDetailed_Closure
{
/**
* Length of @e refunds_to_cmp.
*/
unsigned int refunds_to_cmp_length;
/**
* The refunds we expect to find.
*/
const struct RefundData *refunds_to_cmp;
/**
* Whether to compare the timestamps or not (if we don't have direct control
* of the timestamps, there will be differences on the order of microseconds
* that can be ignored).
*/
bool cmp_timestamps;
/**
* The number of results matching each refund.
*/
unsigned int *results_matching;
/**
* The total number of results from the db.
*/
unsigned int results_length;
};
/**
* Called after test_lookup_refunds_detailed.
* @param cls pointer to a TestLookupRefundsDetailed_Closure.
* @param refund_serial unique serial number of the refund
* @param timestamp time of the refund (for grouping of refunds in the wallet UI)
* @param coin_pub public coin from which the refund comes from
* @param exchange_url URL of the exchange that issued @a coin_pub
* @param rtransaction_id identificator of the refund
* @param reason human-readable explanation of the refund
* @param refund_amount refund amount which is being taken from @a coin_pub
* @param pending true if this refund has not been processed by the wallet/exchange
*/
static void
lookup_refunds_detailed_cb (void *cls,
uint64_t refund_serial,
struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const char *exchange_url,
uint64_t rtransaction_id,
const char *reason,
const struct TALER_Amount *refund_amount,
bool pending)
{
struct TestLookupRefundsDetailed_Closure *cmp = cls;
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i)
{
if (((GNUNET_TIME_timestamp_cmp (cmp->refunds_to_cmp[i].timestamp,
==,
timestamp)) ||
! cmp->cmp_timestamps) &&
(0 == GNUNET_memcmp (cmp->refunds_to_cmp[i].coin_pub,
coin_pub)) &&
(0 == strcmp (cmp->refunds_to_cmp[i].exchange_url,
exchange_url)) &&
(0 == strcmp (cmp->refunds_to_cmp[i].reason,
reason)) &&
(GNUNET_OK ==
TALER_amount_cmp_currency (
&cmp->refunds_to_cmp[i].refund_amount,
refund_amount)) &&
(0 == TALER_amount_cmp (&cmp->refunds_to_cmp[i].refund_amount,
refund_amount)))
{
cmp->results_matching[i] += 1;
}
}
}
/**
* Tests looking up refunds with details from the database.
* @param instance the instance to lookup from.
* @param h_contract_terms the contract terms the refunds were made for.
* @param cmp_timestamps whether to compare timestamps or not.
* @param refunds_length length of @e refunds.
* @param refunds the refunds we expect to be returned.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_refunds_detailed (
const struct InstanceData *instance,
const struct TALER_PrivateContractHashP *h_contract_terms,
bool cmp_timestamps,
unsigned int refunds_length,
const struct RefundData *refunds)
{
unsigned int results_matching[refunds_length];
struct TestLookupRefundsDetailed_Closure cmp = {
.refunds_to_cmp_length = refunds_length,
.refunds_to_cmp = refunds,
.cmp_timestamps = cmp_timestamps,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
if (0 > plugin->lookup_refunds_detailed (plugin->cls,
instance->instance.id,
h_contract_terms,
&lookup_refunds_detailed_cb,
&cmp))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup refunds detailed failed\n");
return 1;
}
if (refunds_length != cmp.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup refunds detailed failed: incorrect number of results\n")
;
return 1;
}
for (unsigned int i = 0; refunds_length > i; ++i)
{
if (1 != cmp.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup refunds detailed failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Closure for get_refund_serial.
*/
struct LookupRefundSerial_Closure
{
/**
* The refund we are looking up the id for.
*/
const struct RefundData *refund;
/**
* The row number found.
*/
uint64_t serial;
};
/**
* Called after get_refund_serial.
* @param cls pointer to a LookupRefundSerial_Closure.
* @param refund_serial unique serial number of the refund
* @param timestamp time of the refund (for grouping of refunds in the wallet UI)
* @param coin_pub public coin from which the refund comes from
* @param exchange_url URL of the exchange that issued @a coin_pub
* @param rtransaction_id identificator of the refund
* @param reason human-readable explanation of the refund
* @param refund_amount refund amount which is being taken from @a coin_pub
* @param pending true if this refund has not been processed by the wallet/exchange
*/
static void
get_refund_serial_cb (void *cls,
uint64_t refund_serial,
struct GNUNET_TIME_Timestamp timestamp,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
const char *exchange_url,
uint64_t rtransaction_id,
const char *reason,
const struct TALER_Amount *refund_amount,
bool pending)
{
struct LookupRefundSerial_Closure *lookup_cls = cls;
if (NULL == lookup_cls)
return;
if ((GNUNET_TIME_timestamp_cmp (lookup_cls->refund->timestamp,
==,
timestamp)) &&
(0 == GNUNET_memcmp (lookup_cls->refund->coin_pub,
coin_pub)) &&
(0 == strcmp (lookup_cls->refund->exchange_url,
exchange_url)) &&
(0 == strcmp (lookup_cls->refund->reason,
reason)) &&
(GNUNET_OK ==
TALER_amount_cmp_currency (
&lookup_cls->refund->refund_amount,
refund_amount)) &&
(0 == TALER_amount_cmp (&lookup_cls->refund->refund_amount,
refund_amount)))
lookup_cls->serial = refund_serial;
}
/**
* Utility function for getting the database row number of a refund.
* @param instance the instance associated with the refund.
* @param h_contract_terms the contract terms the refund was made with.
* @param refund the refund we are querying the row number of.
*
* @return the row number of the refund.
*/
static uint64_t
get_refund_serial (const struct InstanceData *instance,
const struct TALER_PrivateContractHashP *h_contract_terms,
const struct RefundData *refund)
{
struct LookupRefundSerial_Closure lookup_cls = {
.refund = refund,
.serial = 0
};
GNUNET_assert (0 < plugin->lookup_refunds_detailed (plugin->cls,
instance->instance.id,
h_contract_terms,
&get_refund_serial_cb,
&lookup_cls));
GNUNET_assert (0 != lookup_cls.serial);
return lookup_cls.serial;
}
/**
* Tests looking up proof of a refund.
* @param refund_serial the row number of the refund.
* @param expected_exchange_sig the exchange signature we are expecting.
* @param expected_exchange_pub the exchange public key we are expecting.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_refund_proof (uint64_t refund_serial,
const struct
TALER_ExchangeSignatureP *expected_exchange_sig,
const struct
TALER_ExchangePublicKeyP *expected_exchange_pub)
{
struct TALER_ExchangeSignatureP exchange_sig;
struct TALER_ExchangePublicKeyP exchange_pub;
if (1 != plugin->lookup_refund_proof (plugin->cls,
refund_serial,
&exchange_sig,
&exchange_pub))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup refund proof failed\n");
return 1;
}
if ((0 != GNUNET_memcmp (expected_exchange_sig,
&exchange_sig)) ||
(0 != GNUNET_memcmp (expected_exchange_pub,
&exchange_pub)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup refund proof failed: mismatched data\n");
return 1;
}
return 0;
}
/**
* Closure for testing refund functionality.
*/
struct TestRefunds_Closure
{
/**
* The instance.
*/
struct InstanceData instance;
/**
* The merchant account.
*/
struct TALER_MERCHANTDB_AccountDetails account;
/**
* The exchange signing key.
*/
struct ExchangeSignkeyData signkey;
/**
* The order data.
*/
struct OrderData orders[2];
/**
* The deposit data.
*/
struct DepositData deposits[3];
/**
* The refund data.
*/
struct RefundData refunds[3];
/**
* The refund proof data.
*/
struct RefundProofData refund_proof;
};
/**
* Prepares for testing refunds.
* @param cls the closure to initialize.
*/
static void
pre_test_refunds (struct TestRefunds_Closure *cls)
{
/* Instance */
make_instance ("test_inst_refunds",
&cls->instance);
/* Account */
make_account (&cls->account);
cls->account.instance_id = cls->instance.instance.id;
/* Signing key */
make_exchange_signkey (&cls->signkey);
/* Order */
make_order ("test_refunds_od_0",
&cls->orders[0]);
make_order ("test_refunds_od_1",
&cls->orders[1]);
/* Deposit */
make_deposit (&cls->instance,
&cls->account,
&cls->orders[0],
&cls->signkey,
&cls->deposits[0]);
make_deposit (&cls->instance,
&cls->account,
&cls->orders[0],
&cls->signkey,
&cls->deposits[1]);
make_deposit (&cls->instance,
&cls->account,
&cls->orders[1],
&cls->signkey,
&cls->deposits[2]);
/* Refund */
make_refund (&cls->deposits[0],
&cls->refunds[0]);
make_refund (&cls->deposits[2],
&cls->refunds[1]);
make_refund (&cls->deposits[2],
&cls->refunds[2]);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:10.00",
&cls->refunds[1].refund_amount));
cls->refunds[1].reason = "refund 1";
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:10.00",
&cls->refunds[2].refund_amount));
cls->refunds[2].reason = "refund 2";
/* Refund proof */
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:0.02",
&cls->refund_proof.refund_fee));
memset (&cls->refund_proof.exchange_sig,
42,
sizeof (cls->refund_proof.exchange_sig));
}
/**
* Cleans up after testing refunds.
* @param cls the closure.
*/
static void
post_test_refunds (struct TestRefunds_Closure *cls)
{
free_instance_data (&cls->instance);
free_order_data (&cls->orders[0]);
free_order_data (&cls->orders[1]);
}
/**
* Runs the refund tests.
* @param cls the closure.
*
* @return 0 on success, 1 otherwise.
*/
static int
run_test_refunds (struct TestRefunds_Closure *cls)
{
struct TALER_Amount inc;
uint64_t refund_serial;
/* Insert an instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert an account */
TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
&cls->account,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert an order */
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert contract terms */
TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert a signing key */
TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Insert a deposit */
TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
&cls->signkey,
&cls->deposits[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
&cls->signkey,
&cls->deposits[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Mark as paid */
TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
&cls->orders[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test refund coin */
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
plugin->refund_coin (plugin->cls,
cls->instance.instance.id,
&cls->deposits[0].h_contract_terms
,
cls->refunds[0].timestamp,
cls->refunds[0].coin_pub,
cls->refunds[0].reason),
"Refund coin failed\n");
refund_serial = get_refund_serial (&cls->instance,
&cls->deposits[0].h_contract_terms,
&cls->refunds[0]);
/* Test double refund fails */
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
plugin->refund_coin (plugin->cls,
cls->instance.instance.id,
&cls->deposits[0].h_contract_terms
,
cls->refunds[0].timestamp,
cls->refunds[0].coin_pub,
cls->refunds[0].reason),
"Refund coin failed\n");
/* Test lookup refunds */
TEST_RET_ON_FAIL (test_lookup_refunds (&cls->instance,
&cls->deposits[0].h_contract_terms,
1,
cls->refunds[0].coin_pub,
&cls->refunds[0].refund_amount));
/* Test lookup refunds detailed */
TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance,
&cls->deposits[0].
h_contract_terms,
true,
1,
&cls->refunds[0]));
/* Test insert refund proof */
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
plugin->insert_refund_proof (plugin->cls,
refund_serial,
&cls->refund_proof.
exchange_sig,
&cls->signkey.exchange_pub
),
"Insert refund proof failed\n");
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
plugin->insert_refund_proof (plugin->cls,
refund_serial,
&cls->refund_proof.
exchange_sig,
&cls->signkey.exchange_pub
),
"Insert refund proof failed\n");
/* Test that we can't give too much in refunds */
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:1000.00",
&inc));
TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_TOO_HIGH ==
plugin->increase_refund (plugin->cls,
cls->instance.instance.id,
cls->orders[0].id,
&inc,
NULL,
NULL,
"more"),
"Increase refund failed\n");
/* Test increase refund */
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:1.00",
&inc));
TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
plugin->increase_refund (plugin->cls,
cls->instance.instance.id,
cls->orders[0].id,
&inc,
NULL,
NULL,
"more"),
"Increase refund failed\n");
/* Test lookup refund proof */
TEST_RET_ON_FAIL (test_lookup_refund_proof (1,
&cls->refund_proof.exchange_sig,
&cls->signkey.exchange_pub));
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->orders[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
&cls->orders[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
&cls->signkey,
&cls->deposits[2],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
&cls->orders[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test refunding a small amount of the coin, then increasing it */
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:10.00",
&inc));
TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
plugin->increase_refund (plugin->cls,
cls->instance.instance.id,
cls->orders[1].id,
&inc,
NULL,
NULL,
cls->refunds[1].reason),
"Increase refund failed\n");
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount ("EUR:20.00",
&inc));
TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
plugin->increase_refund (plugin->cls,
cls->instance.instance.id,
cls->orders[1].id,
&inc,
NULL,
NULL,
cls->refunds[2].reason),
"Increase refund failed\n");
TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance,
&cls->deposits[2].
h_contract_terms,
false,
2,
&cls->refunds[1]));
return 0;
}
/**
* All logic for testing refunds.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_refunds (void)
{
struct TestRefunds_Closure test_cls;
pre_test_refunds (&test_cls);
int test_result = run_test_refunds (&test_cls);
post_test_refunds (&test_cls);
return test_result;
}
/**
* Convenience function that reverses an array of orders.
* @param orders_length length of @e orders.
* @param orders the array to reverse.
*/
static void
reverse_order_data_array (unsigned int orders_length,
struct OrderData *orders)
{
struct OrderData tmp[orders_length];
for (unsigned int i = 0; i < orders_length; ++i)
tmp[i] = orders[orders_length - 1 - i];
for (unsigned int i = 0; i < orders_length; ++i)
orders[i] = tmp[i];
}
/**
* Closure for testing all the filters for looking up orders.
*/
struct TestLookupOrdersAllFilters_Closure
{
/**
* The instance.
*/
struct InstanceData instance;
/**
* The merchant account.
*/
struct TALER_MERCHANTDB_AccountDetails account;
/**
* The exchange signing key.
*/
struct ExchangeSignkeyData signkey;
/**
* The array of order ids.
*/
char *order_ids[64];
/**
* The array of orders.
*/
struct OrderData orders[64];
/**
* The array of deposits.
*/
struct DepositData deposits[64];
/**
* The array of refunds.
*/
struct RefundData refunds[64];
};
/**
* Sets up for testing lookup order filters.
* @param cls the closure.
*/
static void
pre_test_lookup_orders_all_filters (
struct TestLookupOrdersAllFilters_Closure *cls)
{
make_instance ("test_inst_lookup_orders_all_filters",
&cls->instance);
make_account (&cls->account);
cls->account.instance_id = cls->instance.instance.id;
make_exchange_signkey (&cls->signkey);
for (unsigned int i = 0; i < 64; ++i)
{
(void) GNUNET_asprintf (&cls->order_ids[i],
"test_orders_filter_od_%u",
i);
make_order (cls->order_ids[i],
&cls->orders[i]);
GNUNET_assert (0 ==
json_object_set_new (cls->orders[i].contract,
"order_id",
json_string (cls->order_ids[i])));
make_deposit (&cls->instance,
&cls->account,
&cls->orders[i],
&cls->signkey,
&cls->deposits[i]);
make_refund (&cls->deposits[i],
&cls->refunds[i]);
}
}
/**
* Cleans up after testing lookup order filters.
* @param cls the closure.
*/
static void
post_test_lookup_orders_all_filters (
struct TestLookupOrdersAllFilters_Closure *cls)
{
free_instance_data (&cls->instance);
for (unsigned int i = 0; i < 64; ++i)
{
free_order_data (&cls->orders[i]);
GNUNET_free (cls->order_ids[i]);
}
}
/**
* Runs the tests for lookup order filters.
* @param cls the closure.
*
* @return 0 on success, 1 otherwise.
*/
static int
run_test_lookup_orders_all_filters (
struct TestLookupOrdersAllFilters_Closure *cls)
{
/* Order filter extravaganza */
struct
{
bool claimed;
bool paid;
bool refunded;
bool wired;
} order_status[64];
unsigned int *permutation;
/* Pseudorandomly generate variations for the filter to differentiate */
GNUNET_CRYPTO_seed_weak_random (1);
permutation = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_WEAK,
64);
for (unsigned int i = 0; i < 64; ++i)
{
unsigned int dest = permutation[i];
order_status[dest].claimed = (i & 1) ? true : false;
order_status[dest].paid = (3 == (i & 3)) ? true : false;
order_status[dest].refunded = (5 == (i & 5)) ? true : false;
order_status[dest].wired = (9 == (i & 9)) ? true : false;
}
GNUNET_free (permutation);
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
&cls->account,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
for (unsigned int i = 0; i < 64; ++i)
{
uint64_t order_serial;
TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
&cls->orders[i],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
order_serial = get_order_serial (&cls->instance,
&cls->orders[i]);
if (order_status[i].claimed)
{
TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
&cls->orders[i],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
}
else
{
continue;
}
if (order_status[i].paid)
TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
&cls->orders[i],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
if (order_status[i].refunded)
{
TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
&cls->signkey,
&cls->deposits[i],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
plugin->refund_coin (plugin->cls,
cls->instance.instance.id,
&cls->deposits[i].
h_contract_terms,
cls->refunds[i].timestamp,
cls->refunds[i].coin_pub,
cls->refunds[i].reason),
"Refund coin failed\n");
}
if (order_status[i].wired)
TEST_RET_ON_FAIL (test_mark_order_wired (order_serial,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
}
/* There are 3^3 = 27 possibilities here, not counting inc/dec and start row */
for (unsigned int i = 0; i < 27; ++i)
{
struct TALER_MERCHANTDB_OrderFilter filter = {
.paid = (i % 3) + 1,
.refunded = ((i / 3) % 3) + 1,
.wired = ((i / 9) % 3) + 1,
.date = GNUNET_TIME_UNIT_ZERO_TS,
.start_row = 0,
.delta = 64
};
unsigned int orders_length = 0;
struct OrderData orders[64];
/* Now figure out which orders should make it through the filter */
for (unsigned int j = 0; j < 64; ++j)
{
if (((TALER_EXCHANGE_YNA_YES == filter.paid) &&
(true != order_status[j].paid)) ||
((TALER_EXCHANGE_YNA_NO == filter.paid) &&
(false != order_status[j].paid)) ||
((TALER_EXCHANGE_YNA_YES == filter.refunded) &&
(true != order_status[j].refunded)) ||
((TALER_EXCHANGE_YNA_NO == filter.refunded) &&
(false != order_status[j].refunded)) ||
((TALER_EXCHANGE_YNA_YES == filter.wired) &&
(true != order_status[j].wired)) ||
((TALER_EXCHANGE_YNA_NO == filter.wired) &&
(false != order_status[j].wired)))
continue;
orders[orders_length] = cls->orders[j];
orders_length += 1;
}
/* Test the lookup */
TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
&filter,
orders_length,
orders));
/* Now test decreasing */
filter.start_row = 256;
filter.date = GNUNET_TIME_UNIT_FOREVER_TS;
filter.delta = -64;
reverse_order_data_array (orders_length,
orders);
TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
&filter,
orders_length,
orders));
}
return 0;
}
/**
* Handles all logic for testing lookup order filters.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_lookup_orders_all_filters (void)
{
struct TestLookupOrdersAllFilters_Closure test_cls;
int test_result;
memset (&test_cls,
0,
sizeof (test_cls));
pre_test_lookup_orders_all_filters (&test_cls);
test_result = run_test_lookup_orders_all_filters (&test_cls);
post_test_lookup_orders_all_filters (&test_cls);
return test_result;
}
static void
kyc_status_ok (
void *cls,
const struct TALER_MerchantWireHashP *h_wire,
struct TALER_FullPayto payto_uri,
const char *exchange_url,
struct GNUNET_TIME_Timestamp last_check,
bool kyc_ok,
const struct TALER_AccountAccessTokenP *access_token,
unsigned int last_http_status,
enum TALER_ErrorCode last_ec,
bool in_aml_review,
const json_t *jlimits)
{
bool *fail = cls;
if (kyc_ok)
*fail = false;
}
static void
kyc_status_fail (
void *cls,
const struct TALER_MerchantWireHashP *h_wire,
struct TALER_FullPayto payto_uri,
const char *exchange_url,
struct GNUNET_TIME_Timestamp last_check,
bool kyc_ok,
const struct TALER_AccountAccessTokenP *access_token,
unsigned int last_http_status,
enum TALER_ErrorCode last_ec,
bool in_aml_review,
const json_t *jlimits)
{
bool *fail = cls;
if (! kyc_ok)
*fail = false;
}
/**
* Function that tests the KYC table.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_kyc (void)
{
struct InstanceData instance;
struct TALER_MERCHANTDB_AccountDetails account;
bool fail;
struct GNUNET_TIME_Timestamp now;
make_instance ("test_kyc",
&instance);
make_account (&account);
account.instance_id = instance.instance.id;
TEST_RET_ON_FAIL (test_insert_instance (&instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_account (&instance,
&account,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
now = GNUNET_TIME_timestamp_get ();
TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->account_kyc_set_status (plugin->cls,
instance.instance.id,
&account.h_wire,
"https://exchange.net/",
now,
MHD_HTTP_OK,
TALER_EC_NONE,
NULL,
NULL,
false,
false));
TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->account_kyc_set_status (plugin->cls,
instance.instance.id,
&account.h_wire,
"https://exchange2.com/",
now,
MHD_HTTP_OK,
TALER_EC_NONE,
NULL,
NULL,
false,
false));
TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
plugin->account_kyc_set_status (plugin->cls,
instance.instance.id,
&account.h_wire,
"https://exchange.net/",
now,
MHD_HTTP_OK,
TALER_EC_NONE,
NULL,
NULL,
false,
true));
fail = true;
TEST_RET_ON_FAIL (1 !=
plugin->account_kyc_get_status (plugin->cls,
instance.instance.id,
&account.h_wire,
"https://exchange.net/",
&kyc_status_ok,
&fail));
TEST_RET_ON_FAIL (fail);
fail = true;
TEST_RET_ON_FAIL (1 !=
plugin->account_kyc_get_status (plugin->cls,
instance.instance.id,
NULL,
"https://exchange2.com/",
&kyc_status_fail,
&fail));
TEST_RET_ON_FAIL (fail);
fail = true;
TEST_RET_ON_FAIL (2 !=
plugin->account_kyc_get_status (plugin->cls,
instance.instance.id,
NULL,
NULL,
&kyc_status_fail,
&fail));
TEST_RET_ON_FAIL (fail);
fail = true;
TEST_RET_ON_FAIL (2 !=
plugin->account_kyc_get_status (plugin->cls,
instance.instance.id,
NULL,
NULL,
&kyc_status_ok,
&fail));
TEST_RET_ON_FAIL (fail);
json_decref (instance.instance.address);
json_decref (instance.instance.jurisdiction);
return 0;
}
/* *********** Templates ********** */
/**
* A container for data relevant to a template.
*/
struct TemplateData
{
/**
* The identifier of the template.
*/
const char *id;
/**
* The details of the template.
*/
struct TALER_MERCHANTDB_TemplateDetails template;
};
/**
* Creates a template for testing with.
*
* @param id the id of the template.
* @param template the template data to fill.
*/
static void
make_template (const char *id,
struct TemplateData *template)
{
template->id = id;
template->template.template_description = "This is a test template";
template->template.otp_id = NULL;
template->template.template_contract = json_array ();
GNUNET_assert (NULL != template->template.template_contract);
}
/**
* Frees memory associated with @e TemplateData.
*
* @param template the container to free.
*/
static void
free_template_data (struct TemplateData *template)
{
GNUNET_free (template->template.otp_id);
json_decref (template->template.template_contract);
}
/**
* Compare two templates for equality.
*
* @param a the first template.
* @param b the second template.
* @return 0 on equality, 1 otherwise.
*/
static int
check_templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *a,
const struct TALER_MERCHANTDB_TemplateDetails *b)
{
if ((0 != strcmp (a->template_description,
b->template_description)) ||
( (NULL == a->otp_id) && (NULL != b->otp_id)) ||
( (NULL != a->otp_id) && (NULL == b->otp_id)) ||
( (NULL != a->otp_id) && (0 != strcmp (a->otp_id,
b->otp_id))) ||
(1 != json_equal (a->template_contract,
b->template_contract)))
return 1;
return 0;
}
/**
* Tests inserting template data into the database.
*
* @param instance the instance to insert the template for.
* @param template the template data to insert.
* @param expected_result the result we expect the db to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_insert_template (const struct InstanceData *instance,
const struct TemplateData *template,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_template (plugin->cls,
instance->instance.id,
template->id,
0,
&template->template),
"Insert template failed\n");
return 0;
}
/**
* Tests updating template data in the database.
*
* @param instance the instance to update the template for.
* @param template the template data to update.
* @param expected_result the result we expect the db to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_update_template (const struct InstanceData *instance,
const struct TemplateData *template,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->update_template (plugin->cls,
instance->instance.id,
template->id,
&template->template),
"Update template failed\n");
return 0;
}
/**
* Tests looking up a template from the db.
*
* @param instance the instance to query from.
* @param template the template to query and compare to.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_template (const struct InstanceData *instance,
const struct TemplateData *template)
{
struct TALER_MERCHANTDB_TemplateDetails lookup_result;
if (0 > plugin->lookup_template (plugin->cls,
instance->instance.id,
template->id,
&lookup_result))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup template failed\n");
TALER_MERCHANTDB_template_details_free (&lookup_result);
return 1;
}
const struct TALER_MERCHANTDB_TemplateDetails *to_cmp = &template->template;
if (0 != check_templates_equal (&lookup_result,
to_cmp))
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup template failed: incorrect template returned\n");
TALER_MERCHANTDB_template_details_free (&lookup_result);
return 1;
}
TALER_MERCHANTDB_template_details_free (&lookup_result);
return 0;
}
/**
* Closure for testing template lookup
*/
struct TestLookupTemplates_Closure
{
/**
* Number of template ids to compare to
*/
unsigned int templates_to_cmp_length;
/**
* Pointer to array of template ids
*/
const struct TemplateData *templates_to_cmp;
/**
* Pointer to array of number of matches for each template
*/
unsigned int *results_matching;
/**
* Total number of results returned
*/
unsigned int results_length;
};
/**
* Function called after calling @e test_lookup_templates
*
* @param cls a pointer to the lookup closure.
* @param template_id the identifier of the template found.
*/
static void
lookup_templates_cb (void *cls,
const char *template_id,
const char *template_description)
{
struct TestLookupTemplates_Closure *cmp = cls;
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; cmp->templates_to_cmp_length > i; ++i)
{
if ( (0 == strcmp (cmp->templates_to_cmp[i].id,
template_id)) &&
(0 == strcmp (cmp->templates_to_cmp[i].template.template_description,
template_description)) )
cmp->results_matching[i] += 1;
}
}
/**
* Tests looking up all templates for an instance.
*
* @param instance the instance to query from.
* @param templates_length the number of templates we are expecting.
* @param templates the list of templates that we expect to be found.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_templates (const struct InstanceData *instance,
unsigned int templates_length,
const struct TemplateData *templates)
{
unsigned int results_matching[templates_length];
struct TestLookupTemplates_Closure cls = {
.templates_to_cmp_length = templates_length,
.templates_to_cmp = templates,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching,
0,
sizeof (unsigned int) * templates_length);
if (0 > plugin->lookup_templates (plugin->cls,
instance->instance.id,
&lookup_templates_cb,
&cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup templates failed\n");
return 1;
}
if (templates_length != cls.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup templates failed: incorrect number of results\n");
return 1;
}
for (unsigned int i = 0; templates_length > i; ++i)
{
if (1 != cls.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup templates failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Tests deleting a template.
*
* @param instance the instance to delete the template from.
* @param template the template that should be deleted.
* @param expected_result the result that we expect the plugin to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_delete_template (const struct InstanceData *instance,
const struct TemplateData *template,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->delete_template (plugin->cls,
instance->instance.id,
template->id),
"Delete template failed\n");
return 0;
}
/**
* Closure for template tests.
*/
struct TestTemplates_Closure
{
/**
* The instance to use for this test.
*/
struct InstanceData instance;
/**
* The array of templates.
*/
struct TemplateData templates[2];
};
/**
* Sets up the data structures used in the template tests.
*
* @param cls the closure to fill with test data.
*/
static void
pre_test_templates (struct TestTemplates_Closure *cls)
{
/* Instance */
make_instance ("test_inst_templates",
&cls->instance);
/* Templates */
make_template ("test_templates_pd_0",
&cls->templates[0]);
make_template ("test_templates_pd_1",
&cls->templates[1]);
cls->templates[1].template.template_description =
"This is a another test template";
}
/**
* Handles all teardown after testing.
*
* @param cls the closure containing memory to be freed.
*/
static void
post_test_templates (struct TestTemplates_Closure *cls)
{
free_instance_data (&cls->instance);
free_template_data (&cls->templates[0]);
free_template_data (&cls->templates[1]);
}
/**
* Runs the tests for templates.
*
* @param cls the container of the test data.
* @return 0 on success, 1 otherwise.
*/
static int
run_test_templates (struct TestTemplates_Closure *cls)
{
/* Test that insert without an instance fails */
TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
&cls->templates[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Insert the instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test inserting a template */
TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
&cls->templates[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test that double insert fails */
TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
&cls->templates[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test lookup of individual templates */
TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
&cls->templates[0]));
/* Make sure it fails correctly for templates that don't exist */
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_template (plugin->cls,
cls->instance.instance.id,
"nonexistent_template",
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup template failed\n");
return 1;
}
/* Test template update */
cls->templates[0].template.template_description =
"This is a test template that has been updated!";
GNUNET_free (cls->templates[0].template.otp_id);
cls->templates[0].template.otp_id = GNUNET_strdup ("otp_id");
{
/* ensure OTP device exists */
struct TALER_MERCHANTDB_OtpDeviceDetails td = {
.otp_description = "my otp",
.otp_key = "my key",
.otp_algorithm = 1,
.otp_ctr = 42
};
GNUNET_assert (0 <=
plugin->insert_otp (plugin->cls,
cls->instance.instance.id,
"otp_id",
&td));
}
GNUNET_assert (0 ==
json_array_append_new (
cls->templates[0].template.template_contract,
json_string ("This is a test. 3CH.")));
TEST_RET_ON_FAIL (test_update_template (&cls->instance,
&cls->templates[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
&cls->templates[0]));
TEST_RET_ON_FAIL (test_update_template (&cls->instance,
&cls->templates[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test collective template lookup */
TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
&cls->templates[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
2,
cls->templates));
/* Test template deletion */
TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
&cls->templates[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double deletion fails */
TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
&cls->templates[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
1,
cls->templates));
return 0;
}
/**
* Takes care of template testing.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_templates (void)
{
struct TestTemplates_Closure test_cls;
int test_result;
memset (&test_cls,
0,
sizeof (test_cls));
pre_test_templates (&test_cls);
test_result = run_test_templates (&test_cls);
post_test_templates (&test_cls);
return test_result;
}
/* *********** Webhooks ********** */
/**
* A container for data relevant to a webhook.
*/
struct WebhookData
{
/**
* The identifier of the webhook.
*/
const char *id;
/**
* The details of the webhook.
*/
struct TALER_MERCHANTDB_WebhookDetails webhook;
};
/**
* Creates a webhook for testing with.
*
* @param id the id of the webhook.
* @param webhook the webhook data to fill.
*/
static void
make_webhook (const char *id,
struct WebhookData *webhook)
{
webhook->id = id;
webhook->webhook.event_type = "Paid";
webhook->webhook.url = "https://exampletest.com";
webhook->webhook.http_method = "POST";
webhook->webhook.header_template = "Authorization:XYJAORKJEO";
webhook->webhook.body_template = "$Amount";
}
/**
* Compare two webhooks for equality.
*
* @param a the first webhook.
* @param b the second webhook.
* @return 0 on equality, 1 otherwise.
*/
static int
check_webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *a,
const struct TALER_MERCHANTDB_WebhookDetails *b)
{
if ((0 != strcmp (a->event_type,
b->event_type)) ||
(0 != strcmp (a->url,
b->url)) ||
(0 != strcmp (a->http_method,
b->http_method)) ||
(0 != strcmp (a->header_template,
b->header_template)) ||
(0 != strcmp (a->body_template,
b->body_template)))
return 1;
return 0;
}
/**
* Tests inserting webhook data into the database.
*
* @param instance the instance to insert the webhook for.
* @param webhook the webhook data to insert.
* @param expected_result the result we expect the db to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_insert_webhook (const struct InstanceData *instance,
const struct WebhookData *webhook,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_webhook (plugin->cls,
instance->instance.id,
webhook->id,
&webhook->webhook),
"Insert webhook failed\n");
return 0;
}
/**
* Tests updating webhook data in the database.
*
* @param instance the instance to update the webhook for.
* @param webhook the webhook data to update.
* @param expected_result the result we expect the db to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_update_webhook (const struct InstanceData *instance,
const struct WebhookData *webhook,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->update_webhook (plugin->cls,
instance->instance.id,
webhook->id,
&webhook->webhook),
"Update webhook failed\n");
return 0;
}
/**
* Tests looking up a webhook from the db.
*
* @param instance the instance to query from.
* @param webhook the webhook to query and compare to.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_webhook (const struct InstanceData *instance,
const struct WebhookData *webhook)
{
struct TALER_MERCHANTDB_WebhookDetails lookup_result;
if (0 > plugin->lookup_webhook (plugin->cls,
instance->instance.id,
webhook->id,
&lookup_result))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup webhook failed\n");
TALER_MERCHANTDB_webhook_details_free (&lookup_result);
return 1;
}
const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook;
if (0 != check_webhooks_equal (&lookup_result,
to_cmp))
{
GNUNET_break (0);
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup webhook failed: incorrect webhook returned\n");
TALER_MERCHANTDB_webhook_details_free (&lookup_result);
return 1;
}
TALER_MERCHANTDB_webhook_details_free (&lookup_result);
return 0;
}
/**
* Closure for testing webhook lookup
*/
struct TestLookupWebhooks_Closure
{
/**
* Number of webhook ids to compare to
*/
unsigned int webhooks_to_cmp_length;
/**
* Pointer to array of webhook ids
*/
const struct WebhookData *webhooks_to_cmp;
/**
* Pointer to array of number of matches for each webhook
*/
unsigned int *results_matching;
/**
* Total number of results returned
*/
unsigned int results_length;
};
/**
* Function called after calling @e test_lookup_webhooks
*
* @param cls a pointer to the lookup closure.
* @param webhook_id the identifier of the webhook found.
*/
static void
lookup_webhooks_cb (void *cls,
const char *webhook_id,
const char *event_type)
{
struct TestLookupWebhooks_Closure *cmp = cls;
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
{
if ((0 == strcmp (cmp->webhooks_to_cmp[i].id,
webhook_id)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
event_type)) )
cmp->results_matching[i] += 1;
}
}
/**
* Tests looking up all webhooks for an instance.
*
* @param instance the instance to query from.
* @param webhooks_length the number of webhooks we are expecting.
* @param webhooks the list of webhooks that we expect to be found.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_webhooks (const struct InstanceData *instance,
unsigned int webhooks_length,
const struct WebhookData *webhooks)
{
unsigned int results_matching[webhooks_length];
struct TestLookupWebhooks_Closure cls = {
.webhooks_to_cmp_length = webhooks_length,
.webhooks_to_cmp = webhooks,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
if (0 > plugin->lookup_webhooks (plugin->cls,
instance->instance.id,
&lookup_webhooks_cb,
&cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup webhooks failed\n");
return 1;
}
if (webhooks_length != cls.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup webhooks failed: incorrect number of results\n");
return 1;
}
for (unsigned int i = 0; webhooks_length > i; ++i)
{
if (1 != cls.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup webhooks failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Function called after calling @e test_lookup_webhooks
*
* @param cls a pointer to the lookup closure.
* @param webhook_id the identifier of the webhook found.
*/
static void
lookup_webhook_by_event_cb (void *cls,
uint64_t webhook_serial,
const char *event_type,
const char *url,
const char *http_method,
const char *header_template,
const char *body_template)
{
struct TestLookupWebhooks_Closure *cmp = cls;
if (NULL == cmp)
return;
cmp->results_length += 1;
for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
{
if ((0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
event_type)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].webhook.url,
url)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].webhook.http_method,
http_method)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].webhook.header_template,
header_template)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].webhook.body_template,
body_template)) )
cmp->results_matching[i] += 1;
}
}
/**
* Tests looking up webhooks by event for an instance.
*
* @param instance the instance to query from.
* @param webhooks_length the number of webhooks we are expecting.
* @param webhooks the list of webhooks that we expect to be found.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_webhook_by_event (const struct InstanceData *instance,
unsigned int webhooks_length,
const struct WebhookData *webhooks)
{
unsigned int results_matching[webhooks_length];
struct TestLookupWebhooks_Closure cls = {
.webhooks_to_cmp_length = webhooks_length,
.webhooks_to_cmp = webhooks,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
if (0 > plugin->lookup_webhook_by_event (plugin->cls,
instance->instance.id,
webhooks->webhook.event_type,
&lookup_webhook_by_event_cb,
&cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup webhooks by event failed\n");
return 1;
}
if (webhooks_length != cls.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup webhooks by event failed: incorrect number of results\n");
return 1;
}
for (unsigned int i = 0; webhooks_length > i; ++i)
{
if (1 != cls.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup webhooks by event failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Tests deleting a webhook.
*
* @param instance the instance to delete the webhook from.
* @param webhook the webhook that should be deleted.
* @param expected_result the result that we expect the plugin to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_delete_webhook (const struct InstanceData *instance,
const struct WebhookData *webhook,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->delete_webhook (plugin->cls,
instance->instance.id,
webhook->id),
"Delete webhook failed\n");
return 0;
}
/**
* Closure for webhook tests.
*/
struct TestWebhooks_Closure
{
/**
* The instance to use for this test.
*/
struct InstanceData instance;
/**
* The array of webhooks.
*/
struct WebhookData webhooks[3];
};
/**
* Sets up the data structures used in the webhook tests.
*
* @param cls the closure to fill with test data.
*/
static void
pre_test_webhooks (struct TestWebhooks_Closure *cls)
{
/* Instance */
make_instance ("test_inst_webhooks",
&cls->instance);
/* Webhooks */
make_webhook ("test_webhooks_wb_0",
&cls->webhooks[0]);
make_webhook ("test_webhooks_wb_1",
&cls->webhooks[1]);
cls->webhooks[1].webhook.event_type = "Test paid";
cls->webhooks[1].webhook.url = "https://example.com";
cls->webhooks[1].webhook.http_method = "POST";
cls->webhooks[1].webhook.header_template = "Authorization:1XYJAOR493O";
cls->webhooks[1].webhook.body_template = "$Amount";
make_webhook ("test_webhooks_wb_2",
&cls->webhooks[2]);
cls->webhooks[2].webhook.event_type = "Test paid";
cls->webhooks[2].webhook.url = "https://examplerefund.com";
cls->webhooks[2].webhook.http_method = "POST";
cls->webhooks[2].webhook.header_template = "Authorization:XY6ORK52JEO";
cls->webhooks[2].webhook.body_template = "$Amount";
}
/**
* Handles all teardown after testing.
*
* @param cls the closure containing memory to be freed.
*/
static void
post_test_webhooks (struct TestWebhooks_Closure *cls)
{
free_instance_data (&cls->instance);
}
/**
* Runs the tests for webhooks.
*
* @param cls the container of the test data.
* @return 0 on success, 1 otherwise.
*/
static int
run_test_webhooks (struct TestWebhooks_Closure *cls)
{
/* Test that insert without an instance fails */
TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
&cls->webhooks[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Insert the instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test inserting a webhook */
TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
&cls->webhooks[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test that double insert fails */
TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
&cls->webhooks[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test lookup of individual webhooks */
TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
&cls->webhooks[0]));
/* Make sure it fails correctly for webhooks that don't exist */
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
plugin->lookup_webhook (plugin->cls,
cls->instance.instance.id,
"nonexistent_webhook",
NULL))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup webhook failed\n");
return 1;
}
/* Test webhook update */
cls->webhooks[0].webhook.event_type =
"Test paid";
cls->webhooks[0].webhook.url =
"example.com";
cls->webhooks[0].webhook.http_method =
"POST";
cls->webhooks[0].webhook.header_template =
"Authorization:WEKFOEKEXZ";
cls->webhooks[0].webhook.body_template =
"$Amount";
TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
&cls->webhooks[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
&cls->webhooks[0]));
TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
&cls->webhooks[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Test collective webhook lookup */
TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
&cls->webhooks[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
2,
cls->webhooks));
TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance,
2,
cls->webhooks));
TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
&cls->webhooks[2],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test webhook deletion */
TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance,
&cls->webhooks[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double deletion fails */
TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance,
&cls->webhooks[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
cls->webhooks[1] = cls->webhooks[2];
TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
2,
cls->webhooks));
TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance,
2,
cls->webhooks));
return 0;
}
/**
* Takes care of webhook testing.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_webhooks (void)
{
struct TestWebhooks_Closure test_cls;
pre_test_webhooks (&test_cls);
int test_result = run_test_webhooks (&test_cls);
post_test_webhooks (&test_cls);
return test_result;
}
/* *********** Pending Webhooks ********** */
/**
* A container for data relevant to a pending webhook.
*/
struct PendingWebhookData
{
/**
* Reference to the configured webhook template.
*/
uint64_t webhook_serial;
/**
* The details of the pending webhook.
*/
struct TALER_MERCHANTDB_PendingWebhookDetails pwebhook;
};
/**
* Creates a pending webhook for testing with.
*
* @param serial reference to the configured webhook template.
* @param pwebhook the pending webhook data to fill.
*/
static void
make_pending_webhook (uint64_t webhook_serial,
struct PendingWebhookData *pwebhook)
{
pwebhook->webhook_serial = webhook_serial;
pwebhook->pwebhook.next_attempt = GNUNET_TIME_UNIT_ZERO_ABS;
pwebhook->pwebhook.retries = 0;
pwebhook->pwebhook.url = "https://exampletest.com";
pwebhook->pwebhook.http_method = "POST";
pwebhook->pwebhook.header = "Authorization:XYJAORKJEO";
pwebhook->pwebhook.body = "$Amount";
}
/**
* Tests inserting pending webhook data into the database.
*
* @param instance the instance to insert the pending webhook for.
* @param pending webhook the pending webhook data to insert.
* @param expected_result the result we expect the db to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_insert_pending_webhook (const struct InstanceData *instance,
struct PendingWebhookData *pwebhook,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->insert_pending_webhook (plugin->cls,
instance->instance.id,
pwebhook->
webhook_serial,
pwebhook->pwebhook.url,
pwebhook->pwebhook.
http_method,
pwebhook->pwebhook.
header,
pwebhook->pwebhook.body
),
"Insert pending webhook failed\n");
return 0;
}
/**
* Tests updating pending webhook data in the database.
*
* @param instance the instance to update the pending webhook for.
* @param pending webhook the pending webhook data to update.
* @param expected_result the result we expect the db to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_update_pending_webhook (const struct InstanceData *instance,
struct PendingWebhookData *pwebhook,
enum GNUNET_DB_QueryStatus expected_result)
{
pwebhook->pwebhook.next_attempt = GNUNET_TIME_relative_to_absolute (
GNUNET_TIME_UNIT_HOURS);
pwebhook->pwebhook.retries++;
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->update_pending_webhook (plugin->cls,
pwebhook->
webhook_serial,
pwebhook->pwebhook.
next_attempt),
"Update pending webhook failed\n");
return 0;
}
/**
* Container for information for looking up the row number of a deposit.
*/
struct LookupPendingWebhookSerial_Closure
{
/**
* The pending webhook we're looking for.
*/
const struct PendingWebhookData *pwebhook;
/**
* The serial found.
*/
uint64_t webhook_pending_serial;
};
/**
* Function called after calling @e test_lookup_all_webhook,
* test_lookup_future_webhook and test_lookup_pending_webhook
*
* @param cls a pointer to the lookup closure.
* @param webhook_serial reference to the configured webhook template.
*/
static void
get_pending_serial_cb (void *cls,
uint64_t webhook_pending_serial,
struct GNUNET_TIME_Absolute next_attempt,
uint32_t retries,
const char *url,
const char *http_method,
const char *header,
const char *body)
{
struct LookupPendingWebhookSerial_Closure *lpw = cls;
if ((0 == strcmp (lpw->pwebhook->pwebhook.url,
url)) &&
(0 == strcmp (lpw->pwebhook->pwebhook.http_method,
http_method)) &&
(0 == strcmp (lpw->pwebhook->pwebhook.header,
header)) &&
(0 == strcmp (lpw->pwebhook->pwebhook.body,
body)) )
{
lpw->webhook_pending_serial = webhook_pending_serial;
}
/* else
{
fprintf(stdout, "next_attempt: %lu vs %lu\n", lpw->pwebhook->pwebhook.next_attempt.abs_value_us, next_attempt.abs_value_us);
fprintf(stdout, "retries: %d vs %d\n", lpw->pwebhook->pwebhook.retries, retries);
fprintf(stdout, "url: %s vs %s\n", lpw->pwebhook->pwebhook.url, url);
fprintf(stdout, "http_method: %s vs %s\n", lpw->pwebhook->pwebhook.http_method, http_method);
fprintf(stdout, "header: %s vs %s\n", lpw->pwebhook->pwebhook.header, header);
fprintf(stdout, "body: %s vs %s\n", lpw->pwebhook->pwebhook.body, body);
}*/
}
/**
* Convenience function to retrieve the row number of a webhook pending in the database.
*
* @param instance the instance to get webhook pending(wp) from.
* @param webhook pending the wp to lookup the serial for.
* @return the row number of the deposit.
*/
static uint64_t
get_pending_serial (const struct InstanceData *instance,
const struct PendingWebhookData *pwebhook)
{
struct LookupPendingWebhookSerial_Closure lpw = {
.pwebhook = pwebhook,
.webhook_pending_serial = 0
};
GNUNET_assert (0 <
plugin->lookup_all_webhooks (plugin->cls,
instance->instance.id,
0,
INT_MAX,
&get_pending_serial_cb,
&lpw));
GNUNET_assert (0 != lpw.webhook_pending_serial);
return lpw.webhook_pending_serial;
}
/**
* Closure for testing pending webhook lookup
*/
struct TestLookupPendingWebhooks_Closure
{
/**
* Number of webhook serial to compare to
*/
unsigned int webhooks_to_cmp_length;
/**
* Pointer to array of webhook serials
*/
const struct PendingWebhookData *webhooks_to_cmp;
/**
* Pointer to array of number of matches for each pending webhook
*/
unsigned int *results_matching;
/**
* Total number of results returned
*/
unsigned int results_length;
};
/**
* Function called after calling @e test_lookup_all_webhook,
* test_lookup_future_webhook and test_lookup_pending_webhook
*
* @param cls a pointer to the lookup closure.
* @param webhook_serial reference to the configured webhook template.
*/
static void
lookup_pending_webhooks_cb (void *cls,
uint64_t webhook_pending_serial,
struct GNUNET_TIME_Absolute next_attempt,
uint32_t retries,
const char *url,
const char *http_method,
const char *header,
const char *body)
{
struct TestLookupPendingWebhooks_Closure *cmp = cls;
cmp->results_length++;
for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
{
if ((0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.url,
url)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.http_method,
http_method)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.header,
header)) &&
(0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.body,
body)) )
{
cmp->results_matching[i]++;
}
}
}
/**
* Tests looking up the pending webhook for an instance.
*
* @param instance the instance to query from.
* @param pwebhooks_length the number of pending webhook we are expecting.
* @param pwebhooks the list of pending webhooks that we expect to be found.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_pending_webhooks (const struct InstanceData *instance,
unsigned int pwebhooks_length,
const struct PendingWebhookData *pwebhooks)
{
unsigned int results_matching[pwebhooks_length];
struct TestLookupPendingWebhooks_Closure cls = {
.webhooks_to_cmp_length = pwebhooks_length,
.webhooks_to_cmp = pwebhooks,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching, 0, sizeof (results_matching));
if (0 > plugin->lookup_pending_webhooks (plugin->cls,
&lookup_pending_webhooks_cb,
&cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup pending webhook failed\n");
return 1;
}
if (pwebhooks_length != cls.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup pending webhook failed: incorrect number of results\n");
return 1;
}
for (unsigned int i = 0; i < pwebhooks_length; i++)
{
if (1 != cls.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup pending webhook failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Tests looking up the future webhook to send for an instance.
*
* @param instance the instance to query from.
* @param pwebhooks_length the number of pending webhook we are expecting.
* @param pwebhooks the list of pending webhooks that we expect to be found.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_future_webhook (const struct InstanceData *instance,
unsigned int pwebhooks_length,
const struct PendingWebhookData *pwebhooks)
{
unsigned int results_matching[pwebhooks_length];
struct TestLookupPendingWebhooks_Closure cls = {
.webhooks_to_cmp_length = pwebhooks_length,
.webhooks_to_cmp = pwebhooks,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching, 0, sizeof (results_matching));
if (0 > plugin->lookup_future_webhook (plugin->cls,
&lookup_pending_webhooks_cb,
&cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup future webhook failed\n");
return 1;
}
if (pwebhooks_length != cls.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup future webhook failed: incorrect number of results\n");
return 1;
}
for (unsigned int i = 0; pwebhooks_length > i; ++i)
{
if (1 != cls.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup future webhook failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Tests looking up all the pending webhook for an instance.
*
* @param instance the instance to query from.
* @param pwebhooks_length the number of pending webhook we are expecting.
* @param pwebhooks the list of pending webhooks that we expect to be found.
* @return 0 when successful, 1 otherwise.
*/
static int
test_lookup_all_webhooks (const struct InstanceData *instance,
unsigned int pwebhooks_length,
const struct PendingWebhookData *pwebhooks)
{
uint64_t max_results = 2;
uint64_t min_row = 0;
unsigned int results_matching[GNUNET_NZL (pwebhooks_length)];
struct TestLookupPendingWebhooks_Closure cls = {
.webhooks_to_cmp_length = pwebhooks_length,
.webhooks_to_cmp = pwebhooks,
.results_matching = results_matching,
.results_length = 0
};
memset (results_matching,
0,
sizeof (results_matching));
if (0 > plugin->lookup_all_webhooks (plugin->cls,
instance->instance.id,
min_row,
max_results,
&lookup_pending_webhooks_cb,
&cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup all webhooks failed\n");
return 1;
}
if (pwebhooks_length != cls.results_length)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup all webhooks failed: incorrect number of results\n");
return 1;
}
for (unsigned int i = 0; pwebhooks_length > i; ++i)
{
if (1 != cls.results_matching[i])
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Lookup all webhooks failed: mismatched data\n");
return 1;
}
}
return 0;
}
/**
* Tests deleting a pending webhook.
*
* @param instance the instance to delete the pending webhook from.
* @param pwebhook the pending webhook that should be deleted.
* @param expected_result the result that we expect the plugin to return.
* @return 0 when successful, 1 otherwise.
*/
static int
test_delete_pending_webhook (uint64_t webhooks_pending_serial,
enum GNUNET_DB_QueryStatus expected_result)
{
TEST_COND_RET_ON_FAIL (expected_result ==
plugin->delete_pending_webhook (plugin->cls,
webhooks_pending_serial),
"Delete webhook failed\n");
return 0;
}
/**
* Closure for pending webhook tests.
*/
struct TestPendingWebhooks_Closure
{
/**
* The instance to use for this test.
*/
struct InstanceData instance;
/**
* The array of pending webhooks.
*/
struct PendingWebhookData pwebhooks[2];
};
/**
* Sets up the data structures used in the pending webhook tests.
*
* @param cls the closure to fill with test data.
*/
static void
pre_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
{
/* Instance */
make_instance ("test_inst_pending_webhooks",
&cls->instance);
/* Webhooks */
make_pending_webhook (1,
&cls->pwebhooks[0]);
make_pending_webhook (4,
&cls->pwebhooks[1]);
cls->pwebhooks[1].pwebhook.url = "https://test.com";
cls->pwebhooks[1].pwebhook.http_method = "POST";
cls->pwebhooks[1].pwebhook.header = "Authorization:XYJAO5R06EO";
cls->pwebhooks[1].pwebhook.body = "$Amount";
}
/**
* Handles all teardown after testing.
*
* @param cls the closure containing memory to be freed.
*/
static void
post_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
{
free_instance_data (&cls->instance);
}
/**
* Runs the tests for pending webhooks.
*
* @param cls the container of the test data.
* @return 0 on success, 1 otherwise.
*/
static int
run_test_pending_webhooks (struct TestPendingWebhooks_Closure *cls)
{
/* Test that insert without an instance fails */
TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
&cls->pwebhooks[0],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
/* Insert the instance */
TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test inserting a pending webhook */
TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
&cls->pwebhooks[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
&cls->pwebhooks[1],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test collective pending webhook lookup */
TEST_RET_ON_FAIL (test_lookup_pending_webhooks (&cls->instance,
2,
cls->pwebhooks));
/* Test pending webhook update */
TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
&cls->pwebhooks[0],
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_future_webhook (&cls->instance,
1,
&cls->pwebhooks[1]));
TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
&cls->pwebhooks[1],
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
// ???
TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
2,
cls->pwebhooks));
uint64_t webhook_pending_serial0 = get_pending_serial (&cls->instance,
&cls->pwebhooks[0]);
uint64_t webhook_pending_serial1 = get_pending_serial (&cls->instance,
&cls->pwebhooks[1]);
/* Test webhook deletion */
TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
/* Test double deletion fails */
TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial0,
GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
0,
NULL));
return 0;
}
/**
* Takes care of pending webhook testing.
*
* @return 0 on success, 1 otherwise.
*/
static int
test_pending_webhooks (void)
{
struct TestPendingWebhooks_Closure test_cls;
pre_test_pending_webhooks (&test_cls);
int test_result = run_test_pending_webhooks (&test_cls);
post_test_pending_webhooks (&test_cls);
return test_result;
}
/**
* Function that runs all tests.
*
* @return 0 on success, 1 otherwise.
*/
static int
run_tests (void)
{
TEST_RET_ON_FAIL (test_instances ());
TEST_RET_ON_FAIL (test_products ());
TEST_RET_ON_FAIL (test_orders ());
TEST_RET_ON_FAIL (test_deposits ());
TEST_RET_ON_FAIL (test_transfers ());
TEST_RET_ON_FAIL (test_refunds ());
TEST_RET_ON_FAIL (test_lookup_orders_all_filters ());
TEST_RET_ON_FAIL (test_kyc ());
TEST_RET_ON_FAIL (test_templates ());
TEST_RET_ON_FAIL (test_webhooks ());
TEST_RET_ON_FAIL (test_pending_webhooks ());
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()' */
/* Drop the tables to cleanup anything that might cause issues */
if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg)))
{
result = 77;
return;
}
(void) plugin->drop_tables (plugin->cls);
if (GNUNET_OK !=
plugin->create_tables (plugin->cls))
{
result = 77;
return;
}
if (GNUNET_OK !=
plugin->connect (plugin->cls))
{
GNUNET_break (0);
result = 17;
return;
}
/* Run the preflight */
plugin->preflight (plugin->cls);
result = run_tests ();
if (0 == result)
/** result = run_test_templates ();
if (0 == result)*/
{
/* Test dropping tables */
if (GNUNET_OK != plugin->drop_tables (plugin->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Dropping tables failed\n");
result = 77;
return;
}
}
TALER_MERCHANTDB_plugin_unload (plugin);
plugin = NULL;
}
/**
* Entry point for the tests.
*/
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);
fprintf (stdout, "Using %s\n", config_filename);
cfg = GNUNET_CONFIGURATION_create (TALER_MERCHANT_project_data ());
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 */