summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpriscilla <priscilla.huang@efrei.net>2023-01-11 10:41:24 -0500
committerpriscilla <priscilla.huang@efrei.net>2023-01-11 10:41:54 -0500
commitf05235f55bddb98989812c35d96504c5b7e2a918 (patch)
treeddbf03f55f7bef1040fa3ca6ccab59abf9637003
parent59971311ab1440dde3fda023ebf2c1281f8e9efe (diff)
downloadmerchant-f05235f55bddb98989812c35d96504c5b7e2a918.tar.gz
merchant-f05235f55bddb98989812c35d96504c5b7e2a918.tar.bz2
merchant-f05235f55bddb98989812c35d96504c5b7e2a918.zip
backenddb test ok
-rw-r--r--src/backenddb/merchant-0004.sql8
-rw-r--r--src/backenddb/plugin_merchantdb_postgres.c526
-rw-r--r--src/backenddb/test_merchantdb.c587
-rw-r--r--src/include/taler_merchantdb_plugin.h211
-rw-r--r--src/testing/#testing_api_cmd_post_products.c#332
5 files changed, 1307 insertions, 357 deletions
diff --git a/src/backenddb/merchant-0004.sql b/src/backenddb/merchant-0004.sql
index 93a03dae..55cfa2fc 100644
--- a/src/backenddb/merchant-0004.sql
+++ b/src/backenddb/merchant-0004.sql
@@ -46,7 +46,7 @@ COMMENT ON COLUMN merchant_template.image
IS 'NOT NULL, but can be 0 bytes; must contain an ImageDataUrl';
COMMENT ON COLUMN merchant_template.template_contract
IS 'The template contract will contains some additional information.';
-
+
CREATE TABLE IF NOT EXISTS merchant_webhook
(webhook_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
@@ -73,15 +73,15 @@ COMMENT ON COLUMN merchant_webhook.header_template
COMMENT ON COLUMN merchant_webhook.body_template
IS 'Template for the body of the webhook, to be modified based on trigger data';
-/*
+
CREATE TABLE IF NOT EXISTS merchant_pending_webhooks
(webhook_pending_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY
,merchant_serial BIGINT NOT NULL
REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE
,webhook_serial BIGINT NOT NULL
REFERENCES merchant_webhook (webhook_serial) ON DELETE CASCADE
- ,retries INT4 NOT NULL DEFAULT(0)
,next_attempt INT8 NOT NULL DEFAULT(0)
+ ,retries INT4 NOT NULL DEFAULT(0)
,url VARCHAR NOT NULL
,http_method VARCHAR NOT NULL
,header VARCHAR
@@ -104,7 +104,7 @@ COMMENT ON COLUMN merchant_pending_webhooks.header
IS 'Header of the webhook';
COMMENT ON COLUMN merchant_pending_webhooks.body
IS 'Body of the webhook';
-*/
+
COMMIT;
diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c
index b52f058b..e9ddabe7 100644
--- a/src/backenddb/plugin_merchantdb_postgres.c
+++ b/src/backenddb/plugin_merchantdb_postgres.c
@@ -7113,7 +7113,7 @@ postgres_delete_webhook (void *cls,
* Insert details about a particular webhook.
*
* @param cls closure
- * @param instance_id instance to insert template for
+ * @param instance_id instance to insert webhook for
* @param webhook_id webhook identifier of webhook to insert
* @param wb the webhook details to insert
* @return database result code
@@ -7149,7 +7149,7 @@ postgres_insert_webhook (void *cls,
*
* @param cls closure
* @param instance_id instance to update template for
- * @param webhook_id template to update
+ * @param webhook_id webhook to update
* @param wb update to the webhook details on success, can be NULL
* (in that case we only want to check if the webhook exists)
* @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the webhook
@@ -7349,6 +7349,432 @@ postgres_lookup_webhook (void *cls,
/**
+ * Context used for postgres_lookup_webhook().
+ */
+struct LookupWebhookDetailContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_WebhookDetailCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about webhook.
+ *
+ * @param[in,out] cls of type `struct LookupPendingWebhookContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_webhook_by_event_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupWebhookDetailContext *wlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t webhook_serial;
+ char *event_type;
+ char *url;
+ char *http_method;
+ char *header_template;
+ char *body_template;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("webhook_serial",
+ &webhook_serial),
+ GNUNET_PQ_result_spec_string ("event_type",
+ &event_type),
+ GNUNET_PQ_result_spec_string ("url",
+ &url),
+ GNUNET_PQ_result_spec_string ("http_method",
+ &http_method),
+ GNUNET_PQ_result_spec_string ("header_template",
+ &header_template),
+ GNUNET_PQ_result_spec_string ("body_template",
+ &body_template),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ wlc->extract_failed = true;
+ return;
+ }
+ wlc->cb (wlc->cb_cls,
+ webhook_serial,
+ event_type,
+ url,
+ http_method,
+ header_template,
+ body_template);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+/**
+ * Lookup webhook by event
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup webhook for
+ * @param event_type event that we need to put in the pending webhook
+ * @param[out] cb set to the webhook details on success
+ * @param cb_cls callback closure
+ * @return database result code
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_webhook_by_event(void *cls,
+ const char *instance_id,
+ const char *event_type,
+ TALER_MERCHANTDB_WebhookDetailCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupWebhookDetailContext wlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false,
+ };
+
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_string (event_type),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_webhook_by_event",
+ params,
+ &lookup_webhook_by_event_cb,
+ &wlc);
+
+ if (wlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
+
+/**
+ * Insert webhook in the pending webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert webhook for
+ * @param webhook_serial webhook to insert in the pending webhook
+ * @param url to make the request to
+ * @param http_method for the webhook
+ * @param header of the webhook
+ * @param body of the webhook
+ * @return database result code
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_insert_pending_webhook(void *cls,
+ const char *instance_id,
+ uint64_t webhook_serial,
+ const char *url,
+ const char *http_method,
+ const char *header,
+ const char *body)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&webhook_serial),
+ GNUNET_PQ_query_param_string (url),
+ GNUNET_PQ_query_param_string (http_method),
+ NULL == header
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (header),
+ NULL == body
+ ? GNUNET_PQ_query_param_null ()
+ : GNUNET_PQ_query_param_string (body),
+ GNUNET_PQ_query_param_end
+ };
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "insert_pending_webhook",
+ params);
+}
+
+/**
+ * Context used for postgres_lookup_future_webhook().
+ */
+struct LookupPendingWebhookContext
+{
+ /**
+ * Function to call with the results.
+ */
+ TALER_MERCHANTDB_PendingWebhooksCallback cb;
+
+ /**
+ * Closure for @a cb.
+ */
+ void *cb_cls;
+
+ /**
+ * Did database result extraction fail?
+ */
+ bool extract_failed;
+};
+
+/**
+ * Function to be called with the results of a SELECT statement
+ * that has returned @a num_results results about webhook.
+ *
+ * @param[in,out] cls of type `struct LookupPendingWebhookContext *`
+ * @param result the postgres result
+ * @param num_results the number of results in @a result
+ */
+static void
+lookup_pending_webhooks_cb (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct LookupPendingWebhookContext *pwlc = cls;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t webhook_serial;
+ struct GNUNET_TIME_Absolute next_attempt;
+ uint32_t retries;
+ char *url;
+ char *http_method;
+ char *header;
+ char *body;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("webhook_serial",
+ &webhook_serial),
+ GNUNET_PQ_result_spec_absolute_time ("next_attempt",
+ &next_attempt),
+ GNUNET_PQ_result_spec_uint32 ("retries",
+ &retries),
+ GNUNET_PQ_result_spec_string ("url",
+ &url),
+ GNUNET_PQ_result_spec_string ("http_method",
+ &http_method),
+ GNUNET_PQ_result_spec_string ("header",
+ &header),
+ GNUNET_PQ_result_spec_string ("body",
+ &body),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ pwlc->extract_failed = true;
+ return;
+ }
+ pwlc->cb (pwlc->cb_cls,
+ webhook_serial,
+ next_attempt,
+ retries,
+ url,
+ http_method,
+ header,
+ body);
+ GNUNET_PQ_cleanup_result (rs);
+ }
+}
+
+/**
+ * Lookup the webhook that need to be send in priority.
+ * send.
+ *
+ * @param cls closure
+ * @param cb pending webhook callback
+ * @param cb_cls callback closure
+ */
+// WHERE next_attempt <= now ORDER BY next_attempt ASC
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_pending_webhook(void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupPendingWebhookContext pwlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false,
+ };
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params_null[] = {
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_pending_webhook",
+ params_null,
+ &lookup_pending_webhooks_cb,
+ &pwlc);
+
+ if (pwlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
+
+
+/**
+ * Lookup future webhook in the pending webhook that need to be send.
+ * With that we can know how long the system can 'sleep'.
+ *
+ * @param cls closure
+ * @param cb pending webhook callback
+ * @param cb_cls callback closure
+ */
+// ORDER BY next_attempt ASC LIMIT 1
+static enum GNUNET_DB_QueryStatus
+postgres_lookup_future_webhook(void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct LookupPendingWebhookContext pwlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false,
+ };
+ struct GNUNET_PQ_QueryParam params_null[] = {
+ GNUNET_PQ_query_param_end
+ };
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_future_webhook",
+ params_null,
+ &lookup_pending_webhooks_cb,
+ &pwlc);
+
+ if (pwlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
+
+ /**
+ * Lookup all the webhooks in the pending webhook.
+ * Use by the administrator
+ *
+ * @param cls closure
+ * @param instance_id to lookup webhooks for this instance particularly
+ * @param min_row to see the list of the pending webhook that it is started with this minimum row.
+ * @param max_results to see the list of the pending webhook that it is end with this max results.
+ * @param cb pending webhook callback
+ * @param cb_cls callback closure
+ */
+ // WHERE webhook_pending_serial > min_row ORDER BY webhook_pending_serial ASC LIMIT max_results
+ static enum GNUNET_DB_QueryStatus
+ postgres_lookup_all_webhooks(void *cls,
+ const char *instance_id,
+ uint64_t min_row,
+ uint32_t max_results,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls)
+ {
+ struct PostgresClosure *pg = cls;
+ struct LookupPendingWebhookContext pwlc = {
+ .cb = cb,
+ .cb_cls = cb_cls,
+ .extract_failed = false,
+ };
+ uint64_t max_results64 = max_results;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_string (instance_id),
+ GNUNET_PQ_query_param_uint64 (&min_row),
+ GNUNET_PQ_query_param_uint64 (&max_results64),
+ GNUNET_PQ_query_param_end
+ };
+
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "lookup_all_webhooks",
+ params,
+ &lookup_pending_webhooks_cb,
+ &pwlc);
+
+ if (pwlc.extract_failed)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return qs;
+}
+
+/**
+ * Update the pending webhook. It is use if the webhook can't be send.
+ *
+ * @param cls closure
+ * @param webhook_serial webhook that need to be update
+ * @param next_attempt when we should make the next request to the webhook
+ * @return database result code
+ */
+ static enum GNUNET_DB_QueryStatus
+ postgres_update_pending_webhook(void *cls,
+ uint64_t webhook_serial,
+ struct GNUNET_TIME_Absolute next_attempt)
+ // maybe add: http status of failure?
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&webhook_serial),
+ GNUNET_PQ_query_param_absolute_time (&next_attempt),
+ GNUNET_PQ_query_param_end
+
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "update_pending_webhook",
+ params);
+}
+
+/**
+ * Delete a webhook in the pending webhook if it is successfull
+ *
+ * @param cls closure
+ * @param webhook_serial webhook that need to be delete in the pending webhook
+ * @return database result code
+ */
+ static enum GNUNET_DB_QueryStatus
+ postgres_delete_pending_webhook(void *cls,
+ uint64_t webhook_serial)
+ {
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&webhook_serial),
+ GNUNET_PQ_query_param_end
+ };
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "delete_pending_webhook",
+ params);
+}
+
+
+/**
* Establish connection to the database.
*
* @param cls plugin context
@@ -9906,6 +10332,95 @@ postgres_connect (void *cls)
" FROM merchant_instances"
" WHERE merchant_id=$1)"
" AND webhook_id=$2"),
+ /* for postgres_lookup_webhook_by_event() */
+ GNUNET_PQ_make_prepare ("lookup_webhook_by_event",
+ "SELECT"
+ " webhook_serial"
+ ",event_type"
+ ",url"
+ ",http_method"
+ ",header_template"
+ ",body_template"
+ " FROM merchant_webhook"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND event_type=$2"),
+ /* for postgres_delete_pending_webhook() */
+ GNUNET_PQ_make_prepare ("delete_pending_webhook",
+ "DELETE"
+ " FROM merchant_pending_webhooks"
+ " WHERE merchant_pending_webhooks.webhook_serial="
+ " (SELECT webhook_serial "
+ " FROM merchant_webhook"
+ " WHERE webhook_serial=$1)"),
+ /* for postgres_insert_pending_webhook() */
+ GNUNET_PQ_make_prepare ("insert_pending_webhook",
+ "INSERT INTO merchant_pending_webhooks"
+ "(merchant_serial"
+ ",webhook_serial"
+ ",url"
+ ",http_method"
+ ",header"
+ ",body"
+ ")"
+ " SELECT mi.merchant_serial,"
+ " $2, $3, $4, $5, $6"
+ " FROM merchant_instances mi"
+ " WHERE mi.merchant_id=$1"),
+ /* for postgres_update_pending_webhook() */
+ GNUNET_PQ_make_prepare ("update_pending_webhook",
+ "UPDATE merchant_pending_webhooks SET"
+ " retries=retries+1"
+ ",next_attempt=$2"
+ " WHERE webhook_serial="
+ " (SELECT webhook_serial"
+ " FROM merchant_webhook"
+ " WHERE webhook_serial=$1)"),
+ /* for postgres_lookup_pending_webhook() */
+ GNUNET_PQ_make_prepare ("lookup_pending_webhook",
+ "SELECT"
+ " webhook_serial"
+ ",next_attempt"
+ ",retries"
+ ",url"
+ ",http_method"
+ ",header"
+ ",body"
+ " FROM merchant_pending_webhooks"
+ " WHERE next_attempt <= $1"
+ " ORDER BY next_attempt ASC"
+ ),
+ /* for postgres_lookup_future_webhook() */
+ GNUNET_PQ_make_prepare ("lookup_future_webhook",
+ "SELECT"
+ " webhook_serial"
+ ",next_attempt"
+ ",retries"
+ ",url"
+ ",http_method"
+ ",header"
+ ",body"
+ " FROM merchant_pending_webhooks"
+ " ORDER BY next_attempt ASC LIMIT 1"
+ ),
+ /* for postgres_lookup_all_webhooks() */
+ GNUNET_PQ_make_prepare ("lookup_all_webhooks",
+ " SELECT"
+ " webhook_serial"
+ ",next_attempt"
+ ",retries"
+ ",url"
+ ",http_method"
+ ",header"
+ ",body"
+ " FROM merchant_pending_webhooks"
+ " JOIN merchant_instances"
+ " USING (merchant_serial)"
+ " WHERE merchant_instances.merchant_id=$1"
+ " AND webhook_serial > $2"
+ " ORDER BY webhook_serial"
+ " ASC LIMIT $3"),
GNUNET_PQ_PREPARED_STATEMENT_END
};
struct GNUNET_PQ_ExecuteStatement es[] = {
@@ -10065,6 +10580,13 @@ libtaler_plugin_merchantdb_postgres_init (void *cls)
plugin->delete_webhook = &postgres_delete_webhook;
plugin->insert_webhook = &postgres_insert_webhook;
plugin->update_webhook = &postgres_update_webhook;
+ plugin->lookup_webhook_by_event = &postgres_lookup_webhook_by_event;
+ plugin->lookup_all_webhooks = &postgres_lookup_all_webhooks;
+ plugin->lookup_future_webhook = &postgres_lookup_future_webhook;
+ plugin->lookup_pending_webhook = &postgres_lookup_pending_webhook;
+ plugin->delete_pending_webhook = &postgres_delete_pending_webhook;
+ plugin->insert_pending_webhook = &postgres_insert_pending_webhook;
+ plugin->update_pending_webhook = &postgres_update_pending_webhook;
return plugin;
}
diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c
index 589d14a4..e24e4c04 100644
--- a/src/backenddb/test_merchantdb.c
+++ b/src/backenddb/test_merchantdb.c
@@ -818,7 +818,8 @@ check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a,
(GNUNET_TIME_timestamp_cmp (a->next_restock,
!=,
b->next_restock)))
- return 1;
+
+ return 1;
return 0;
}
@@ -6865,8 +6866,7 @@ make_template (const char *id,
{
template->id = id;
template->template.template_description = "This is a test template";
- template->template.image = GNUNET_strdup ("");
- GNUNET_assert (NULL != template->template.image);
+ template->template.image = NULL;
template->template.template_contract = json_array ();
GNUNET_assert (NULL != template->template.template_contract);
}
@@ -6898,8 +6898,9 @@ check_templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *a,
{
if ((0 != strcmp (a->template_description,
b->template_description)) ||
- (0 != strcmp (a->image,
- b->image)) ||
+ ( (NULL == a->image) && (NULL != b->image)) ||
+ ( (NULL != a->image) && (NULL == b->image)) ||
+ ( (NULL != a->image) && (0 != strcmp (a->image, b->image))) ||
(1 != json_equal (a->template_contract,
b->template_contract)))
return 1;
@@ -7034,8 +7035,10 @@ lookup_templates_cb (void *cls,
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))
+ 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;
}
}
@@ -7269,6 +7272,7 @@ test_templates (void)
*/
struct WebhookData
{
+
/**
* The identifier of the webhook.
*/
@@ -7293,7 +7297,7 @@ make_webhook (const char *id,
{
webhook->id = id;
webhook->webhook.event_type = "Paid";
- webhook->webhook.url = "https://example.com";
+ webhook->webhook.url = "https://exampletest.com";
webhook->webhook.http_method = "POST";
webhook->webhook.header_template = "Authorization:XYJAORKJEO";
webhook->webhook.body_template = "$Amount";
@@ -7453,8 +7457,10 @@ lookup_webhooks_cb (void *cls,
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))
+ 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;
}
}
@@ -7508,6 +7514,92 @@ test_lookup_webhooks (const struct InstanceData *instance,
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.
@@ -7544,7 +7636,7 @@ struct TestWebhooks_Closure
/**
* The array of webhooks.
*/
- struct WebhookData webhooks[2];
+ struct WebhookData webhooks[3];
};
@@ -7567,6 +7659,18 @@ pre_test_webhooks (struct TestWebhooks_Closure *cls)
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";
}
@@ -7625,7 +7729,7 @@ run_test_webhooks (struct TestWebhooks_Closure *cls)
cls->webhooks[0].webhook.event_type =
"Test paid";
cls->webhooks[0].webhook.url =
- "https://example.com";
+ "example.com";
cls->webhooks[0].webhook.http_method =
"POST";
cls->webhooks[0].webhook.header_template =
@@ -7648,6 +7752,12 @@ run_test_webhooks (struct TestWebhooks_Closure *cls)
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,
@@ -7657,9 +7767,13 @@ run_test_webhooks (struct TestWebhooks_Closure *cls)
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,
- 1,
+ 2,
cls->webhooks));
+ TEST_RET_ON_FAIL (test_lookup_webhook_by_event (&cls->instance,
+ 2,
+ cls->webhooks));
return 0;
}
@@ -7680,6 +7794,452 @@ test_webhooks (void)
}
+
+/* *********** 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.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)
+{
+ 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;
+}
+
+
+/**
+ * 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_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;
+ if (NULL == cmp)
+ return;
+ cmp->results_length += 1;
+ for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
+ {
+ if ((cmp->webhooks_to_cmp[i].webhook_serial != webhook_serial) &&
+ (GNUNET_TIME_absolute_cmp (cmp->webhooks_to_cmp[i].pwebhook.next_attempt,
+ !=,
+ next_attempt)))
+ cmp->results_matching[i] += 1;
+ }
+}
+
+
+/**
+ * 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_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 (unsigned int) * pwebhooks_length);
+ if (0 > plugin->lookup_pending_webhook (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; pwebhooks_length > i; ++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 (unsigned int) * pwebhooks_length);
+ 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)
+{
+ uint32_t max_results=pwebhooks_length;
+ uint64_t min_row=0;
+ 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 (unsigned int) * pwebhooks_length);
+ 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 (const struct PendingWebhookData *webhook,
+ enum GNUNET_DB_QueryStatus expected_result)
+{
+ TEST_COND_RET_ON_FAIL (expected_result ==
+ plugin->delete_pending_webhook (plugin->cls,
+ webhook->webhook_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 pending webhook update */
+ cls->pwebhooks[0].pwebhook.next_attempt = GNUNET_TIME_absolute_get ();
+
+ TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
+ &cls->pwebhooks[0],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
+ &cls->pwebhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+
+ /* Test collective pending webhook lookup */
+ TEST_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
+ &cls->pwebhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ TEST_RET_ON_FAIL (test_lookup_future_webhook (&cls->instance,
+ 1,
+ cls->pwebhooks));
+ TEST_RET_ON_FAIL (test_lookup_pending_webhook (&cls->instance,
+ 2,
+ cls->pwebhooks));
+ TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
+ 2,
+ cls->pwebhooks));
+
+ /* Test webhook deletion */
+ TEST_RET_ON_FAIL (test_delete_pending_webhook (&cls->pwebhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
+ /* Test double deletion fails */
+ TEST_RET_ON_FAIL (test_delete_pending_webhook (&cls->pwebhooks[1],
+ GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
+ TEST_RET_ON_FAIL (test_delete_pending_webhook (&cls->pwebhooks[0],
+ 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.
*
@@ -7699,6 +8259,7 @@ run_tests (void)
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;
}
diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h
index 0e70ec02..c2823d92 100644
--- a/src/include/taler_merchantdb_plugin.h
+++ b/src/include/taler_merchantdb_plugin.h
@@ -1,6 +1,6 @@
/*
This file is part of TALER
- Copyright (C) 2014-2021 Taler Systems SA
+ Copyright (C) 2014-2023 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
@@ -307,6 +307,7 @@ struct TALER_MERCHANTDB_ProductDetails
*
* @param cls a `json_t *` JSON array to build
* @param template_id ID of the template
+ * @param template_description description of the template
*/
typedef void
(*TALER_MERCHANTDB_TemplatesCallback)(void *cls,
@@ -325,7 +326,7 @@ struct TALER_MERCHANTDB_TemplateDetails
char *template_description;
/**
- * Base64-encoded product image, or NULL.
+ * Base64-encoded image, or NULL.
*/
char *image;
@@ -341,10 +342,11 @@ struct TALER_MERCHANTDB_TemplateDetails
*
* @param cls a `json_t *` JSON array to build
* @param webhook_id ID of the webhook
+ * @param event_type event of the webhook
*/
typedef void
(*TALER_MERCHANTDB_WebhooksCallback)(void *cls,
- const char *webhook_id,
+ const char *webhook_id,
const char *event_type);
@@ -353,6 +355,7 @@ typedef void
*/
struct TALER_MERCHANTDB_WebhookDetails
{
+
/**
* event of the webhook.
*/
@@ -368,13 +371,11 @@ struct TALER_MERCHANTDB_WebhookDetails
*/
char *http_method;
-
/**
* Header template of the webhook.
*/
char *header_template;
-
/**
* Body template of the webhook.
*/
@@ -382,6 +383,92 @@ struct TALER_MERCHANTDB_WebhookDetails
};
+/**
+ * Typically called by `lookup_webhook_by_event`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param webhook_serial reference to the configured webhook template.
+ * @param next_attempt is the time we should make the next request to the webhook.
+ * @param url to make request to
+ * @param http_method use for the webhook
+ * @param header of the webhook
+ * @param body of the webhook
+ */
+typedef void
+(*TALER_MERCHANTDB_WebhookDetailCallback)(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);
+
+
+
+/**
+ * Typically called by `lookup_pending_webhooks`.
+ *
+ * @param cls a `json_t *` JSON array to build
+ * @param webhook_serial reference to the configured webhook template.
+ * @param next_attempt is the time we should make the next request to the webhook.
+ * @param retries how often have we tried this request to the webhook.
+ * @param url to make request to
+ * @param http_method use for the webhook
+ * @param header of the webhook
+ * @param body of the webhook
+ */
+typedef void
+(*TALER_MERCHANTDB_PendingWebhooksCallback)(void *cls,
+ uint64_t webhook_serial,
+ struct GNUNET_TIME_Absolute next_attempt,
+ uint32_t retries,
+ const char *url,
+ const char *http_method,
+ const char *header,
+ const char *body);
+
+
+/**
+ * Details about the pending webhook.
+ */
+struct TALER_MERCHANTDB_PendingWebhookDetails
+{
+ /**
+ * How often have we tried this request so far.
+ */
+ uint32_t retries;
+
+ /**
+ * Identifies when we should make the next request to the webhook. 0 for unknown,
+ * #GNUNET_TIME_UNIT_FOREVER_ABS for never.
+ */
+ struct GNUNET_TIME_Absolute next_attempt;
+
+ /**
+ * URL of the webhook. The customer will be redirected on this url.
+ */
+ char *url;
+
+ /**
+ * Http method used for the webhook.
+ */
+ char *http_method;
+
+
+ /**
+ * Header of the webhook.
+ */
+ char *header;
+
+
+ /**
+ * Body of the webhook.
+ */
+ char *body;
+
+};
+
+
/**
* Filter preferences.
@@ -2600,7 +2687,7 @@ struct TALER_MERCHANTDB_Plugin
* Insert details about a particular webhook.
*
* @param cls closure
- * @param instance_id instance to insert template for
+ * @param instance_id instance to insert webhook for
* @param webhook_id webhook identifier of webhook to insert
* @param wb the webhook details to insert
* @return database result code
@@ -2630,6 +2717,118 @@ struct TALER_MERCHANTDB_Plugin
const char *webhook_id,
const struct TALER_MERCHANTDB_WebhookDetails *wb);
+ /**
+ * Lookup webhook by event
+ *
+ * @param cls closure
+ * @param instance_id instance to lookup webhook for
+ * @param event_type event that we need to put in the pending webhook
+ * @param[out] cb set to the webhook details on success
+ * @param cb_cls callback closure
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*lookup_webhook_by_event)(void *cls,
+ const char *instance_id,
+ const char *event_type,
+ TALER_MERCHANTDB_WebhookDetailCallback cb,
+ void *cb_cls);
+
+/**
+ * Insert webhook in the pending webhook.
+ *
+ * @param cls closure
+ * @param instance_id instance to insert webhook for
+ * @param webhook_serial webhook to insert in the pending webhook
+ * @param url to make the request to
+ * @param http_method for the webhook
+ * @param header of the webhook
+ * @param body of the webhook
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*insert_pending_webhook)(void *cls,
+ const char *instance_id,
+ uint64_t webhook_serial,
+ const char *url,
+ const char *http_method,
+ const char *header,
+ const char *body);
+
+ /**
+ * Lookup the webhook that need to be send in priority. These webhooks are not successfully
+ * send.
+ *
+ * @param cls closure
+ * @param cb pending webhook callback
+ * @param cb_cls callback closure
+ */
+ // WHERE next_attempt <= now ORDER BY next_attempt ASC
+ enum GNUNET_DB_QueryStatus
+ (*lookup_pending_webhook)(void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
+
+ /**
+ * Lookup future webhook in the pending webhook that need to be send.
+ * With that we can know how long the system can 'sleep'.
+ *
+ * @param cls closure
+ * @param cb pending webhook callback
+ * @param cb_cls callback closure
+ */
+ // ORDER BY next_attempt ASC LIMIT 1
+ enum GNUNET_DB_QueryStatus
+ (*lookup_future_webhook)(void *cls,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
+
+ /**
+ * Lookup all the webhooks in the pending webhook.
+ * Use by the administrator
+ *
+ * @param cls closure
+ * @param instance_id to lookup webhooks for this instance particularly
+ * @param min_row to see the list of the pending webhook that it is started with this minimum row.
+ * @param max_results to see the list of the pending webhook that it is end with this max results.
+ * @param cb pending webhook callback
+ * @param cb_cls callback closure
+ */
+ // WHERE webhook_pending_serial > min_row ORDER BY webhook_pending_serial ASC LIMIT max_results
+ enum GNUNET_DB_QueryStatus
+ (*lookup_all_webhooks)(void *cls,
+ const char *instance_id,
+ uint64_t min_row,
+ uint32_t max_results,
+ TALER_MERCHANTDB_PendingWebhooksCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Update the pending webhook. It is use if the webhook can't be send.
+ *
+ * @param cls closure
+ * @param webhook_serial webhook that need to be update
+ * @param next_attempt when we should make the next request to the webhook
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*update_pending_webhook)(void *cls,
+ uint64_t webhook_serial,
+ struct GNUNET_TIME_Absolute next_attempt);
+ // maybe add: http status of failure?
+
+/**
+ * Delete a webhook in the pending webhook if it is successfull
+ *
+ * @param cls closure
+ * @param webhook_serial webhook that need to be delete in the pending webhook
+ * @return database result code
+ */
+ enum GNUNET_DB_QueryStatus
+ (*delete_pending_webhook)(void *cls,
+ uint64_t webhook_serial);
+
};
#endif
diff --git a/src/testing/#testing_api_cmd_post_products.c# b/src/testing/#testing_api_cmd_post_products.c#
deleted file mode 100644
index be3c3071..00000000
--- a/src/testing/#testing_api_cmd_post_products.c#
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 Taler Systems SA
-
- TALER is free software; you can redistribute it and/or modify
- it under the terms of the GNU 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
- <http://www.gnu.org/licenses/>
-*/
-/**
- * @file testing_api_cmd_post_products.c
- * @brief command to test POST /products
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <taler/taler_exchange_service.h>
-#include <taler/taler_testing_lib.h>
-#include "taler_merchant_service.h"
-#include "taler_merchant_testing_lib.h"
-
-
-/**
- * State of a "POST /products" CMD.
- */
-struct PostProductsState
-{
-
- /**
- * Handle for a "GET product" request.
- */
- struct TALER_MERCHANT_ProductsPostHandle *iph;
-
- /**
- * The interpreter state.
- */
- struct TALER_TESTING_Interpreter *is;
-
- /**
- * Base URL of the merchant serving the request.
- */
- const char *merchant_url;
-
- /**
- * ID of the product to run POST for.
- */
- const char *product_id;
-
- /**
- * description of the product
- */
- const char *description;
-
- /**
- * Map from IETF BCP 47 language tags to localized descriptions
- */
- json_t *description_i18n;
-
- /**
- * unit in which the product is measured (liters, kilograms, packages, etc.)
- */
- const char *unit;
-
- /**
- * the price for one @a unit of the product
- */
- struct TALER_Amount price;
-
- /**
- * base64-encoded product image
- */
- char *image;
-
- /**
- * list of taxes paid by the merchant
- */
- json_t *taxes;
-
- /**
- * in @e units, -1 to indicate "infinite" (i.e. electronic books)
- */
- int64_t total_stock;
-
- /**
- * where the product is in stock
- */
- json_t *address;
-
- /**
- * when the next restocking is expected to happen, 0 for unknown,
- */
- struct GNUNET_TIME_Timestamp next_restock;
-
- /**
- * Expected HTTP response code.
- */
- unsigned int http_status;
-
-};
-
-
-/**
- * Callback for a POST /products operation.
- *
- * @param cls closure for this function
- * @param hr response being processed
- */
-static void
-post_products_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr)
-{
- struct PostProductsState *pis = cls;
-
- pis->iph = NULL;
- if (pis->http_status != hr->http_status)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Unexpected response code %u (%d) to command %s\n",
- hr->http_status,
- (int) hr->ec,
- TALER_TESTING_interpreter_get_current_label (pis->is));
- TALER_TESTING_interpreter_fail (pis->is);
- return;
- }
- switch (hr->http_status)
- {
- case MHD_HTTP_NO_CONTENT:
- break;
- case MHD_HTTP_UNAUTHORIZED:
- break;
- case MHD_HTTP_FORBIDDEN:
- break;
- case MHD_HTTP_NOT_FOUND:
- break;
- case MHD_HTTP_CONFLICT:
- break;
- default:
- GNUNET_break (0);
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Unhandled HTTP status %u for POST /products.\n",
- hr->http_status);
- }
- TALER_TESTING_interpreter_next (pis->is);
-}
-
-
-/**
- * Run the "POST /products" CMD.
- *
- *
- * @param cls closure.
- * @param cmd command being run now.
- * @param is interpreter state.
- */
-static void
-post_products_run (void *cls,
- const struct TALER_TESTING_Command *cmd,
- struct TALER_TESTING_Interpreter *is)
-{
- struct PostProductsState *pis = cls;
-
- pis->is = is;
- pis->iph = TALER_MERCHANT_products_post (is->ctx,
- pis->merchant_url,
- pis->product_id,
- pis->description,
- pis->description_i18n,
- pis->unit,
- &pis->price,
- pis->image,
- pis->taxes,
- pis->total_stock,
- pis->address,
- pis->next_restock,
- &post_products_cb,
- pis);
- GNUNET_assert (NULL != pis->iph);
-}
-
-
-/**
- * Offers information from the POST /products CMD state to other
- * commands.
- *
- * @param cls closure
- * @param[out] ret result (could be anything)
- * @param trait name of the trait
- * @param index index number of the object to extract.
- * @return #GNUNET_OK on success
- */
-static int
-post_products_traits (void *cls,
- const void **ret,
- const char *trait,
- unsigned int index)
-{
- struct PostProductsState *pps = cls;
- struct TALER_TESTING_Trait traits[] = {
- TALER_TESTING_make_trait_product_description (&pps->description),
- TALER_TESTING_make_trait_i18n_description (pps->description_i18n),
- TALER_TESTING_make_trait_product_unit (&pps->unit),
- TALER_TESTING_make_trait_amount (&pps->price),
- TALER_TESTING_make_trait_product_image (
- (const char **) &pps->image),
- TALER_TESTING_make_trait_taxes (pps->taxes),
- TALER_TESTING_make_trait_product_stock (&pps->total_stock),
- TALER_TESTING_make_trait_address (pps->address),
- TALER_TESTING_make_trait_timestamp (0,
- &pps->next_restock),
- TALER_TESTING_make_trait_product_id (&pps->product_id),
- TALER_TESTING_trait_end (),
- };
-
- return TALER_TESTING_get_trait (traits,
- ret,
- trait,
- index);
-}
-
-
-/**
- * Free the state of a "POST product" CMD, and possibly
- * cancel a pending operation thereof.
- *
- * @param cls closure.
- * @param cmd command being run.
- */
-static void
-post_products_cleanup (void *cls,
- const struct TALER_TESTING_Command *cmd)
-{
- struct PostProductsState *pis = cls;
-
- if (NULL != pis->iph)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "POST /products operation did not complete\n");
- TALER_MERCHANT_products_post_cancel (pis->iph);
- }
- json_decref (pis->description_i18n);
- GNUNET_free (pis->image);
- json_decref (pis->taxes);
- json_decref (pis->address);
- GNUNET_free (pis);
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_products2 (
- const char *label,
- const char *merchant_url,
- const char *product_id,
- const char *description,
- json_t *description_i18n,
- const char *unit,
- const char *price,
- const char *image,
- json_t *taxes,
- int64_t total_stock,
- json_t *address,
- struct GNUNET_TIME_Timestamp next_restock,
- unsigned int http_status)
-{
- struct PostProductsState *pis;
-
- GNUNET_assert ((NULL == taxes) ||
- json_is_array (taxes));
- GNUNET_assert ((NULL == description_i18n) ||
- json_is_object (description_i18n));
- pis = GNUNET_new (struct PostProductsState);
- pis->merchant_url = merchant_url;
- pis->product_id = product_id;
- pis->http_status = http_status;
- pis->description = description;
- pis->description_i18n = description_i18n; /* ownership taken */
- pis->unit = unit;
- GNUNET_assert (GNUNET_OK ==
- TALER_string_to_amount (price,
- &pis->price));
- pis->image = GNUNET_strdup (image);
- pis->taxes = taxes; /* ownership taken */
- pis->total_stock = total_stock;
- pis->address = address; /* ownership taken */
- pis->next_restock = next_restock;
- {
- struct TALER_TESTING_Command cmd = {
- .cls = pis,
- .label = label,
- .run = &post_products_run,
- .cleanup = &post_products_cleanup,
- .traits = &post_products_traits
- };
-
- return cmd;
- }
-}
-
-
-struct TALER_TESTING_Command
-TALER_TESTING_cmd_merchant_post_products (const char *label,
- const char *merchant_url,
- const char *product_id,
- const char *description,
- const char *price,
- unsigned int http_status)
-{
- return TALER_TESTING_cmd_merchant_post_products2 (
- label,
- merchant_url,
- product_id,
- description,
- json_pack ("{s:s}", "en", description),
- "test-unit",
- price,
- "",
- json_array (),
- 4,
- json_pack ("{s:s}", "street", "my street"),
- GNUNET_TIME_UNIT_ZERO_TS,
- http_status);
-}
-
-
-/* end of testing_api_cmd_post_products.c */