diff options
author | priscilla <priscilla.huang@efrei.net> | 2022-11-25 08:42:25 -0500 |
---|---|---|
committer | priscilla <priscilla.huang@efrei.net> | 2022-11-25 08:42:25 -0500 |
commit | 7bd9e0d6c7b1dec25b986a3db77027334e6a6085 (patch) | |
tree | b60249b3a5ae22b7227232a5872e4782e70c141b | |
parent | f6492a1e124bb71bd8082ffc1e07d6a663c81393 (diff) |
backenddb webhook
-rw-r--r-- | src/backenddb/merchant-0004.sql | 28 | ||||
-rw-r--r-- | src/backenddb/merchantdb_helper.c | 12 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 344 | ||||
-rw-r--r-- | src/backenddb/test_merchantdb.c | 425 | ||||
-rw-r--r-- | src/include/taler_merchantdb_lib.h | 10 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 143 |
6 files changed, 919 insertions, 43 deletions
diff --git a/src/backenddb/merchant-0004.sql b/src/backenddb/merchant-0004.sql index 8e341583..10897f1a 100644 --- a/src/backenddb/merchant-0004.sql +++ b/src/backenddb/merchant-0004.sql @@ -44,7 +44,7 @@ COMMENT ON COLUMN merchant_template.template_contract COMMIT; -CREATE TABLE IF NOT EXISTS merchant_webhooks +CREATE TABLE IF NOT EXISTS merchant_webhook (webhook_serial BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY ,merchant_serial BIGINT NOT NULL REFERENCES merchant_instances (merchant_serial) ON DELETE CASCADE @@ -52,22 +52,22 @@ CREATE TABLE IF NOT EXISTS merchant_webhooks ,event_type VARCHAR NOT NULL ,url VARCHAR NOT NULL ,http_method VARCHAR NOT NULL - ,header_template VARCHAR NOT NULL - ,body_template VARCHAR NOT NULL - ,UNIQUE (merchant_serial, webhooks_id) + ,header_template VARCHAR + ,body_template VARCHAR + ,UNIQUE (merchant_serial, webhook_id) ); -COMMENT ON TABLE merchant_webhooks - IS 'webhooks used by the merchant (may be incomplete, frontend can override)'; -COMMENT ON COLUMN merchant_webhooks.event_type - IS 'Event of the webhooks'; -COMMENT ON COLUMN merchant_webhooks.url +COMMENT ON TABLE merchant_webhook + IS 'webhook used by the merchant (may be incomplete, frontend can override)'; +COMMENT ON COLUMN merchant_webhook.event_type + IS 'Event of the webhook'; +COMMENT ON COLUMN merchant_webhook.url IS 'URL use by the customer'; -COMMENT ON COLUMN merchant_webhooks.http_method +COMMENT ON COLUMN merchant_webhook.http_method IS 'http method use by the merchant'; -COMMENT ON COLUMN merchant_webhooks.header_template - IS 'Header of the webhooks'; -COMMENT ON COLUMN merchant_webhooks.body_template - IS 'Body of the webhooks'; +COMMENT ON COLUMN merchant_webhook.header_template + IS 'Header of the webhook'; +COMMENT ON COLUMN merchant_webhook.body_template + IS 'Body of the webhook'; COMMIT; diff --git a/src/backenddb/merchantdb_helper.c b/src/backenddb/merchantdb_helper.c index 51991ca7..e7d9f459 100644 --- a/src/backenddb/merchantdb_helper.c +++ b/src/backenddb/merchantdb_helper.c @@ -48,5 +48,17 @@ TALER_MERCHANTDB_template_details_free ( } +void +TALER_MERCHANTDB_webhook_details_free ( + struct TALER_MERCHANTDB_WebhookDetails *wb) +{ + GNUNET_free (wb->event_type); + GNUNET_free (wb->url); + GNUNET_free (wb->http_method); + GNUNET_free (wb->header_template); + GNUNET_free (wb->body_template); +} + + /* end of merchantdb_helper.c */ diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c index 6a2bcb65..1176cdd5 100644 --- a/src/backenddb/plugin_merchantdb_postgres.c +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -6850,22 +6850,22 @@ postgres_delete_template (void *cls, * @param cls closure * @param instance_id instance to insert template for * @param template_id template identifier of template to insert - * @param pd the template details to insert + * @param td the template details to insert * @return database result code */ static enum GNUNET_DB_QueryStatus postgres_insert_template (void *cls, const char *instance_id, const char *template_id, - const struct TALER_MERCHANTDB_TemplateDetails *pd) + const struct TALER_MERCHANTDB_TemplateDetails *td) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (template_id), - GNUNET_PQ_query_param_string (pd->template_description), - GNUNET_PQ_query_param_string (pd->image), - TALER_PQ_query_param_json (pd->template_contract), + GNUNET_PQ_query_param_string (td->template_description), + GNUNET_PQ_query_param_string (td->image), + TALER_PQ_query_param_json (td->template_contract), GNUNET_PQ_query_param_end }; @@ -6883,7 +6883,7 @@ postgres_insert_template (void *cls, * @param cls closure * @param instance_id instance to update template for * @param template_id template to update - * @param pd update to the template details on success, can be NULL + * @param td update to the template details on success, can be NULL * (in that case we only want to check if the template exists) * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template * does not yet exist. @@ -6892,15 +6892,15 @@ static enum GNUNET_DB_QueryStatus postgres_update_template (void *cls, const char *instance_id, const char *template_id, - const struct TALER_MERCHANTDB_TemplateDetails *pd) + const struct TALER_MERCHANTDB_TemplateDetails *td) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), GNUNET_PQ_query_param_string (template_id), - GNUNET_PQ_query_param_string (pd->template_description), - GNUNET_PQ_query_param_string (pd->image), - TALER_PQ_query_param_json (pd->template_contract), + GNUNET_PQ_query_param_string (td->template_description), + GNUNET_PQ_query_param_string (td->image), + TALER_PQ_query_param_json (td->template_contract), GNUNET_PQ_query_param_end }; @@ -7025,7 +7025,7 @@ postgres_lookup_templates (void *cls, * @param cls closure * @param instance_id instance to lookup template for * @param template_id template to lookup - * @param[out] pd set to the template details on success, can be NULL + * @param[out] td set to the template details on success, can be NULL * (in that case we only want to check if the template exists) * @return database result code */ @@ -7033,7 +7033,7 @@ static enum GNUNET_DB_QueryStatus postgres_lookup_template (void *cls, const char *instance_id, const char *template_id, - struct TALER_MERCHANTDB_TemplateDetails *pd) + struct TALER_MERCHANTDB_TemplateDetails *td) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { @@ -7042,7 +7042,7 @@ postgres_lookup_template (void *cls, GNUNET_PQ_query_param_end }; - if (NULL == pd) + if (NULL == td) { struct GNUNET_PQ_ResultSpec rs_null[] = { GNUNET_PQ_result_spec_end @@ -7058,11 +7058,11 @@ postgres_lookup_template (void *cls, { struct GNUNET_PQ_ResultSpec rs[] = { GNUNET_PQ_result_spec_string ("template_description", - &pd->template_description), + &td->template_description), GNUNET_PQ_result_spec_string ("image", - &pd->image), + &td->image), TALER_PQ_result_spec_json ("template_contract", - &pd->template_contract), + &td->template_contract), GNUNET_PQ_result_spec_end }; @@ -7082,25 +7082,263 @@ postgres_lookup_template (void *cls, * @param webhook_id webhook to delete * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS * if webhook unknown. + */ +static enum GNUNET_DB_QueryStatus +postgres_delete_webhook (void *cls, + const char *instance_id, + const char *webhook_id) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (webhook_id), + GNUNET_PQ_query_param_end + }; + + check_connection (pg); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "delete_webhook", + params); +} + + +/** + * Insert details about a particular webhook. * + * @param cls closure + * @param instance_id instance to insert template for + * @param webhook_id webhook identifier of webhook to insert + * @param wb the webhook details to insert + * @return database result code + */ static enum GNUNET_DB_QueryStatus -postgres_delete_template (void *cls, +postgres_insert_webhook (void *cls, const char *instance_id, - const char *template_id) + const char *webhook_id, + const struct TALER_MERCHANTDB_WebhookDetails *wb) { struct PostgresClosure *pg = cls; struct GNUNET_PQ_QueryParam params[] = { GNUNET_PQ_query_param_string (instance_id), - GNUNET_PQ_query_param_string (template_id), + GNUNET_PQ_query_param_string (webhook_id), + GNUNET_PQ_query_param_string (wb->event_type), + GNUNET_PQ_query_param_string (wb->url), + GNUNET_PQ_query_param_string (wb->http_method), + GNUNET_PQ_query_param_string (wb->header_template), + GNUNET_PQ_query_param_string (wb->body_template), GNUNET_PQ_query_param_end + }; check_connection (pg); return GNUNET_PQ_eval_prepared_non_select (pg->conn, - "delete_template", + "insert_webhook", + params); +} + + +/** + * Update details about a particular webhook. + * + * @param cls closure + * @param instance_id instance to update template for + * @param webhook_id template 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 + * does not yet exist. + */ +static enum GNUNET_DB_QueryStatus +postgres_update_webhook (void *cls, + const char *instance_id, + const char *webhook_id, + const struct TALER_MERCHANTDB_WebhookDetails *wb) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (webhook_id), + GNUNET_PQ_query_param_string (wb->event_type), + GNUNET_PQ_query_param_string (wb->url), + GNUNET_PQ_query_param_string (wb->http_method), + GNUNET_PQ_query_param_string (wb->header_template), + GNUNET_PQ_query_param_string (wb->body_template), + GNUNET_PQ_query_param_end + }; + + + check_connection (pg); + return GNUNET_PQ_eval_prepared_non_select (pg->conn, + "update_webhook", params); } - */ + + +/** + * Context used for postgres_lookup_webhook(). + */ +struct LookupWebhookContext +{ + /** + * Function to call with the results. + */ + TALER_MERCHANTDB_WebhooksCallback 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 LookupWebhookContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +lookup_webhooks_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct LookupWebhookContext *wlc = cls; + + for (unsigned int i = 0; i < num_results; i++) + { + char *webhook_id; + char *event_type; + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("webhook_id", + &webhook_id), + GNUNET_PQ_result_spec_string ("event_type", + &event_type), + 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_id, + event_type); + GNUNET_PQ_cleanup_result (rs); + } +} + +/** + * Lookup all of the webhooks the given instance has configured. + * + * @param cls closure + * @param instance_id instance to lookup webhook for + * @param cb function to call on all webhook found + * @param cb_cls closure for @a cb + * @return database result code + */ +static enum GNUNET_DB_QueryStatus +postgres_lookup_webhooks (void *cls, + const char *instance_id, + TALER_MERCHANTDB_WebhooksCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct LookupWebhookContext wlc = { + .cb = cb, + .cb_cls = cb_cls, + /* Can be overwritten by the lookup_webhook_cb */ + .extract_failed = false, + }; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + + check_connection (pg); + qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn, + "lookup_webhooks", + params, + &lookup_webhooks_cb, + &wlc); + /* If there was an error inside lookup_webhook_cb, return a hard error. */ + if (wlc.extract_failed) + return GNUNET_DB_STATUS_HARD_ERROR; + return qs; +} + + +/** + * Lookup details about a particular webhook. + * + * @param cls closure + * @param instance_id instance to lookup webhook for + * @param webhook_id webhook to lookup + * @param[out] wb set 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 + */ +static enum GNUNET_DB_QueryStatus +postgres_lookup_webhook (void *cls, + const char *instance_id, + const char *webhook_id, + struct TALER_MERCHANTDB_WebhookDetails *wb) +{ + struct PostgresClosure *pg = cls; + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_string (instance_id), + GNUNET_PQ_query_param_string (webhook_id), + GNUNET_PQ_query_param_end + }; + + if (NULL == wb) + { + struct GNUNET_PQ_ResultSpec rs_null[] = { + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_webhook", + params, + rs_null); + } + else + { + struct GNUNET_PQ_ResultSpec rs[] = { + GNUNET_PQ_result_spec_string ("event_type", + &wb->event_type), + GNUNET_PQ_result_spec_string ("url", + &wb->url), + GNUNET_PQ_result_spec_string ("http_method", + &wb->http_method), + GNUNET_PQ_result_spec_string ("header_template", + &wb->header_template), + GNUNET_PQ_result_spec_string ("body_template", + &wb->body_template), + GNUNET_PQ_result_spec_end + }; + + check_connection (pg); + return GNUNET_PQ_eval_prepared_singleton_select (pg->conn, + "lookup_webhook", + params, + rs); + } +} + /** * Establish connection to the database. @@ -9601,6 +9839,65 @@ postgres_connect (void *cls) " FROM merchant_instances" " WHERE merchant_id=$1)" " AND template_id=$2"), + /* for postgres_lookup_webhooks() */ + GNUNET_PQ_make_prepare ("lookup_webhooks", + "SELECT" + " webhook_id" + ",event_type" + " FROM merchant_webhook" + " JOIN merchant_instances" + " USING (merchant_serial)" + " WHERE merchant_instances.merchant_id=$1"), + /* for postgres_lookup_webhook() */ + GNUNET_PQ_make_prepare ("lookup_webhook", + "SELECT" + " 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 merchant_webhook.webhook_id=$2"), + /* for postgres_delete_webhook() */ + GNUNET_PQ_make_prepare ("delete_webhook", + "DELETE" + " FROM merchant_webhook" + " WHERE merchant_template.merchant_serial=" + " (SELECT merchant_serial " + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND merchant_webhook.webhook_id=$2"), + /* for postgres_insert_webhook() */ + GNUNET_PQ_make_prepare ("insert_webhook", + "INSERT INTO merchant_webhook" + "(merchant_serial" + ",webhook_id" + ",event_type" + ",url" + ",http_method" + ",header_template" + ",body_template" + ")" + " SELECT merchant_serial," + " $2, $3, $4, $5, $6, $7" + " FROM merchant_instances" + " WHERE merchant_id=$1"), + /* for postgres_update_webhook() */ + GNUNET_PQ_make_prepare ("update_webhook", + "UPDATE merchant_webhook SET" + " event_type=$3" + ",url=$4" + ",http_method=$5" + ",header_template=$6" + ",body_template=$7" + " WHERE merchant_serial=" + " (SELECT merchant_serial" + " FROM merchant_instances" + " WHERE merchant_id=$1)" + " AND webhook_id=$2"), GNUNET_PQ_PREPARED_STATEMENT_END }; struct GNUNET_PQ_ExecuteStatement es[] = { @@ -9755,6 +10052,11 @@ libtaler_plugin_merchantdb_postgres_init (void *cls) plugin->delete_template = &postgres_delete_template; plugin->insert_template = &postgres_insert_template; plugin->update_template = &postgres_update_template; + plugin->lookup_webhooks = &postgres_lookup_webhooks; + plugin->lookup_webhook = &postgres_lookup_webhook; + plugin->delete_webhook = &postgres_delete_webhook; + plugin->insert_webhook = &postgres_insert_webhook; + plugin->update_webhook = &postgres_update_webhook; return plugin; } diff --git a/src/backenddb/test_merchantdb.c b/src/backenddb/test_merchantdb.c index 5a5b7d6b..1b186d9c 100644 --- a/src/backenddb/test_merchantdb.c +++ b/src/backenddb/test_merchantdb.c @@ -6889,7 +6889,7 @@ free_template_data (struct TemplateData *template) /** - * Compare two template for equality. + * Compare two templates for equality. * * @param a the first template. * @param b the second template. @@ -7262,6 +7262,428 @@ test_templates (void) 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://example.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)) + 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; +} + + +/** + * 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[2]; +}; + + +/** + * 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"; +} + + +/** + * 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= + "https://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 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)); + TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance, + 1, + 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; +} + + + + /** * Function that runs all tests. * @@ -7280,6 +7702,7 @@ run_tests (void) 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 ()); return 0; } diff --git a/src/include/taler_merchantdb_lib.h b/src/include/taler_merchantdb_lib.h index 8d84db79..09184661 100644 --- a/src/include/taler_merchantdb_lib.h +++ b/src/include/taler_merchantdb_lib.h @@ -68,6 +68,16 @@ void TALER_MERCHANTDB_template_details_free ( struct TALER_MERCHANTDB_TemplateDetails *tp); + +/** + * Free members of @a wb, but not @a wb itself. + * + * @param[in] wb webhook details to clean up + */ +void +TALER_MERCHANTDB_webhook_details_free ( + struct TALER_MERCHANTDB_WebhookDetails *wb); + #endif /* MERCHANT_DB_H */ /* end of taler_merchantdb_lib.h */ diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h index 1fa58ecd..0e70ec02 100644 --- a/src/include/taler_merchantdb_plugin.h +++ b/src/include/taler_merchantdb_plugin.h @@ -337,6 +337,53 @@ struct TALER_MERCHANTDB_TemplateDetails /** + * Typically called by `lookup_webhooks`. + * + * @param cls a `json_t *` JSON array to build + * @param webhook_id ID of the webhook + */ +typedef void +(*TALER_MERCHANTDB_WebhooksCallback)(void *cls, + const char *webhook_id, + const char *event_type); + + +/** + * Details about a webhook. + */ +struct TALER_MERCHANTDB_WebhookDetails +{ + /** + * event of the webhook. + */ + char *event_type; + + /** + * URL of the webhook. The customer will be redirected on this url. + */ + char *url; + + /** + * Http method used by the webhook. + */ + char *http_method; + + + /** + * Header template of the webhook. + */ + char *header_template; + + + /** + * Body template of the webhook. + */ + char *body_template; + +}; + + +/** * Filter preferences. */ struct TALER_MERCHANTDB_OrderFilter @@ -2442,7 +2489,7 @@ struct TALER_MERCHANTDB_Plugin * @param cls closure * @param instance_id instance to lookup template for * @param template_id template to lookup - * @param[out] pd set to the template details on success, can be NULL + * @param[out] td set to the template details on success, can be NULL * (in that case we only want to check if the template exists) * @return database result code */ @@ -2450,13 +2497,13 @@ struct TALER_MERCHANTDB_Plugin (*lookup_template)(void *cls, const char *instance_id, const char *template_id, - struct TALER_MERCHANTDB_TemplateDetails *pd); + struct TALER_MERCHANTDB_TemplateDetails *td); /** * Delete information about a template. * * @param cls closure - * @param instance_id instance to delete product of + * @param instance_id instance to delete template of * @param template_id template to delete * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS * if template unknown. @@ -2473,14 +2520,14 @@ struct TALER_MERCHANTDB_Plugin * @param cls closure * @param instance_id instance to insert template for * @param template_id template identifier of template to insert - * @param pd the template details to insert + * @param td the template details to insert * @return database result code */ enum GNUNET_DB_QueryStatus (*insert_template)(void *cls, const char *instance_id, const char *template_id, - const struct TALER_MERCHANTDB_TemplateDetails *pd); + const struct TALER_MERCHANTDB_TemplateDetails *td); /** @@ -2489,7 +2536,7 @@ struct TALER_MERCHANTDB_Plugin * @param cls closure * @param instance_id instance to update template for * @param template_id template to update - * @param pd update to the template details on success, can be NULL + * @param td update to the template details on success, can be NULL * (in that case we only want to check if the template exists) * @return database result code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if the template * does not yet exist. @@ -2499,7 +2546,89 @@ struct TALER_MERCHANTDB_Plugin (*update_template)(void *cls, const char *instance_id, const char *template_id, - const struct TALER_MERCHANTDB_TemplateDetails *pd); + const struct TALER_MERCHANTDB_TemplateDetails *td); + + +/** + * Lookup all of the webhooks the given instance has configured. + * + * @param cls closure + * @param instance_id instance to lookup webhook for + * @param cb function to call on all webhook found + * @param cb_cls closure for @a cb + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*lookup_webhooks)(void *cls, + const char *instance_id, + TALER_MERCHANTDB_WebhooksCallback cb, + void *cb_cls); + + +/** + * Lookup details about a particular webhook. + * + * @param cls closure + * @param instance_id instance to lookup webhook for + * @param webhook_id webhook to lookup + * @param[out] wb set 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 + */ + enum GNUNET_DB_QueryStatus + (*lookup_webhook)(void *cls, + const char *instance_id, + const char *webhook_id, + struct TALER_MERCHANTDB_WebhookDetails *wb); + +/** + * Delete information about a webhook. + * + * @param cls closure + * @param instance_id instance to delete webhook of + * @param webhook_id webhook to delete + * @return DB status code, #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS + * if webhook unknown. + */ + enum GNUNET_DB_QueryStatus + (*delete_webhook)(void *cls, + const char *instance_id, + const char *webhook_id); + + +/** + * Insert details about a particular webhook. + * + * @param cls closure + * @param instance_id instance to insert template for + * @param webhook_id webhook identifier of webhook to insert + * @param wb the webhook details to insert + * @return database result code + */ + enum GNUNET_DB_QueryStatus + (*insert_webhook)(void *cls, + const char *instance_id, + const char *webhook_id, + const struct TALER_MERCHANTDB_WebhookDetails *wb); + + +/** + * Update details about a particular webhook. + * + * @param cls closure + * @param instance_id instance to update webhook for + * @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 + * does not yet exist. + */ + + enum GNUNET_DB_QueryStatus + (*update_webhook)(void *cls, + const char *instance_id, + const char *webhook_id, + const struct TALER_MERCHANTDB_WebhookDetails *wb); }; |