summaryrefslogtreecommitdiff
path: root/src/lib/test_merchant_api.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/test_merchant_api.c')
-rw-r--r--src/lib/test_merchant_api.c357
1 files changed, 219 insertions, 138 deletions
diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c
index 77450b72..b7081212 100644
--- a/src/lib/test_merchant_api.c
+++ b/src/lib/test_merchant_api.c
@@ -24,6 +24,7 @@
#include <taler/taler_exchange_service.h>
#include <taler/taler_json_lib.h>
#include "taler_merchant_service.h"
+#include "taler_merchantdb_lib.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include <microhttpd.h>
@@ -96,6 +97,11 @@ enum OpCode
OC_WITHDRAW_SIGN,
/**
+ * Get backend to sign a contract.
+ */
+ OC_CONTRACT,
+
+ /**
* Pay with coins.
*/
OC_PAY
@@ -287,21 +293,49 @@ struct Command
} reserve_withdraw;
/**
- * Information for a #OC_PAY command.
- * FIXME: support tests where we pay with multiple coins at once.
+ * Information for an #OC_CONTRACT command.
*/
struct
{
/**
- * Amount to pay (total for the entire contract).
+ * Contract proposal (without merchant_pub, exchanges or H_wire).
+ */
+ const char *proposal;
+
+ /**
+ * Handle to the active /contract operation, or NULL.
+ */
+ struct TALER_MERCHANT_ContractOperation *co;
+
+ /**
+ * Full contract in JSON, set by the /contract operation.
*/
- const char *total_amount;
+ json_t *contract;
/**
- * Maximum fee covered by merchant.
+ * Signature, set by the /contract operation.
*/
- const char *max_fee;
+ struct TALER_MerchantSignatureP merchant_sig;
+
+ /**
+ * Hash of the full contract, set by the /contract operation.
+ */
+ struct GNUNET_HashCode h_contract;
+
+ } contract;
+
+ /**
+ * Information for a #OC_PAY command.
+ * FIXME: support tests where we pay with multiple coins at once.
+ */
+ struct
+ {
+
+ /**
+ * Reference to the contract.
+ */
+ const char *contract_ref;
/**
* Reference to a reserve_withdraw operation for a coin to
@@ -330,27 +364,6 @@ struct Command
const char *amount_without_fee;
/**
- * JSON string describing the merchant's "wire details".
- */
- const char *wire_details;
-
- /**
- * JSON string describing the contract between the two parties.
- */
- const char *contract;
-
- /**
- * Transaction ID to use.
- */
- uint64_t transaction_id;
-
- /**
- * Relative time (to add to 'now') to compute the refund deadline.
- * Zero for no refunds.
- */
- struct GNUNET_TIME_Relative refund_deadline;
-
- /**
* Deposit handle while operation is running.
*/
struct TALER_MERCHANT_Pay *ph;
@@ -399,6 +412,10 @@ struct InterpreterState
static void
fail (struct InterpreterState *is)
{
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Interpreter failed at step %s (#%u)\n",
+ is->commands[is->ip].label,
+ is->ip);
result = GNUNET_SYSERR;
GNUNET_SCHEDULER_shutdown ();
}
@@ -716,6 +733,54 @@ reserve_withdraw_cb (void *cls,
/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * /contract request to a merchant.
+ *
+ * @param cls closure
+ * @param http_status HTTP response code, 200 indicates success;
+ * 0 if the backend's reply is bogus (fails to follow the protocol)
+ * @param obj the full received JSON reply, or
+ * error details if the request failed
+ * @param contract completed contract, NULL on error
+ * @param sig merchant's signature over the contract, NULL on error
+ * @param h_contract hash of the contract, NULL on error
+ */
+static void
+contract_cb (void *cls,
+ unsigned int http_status,
+ const json_t *obj,
+ const json_t *contract,
+ const struct TALER_MerchantSignatureP *sig,
+ const struct GNUNET_HashCode *h_contract)
+{
+ struct InterpreterState *is = cls;
+ struct Command *cmd = &is->commands[is->ip];
+
+ cmd->details.contract.co = NULL;
+ switch (http_status)
+ {
+ case MHD_HTTP_OK:
+ cmd->details.contract.contract = json_incref ((json_t *) contract);
+ cmd->details.contract.merchant_sig = *sig;
+ cmd->details.contract.h_contract = *h_contract;
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "/contract responded with unexpected status code %u in step %u\n",
+ http_status,
+ is->ip);
+ json_dumpf (obj, stderr, 0);
+ GNUNET_break (0);
+ fail (is);
+ return;
+ }
+ is->ip++;
+ is->task = GNUNET_SCHEDULER_add_now (&interpreter_run,
+ is);
+}
+
+
+/**
* Function called with the result of a /pay operation.
*
* @param cls closure with the interpreter state
@@ -727,7 +792,6 @@ reserve_withdraw_cb (void *cls,
static void
pay_cb (void *cls,
unsigned int http_status,
- const char *redirect_uri,
const json_t *obj)
{
struct InterpreterState *is = cls;
@@ -977,83 +1041,93 @@ interpreter_run (void *cls)
return;
}
return;
- case OC_PAY:
+ case OC_CONTRACT:
{
- struct TALER_MERCHANT_PayCoin pc;
- struct TALER_Amount amount;
- struct TALER_Amount max_fee;
- json_t *wire;
- json_t *contract;
- struct GNUNET_HashCode h_wire;
- struct GNUNET_HashCode h_contract;
- struct GNUNET_TIME_Absolute refund_deadline;
- struct GNUNET_TIME_Absolute timestamp;
- struct TALER_MerchantSignatureP merchant_sig;
-
- /* get amount */
- if (GNUNET_OK !=
- TALER_string_to_amount (cmd->details.pay.total_amount,
- &amount))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse total amount `%s' at %u\n",
- cmd->details.pay.total_amount,
- is->ip);
- fail (is);
- return;
- }
-
- /* get max_fee */
- if (GNUNET_OK !=
- TALER_string_to_amount (cmd->details.pay.max_fee,
- &max_fee))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse max_fee `%s' at %u\n",
- cmd->details.pay.max_fee,
- is->ip);
- fail (is);
- return;
- }
+ json_t *proposal;
+ json_error_t error;
/* parse wire details */
- wire = json_loads (cmd->details.pay.wire_details,
- JSON_REJECT_DUPLICATES,
- NULL);
- if (NULL == wire)
+ proposal = json_loads (cmd->details.contract.proposal,
+ JSON_REJECT_DUPLICATES,
+ &error);
+ if (NULL == proposal)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse wire details `%s' at %u\n",
- cmd->details.pay.wire_details,
- is->ip);
+ "Failed to parse proposal `%s' at command #%u: %s at %u\n",
+ cmd->details.contract.proposal,
+ is->ip,
+ error.text,
+ (unsigned int) error.column);
fail (is);
return;
}
- TALER_JSON_hash (wire,
- &h_wire);
- json_decref (wire);
-
- /* parse contract */
- contract = json_loads (cmd->details.pay.contract,
- JSON_REJECT_DUPLICATES,
- NULL);
- if (NULL == contract)
+ cmd->details.contract.co
+ = TALER_MERCHANT_contract_sign (ctx,
+ MERCHANT_URI "contract",
+ proposal,
+ &contract_cb,
+ is);
+ if (NULL == cmd->details.contract.co)
{
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failed to parse contract details `%s' at instruction %u\n",
- cmd->details.pay.contract,
- is->ip);
+ GNUNET_break (0);
fail (is);
return;
}
- TALER_JSON_hash (contract,
+ return;
+ }
+ case OC_PAY:
+ {
+ struct TALER_MERCHANT_PayCoin pc;
+ uint64_t transaction_id;
+ struct GNUNET_TIME_Absolute refund_deadline;
+ struct GNUNET_TIME_Absolute timestamp;
+ struct GNUNET_HashCode h_wire;
+ struct TALER_MerchantPublicKeyP merchant_pub;
+ struct TALER_MerchantSignatureP merchant_sig;
+ struct GNUNET_HashCode h_contract;
+ struct TALER_Amount total_amount;
+ struct TALER_Amount max_fee;
+ const char *error_name;
+ unsigned int error_line;
+
+ /* get amount */
+ ref = find_command (is,
+ cmd->details.pay.contract_ref);
+ merchant_sig = ref->details.contract.merchant_sig;
+ GNUNET_assert (NULL != ref->details.contract.contract);
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint64 ("transaction_id", &transaction_id),
+ GNUNET_JSON_spec_absolute_time ("refund_deadline", &refund_deadline),
+ GNUNET_JSON_spec_absolute_time ("timestamp", &timestamp),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub", &merchant_pub),
+ GNUNET_JSON_spec_fixed_auto ("H_wire", &h_wire),
+ TALER_JSON_spec_amount ("amount", &total_amount),
+ TALER_JSON_spec_amount ("max_fee", &max_fee),
+ GNUNET_JSON_spec_end()
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (ref->details.contract.contract,
+ spec,
+ &error_name,
+ &error_line))
+ {
+ GNUNET_break_op (0);
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Parser failed on %s:%u\n",
+ error_name,
+ error_line);
+ fail (is);
+ return;
+ }
+ }
+
+ TALER_JSON_hash (ref->details.contract.contract,
&h_contract);
- json_decref (contract);
/* initialize 'pc' (FIXME: to do in a loop later...) */
{
- const struct Command *ref;
-
memset (&pc, 0, sizeof (pc));
ref = find_command (is,
cmd->details.pay.coin_ref);
@@ -1064,6 +1138,7 @@ interpreter_run (void *cls)
pc.coin_priv = ref->details.reserve_withdraw.coin_priv;
pc.denom_pub = ref->details.reserve_withdraw.pk->key;
pc.denom_sig = ref->details.reserve_withdraw.sig;
+ pc.denom_value = ref->details.reserve_withdraw.pk->value;
break;
default:
GNUNET_assert (0);
@@ -1094,21 +1169,12 @@ interpreter_run (void *cls)
}
}
- if (0 == cmd->details.pay.refund_deadline.rel_value_us)
- refund_deadline = GNUNET_TIME_UNIT_ZERO_ABS; /* no refunds */
- else
- refund_deadline = GNUNET_TIME_relative_to_absolute (cmd->details.pay.refund_deadline);
- GNUNET_TIME_round_abs (&refund_deadline);
- timestamp = GNUNET_TIME_absolute_get ();
- GNUNET_TIME_round_abs (&timestamp);
- memset (&merchant_sig, 0, sizeof (merchant_sig)); // FIXME: init properly!
- GNUNET_break (0);
cmd->details.pay.ph
= TALER_MERCHANT_pay_wallet (ctx,
MERCHANT_URI "pay",
&h_contract,
- cmd->details.pay.transaction_id,
- &amount,
+ transaction_id,
+ &total_amount,
&max_fee,
&merchant_pub,
&merchant_sig,
@@ -1225,6 +1291,22 @@ do_shutdown (void *cls)
cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key = NULL;
}
break;
+ case OC_CONTRACT:
+ if (NULL != cmd->details.contract.co)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Command %u (%s) did not complete\n",
+ i,
+ cmd->label);
+ TALER_MERCHANT_contract_sign_cancel (cmd->details.contract.co);
+ cmd->details.contract.co = NULL;
+ }
+ if (NULL != cmd->details.contract.contract)
+ {
+ json_decref (cmd->details.contract.contract);
+ cmd->details.contract.contract = NULL;
+ }
+ break;
case OC_PAY:
if (NULL != cmd->details.pay.ph)
{
@@ -1335,57 +1417,33 @@ run (void *cls)
.expected_response_code = MHD_HTTP_OK,
.details.reserve_status.reserve_reference = "create-reserve-1",
.details.reserve_status.expected_balance = "EUR:0" },
- /* Try to pay with the 5 EUR coin (in full) */
+ /* Create contract */
+ { .oc = OC_CONTRACT,
+ .label = "create-contract-1",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.contract.proposal = "{ \"max_fee\":{\"currency\":\"EUR\", \"value\":0, \"fraction\":500000}, \"transaction_id\":1, \"timestamp\":\"\\/Date(42)\\/\", \"refund_deadline\":\"\\/Date(0)\\/\", \"expiry\":\"\\/Date(999999999)\\/\", \"amount\":{\"currency\":\"EUR\", \"value\":5, \"fraction\":0}, \"products\":[ {\"description\":\"ice cream\", \"value\":\"{EUR:5}\"} ] }" },
{ .oc = OC_PAY,
.label = "deposit-simple",
.expected_response_code = MHD_HTTP_OK,
- .details.pay.total_amount = "EUR:5",
- .details.pay.max_fee = "EUR:0.5",
+ .details.pay.contract_ref = "create-contract-1",
.details.pay.coin_ref = "withdraw-coin-1",
.details.pay.amount_with_fee = "EUR:5",
- .details.pay.amount_without_fee = "EUR:4.99",
- .details.pay.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":62 }",
- .details.pay.contract = "{ \"items\":[ {\"name\":\"ice cream\", \"value\":1} ] }",
- .details.pay.transaction_id = 1 },
+ .details.pay.amount_without_fee = "EUR:4.99" },
+ /* Create another contract */
+ { .oc = OC_CONTRACT,
+ .label = "create-contract-2",
+ .expected_response_code = MHD_HTTP_OK,
+ .details.contract.proposal = "{ \"max_fee\":{\"currency\":\"EUR\", \"value\":0, \"fraction\":500000}, \"transaction_id\":2, \"timestamp\":\"\\/Date(42)\\/\", \"refund_deadline\":\"\\/Date(0)\\/\", \"expiry\":\"\\/Date(999999999)\\/\", \"amount\":{\"currency\":\"EUR\", \"value\":5, \"fraction\":0}, \"products\":[ {\"description\":\"ice cream\", \"value\":\"{EUR:5}\"} ] }" },
- /* Try to double-spend the 5 EUR coin with different wire details */
- { .oc = OC_PAY,
- .label = "deposit-double-1",
- .expected_response_code = MHD_HTTP_FORBIDDEN,
- .details.pay.total_amount = "EUR:5",
- .details.pay.max_fee = "EUR:0.5",
- .details.pay.coin_ref = "withdraw-coin-1",
- .details.pay.amount_with_fee = "EUR:5",
- .details.pay.amount_without_fee = "EUR:4.99",
- .details.pay.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":62 }",
- .details.pay.contract = "{ \"items\":[{ \"name\":\"ice cream\", \"value\":1} ] }",
- .details.pay.transaction_id = 1 },
/* Try to double-spend the 5 EUR coin at the same merchant (but different
transaction ID) */
{ .oc = OC_PAY,
.label = "deposit-double-2",
.expected_response_code = MHD_HTTP_FORBIDDEN,
- .details.pay.total_amount = "EUR:5",
- .details.pay.max_fee = "EUR:0.5",
+ .details.pay.contract_ref = "create-contract-2",
.details.pay.coin_ref = "withdraw-coin-1",
.details.pay.amount_with_fee = "EUR:5",
- .details.pay.amount_without_fee = "EUR:4.99",
- .details.pay.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":62 }",
- .details.pay.contract = "{ \"items\":[ {\"name\":\"ice cream\", \"value\":1} ] }",
- .details.pay.transaction_id = 2 },
- /* Try to double-spend the 5 EUR coin at the same merchant (but different
- contract) */
- { .oc = OC_PAY,
- .label = "deposit-double-3",
- .expected_response_code = MHD_HTTP_FORBIDDEN,
- .details.pay.total_amount = "EUR:5",
- .details.pay.max_fee = "EUR:0.5",
- .details.pay.coin_ref = "withdraw-coin-1",
- .details.pay.amount_with_fee = "EUR:5",
- .details.pay.amount_without_fee = "EUR:4.99",
- .details.pay.wire_details = "{ \"type\":\"test\", \"bank_uri\":\"http://localhost/\", \"account_number\":62 }",
- .details.pay.contract = "{ \"items\":[ {\"name\":\"ice cream\", \"value\":2} ] }",
- .details.pay.transaction_id = 1 },
+ .details.pay.amount_without_fee = "EUR:4.99" },
{ .oc = OC_END }
};
@@ -1428,12 +1486,35 @@ main (int argc,
struct GNUNET_OS_Process *proc;
struct GNUNET_OS_Process *exchanged;
struct GNUNET_OS_Process *merchantd;
+ struct TALER_MERCHANTDB_Plugin *db;
+ struct GNUNET_CONFIGURATION_Handle *cfg;
unsetenv ("XDG_DATA_HOME");
unsetenv ("XDG_CONFIG_HOME");
GNUNET_log_setup ("test-merchant-api",
"WARNING",
NULL);
+ cfg = GNUNET_CONFIGURATION_create ();
+ GNUNET_assert (GNUNET_OK ==
+ GNUNET_CONFIGURATION_load (cfg,
+ "test_merchant_api.conf"));
+ db = TALER_MERCHANTDB_plugin_load (cfg);
+ if (NULL == db)
+ {
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return 77;
+ }
+ (void) db->drop_tables (db->cls);
+ if (GNUNET_OK != db->initialize (db->cls))
+ {
+ TALER_MERCHANTDB_plugin_unload (db);
+ GNUNET_CONFIGURATION_destroy (cfg);
+ return 77;
+ }
+ TALER_MERCHANTDB_plugin_unload (db);
+ GNUNET_CONFIGURATION_destroy (cfg);
+
+
GNUNET_assert (GNUNET_OK ==
GNUNET_STRINGS_string_to_data (merchant_pub_str,
strlen (merchant_pub_str),