/*
This file is part of TALER
(C) 2014, 2015, 2016 INRIA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
TALER; see the file COPYING. If not, see
*/
/**
* @file merchant/test_merchantdb_postgres.c
* @brief testcase for merchant's postgres db plugin
* @author Marcello Stanisci
*/
#include "platform.h"
#include
#include
#include "taler_merchantdb_lib.h"
#include
#define FAILIF(cond) \
do { \
if (!(cond)){ break;} \
GNUNET_break (0); \
goto drop; \
} while (0)
#define RND_BLK(ptr) \
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, sizeof (*ptr))
/**
* Currency we use for the coins.
*/
#define CURRENCY "EUR"
/**
* URI we use for the exchange in the database.
* Note that an exchange does not actually have
* to run at this address.
*/
#define EXCHANGE_URI "http://localhost:8888/"
/**
* 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;
/**
* Hash of the wire transfer address. Set to some random value.
*/
static struct GNUNET_HashCode h_wire;
/**
* Transaction ID.
*/
const char *order_id;
/**
* Transaction ID used to test the db query
* `find_contract_terms_by_date_and_range_future`
*/
const char *order_id_future;
/**
* Proposal's hash
*/
struct GNUNET_HashCode h_contract_terms;
/**
* Proposal's hash.
*/
struct GNUNET_HashCode h_contract_terms;
/**
* Time of the transaction.
*/
static struct GNUNET_TIME_Absolute timestamp;
/**
* Delta aimed to test the "by_date" query on transactions.
*/
static struct GNUNET_TIME_Relative delta;
/**
* Deadline until which refunds are allowed.
*/
static struct GNUNET_TIME_Absolute refund_deadline;
/**
* Total amount, including deposit fee.
*/
static struct TALER_Amount amount_with_fee;
/**
* Deposit fee for the coin.
*/
static struct TALER_Amount deposit_fee;
/**
* Refund fee for the coin.
*/
static struct TALER_Amount refund_fee;
/**
* Amount to be refunded.
*/
static struct TALER_Amount refund_amount;
/**
* Amount to be refunded. Used to trigger error about
* subsequest refund amount being lesser than the previous
* ones.
*/
static struct TALER_Amount little_refund_amount;
/**
* Amount to be refunded in a call which is subsequent
* to the good one, expected to succeed.
*/
static struct TALER_Amount right_second_refund_amount;
/**
* Refund amount meant to raise an error because the
* contract's coins aren't enough to pay it back
*/
static struct TALER_Amount too_big_refund_amount;
/**
* Public key of the coin. Set to some random value.
*/
static struct TALER_CoinSpendPublicKeyP coin_pub;
/**
* Public key of the exchange. Set to some random value.
*/
static struct TALER_ExchangePublicKeyP signkey_pub;
/**
* Public Key of the merchant. Set to some random value.
* Used as merchant instances now do store their keys.
*/
static struct TALER_MerchantPublicKeyP merchant_pub;
/**
* Wire transfer identifier. Set to some random value.
*/
static struct TALER_WireTransferIdentifierRawP wtid;
/**
* "Proof" of deposit from the exchange. Set to some valid JSON.
*/
static json_t *deposit_proof;
/**
* "Proof" of wire transfer from the exchange. Set to some valid JSON.
*/
static json_t *transfer_proof;
/**
* A mock contract, not need to be well-formed
*/
static json_t *contract;
/**
* Mock proposal data, not need to be well-formed
*/
static json_t *contract_terms;
/**
* Mock proposal data, not need to be well-formed
*/
static json_t *contract_terms_future;
/**
* Function called with information about a transaction.
*
* @param cls closure
* @param transaction_id of the contract
* @param merchant_pub public key of the merchant
* @param exchange_uri URI of the exchange
* @param h_wire hash of our wire details
* @param timestamp time of the confirmation
* @param refund_deadline refund deadline
* @param total_amount total amount we receive for the contract after fees
*/
static void
transaction_cb (void *cls,
const struct TALER_MerchantPublicKeyP *amerchant_pub,
const char *aexchange_uri,
const struct GNUNET_HashCode *ah_contract_terms,
const struct GNUNET_HashCode *ah_wire,
struct GNUNET_TIME_Absolute atimestamp,
struct GNUNET_TIME_Absolute arefund_deadline,
const struct TALER_Amount *atotal_amount)
{
#define CHECK(a) do { if (! (a)) { GNUNET_break (0); result = 3; } } while (0)
CHECK (0 == memcmp (amerchant_pub,
&merchant_pub,
sizeof (struct TALER_MerchantPublicKeyP)));
CHECK (0 == memcmp (ah_contract_terms,
&h_contract_terms,
sizeof (struct GNUNET_HashCode)));
CHECK (0 == strcmp (aexchange_uri,
EXCHANGE_URI));
CHECK (0 == memcmp (ah_wire,
&h_wire,
sizeof (struct GNUNET_HashCode)));
CHECK (atimestamp.abs_value_us == timestamp.abs_value_us);
CHECK (arefund_deadline.abs_value_us == refund_deadline.abs_value_us);
CHECK (0 == TALER_amount_cmp (atotal_amount,
&amount_with_fee));
}
/**
* Function called with information about a refund.
*
* @param cls closure
* @param coin_pub public coin from which the refund comes from
* @param rtransaction_id identificator of the refund
* @param reason human-readable explaination of the refund
* @param refund_amount refund amount which is being taken from coin_pub
* @param refund_fee cost of this refund operation
*/
void
refund_cb(void *cls,
const struct TALER_CoinSpendPublicKeyP *coin_pub,
uint64_t rtransaction_id,
const char *reason,
const struct TALER_Amount *refund_amount,
const struct TALER_Amount *refund_fee)
{
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "refund_cb\n");
/* FIXME, more logic here? */
}
/**
* Callback for `find_contract_terms_by_date`.
*
* @param cls closure
* @param order_id order id
* @param row_id row id in db
* @param contract_terms proposal data
*/
static void
pd_cb (void *cls,
const char *order_id,
uint64_t row_id,
const json_t *contract_terms)
{
return;
}
/**
* Function called with information about a coin that was deposited.
*
* @param cls closure
* @param transaction_id of the contract
* @param coin_pub public key of the coin
* @param aamount_with_fee amount the exchange will deposit for this coin
* @param adeposit_fee fee the exchange will charge for this coin
* @param adeposit_fee fee the exchange will charge for refunding this coin
* @param exchange_proof proof from exchange that coin was accepted
*/
static void
deposit_cb (void *cls,
const struct GNUNET_HashCode *ah_contract_terms,
const struct TALER_CoinSpendPublicKeyP *acoin_pub,
const struct TALER_Amount *aamount_with_fee,
const struct TALER_Amount *adeposit_fee,
const struct TALER_Amount *arefund_fee,
const json_t *aexchange_proof)
{
CHECK ((0 == memcmp (ah_contract_terms,
&h_contract_terms,
sizeof (struct GNUNET_HashCode))));
CHECK (0 == memcmp (acoin_pub,
&coin_pub,
sizeof (struct TALER_CoinSpendPublicKeyP)));
CHECK (0 == TALER_amount_cmp (aamount_with_fee,
&amount_with_fee));
CHECK (0 == TALER_amount_cmp (adeposit_fee,
&deposit_fee));
CHECK (1 == json_equal ((json_t *) aexchange_proof,
deposit_proof));
}
/**
* Information about the wire transfer corresponding to
* a deposit operation. Note that it is in theory possible
* that we have a @a transaction_id and @a coin_pub in the
* result that do not match a deposit that we know about,
* for example because someone else deposited funds into
* our account.
*
* @param cls closure
* @param transaction_id ID of the contract
* @param coin_pub public key of the coin
* @param wtid identifier of the wire transfer in which the exchange
* send us the money for the coin deposit
* @param execution_time when was the @a wtid transfer executed
* @param exchange_proof proof from exchange about what the deposit was for
* NULL if we have not asked for this signature
*/
static void
transfer_cb (void *cls,
const struct GNUNET_HashCode *ah_contract_terms,
const struct TALER_CoinSpendPublicKeyP *acoin_pub,
const struct TALER_WireTransferIdentifierRawP *awtid,
struct GNUNET_TIME_Absolute execution_time,
const json_t *exchange_proof)
{
CHECK (0 == memcmp (ah_contract_terms,
&h_contract_terms,
sizeof (struct GNUNET_HashCode)));
CHECK (0 == memcmp (acoin_pub,
&coin_pub,
sizeof (struct TALER_CoinSpendPublicKeyP)));
CHECK (0 == memcmp (awtid,
&wtid,
sizeof (struct TALER_WireTransferIdentifierRawP)));
CHECK (1 == json_equal ((json_t *) exchange_proof,
transfer_proof));
}
/**
* Function called with information about a wire transfer identifier.
*
* @param cls closure
* @param proof proof from exchange about what the wire transfer was for
*/
static void
proof_cb (void *cls,
const json_t *proof)
{
CHECK (1 == json_equal ((json_t *) proof,
transfer_proof));
}
#undef CHECK
/**
* 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;
struct GNUNET_TIME_Absolute fake_now;
/* Data for 'store_payment()' */
if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg)))
{
result = 77;
return;
}
if (GNUNET_OK != plugin->drop_tables (plugin->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Dropping tables failed\n");
result = 77;
return;
}
if (GNUNET_OK != plugin->initialize (plugin->cls))
{
result = 77;
return;
}
/* Prepare data for 'store_payment()' */
RND_BLK (&h_wire);
RND_BLK (&h_contract_terms);
order_id = "test_ID";
order_id_future = "test_ID_future";
RND_BLK (&signkey_pub);
RND_BLK (&merchant_pub);
RND_BLK (&wtid);
timestamp = GNUNET_TIME_absolute_get ();
GNUNET_TIME_round_abs (×tamp);
delta = GNUNET_TIME_UNIT_MINUTES;
fake_now = GNUNET_TIME_absolute_add (timestamp, delta);
refund_deadline = GNUNET_TIME_absolute_get();
GNUNET_TIME_round_abs (&refund_deadline);
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":5",
&amount_with_fee));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
&deposit_fee));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":0.000010",
&refund_fee));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":2",
&refund_amount));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":1",
&little_refund_amount));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":3",
&right_second_refund_amount));
GNUNET_assert (GNUNET_OK ==
TALER_string_to_amount (CURRENCY ":30",
&too_big_refund_amount));
RND_BLK (&coin_pub);
deposit_proof = json_object ();
GNUNET_assert (0 ==
json_object_set_new (deposit_proof,
"test",
json_string ("backenddb test A")));
transfer_proof = json_object ();
GNUNET_assert (0 ==
json_object_set_new (transfer_proof,
"test",
json_string ("backenddb test B")));
contract = json_object ();
contract_terms = json_object ();
GNUNET_assert (0 ==
json_object_set_new (contract_terms,
"order",
json_string ("1")));
contract_terms_future = json_object ();
GNUNET_assert (0 ==
json_object_set_new (contract_terms_future,
"order",
json_string ("2")));
TALER_JSON_hash (contract_terms,
&h_contract_terms);
FAILIF (GNUNET_OK !=
plugin->insert_contract_terms (plugin->cls,
order_id,
&merchant_pub,
timestamp,
contract_terms));
json_t *out;
FAILIF (GNUNET_OK !=
plugin->find_contract_terms (plugin->cls,
&out,
order_id,
&merchant_pub));
FAILIF (GNUNET_OK !=
plugin->find_contract_terms_history (plugin->cls,
order_id,
&merchant_pub,
&pd_cb,
NULL));
FAILIF (GNUNET_OK !=
plugin->find_contract_terms_from_hash (plugin->cls,
&out,
&h_contract_terms,
&merchant_pub));
FAILIF (1 !=
plugin->find_contract_terms_by_date_and_range (plugin->cls,
fake_now,
&merchant_pub,
2,
1,
GNUNET_NO,
&pd_cb,
NULL));
timestamp = GNUNET_TIME_absolute_get ();
GNUNET_TIME_round_abs (×tamp);
FAILIF (GNUNET_OK !=
plugin->insert_contract_terms (plugin->cls,
order_id_future,
&merchant_pub,
timestamp,
contract_terms_future));
fake_now = GNUNET_TIME_absolute_subtract (timestamp, delta);
FAILIF (2 !=
plugin->find_contract_terms_by_date_and_range (plugin->cls,
fake_now,
&merchant_pub,
0,
5,
GNUNET_YES,
&pd_cb,
NULL));
FAILIF (0 !=
plugin->find_contract_terms_by_date (plugin->cls,
fake_now,
&merchant_pub,
1,
&pd_cb,
NULL));
FAILIF (GNUNET_OK !=
plugin->store_transaction (plugin->cls,
&h_contract_terms,
&merchant_pub,
EXCHANGE_URI,
&h_wire,
timestamp,
refund_deadline,
&amount_with_fee));
FAILIF (GNUNET_OK !=
plugin->store_deposit (plugin->cls,
&h_contract_terms,
&merchant_pub,
&coin_pub,
&amount_with_fee,
&deposit_fee,
&refund_fee,
&signkey_pub,
deposit_proof));
FAILIF (GNUNET_OK !=
plugin->store_coin_to_transfer (plugin->cls,
&h_contract_terms,
&coin_pub,
&wtid));
FAILIF (GNUNET_OK !=
plugin->store_transfer_to_proof (plugin->cls,
EXCHANGE_URI,
&wtid,
GNUNET_TIME_UNIT_ZERO_ABS,
&signkey_pub,
transfer_proof));
FAILIF (GNUNET_OK !=
plugin->find_transaction (plugin->cls,
&h_contract_terms,
&merchant_pub,
&transaction_cb,
NULL));
FAILIF (GNUNET_OK !=
plugin->find_payments (plugin->cls,
&h_contract_terms,
&merchant_pub,
&deposit_cb,
NULL));
FAILIF (GNUNET_OK !=
plugin->find_transfers_by_hash (plugin->cls,
&h_contract_terms,
&transfer_cb,
NULL));
FAILIF (GNUNET_OK !=
plugin->find_deposits_by_wtid (plugin->cls,
&wtid,
&deposit_cb,
NULL));
FAILIF (GNUNET_OK !=
plugin->find_proof_by_wtid (plugin->cls,
EXCHANGE_URI,
&wtid,
&proof_cb,
NULL));
FAILIF (GNUNET_NO !=
plugin->get_refunds_from_contract_terms_hash (plugin->cls,
&merchant_pub,
&h_contract_terms,
&refund_cb,
NULL));
FAILIF (GNUNET_OK !=
plugin->increase_refund_for_contract (plugin->cls,
&h_contract_terms,
&merchant_pub,
&refund_amount,
"refund testing"));
FAILIF (GNUNET_NO !=
plugin->increase_refund_for_contract (plugin->cls,
&h_contract_terms,
&merchant_pub,
&refund_amount,
"same refund amount as "
"the previous one, should fail"));
/*Should fail as this refund a lesser amount respect to the previous one*/
FAILIF (GNUNET_NO !=
plugin->increase_refund_for_contract (plugin->cls,
&h_contract_terms,
&merchant_pub,
&little_refund_amount,
"make refund testing fail"));
FAILIF (GNUNET_OK !=
plugin->increase_refund_for_contract (plugin->cls,
&h_contract_terms,
&merchant_pub,
&right_second_refund_amount,
"right refund increase"));
FAILIF (GNUNET_NO !=
plugin->increase_refund_for_contract (plugin->cls,
&h_contract_terms,
&merchant_pub,
&too_big_refund_amount,
"make refund testing fail due"
" to too big refund amount"));
if (-1 == result)
result = 0;
drop:
GNUNET_break (GNUNET_OK == plugin->drop_tables (plugin->cls));
TALER_MERCHANTDB_plugin_unload (plugin);
plugin = NULL;
if (NULL != deposit_proof)
json_decref (deposit_proof);
if (NULL != transfer_proof)
json_decref (transfer_proof);
}
int
main (int argc,
char *const argv[])
{
const char *plugin_name;
char *config_filename;
char *testname;
struct GNUNET_CONFIGURATION_Handle *cfg;
result = -1;
if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
{
GNUNET_break (0);
return -1;
}
GNUNET_log_setup (argv[0], "DEBUG", NULL);
plugin_name++;
(void) GNUNET_asprintf (&testname,
"test-merchantdb-%s",
plugin_name);
(void) GNUNET_asprintf (&config_filename,
"%s.conf",
testname);
cfg = GNUNET_CONFIGURATION_create ();
if (GNUNET_OK !=
GNUNET_CONFIGURATION_parse (cfg,
config_filename))
{
GNUNET_break (0);
GNUNET_free (config_filename);
GNUNET_free (testname);
return 2;
}
GNUNET_SCHEDULER_run (&run, cfg);
GNUNET_CONFIGURATION_destroy (cfg);
GNUNET_free (config_filename);
GNUNET_free (testname);
return result;
}
/* end of test_merchantdb.c */