diff options
author | priscilla <priscilla.huang@efrei.net> | 2023-01-11 10:41:24 -0500 |
---|---|---|
committer | priscilla <priscilla.huang@efrei.net> | 2023-01-11 10:41:54 -0500 |
commit | f05235f55bddb98989812c35d96504c5b7e2a918 (patch) | |
tree | ddbf03f55f7bef1040fa3ca6ccab59abf9637003 | |
parent | 59971311ab1440dde3fda023ebf2c1281f8e9efe (diff) |
backenddb test ok
-rw-r--r-- | src/backenddb/merchant-0004.sql | 8 | ||||
-rw-r--r-- | src/backenddb/plugin_merchantdb_postgres.c | 526 | ||||
-rw-r--r-- | src/backenddb/test_merchantdb.c | 587 | ||||
-rw-r--r-- | src/include/taler_merchantdb_plugin.h | 211 | ||||
-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 */ |