summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2020-04-24 17:53:31 +0200
committerChristian Grothoff <christian@grothoff.org>2020-04-24 17:53:31 +0200
commitf4b19c002aa3ef8054729f7e61254232d575b7a6 (patch)
tree79e8aed1c7081470a634430b17b2660568c3e4e6
parent0b57eac8f3b99941a11f11f35feac1926cd21d31 (diff)
downloadmerchant-f4b19c002aa3ef8054729f7e61254232d575b7a6.tar.gz
merchant-f4b19c002aa3ef8054729f7e61254232d575b7a6.tar.bz2
merchant-f4b19c002aa3ef8054729f7e61254232d575b7a6.zip
toward stesting
-rw-r--r--src/backend/taler-merchant-httpd.c8
-rw-r--r--src/include/taler_merchant_service.h30
-rw-r--r--src/include/taler_merchant_testing_lib.h102
-rw-r--r--src/lib/merchant_api_delete_product.c15
-rw-r--r--src/lib/merchant_api_get_product.c15
-rw-r--r--src/lib/merchant_api_get_products.c21
-rw-r--r--src/lib/merchant_api_lock_product.c15
-rw-r--r--src/lib/merchant_api_patch_product.c15
-rw-r--r--src/lib/merchant_api_post_products.c21
-rw-r--r--src/testing/Makefile.am5
-rw-r--r--src/testing/testing_api_cmd_delete_product.c182
-rw-r--r--src/testing/testing_api_cmd_get_product.c224
-rw-r--r--src/testing/testing_api_cmd_get_products.c181
-rw-r--r--src/testing/testing_api_cmd_lock_product.c200
-rw-r--r--src/testing/testing_api_cmd_patch_product.c283
-rw-r--r--src/testing/testing_api_cmd_post_products.c315
16 files changed, 1514 insertions, 118 deletions
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c
index 4efa6ad2..25c7bab4 100644
--- a/src/backend/taler-merchant-httpd.c
+++ b/src/backend/taler-merchant-httpd.c
@@ -955,8 +955,8 @@ url_handler (void *cls,
handlers = public_handlers;
}
}
- if (strcmp (url,
- ""))
+ if (0 == strcmp (url,
+ ""))
url = "/"; /* code below does not like empty string */
{
@@ -1010,8 +1010,8 @@ url_handler (void *cls,
if ( (NULL == infix_url)
^ (GNUNET_NO == rh->have_id_segment) )
continue; /* infix existence missmatch */
- if ( (NULL == suffix_url)
- ^ (NULL != rh->url_suffix) )
+ if ( ( (NULL == suffix_url)
+ ^ (NULL == rh->url_suffix) ) )
continue; /* suffix existence missmatch */
if ( (NULL != suffix_url) &&
( (suffix_strlen != strlen (rh->url_suffix)) ||
diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h
index 42ea1fa7..435f65d2 100644
--- a/src/include/taler_merchant_service.h
+++ b/src/include/taler_merchant_service.h
@@ -421,7 +421,7 @@ typedef void
/**
- * Setup an new instance in the backend.
+ * Modify an existing instance in the backend.
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
@@ -722,7 +722,7 @@ struct TALER_MERCHANT_InventoryEntry
typedef void
(*TALER_MERCHANT_ProductsGetCallback)(
void *cls,
- struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_MERCHANT_HttpResponse *hr,
unsigned int products_length,
const struct TALER_MERCHANT_InventoryEntry products[]);
@@ -732,8 +732,6 @@ typedef void
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to query about its products,
- * NULL to query the default instance
* @param cb function to call with the backend's inventory information
* @param cb_cls closure for @a cb
* @return the request handle; NULL upon error
@@ -742,7 +740,6 @@ struct TALER_MERCHANT_ProductsGetHandle *
TALER_MERCHANT_products_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
TALER_MERCHANT_ProductsGetCallback cb,
void *cb_cls);
@@ -793,7 +790,7 @@ struct TALER_MERCHANT_ProductGetHandle;
typedef void
(*TALER_MERCHANT_ProductGetCallback)(
void *cls,
- struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_MERCHANT_HttpResponse *hr,
const char *description,
const json_t *description_i18n,
const char *unit,
@@ -813,8 +810,6 @@ typedef void
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to query about its products,
- * NULL to query the default instance
* @param product_id identifier of the product to inquire about
* @param cb function to call with the backend's product information
* @param cb_cls closure for @a cb
@@ -824,7 +819,6 @@ struct TALER_MERCHANT_ProductGetHandle *
TALER_MERCHANT_product_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
TALER_MERCHANT_ProductGetCallback cb,
void *cb_cls);
@@ -864,8 +858,6 @@ typedef void
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to add a product to,
- * NULL to query the default instance
* @param product_id identifier to use for the product
* @param description description of the product
* @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
@@ -888,7 +880,6 @@ struct TALER_MERCHANT_ProductsPostHandle *
TALER_MERCHANT_products_post (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
const char *description,
const json_t *description_i18n,
@@ -928,7 +919,7 @@ struct TALER_MERCHANT_ProductPatchHandle;
typedef void
(*TALER_MERCHANT_ProductPatchCallback)(
void *cls,
- struct TALER_MERCHANT_HttpResponse *hr);
+ const struct TALER_MERCHANT_HttpResponse *hr);
/**
@@ -937,8 +928,6 @@ typedef void
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to add a product to,
- * NULL to query the default instance
* @param product_id identifier to use for the product; the product must exist,
* or the transaction will fail with a #MHD_HTTP_NOT_FOUND
* HTTP status code
@@ -966,7 +955,6 @@ struct TALER_MERCHANT_ProductPatchHandle *
TALER_MERCHANT_product_patch (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
const char *description,
const json_t *description_i18n,
@@ -1007,7 +995,7 @@ struct TALER_MERCHANT_ProductLockHandle;
typedef void
(*TALER_MERCHANT_ProductLockCallback)(
void *cls,
- struct TALER_MERCHANT_HttpResponse *hr);
+ const struct TALER_MERCHANT_HttpResponse *hr);
/**
@@ -1016,8 +1004,6 @@ typedef void
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to query about its products,
- * NULL to query the default instance
* @param product_id identifier of the product
* @param uuid UUID that identifies the client holding the lock
* @param duration how long should the lock be held
@@ -1030,7 +1016,6 @@ struct TALER_MERCHANT_ProductLockHandle *
TALER_MERCHANT_product_lock (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
const struct GNUNET_Uuid *uuid,
struct GNUNET_TIME_Relative duration,
@@ -1065,7 +1050,7 @@ struct TALER_MERCHANT_ProductDeleteHandle;
typedef void
(*TALER_MERCHANT_ProductDeleteCallback)(
void *cls,
- struct TALER_MERCHANT_HttpResponse *hr);
+ const struct TALER_MERCHANT_HttpResponse *hr);
/**
@@ -1074,8 +1059,6 @@ typedef void
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to query about its products,
- * NULL to query the default instance
* @param product_id identifier of the product
* @param cb function to call with the backend's deletion status
* @param cb_cls closure for @a cb
@@ -1085,7 +1068,6 @@ struct TALER_MERCHANT_ProductDeleteHandle *
TALER_MERCHANT_product_delete (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
TALER_MERCHANT_ProductDeleteCallback cb,
void *cb_cls);
diff --git a/src/include/taler_merchant_testing_lib.h b/src/include/taler_merchant_testing_lib.h
index 95f945c9..d9d3c8e1 100644
--- a/src/include/taler_merchant_testing_lib.h
+++ b/src/include/taler_merchant_testing_lib.h
@@ -256,8 +256,6 @@ TALER_TESTING_cmd_merchant_delete_instance (const char *label,
* @param label command label.
* @param merchant_url base URL of the merchant serving the
* POST /products request.
- * @param instance_id instance to add a product to,
- * NULL to query the default instance
* @param product_id the ID of the product to query
* @param description description of the product
* @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
@@ -280,7 +278,6 @@ TALER_TESTING_cmd_merchant_post_products2 (
const char *label,
const char *merchant_url,
const char *product_id,
- const char *instance_id,
const char *description,
json_t *description_i18n,
const char *unit,
@@ -299,8 +296,6 @@ TALER_TESTING_cmd_merchant_post_products2 (
* @param label command label.
* @param merchant_url base URL of the merchant serving the
* POST /products request.
- * @param instance_id instance to add a product to,
- * NULL to query the default instance
* @param product_id the ID of the product to create
* @param description name of the product
* @param price price of the product
@@ -310,13 +305,108 @@ TALER_TESTING_cmd_merchant_post_products2 (
struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_products (const char *label,
const char *merchant_url,
- const char *instance_id,
const char *product_id,
const char *description,
const char *price,
unsigned int http_status);
+/**
+ * Define a "PATCH /products/$ID" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * PATCH /product request.
+ * @param product_id the ID of the product to query
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to imply that
+ * this product is not sold separately or that the price is not fixed and
+ * must be supplied by the front-end. If non-zero, price must include
+ * applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stocked in @a units, -1 to indicate "infinite" (i.e. electronic books)
+ * @param total_lost in @a units, must be larger than previous values, and may
+ * not exceed total_stocked minus total_sold; if it does, the transaction
+ * will fail with a #MHD_HTTP_CONFLICT HTTP status code
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for unknown,
+ * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_product (
+ const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ const char *description,
+ json_t *description_i18n,
+ const char *unit,
+ const struct TALER_Amount *price,
+ json_t *image,
+ json_t *taxes,
+ int64_t total_stocked,
+ uint64_t total_lost,
+ json_t *address,
+ struct GNUNET_TIME_Absolute next_restock,
+ unsigned int http_status);
+
+
+/**
+ * Define a "GET /products" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * GET /products request.
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_products (const char *label,
+ const char *merchant_url,
+ unsigned int http_status);
+
+
+/**
+ * Define a "GET product" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * GET /products/$ID request.
+ * @param product_id the ID of the product to query
+ * @param http_status expected HTTP response code.
+ * @param product_reference reference to a "POST /products" or "PATCH /products/$ID" CMD
+ * that will provide what we expect the backend to return to us
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_product (const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ unsigned int http_status,
+ const char *product_reference);
+
+
+/**
+ * Define a "DELETE product" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * DELETE /products/$ID request.
+ * @param product_id the ID of the product to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_product (const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ unsigned int http_status);
+
+
/* ******************** OLD ******************* */
/**
diff --git a/src/lib/merchant_api_delete_product.c b/src/lib/merchant_api_delete_product.c
index e00f0a34..e406581c 100644
--- a/src/lib/merchant_api_delete_product.c
+++ b/src/lib/merchant_api_delete_product.c
@@ -121,8 +121,6 @@ handle_delete_product_finished (void *cls,
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to query about its products,
- * NULL to query the default instance
* @param product_id identifier of the product
* @param cb function to call with the backend's deletion status
* @param cb_cls closure for @a cb
@@ -132,7 +130,6 @@ struct TALER_MERCHANT_ProductDeleteHandle *
TALER_MERCHANT_product_delete (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
TALER_MERCHANT_ProductDeleteCallback cb,
void *cb_cls)
@@ -146,15 +143,9 @@ TALER_MERCHANT_product_delete (
{
char *path;
- if (NULL == instance_id)
- GNUNET_asprintf (&path,
- "products/%s",
- product_id);
- else
- GNUNET_asprintf (&path,
- "instances/%s/products/%s",
- instance_id,
- product_id);
+ GNUNET_asprintf (&path,
+ "products/%s",
+ product_id);
pdh->url = TALER_url_join (backend_url,
path,
NULL);
diff --git a/src/lib/merchant_api_get_product.c b/src/lib/merchant_api_get_product.c
index f0f7cca1..fc33dedf 100644
--- a/src/lib/merchant_api_get_product.c
+++ b/src/lib/merchant_api_get_product.c
@@ -188,8 +188,6 @@ handle_get_product_finished (void *cls,
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to query about its products,
- * NULL to query the default instance
* @param product_id identifier of the product to inquire about
* @param cb function to call with the backend's product information
* @param cb_cls closure for @a cb
@@ -199,7 +197,6 @@ struct TALER_MERCHANT_ProductGetHandle *
TALER_MERCHANT_product_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
TALER_MERCHANT_ProductGetCallback cb,
void *cb_cls)
@@ -214,15 +211,9 @@ TALER_MERCHANT_product_get (
{
char *path;
- if (NULL == instance_id)
- GNUNET_asprintf (&path,
- "products/%s",
- product_id);
- else
- GNUNET_asprintf (&path,
- "instances/%s/products/%s",
- instance_id,
- product_id);
+ GNUNET_asprintf (&path,
+ "products/%s",
+ product_id);
pgh->url = TALER_url_join (backend_url,
path,
NULL);
diff --git a/src/lib/merchant_api_get_products.c b/src/lib/merchant_api_get_products.c
index 3c7eae54..86e93cdc 100644
--- a/src/lib/merchant_api_get_products.c
+++ b/src/lib/merchant_api_get_products.c
@@ -203,8 +203,6 @@ handle_get_products_finished (void *cls,
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to query about its products,
- * NULL to query the default instance
* @param cb function to call with the backend's inventory information
* @param cb_cls closure for @a cb
* @return the request handle; NULL upon error
@@ -213,7 +211,6 @@ struct TALER_MERCHANT_ProductsGetHandle *
TALER_MERCHANT_products_get (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
TALER_MERCHANT_ProductsGetCallback cb,
void *cb_cls)
{
@@ -224,21 +221,9 @@ TALER_MERCHANT_products_get (
pgh->ctx = ctx;
pgh->cb = cb;
pgh->cb_cls = cb_cls;
- {
- char *path;
-
- if (NULL == instance_id)
- GNUNET_asprintf (&path,
- "products");
- else
- GNUNET_asprintf (&path,
- "instances/%s/products",
- instance_id);
- pgh->url = TALER_url_join (backend_url,
- path,
- NULL);
- GNUNET_free (path);
- }
+ pgh->url = TALER_url_join (backend_url,
+ "products",
+ NULL);
if (NULL == pgh->url)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
diff --git a/src/lib/merchant_api_lock_product.c b/src/lib/merchant_api_lock_product.c
index ddd5e8e3..9a831508 100644
--- a/src/lib/merchant_api_lock_product.c
+++ b/src/lib/merchant_api_lock_product.c
@@ -155,8 +155,6 @@ handle_lock_product_finished (void *cls,
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to query about its products,
- * NULL to query the default instance
* @param product_id identifier of the product
* @param uuid UUID that identifies the client holding the lock
* @param duration how long should the lock be held
@@ -169,7 +167,6 @@ struct TALER_MERCHANT_ProductLockHandle *
TALER_MERCHANT_product_lock (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
const struct GNUNET_Uuid *uuid,
struct GNUNET_TIME_Relative duration,
@@ -199,15 +196,9 @@ TALER_MERCHANT_product_lock (
{
char *path;
- if (NULL == instance_id)
- GNUNET_asprintf (&path,
- "products/%s/lock",
- product_id);
- else
- GNUNET_asprintf (&path,
- "instances/%s/products/%s/lock",
- instance_id,
- product_id);
+ GNUNET_asprintf (&path,
+ "products/%s/lock",
+ product_id);
plh->url = TALER_url_join (backend_url,
path,
NULL);
diff --git a/src/lib/merchant_api_patch_product.c b/src/lib/merchant_api_patch_product.c
index d90ae582..9b0aead2 100644
--- a/src/lib/merchant_api_patch_product.c
+++ b/src/lib/merchant_api_patch_product.c
@@ -155,8 +155,6 @@ handle_patch_product_finished (void *cls,
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to add a product to,
- * NULL to query the default instance
* @param product_id identifier to use for the product; the product must exist,
* or the transaction will fail with a #MHD_HTTP_NOT_FOUND
* HTTP status code
@@ -184,7 +182,6 @@ struct TALER_MERCHANT_ProductPatchHandle *
TALER_MERCHANT_product_patch (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
const char *description,
const json_t *description_i18n,
@@ -237,15 +234,9 @@ TALER_MERCHANT_product_patch (
{
char *path;
- if (NULL == instance_id)
- GNUNET_asprintf (&path,
- "products/%s",
- product_id);
- else
- GNUNET_asprintf (&path,
- "instances/%s/products/%s",
- instance_id,
- product_id);
+ GNUNET_asprintf (&path,
+ "products/%s",
+ product_id);
pph->url = TALER_url_join (backend_url,
path,
NULL);
diff --git a/src/lib/merchant_api_post_products.c b/src/lib/merchant_api_post_products.c
index b15866dc..77cc8590 100644
--- a/src/lib/merchant_api_post_products.c
+++ b/src/lib/merchant_api_post_products.c
@@ -157,8 +157,6 @@ handle_post_products_finished (void *cls,
*
* @param ctx the context
* @param backend_url HTTP base URL for the backend
- * @param instance_id instance to add a product to,
- * NULL to query the default instance
* @param product_id identifier to use for the product
* @param description description of the product
* @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
@@ -181,7 +179,6 @@ struct TALER_MERCHANT_ProductsPostHandle *
TALER_MERCHANT_products_post (
struct GNUNET_CURL_Context *ctx,
const char *backend_url,
- const char *instance_id,
const char *product_id,
const char *description,
const json_t *description_i18n,
@@ -230,21 +227,9 @@ TALER_MERCHANT_products_post (
pph->ctx = ctx;
pph->cb = cb;
pph->cb_cls = cb_cls;
- {
- char *path;
-
- if (NULL == instance_id)
- GNUNET_asprintf (&path,
- "products");
- else
- GNUNET_asprintf (&path,
- "instances/%s/products",
- instance_id);
- pph->url = TALER_url_join (backend_url,
- path,
- NULL);
- GNUNET_free (path);
- }
+ pph->url = TALER_url_join (backend_url,
+ "products",
+ NULL);
if (NULL == pph->url)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am
index 3aecec65..df922b0d 100644
--- a/src/testing/Makefile.am
+++ b/src/testing/Makefile.am
@@ -17,10 +17,15 @@ libtalermerchanttesting_la_SOURCES = \
testing_api_cmd_config.c \
testing_api_cmd_get_instance.c \
testing_api_cmd_get_instances.c \
+ testing_api_cmd_get_product.c \
+ testing_api_cmd_get_products.c \
testing_api_cmd_delete_instance.c \
+ testing_api_cmd_delete_product.c \
+ testing_api_cmd_lock_product.c \
testing_api_cmd_post_instances.c \
testing_api_cmd_post_products.c \
testing_api_cmd_patch_instance.c \
+ testing_api_cmd_patch_product.c \
\
testing_api_cmd_check_payment.c \
testing_api_cmd_history.c \
diff --git a/src/testing/testing_api_cmd_delete_product.c b/src/testing/testing_api_cmd_delete_product.c
new file mode 100644
index 00000000..f306c762
--- /dev/null
+++ b/src/testing/testing_api_cmd_delete_product.c
@@ -0,0 +1,182 @@
+/*
+ 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 lib/testing_api_cmd_delete_product.c
+ * @brief command to test DELETE /product/$ID
+ * @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 "DELETE /products/$ID" CMD.
+ */
+struct DeleteProductState
+{
+
+ /**
+ * Handle for a "DELETE product" request.
+ */
+ struct TALER_MERCHANT_ProductDeleteHandle *pdh;
+
+ /**
+ * 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 DELETE for.
+ */
+ const char *product_id;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /delete/products/$ID operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+delete_product_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct DeleteProductState *dis = cls;
+
+ dis->pdh = NULL;
+ if (dis->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 (dis->is));
+ TALER_TESTING_interpreter_fail (dis->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ TALER_TESTING_interpreter_next (dis->is);
+}
+
+
+/**
+ * Run the "DELETE product" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+delete_product_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct DeleteProductState *dis = cls;
+
+ dis->is = is;
+ dis->pdh = TALER_MERCHANT_product_delete (is->ctx,
+ dis->merchant_url,
+ dis->product_id,
+ &delete_product_cb,
+ dis);
+ GNUNET_assert (NULL != dis->pdh);
+}
+
+
+/**
+ * Free the state of a "DELETE product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+delete_product_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct DeleteProductState *dis = cls;
+
+ if (NULL != dis->pdh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "DELETE /products/$ID operation did not complete\n");
+ TALER_MERCHANT_product_delete_cancel (dis->pdh);
+ }
+ GNUNET_free (dis);
+}
+
+
+/**
+ * Define a "DELETE product" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * DELETE /products/$ID request.
+ * @param product_id the ID of the product to query
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_delete_product (const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ unsigned int http_status)
+{
+ struct DeleteProductState *dis;
+
+ dis = GNUNET_new (struct DeleteProductState);
+ dis->merchant_url = merchant_url;
+ dis->product_id = product_id;
+ dis->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = dis,
+ .label = label,
+ .run = &delete_product_run,
+ .cleanup = &delete_product_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_delete_product.c */
diff --git a/src/testing/testing_api_cmd_get_product.c b/src/testing/testing_api_cmd_get_product.c
new file mode 100644
index 00000000..43d39bbc
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_product.c
@@ -0,0 +1,224 @@
+/*
+ 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 lib/testing_api_cmd_get_product.c
+ * @brief command to test GET /product/$ID
+ * @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 "GET product" CMD.
+ */
+struct GetProductState
+{
+
+ /**
+ * Handle for a "GET product" request.
+ */
+ struct TALER_MERCHANT_ProductGetHandle *igh;
+
+ /**
+ * 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 GET for.
+ */
+ const char *product_id;
+
+ /**
+ * Reference for a POST or PATCH /products CMD (optional).
+ */
+ const char *product_reference;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a /get/product/$ID operation.
+ *
+ * @param cls closure for this function
+ * @param hr HTTP response details
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to imply that
+ * this product is not sold separately or that the price is not fixed and
+ * must be supplied by the front-end. If non-zero, price must include
+ * applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stocked in @a units, -1 to indicate "infinite" (i.e. electronic books),
+ * does NOT indicate remaining stocks, to get remaining stocks,
+ * subtract @a total_sold and @a total_lost. Note that this still
+ * does not then say how many of the remaining inventory are locked.
+ * @param total_sold in @a units, total number of @a unit of product sold
+ * @param total_lost in @a units, total number of @a unit of product lost from inventory
+ * @param location where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for unknown,
+ * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ */
+static void
+get_product_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const char *description,
+ const json_t *description_i18n,
+ const char *unit,
+ const struct TALER_Amount *price,
+ const json_t *image,
+ const json_t *taxes,
+ int64_t total_stocked,
+ uint64_t total_sold,
+ uint64_t total_lost,
+ const json_t *location,
+ struct GNUNET_TIME_Absolute next_restock)
+{
+ /* FIXME, deeper checks should be implemented here. */
+ struct GetProductState *gis = cls;
+
+ gis->igh = NULL;
+ if (gis->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 (gis->is));
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ // FIXME: use gis->product_reference here to
+ // check if the data returned matches that from the POST / PATCH
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET product" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_product_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetProductState *gis = cls;
+
+ gis->is = is;
+ gis->igh = TALER_MERCHANT_product_get (is->ctx,
+ gis->merchant_url,
+ gis->product_id,
+ &get_product_cb,
+ gis);
+ GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_product_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetProductState *gis = cls;
+
+ if (NULL != gis->igh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /products/$ID operation did not complete\n");
+ TALER_MERCHANT_product_get_cancel (gis->igh);
+ }
+ GNUNET_free (gis);
+}
+
+
+/**
+ * Define a "GET product" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * GET /products/$ID request.
+ * @param product_id the ID of the product to query
+ * @param http_status expected HTTP response code.
+ * @param product_reference reference to a "POST /products" or "PATCH /products/$ID" CMD
+ * that will provide what we expect the backend to return to us
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_product (const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ unsigned int http_status,
+ const char *product_reference)
+{
+ struct GetProductState *gis;
+
+ gis = GNUNET_new (struct GetProductState);
+ gis->merchant_url = merchant_url;
+ gis->product_id = product_id;
+ gis->http_status = http_status;
+ gis->product_reference = product_reference;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gis,
+ .label = label,
+ .run = &get_product_run,
+ .cleanup = &get_product_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_get_product.c */
diff --git a/src/testing/testing_api_cmd_get_products.c b/src/testing/testing_api_cmd_get_products.c
new file mode 100644
index 00000000..e9e2bd49
--- /dev/null
+++ b/src/testing/testing_api_cmd_get_products.c
@@ -0,0 +1,181 @@
+/*
+ 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 lib/testing_api_cmd_get_products.c
+ * @brief command to test GET /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 "GET products" CMD.
+ */
+struct GetProductsState
+{
+
+ /**
+ * Handle for a "GET product" request.
+ */
+ struct TALER_MERCHANT_ProductsGetHandle *igh;
+
+ /**
+ * The interpreter state.
+ */
+ struct TALER_TESTING_Interpreter *is;
+
+ /**
+ * Base URL of the merchant serving the request.
+ */
+ const char *merchant_url;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a GET /products operation.
+ *
+ * @param cls closure for this function
+ * @param hr HTTP response details
+ * @param products_length length of the @a products array
+ * @param products array of products the requested instance offers
+ */
+static void
+get_products_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ unsigned int products_length,
+ const struct TALER_MERCHANT_InventoryEntry products[])
+{
+ /* FIXME, deeper checks should be implemented here. */
+ struct GetProductsState *gis = cls;
+
+ gis->igh = NULL;
+ if (gis->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 (gis->is));
+ TALER_TESTING_interpreter_fail (gis->is);
+ return;
+ }
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ // FIXME: use gis->product_reference here to
+ // check if the data returned matches that from the POST / PATCH
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ TALER_TESTING_interpreter_next (gis->is);
+}
+
+
+/**
+ * Run the "GET /products" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+get_products_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct GetProductsState *gis = cls;
+
+ gis->is = is;
+ gis->igh = TALER_MERCHANT_products_get (is->ctx,
+ gis->merchant_url,
+ &get_products_cb,
+ gis);
+ GNUNET_assert (NULL != gis->igh);
+}
+
+
+/**
+ * Free the state of a "GET product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+get_products_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct GetProductsState *gis = cls;
+
+ if (NULL != gis->igh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "GET /products/$ID operation did not complete\n");
+ TALER_MERCHANT_products_get_cancel (gis->igh);
+ }
+ GNUNET_free (gis);
+}
+
+
+/**
+ * Define a "GET /products" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * GET /products request.
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_get_products (const char *label,
+ const char *merchant_url,
+ unsigned int http_status)
+{
+ struct GetProductsState *gis;
+
+ gis = GNUNET_new (struct GetProductsState);
+ gis->merchant_url = merchant_url;
+ gis->http_status = http_status;
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = gis,
+ .label = label,
+ .run = &get_products_run,
+ .cleanup = &get_products_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_get_products.c */
diff --git a/src/testing/testing_api_cmd_lock_product.c b/src/testing/testing_api_cmd_lock_product.c
new file mode 100644
index 00000000..009783ee
--- /dev/null
+++ b/src/testing/testing_api_cmd_lock_product.c
@@ -0,0 +1,200 @@
+/*
+ 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 lib/testing_api_cmd_lock_product.c
+ * @brief command to test LOCK /product
+ * @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 "LOCK /product" CMD.
+ */
+struct LockProductState
+{
+
+ /**
+ * Handle for a "GET product" request.
+ */
+ struct TALER_MERCHANT_ProductLockHandle *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 GET for.
+ */
+ const char *product_id;
+
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a POST /products/$ID/lock operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+lock_product_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct LockProductState *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_OK:
+ break;
+ // FIXME: add other legitimate states here...
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "LOCK /products/$ID" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+lock_product_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct LockProductState *pis = cls;
+
+ pis->is = is;
+ pis->iph = TALER_MERCHANT_product_lock (is->ctx,
+ pis->merchant_url,
+ pis->product_id,
+ ...
+ & lock_product_cb,
+ pis);
+ GNUNET_assert (NULL != pis->iph);
+}
+
+
+/**
+ * Free the state of a "GET product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+lock_product_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct LockProductState *pis = cls;
+
+ if (NULL != pis->iph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "POST /product/$ID/lock operation did not complete\n");
+ TALER_MERCHANT_product_lock_cancel (pis->iph);
+ }
+ json_decref (pis->address);
+ json_decref (pis->jurisdiction);
+ GNUNET_free (pis);
+}
+
+
+/**
+ * Define a "LOCK /products/$ID" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * LOCK /product request.
+ * @param product_id the ID of the product to query
+ * @param payto_uris_length length of the @a accounts array
+ * @param payto_uris URIs of the bank accounts of the merchant product
+ * @param name name of the merchant product
+ * @param address physical address of the merchant product
+ * @param jurisdiction jurisdiction of the merchant product
+ * @param default_max_wire_fee default maximum wire fee merchant is willing to fully pay
+ * @param default_wire_fee_amortization default amortization factor for excess wire fees
+ * @param default_max_deposit_fee default maximum deposit fee merchant is willing to pay
+ * @param default_wire_transfer_delay default wire transfer delay merchant will ask for
+ * @param default_pay_delay default validity period for offers merchant makes
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_lock_product (
+ const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ ...
+ unsigned int http_status)
+{
+ struct LockProductState *pis;
+
+ pis = GNUNET_new (struct LockProductState);
+ pis->merchant_url = merchant_url;
+ pis->product_id = product_id;
+ pis->http_status = http_status;
+
+ {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pis,
+ .label = label,
+ .run = &lock_product_run,
+ .cleanup = &lock_product_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_lock_product.c */
diff --git a/src/testing/testing_api_cmd_patch_product.c b/src/testing/testing_api_cmd_patch_product.c
new file mode 100644
index 00000000..8088932d
--- /dev/null
+++ b/src/testing/testing_api_cmd_patch_product.c
@@ -0,0 +1,283 @@
+/*
+ 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 lib/testing_api_cmd_patch_product.c
+ * @brief command to test PATCH /product
+ * @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 "PATCH /product" CMD.
+ */
+struct PatchProductState
+{
+
+ /**
+ * Handle for a "GET product" request.
+ */
+ struct TALER_MERCHANT_ProductPatchHandle *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 GET 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
+ */
+ json_t *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_stocked;
+
+ /**
+ * in @e units.
+ */
+ int64_t total_lost;
+
+ /**
+ * where the product is in stock
+ */
+ json_t *address;
+
+ /**
+ * when the next restocking is expected to happen, 0 for unknown,
+ */
+ struct GNUNET_TIME_Absolute next_restock;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a PATCH /products/$ID operation.
+ *
+ * @param cls closure for this function
+ */
+static void
+patch_product_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr)
+{
+ struct PatchProductState *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_OK:
+ break;
+ // FIXME: add other legitimate states here...
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ TALER_TESTING_interpreter_next (pis->is);
+}
+
+
+/**
+ * Run the "PATCH /products/$ID" CMD.
+ *
+ *
+ * @param cls closure.
+ * @param cmd command being run now.
+ * @param is interpreter state.
+ */
+static void
+patch_product_run (void *cls,
+ const struct TALER_TESTING_Command *cmd,
+ struct TALER_TESTING_Interpreter *is)
+{
+ struct PatchProductState *pis = cls;
+
+ pis->is = is;
+ pis->iph = TALER_MERCHANT_product_patch (is->ctx,
+ pis->merchant_url,
+ pis->product_id,
+ pis->description,
+ pis->description_i18n,
+ pis->unit,
+ &pis->price,
+ pis->image,
+ pis->taxes,
+ pis->total_stocked,
+ pis->total_lost,
+ pis->address,
+ pis->next_restock,
+ &patch_product_cb,
+ pis);
+ GNUNET_assert (NULL != pis->iph);
+}
+
+
+/**
+ * Free the state of a "GET product" CMD, and possibly
+ * cancel a pending operation thereof.
+ *
+ * @param cls closure.
+ * @param cmd command being run.
+ */
+static void
+patch_product_cleanup (void *cls,
+ const struct TALER_TESTING_Command *cmd)
+{
+ struct PatchProductState *pis = cls;
+
+ if (NULL != pis->iph)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "PATCH /products/$ID operation did not complete\n");
+ TALER_MERCHANT_product_patch_cancel (pis->iph);
+ }
+ json_decref (pis->description_i18n);
+ json_decref (pis->image);
+ json_decref (pis->taxes);
+ json_decref (pis->address);
+ GNUNET_free (pis);
+}
+
+
+/**
+ * Define a "PATCH /products/$ID" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * PATCH /product request.
+ * @param product_id the ID of the product to query
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to imply that
+ * this product is not sold separately or that the price is not fixed and
+ * must be supplied by the front-end. If non-zero, price must include
+ * applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stocked in @a units, -1 to indicate "infinite" (i.e. electronic books)
+ * @param total_lost in @a units, must be larger than previous values, and may
+ * not exceed total_stocked minus total_sold; if it does, the transaction
+ * will fail with a #MHD_HTTP_CONFLICT HTTP status code
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for unknown,
+ * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+struct TALER_TESTING_Command
+TALER_TESTING_cmd_merchant_patch_product (
+ const char *label,
+ const char *merchant_url,
+ const char *product_id,
+ const char *description,
+ json_t *description_i18n,
+ const char *unit,
+ const struct TALER_Amount *price,
+ json_t *image,
+ json_t *taxes,
+ int64_t total_stocked,
+ uint64_t total_lost,
+ json_t *address,
+ struct GNUNET_TIME_Absolute next_restock,
+ unsigned int http_status)
+{
+ struct PatchProductState *pis;
+
+ pis = GNUNET_new (struct PatchProductState);
+ 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;
+ pis->price = *price;
+ pis->image = image; /* ownership taken */
+ pis->taxes = taxes; /* ownership taken */
+ pis->total_stocked = total_stocked;
+ pis->total_lost = total_lost;
+ pis->address = address; /* ownership taken */
+ pis->next_restock = next_restock; {
+ struct TALER_TESTING_Command cmd = {
+ .cls = pis,
+ .label = label,
+ .run = &patch_product_run,
+ .cleanup = &patch_product_cleanup
+ };
+
+ return cmd;
+ }
+}
+
+
+/* end of testing_api_cmd_patch_product.c */
diff --git a/src/testing/testing_api_cmd_post_products.c b/src/testing/testing_api_cmd_post_products.c
new file mode 100644
index 00000000..f5fed272
--- /dev/null
+++ b/src/testing/testing_api_cmd_post_products.c
@@ -0,0 +1,315 @@
+/*
+ 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 lib/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
+ */
+ json_t *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_stocked;
+
+ /**
+ * where the product is in stock
+ */
+ json_t *address;
+
+ /**
+ * when the next restocking is expected to happen, 0 for unknown,
+ */
+ struct GNUNET_TIME_Absolute next_restock;
+
+ /**
+ * Expected HTTP response code.
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Callback for a POST /products operation.
+ *
+ * @param cls closure for this function
+ */
+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_OK:
+ break;
+ // FIXME: add other legitimate states here...
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Unhandled HTTP status.\n");
+ }
+ 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_stocked,
+ pis->address,
+ pis->next_restock,
+ &post_products_cb,
+ pis);
+ GNUNET_assert (NULL != pis->iph);
+}
+
+
+/**
+ * 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);
+ json_decref (pis->image);
+ json_decref (pis->taxes);
+ json_decref (pis->address);
+ GNUNET_free (pis);
+}
+
+
+/**
+ * Define a "POST /products" CMD.
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /products request.
+ * @param product_id the ID of the product to query
+ * @param description description of the product
+ * @param description_i18n Map from IETF BCP 47 language tags to localized descriptions
+ * @param unit unit in which the product is measured (liters, kilograms, packages, etc.)
+ * @param price the price for one @a unit of the product, zero is used to imply that
+ * this product is not sold separately or that the price is not fixed and
+ * must be supplied by the front-end. If non-zero, price must include
+ * applicable taxes.
+ * @param image base64-encoded product image
+ * @param taxes list of taxes paid by the merchant
+ * @param total_stocked in @a units, -1 to indicate "infinite" (i.e. electronic books)
+ * @param address where the product is in stock
+ * @param next_restock when the next restocking is expected to happen, 0 for unknown,
+ * #GNUNET_TIME_UNIT_FOREVER_ABS for 'never'.
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+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 struct TALER_Amount *price,
+ json_t *image,
+ json_t *taxes,
+ int64_t total_stocked,
+ json_t *address,
+ struct GNUNET_TIME_Absolute next_restock,
+ unsigned int http_status)
+{
+ struct PostProductsState *pis;
+
+ 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;
+ pis->price = *price;
+ pis->image = image; /* ownership taken */
+ pis->taxes = taxes; /* ownership taken */
+ pis->total_stocked = total_stocked;
+ 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
+ };
+
+ return cmd;
+ }
+}
+
+
+/**
+ * Define a "POST /products" CMD, simple version
+ *
+ * @param label command label.
+ * @param merchant_url base URL of the merchant serving the
+ * POST /products request.
+ * @param product_id the ID of the product to create
+ * @param description name of the product
+ * @param price price of the product
+ * @param http_status expected HTTP response code.
+ * @return the command.
+ */
+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)
+{
+ struct TALER_Amount amount;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_string_to_amount (price,
+ &amount));
+ return TALER_TESTING_cmd_merchant_post_products2 (
+ label,
+ merchant_url,
+ product_id,
+ description,
+ json_pack ("{s:s}", "en", description),
+ "test-unit",
+ &amount,
+ json_object (),
+ json_object (),
+ 4,
+ json_pack ("{s:s}", "street", "my street"),
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ http_status);
+}
+
+
+/* end of testing_api_cmd_post_products.c */