/*
  This file is part of TALER
  (C) 2014-2024 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
  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 test_merchantdb.c
 * @brief testcase for merchant's postgres db plugin
 * @author Marcello Stanisci
 * @author Christian Grothoff
 * @author Pricilla Huang
 */
#include "platform.h"
#include "microhttpd.h"
#include <taler/taler_util.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_signatures.h>
#include "taler_merchant_util.h"
#include "taler_merchantdb_lib.h"


/**
 * Global return value for the test.  Initially -1, set to 0 upon
 * completion.  Other values indicate some kind of error.
 */
static int result;

/**
 * Handle to the plugin we are testing.
 */
static struct TALER_MERCHANTDB_Plugin *plugin;

/**
 * @param test 0 on success, non-zero on failure
 */
#define TEST_WITH_FAIL_CLAUSE(test, on_fail) \
  if ((test)) \
  { \
    GNUNET_break (0); \
    on_fail \
  }

#define TEST_COND_RET_ON_FAIL(cond, msg) \
  if (! (cond)) \
  { \
    GNUNET_break (0); \
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \
                msg); \
    return 1; \
  }

/**
 * @param __test 0 on success, non-zero on failure
 */
#define TEST_RET_ON_FAIL(__test) \
  TEST_WITH_FAIL_CLAUSE (__test, \
                         return 1; \
                         )


/* ********** Instances ********** */


/**
 * Container for instance settings along with keys.
 */
struct InstanceData
{
  /**
   * The instance settings.
   */
  struct TALER_MERCHANTDB_InstanceSettings instance;

  /**
   * The public key for the instance.
   */
  struct TALER_MerchantPublicKeyP merchant_pub;

  /**
   * The private key for the instance.
   */
  struct TALER_MerchantPrivateKeyP merchant_priv;
};


/**
 * Creates data for an instance to use with testing.
 *
 * @param instance_id the identifier for this instance.
 * @param instance the instance data to be filled.
 */
static void
make_instance (const char *instance_id,
               struct InstanceData *instance)
{
  memset (instance,
          0,
          sizeof (*instance));
  GNUNET_CRYPTO_eddsa_key_create (&instance->merchant_priv.eddsa_priv);
  GNUNET_CRYPTO_eddsa_key_get_public (&instance->merchant_priv.eddsa_priv,
                                      &instance->merchant_pub.eddsa_pub);
  instance->instance.id = (char *) instance_id;
  instance->instance.name = (char *) "Test";
  instance->instance.address = json_array ();
  GNUNET_assert (NULL != instance->instance.address);
  GNUNET_assert (0 == json_array_append_new (instance->instance.address,
                                             json_string ("123 Example St")));
  instance->instance.jurisdiction = json_array ();
  GNUNET_assert (NULL != instance->instance.jurisdiction);
  GNUNET_assert (0 == json_array_append_new (instance->instance.jurisdiction,
                                             json_string ("Ohio")));
  instance->instance.use_stefan = true;
  instance->instance.default_wire_transfer_delay =
    GNUNET_TIME_relative_get_minute_ ();
  instance->instance.default_pay_delay = GNUNET_TIME_UNIT_SECONDS;
  instance->instance.default_refund_delay = GNUNET_TIME_UNIT_MINUTES;
}


/**
 * Frees memory allocated when creating an instance for testing.
 *
 * @param instance the instance containing the memory to be freed.
 */
static void
free_instance_data (struct InstanceData *instance)
{
  json_decref (instance->instance.address);
  json_decref (instance->instance.jurisdiction);
}


/**
 * Creates an account with test data for an instance.
 *
 * @param account the account to initialize.
 */
static void
make_account (struct TALER_MERCHANTDB_AccountDetails *account)
{
  memset (account,
          0,
          sizeof (*account));
  GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_STRONG,
                                    &account->h_wire.hash);
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
                              &account->salt,
                              sizeof (account->salt));
  account->payto_uri.full_payto
    = (char *) "payto://x-taler-bank/bank.demo.taler.net/4";
  account->active = true;
}


/**
 * Instance settings along with corresponding accounts.
 */
struct InstanceWithAccounts
{
  /**
   * Pointer to the instance settings.
   */
  const struct TALER_MERCHANTDB_InstanceSettings *instance;

  /**
   * Length of the array of accounts.
   */
  unsigned int accounts_length;

  /**
   * Pointer to the array of accounts.
   */
  const struct TALER_MERCHANTDB_AccountDetails *accounts;

};


/**
 * Closure for testing instance lookup.
 */
struct TestLookupInstances_Closure
{
  /**
   * Number of instances to compare to.
   */
  unsigned int instances_to_cmp_length;

  /**
   * Pointer to array of instances.
   */
  const struct InstanceWithAccounts *instances_to_cmp;

  /**
   * Pointer to array of number of matches for each instance.
   */
  unsigned int *results_matching;

  /**
   * Total number of results returned.
   */
  unsigned int results_length;
};


/**
 * Compares two instances for equality.
 *
 * @param a the first instance.
 * @param b the second instance.
 * @return 0 on equality, 1 otherwise.
 */
static int
check_instances_equal (const struct TALER_MERCHANTDB_InstanceSettings *a,
                       const struct TALER_MERCHANTDB_InstanceSettings *b)
{
  if ((0 != strcmp (a->id,
                    b->id)) ||
      (0 != strcmp (a->name,
                    b->name)) ||
      (1 != json_equal (a->address,
                        b->address)) ||
      (1 != json_equal (a->jurisdiction,
                        b->jurisdiction)) ||
      (a->use_stefan != b->use_stefan) ||
      (a->default_wire_transfer_delay.rel_value_us !=
       b->default_wire_transfer_delay.rel_value_us) ||
      (a->default_pay_delay.rel_value_us != b->default_pay_delay.rel_value_us))
    return 1;
  return 0;
}


#if 0
/**
 * Compares two accounts for equality.
 *
 * @param a the first account.
 * @param b the second account.
 * @return 0 on equality, 1 otherwise.
 */
static int
check_accounts_equal (const struct TALER_MERCHANTDB_AccountDetails *a,
                      const struct TALER_MERCHANTDB_AccountDetails *b)
{
  if ((0 != GNUNET_memcmp (&a->h_wire,
                           &b->h_wire)) ||
      (0 != GNUNET_memcmp (&a->salt,
                           &b->salt)) ||
      (0 != TALER_full_payto_cmp (a->payto_uri,
                                  b->payto_uri)) ||
      (a->active != b->active))
    return 1;
  return 0;
}


#endif


/**
 * Called after testing 'lookup_instances'.
 *
 * @param cls pointer to 'struct TestLookupInstances_Closure'.
 * @param merchant_pub public key of the instance
 * @param merchant_priv private key of the instance, NULL if not available
 * @param is general instance settings
 * @param ias instance authentication settings
*/
static void
lookup_instances_cb (void *cls,
                     const struct TALER_MerchantPublicKeyP *merchant_pub,
                     const struct TALER_MerchantPrivateKeyP *merchant_priv,
                     const struct TALER_MERCHANTDB_InstanceSettings *is,
                     const struct TALER_MERCHANTDB_InstanceAuthSettings *ias)
{
  struct TestLookupInstances_Closure *cmp = cls;

  if (NULL == cmp)
    return;
  cmp->results_length += 1;
  /* Look through the closure and test each instance for equality */
  for (unsigned int i = 0; cmp->instances_to_cmp_length > i; ++i)
  {
    if (0 != check_instances_equal (cmp->instances_to_cmp[i].instance,
                                    is))
      continue;
    cmp->results_matching[i] += 1;
  }
}


/**
 * Tests @e insert_instance.
 *
 * @param instance the instance data to insert.
 * @param expected_result the result that should be returned from the plugin.
 * @return 0 on success, 1 on failure.
 */
static int
test_insert_instance (const struct InstanceData *instance,
                      enum GNUNET_DB_QueryStatus expected_result)
{
  struct TALER_MERCHANTDB_InstanceAuthSettings ias = { 0 };

  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_instance (plugin->cls,
                                                  &instance->merchant_pub,
                                                  &instance->merchant_priv,
                                                  &instance->instance,
                                                  &ias,
                                                  false),
                         "Insert instance failed\n");
  return 0;
}


/**
 * Tests @e update_instance.
 *
 * @param updated_data the instance data to update the row in the database to.
 * @param expected_result the result that should be returned from the plugin.
 * @return 0 on success, 1 on failure.
 */
static int
test_update_instance (const struct InstanceData *updated_data,
                      enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->update_instance (plugin->cls,
                                                  &updated_data->instance),
                         "Update instance failed\n");
  return 0;
}


/**
 * Tests @e lookup_instances.
 *
 * @param active_only whether to lookup all instance, or only active ones.
 * @param instances_length number of instances to compare to in @e instances.
 * @param instances a list of instances that will be compared with the results
 *        found.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_instances (bool active_only,
                       unsigned int instances_length,
                       struct InstanceWithAccounts instances[])
{
  unsigned int results_matching[GNUNET_NZL (instances_length)];
  struct TestLookupInstances_Closure cmp = {
    .instances_to_cmp_length = instances_length,
    .instances_to_cmp = instances,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching, 0, sizeof (unsigned int) * instances_length);
  if (0 > plugin->lookup_instances (plugin->cls,
                                    active_only,
                                    &lookup_instances_cb,
                                    &cmp))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup instances failed\n");
    return 1;
  }
  if (instances_length != cmp.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup instances failed: incorrect number of results\n");
    return 1;
  }
  for (unsigned int i = 0; instances_length > i; ++i)
  {
    if (1 != cmp.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup instances failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Tests removing the private key of the given instance from the database.
 *
 * @param instance the instance whose private key to delete.
 * @param expected_result the result we expect the db to return.
 * @return 0 on success, 1 otherwise.
 */
static int
test_delete_instance_private_key (const struct InstanceData *instance,
                                  enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->delete_instance_private_key (
                           plugin->cls,
                           instance->instance.id),
                         "Delete instance private key failed\n");
  return 0;
}


/**
 * Tests purging all data for an instance from the database.
 *
 * @param instance the instance to purge.
 * @param expected_result the result we expect the db to return.
 * @return 0 on success, 1 otherwise.
 */
static int
test_purge_instance (const struct InstanceData *instance,
                     enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->purge_instance (plugin->cls,
                                                 instance->instance.id),
                         "Purge instance failed\n");
  return 0;
}


/**
 * Tests inserting an account for a merchant instance.
 *
 * @param instance the instance to associate the account with.
 * @param account the account to insert.
 * @param expected_result the result expected from the db.
 * @return 0 on success, 1 otherwise.
 */
static int
test_insert_account (const struct InstanceData *instance,
                     const struct TALER_MERCHANTDB_AccountDetails *account,
                     enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_account (plugin->cls,
                                                 account),
                         "Insert account failed\n");
  return 0;
}


/**
 * Tests deactivating an account.
 *
 * @param account the account to deactivate.
 * @param expected_result the result expected from the plugin.
 * @return 0 on success, 1 otherwise.
 */
static int
test_inactivate_account (const struct InstanceData *instance,
                         const struct TALER_MERCHANTDB_AccountDetails *account,
                         enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->inactivate_account (plugin->cls,
                                                     instance->instance.id,
                                                     &account->h_wire),
                         "Deactivate account failed\n");
  return 0;
}


/**
 * Closure for instance tests
 */
struct TestInstances_Closure
{
  /**
   * The list of instances that we use for testing instances.
   */
  struct InstanceData instances[2];

  /**
   * The list of accounts to use with the instances.
   */
  struct TALER_MERCHANTDB_AccountDetails accounts[2];

};


/**
 * Sets up the data structures used in the instance tests
 *
 * @cls the closure to initialize with test data.
 */
static void
pre_test_instances (struct TestInstances_Closure *cls)
{
  /* Instance */
  make_instance ("test_instances_inst0",
                 &cls->instances[0]);
  make_instance ("test_instances_inst1",
                 &cls->instances[1]);

  /* Accounts */
  make_account (&cls->accounts[0]);
  cls->accounts[0].instance_id
    = cls->instances[0].instance.id;
  make_account (&cls->accounts[1]);
  cls->accounts[1].instance_id
    = cls->instances[1].instance.id;
}


/**
 * Handles all teardown after testing
 *
 * @cls the closure to free data from
 */
static void
post_test_instances (struct TestInstances_Closure *cls)
{
  free_instance_data (&cls->instances[0]);
  free_instance_data (&cls->instances[1]);
}


/**
 * Function that tests instances.
 *
 * @param cls closure with config
 * @return 0 on success, 1 if failure.
 */
static int
run_test_instances (struct TestInstances_Closure *cls)
{
  struct InstanceWithAccounts instances[2] = {
    {
      .accounts_length = 0,
      .accounts = cls->accounts,
      .instance = &cls->instances[0].instance
    },
    {
      .accounts_length = 0,
      .accounts = cls->accounts,
      .instance = &cls->instances[1].instance
    }
  };
  uint64_t account_serial;

  /* Test inserting an instance */
  TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double insertion fails */
  TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[0],
                                          GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test lookup instances- is our new instance there? */
  TEST_RET_ON_FAIL (test_lookup_instances (false,
                                           1,
                                           instances));
  /* Test update instance */
  cls->instances[0].instance.name = "Test - updated";
  json_array_append_new (cls->instances[0].instance.address,
                         json_pack ("{s:s, s:I}",
                                    "this", "is",
                                    "more data", 47));
  json_array_append_new (cls->instances[0].instance.jurisdiction,
                         json_pack ("{s:s}",
                                    "vegetables", "bad"));
  cls->instances[0].instance.use_stefan = false;
  cls->instances[0].instance.default_wire_transfer_delay =
    GNUNET_TIME_UNIT_HOURS;
  cls->instances[0].instance.default_pay_delay = GNUNET_TIME_UNIT_MINUTES;

  TEST_RET_ON_FAIL (test_update_instance (&cls->instances[0],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_instances (false,
                                           1,
                                           instances));
  TEST_RET_ON_FAIL (test_update_instance (&cls->instances[1],
                                          GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test account creation */
  TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0],
                                         &cls->accounts[0],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double account insertion fails */
  TEST_RET_ON_FAIL (test_insert_account (&cls->instances[1],
                                         &cls->accounts[1],
                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  TEST_RET_ON_FAIL (test_insert_account (&cls->instances[0],
                                         &cls->accounts[0],
                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  instances[0].accounts_length = 1;
  TEST_RET_ON_FAIL (test_lookup_instances (false,
                                           1,
                                           instances));
  /* Test deactivate account */
  TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[0],
                                             &cls->accounts[0],
                                             GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_inactivate_account (&cls->instances[1],
                                             &cls->accounts[1],
                                             GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  cls->accounts[0].active = false;
  TEST_RET_ON_FAIL (test_lookup_instances (false,
                                           1,
                                           instances));
  /* Test lookup account */
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
      plugin->lookup_account (plugin->cls,
                              cls->instances[0].instance.id,
                              cls->accounts[0].payto_uri,
                              &account_serial))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup account failed\n");
    return 1;
  }
  if (1 != account_serial)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup account failed: incorrect serial number found\n");
    return 1;
  }
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
      plugin->lookup_account (plugin->cls,
                              cls->instances[0].instance.id,
                              (struct TALER_FullPayto) {
    (char *) "payto://other-uri"
  },
                              &account_serial))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup account failed: account found where there is none\n");
    return 1;
  }
  /* Test instance private key deletion */
  TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[0],
                                                      GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_delete_instance_private_key (&cls->instances[1],
                                                      GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  TEST_RET_ON_FAIL (test_lookup_instances (true,
                                           0,
                                           NULL));
  TEST_RET_ON_FAIL (test_lookup_instances (false,
                                           1,
                                           instances));
  TEST_RET_ON_FAIL (test_insert_instance (&cls->instances[1],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_instances (false,
                                           2,
                                           instances));
  TEST_RET_ON_FAIL (test_lookup_instances (true,
                                           1,
                                           &instances[1]));
  TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_purge_instance (&cls->instances[1],
                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test that the instance is gone. */
  TEST_RET_ON_FAIL (test_lookup_instances (false,
                                           1,
                                           instances));
  return 0;
}


/**
 * Function that tests instances.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_instances (void)
{
  struct TestInstances_Closure test_cls;
  int test_result;

  pre_test_instances (&test_cls);
  test_result = run_test_instances (&test_cls);
  post_test_instances (&test_cls);
  return test_result;
}


/* *********** Products ********** */


/**
 * A container for data relevant to a product.
 */
struct ProductData
{
  /**
   * The identifier of the product.
   */
  const char *id;

  /**
   * The details of the product.
   */
  struct TALER_MERCHANTDB_ProductDetails product;
};


/**
 * Creates a product for testing with.
 *
 * @param id the id of the product.
 * @param product the product data to fill.
 */
static void
make_product (const char *id,
              struct ProductData *product)
{
  memset (product,
          0,
          sizeof (*product));
  product->id = id;
  product->product.product_name = "Test product";
  product->product.description = "This is a test product";
  product->product.description_i18n = json_array ();
  GNUNET_assert (NULL != product->product.description_i18n);
  product->product.unit = "boxes";
  product->product.minimum_age = 0;
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:120.40",
                                         &product->product.price));
  product->product.taxes = json_array ();
  GNUNET_assert (NULL != product->product.taxes);
  product->product.total_stock = 55;
  product->product.total_sold = 0;
  product->product.total_lost = 0;
  product->product.image = GNUNET_strdup ("");
  GNUNET_assert (NULL != product->product.image);
  product->product.address = json_array ();
  GNUNET_assert (NULL != product->product.address);
  product->product.next_restock = GNUNET_TIME_UNIT_ZERO_TS;
}


/**
 * Frees memory associated with @e ProductData.
 *
 * @param product the container to free.
 */
static void
free_product_data (struct ProductData *product)
{
  json_decref (product->product.description_i18n);
  json_decref (product->product.taxes);
  GNUNET_free (product->product.image);
  json_decref (product->product.address);
}


/**
 * Compare two products for equality.
 *
 * @param a the first product.
 * @param b the second product.
 * @return 0 on equality, 1 otherwise.
 */
static int
check_products_equal (const struct TALER_MERCHANTDB_ProductDetails *a,
                      const struct TALER_MERCHANTDB_ProductDetails *b)
{
  if ((0 != strcmp (a->description,
                    b->description)) ||
      (1 != json_equal (a->description_i18n,
                        b->description_i18n)) ||
      (0 != strcmp (a->unit,
                    b->unit)) ||
      (GNUNET_OK !=
       TALER_amount_cmp_currency (&a->price,
                                  &b->price)) ||
      (0 != TALER_amount_cmp (&a->price,
                              &b->price)) ||
      (1 != json_equal (a->taxes,
                        b->taxes)) ||
      (a->total_stock != b->total_stock) ||
      (a->total_sold != b->total_sold) ||
      (a->total_lost != b->total_lost) ||
      (0 != strcmp (a->image,
                    b->image)) ||
      (1 != json_equal (a->address,
                        b->address)) ||
      (GNUNET_TIME_timestamp_cmp (a->next_restock,
                                  !=,
                                  b->next_restock)))

    return 1;
  return 0;
}


/**
 * Tests inserting product data into the database.
 *
 * @param instance the instance to insert the product for.
 * @param product the product data to insert.
 * @param num_cats length of the @a cats array
 * @param cats array of categories for the product
 * @param expected_result the result we expect the db to return.
 * @param expect_conflict expected conflict status
 * @param expect_no_instance expected instance missing status
 * @param expected_no_cat expected category missing index
 * @return 0 when successful, 1 otherwise.
 */
static int
test_insert_product (const struct InstanceData *instance,
                     const struct ProductData *product,
                     unsigned int num_cats,
                     const uint64_t *cats,
                     enum GNUNET_DB_QueryStatus expected_result,
                     bool expect_conflict,
                     bool expect_no_instance,
                     ssize_t expected_no_cat)
{
  bool conflict;
  bool no_instance;
  ssize_t no_cat;

  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_product (plugin->cls,
                                                 instance->instance.id,
                                                 product->id,
                                                 &product->product,
                                                 num_cats,
                                                 cats,
                                                 &no_instance,
                                                 &conflict,
                                                 &no_cat),
                         "Insert product failed\n");
  if (expected_result > 0)
  {
    TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance,
                           "No instance wrong");
    TEST_COND_RET_ON_FAIL (conflict == expect_conflict,
                           "Conflict wrong");
    TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat,
                           "Wrong category missing returned");
  }
  return 0;
}


/**
 * Tests updating product data in the database.
 *
 * @param instance the instance to update the product for.
 * @param product the product data to update.
 * @param expected_result the result we expect the db to return.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_update_product (const struct InstanceData *instance,
                     const struct ProductData *product,
                     unsigned int num_cats,
                     const uint64_t *cats,
                     enum GNUNET_DB_QueryStatus expected_result,
                     bool expect_no_instance,
                     bool expect_no_product,
                     bool expect_lost_reduced,
                     bool expect_sold_reduced,
                     bool expect_stocked_reduced,
                     ssize_t expected_no_cat)

{
  bool no_instance;
  ssize_t no_cat;
  bool no_product;
  bool lost_reduced;
  bool sold_reduced;
  bool stocked_reduced;

  TEST_COND_RET_ON_FAIL (
    expected_result ==
    plugin->update_product (plugin->cls,
                            instance->instance.id,
                            product->id,
                            &product->product,
                            num_cats,
                            cats,
                            &no_instance,
                            &no_cat,
                            &no_product,
                            &lost_reduced,
                            &sold_reduced,
                            &stocked_reduced),
    "Update product failed\n");
  if (expected_result > 0)
  {
    TEST_COND_RET_ON_FAIL (no_instance == expect_no_instance,
                           "No instance wrong");
    TEST_COND_RET_ON_FAIL (no_product == expect_no_product,
                           "No product wrong");
    TEST_COND_RET_ON_FAIL (lost_reduced == expect_lost_reduced,
                           "No product wrong");
    TEST_COND_RET_ON_FAIL (stocked_reduced == expect_stocked_reduced,
                           "Stocked reduced wrong");
    TEST_COND_RET_ON_FAIL (sold_reduced == expect_sold_reduced,
                           "Sold reduced wrong");
    TEST_COND_RET_ON_FAIL (no_cat == expected_no_cat,
                           "Wrong category missing returned");
  }
  return 0;
}


/**
 * Tests looking up a product from the db.
 *
 * @param instance the instance to query from.
 * @param product the product to query and compare to.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_lookup_product (const struct InstanceData *instance,
                     const struct ProductData *product)
{
  struct TALER_MERCHANTDB_ProductDetails lookup_result;
  size_t num_categories = 0;
  uint64_t *categories = NULL;

  if (0 > plugin->lookup_product (plugin->cls,
                                  instance->instance.id,
                                  product->id,
                                  &lookup_result,
                                  &num_categories,
                                  &categories))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup product failed\n");
    TALER_MERCHANTDB_product_details_free (&lookup_result);
    return 1;
  }
  GNUNET_free (categories);
  {
    const struct TALER_MERCHANTDB_ProductDetails *to_cmp = &product->product;

    if (0 != check_products_equal (&lookup_result,
                                   to_cmp))
    {
      GNUNET_break (0);
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup product failed: incorrect product returned\n");
      TALER_MERCHANTDB_product_details_free (&lookup_result);
      return 1;
    }
  }
  TALER_MERCHANTDB_product_details_free (&lookup_result);
  return 0;
}


/**
 * Closure for testing product lookup
 */
struct TestLookupProducts_Closure
{
  /**
   * Number of product ids to compare to
   */
  unsigned int products_to_cmp_length;

  /**
   * Pointer to array of product ids
   */
  const struct ProductData *products_to_cmp;

  /**
   * Pointer to array of number of matches for each product
   */
  unsigned int *results_matching;

  /**
   * Total number of results returned
   */
  unsigned int results_length;
};


/**
 * Function called after calling @e test_lookup_products
 *
 * @param cls a pointer to the lookup closure.
 * @param product_serial DB row ID
 * @param product_id the identifier of the product found.
 */
static void
lookup_products_cb (void *cls,
                    uint64_t product_serial,
                    const char *product_id)
{
  struct TestLookupProducts_Closure *cmp = cls;

  GNUNET_assert (product_serial > 0);
  if (NULL == cmp)
    return;
  cmp->results_length += 1;
  for (unsigned int i = 0; cmp->products_to_cmp_length > i; ++i)
  {
    if (0 == strcmp (cmp->products_to_cmp[i].id,
                     product_id))
      cmp->results_matching[i] += 1;
  }
}


/**
 * Tests looking up all products for an instance.
 *
 * @param instance the instance to query from.
 * @param products_length the number of products we are expecting.
 * @param products the list of products that we expect to be found.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_lookup_products (const struct InstanceData *instance,
                      unsigned int products_length,
                      const struct ProductData *products)
{
  unsigned int results_matching[GNUNET_NZL (products_length)];
  struct TestLookupProducts_Closure cls = {
    .products_to_cmp_length = products_length,
    .products_to_cmp = products,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching, 0, sizeof (unsigned int) * products_length);
  if (0 > plugin->lookup_products (plugin->cls,
                                   instance->instance.id,
                                   0,
                                   20,
                                   NULL,
                                   NULL,
                                   NULL,
                                   &lookup_products_cb,
                                   &cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup products failed\n");
    return 1;
  }
  if (products_length != cls.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup products failed: incorrect number of results\n");
    return 1;
  }
  for (unsigned int i = 0; products_length > i; ++i)
  {
    if (1 != cls.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup products failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Tests deleting a product.
 *
 * @param instance the instance to delete the product from.
 * @param product the product 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_product (const struct InstanceData *instance,
                     const struct ProductData *product,
                     enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->delete_product (plugin->cls,
                                                 instance->instance.id,
                                                 product->id),
                         "Delete product failed\n");
  return 0;
}


/**
 * Closure for product tests.
 */
struct TestProducts_Closure
{
  /**
   * The instance to use for this test.
   */
  struct InstanceData instance;

  /**
   * The array of products.
   */
  struct ProductData products[2];
};


/**
 * Sets up the data structures used in the product tests.
 *
 * @param cls the closure to fill with test data.
 */
static void
pre_test_products (struct TestProducts_Closure *cls)
{
  /* Instance */
  make_instance ("test_inst_products",
                 &cls->instance);

  /* Products */
  make_product ("test_products_pd_0",
                &cls->products[0]);

  make_product ("test_products_pd_1",
                &cls->products[1]);
  cls->products[1].product.description = "This is a another test product";
  cls->products[1].product.unit = "cans";
  cls->products[1].product.minimum_age = 0;
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:4.95",
                                         &cls->products[1].product.price));
  cls->products[1].product.total_stock = 5001;
}


/**
 * Handles all teardown after testing.
 *
 * @param cls the closure containing memory to be freed.
 */
static void
post_test_products (struct TestProducts_Closure *cls)
{
  free_instance_data (&cls->instance);
  free_product_data (&cls->products[0]);
  free_product_data (&cls->products[1]);
}


/**
 * Runs the tests for products.
 *
 * @param cls the container of the test data.
 * @return 0 on success, 1 otherwise.
 */
static int
run_test_products (struct TestProducts_Closure *cls)
{
  struct GNUNET_Uuid uuid;
  struct GNUNET_TIME_Timestamp refund_deadline =
    GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS);

  /* Test that insert without an instance fails */
  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
                                         &cls->products[0],
                                         0,
                                         NULL,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                                         false,
                                         true,
                                         -1));
  /* Insert the instance */
  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test inserting a product */
  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
                                         &cls->products[0],
                                         0,
                                         NULL,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                                         false,
                                         false,
                                         -1));
  /* Test that double insert succeeds */
  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
                                         &cls->products[0],
                                         0,
                                         NULL,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                                         false,
                                         false,
                                         -1));
  /* Test that conflicting insert fails */
  {
    uint64_t cat = 42;

    TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
                                           &cls->products[0],
                                           1,
                                           &cat,
                                           GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                                           true,
                                           false,
                                           -1));
  }
  /* Test lookup of individual products */
  TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
                                         &cls->products[0]));
  /* Make sure it fails correctly for products that don't exist */
  {
    size_t num_categories = 0;
    uint64_t *categories = NULL;

    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
        plugin->lookup_product (plugin->cls,
                                cls->instance.instance.id,
                                "nonexistent_product",
                                NULL,
                                &num_categories,
                                &categories))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup product failed\n");
      return 1;
    }
    GNUNET_free (categories);
  }
  /* Test product update */
  cls->products[0].product.description =
    "This is a test product that has been updated!";
  GNUNET_assert (0 ==
                 json_array_append_new (
                   cls->products[0].product.description_i18n,
                   json_string (
                     "description in another language")));
  cls->products[0].product.unit = "barrels";
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:7.68",
                                         &cls->products[0].product.price));
  GNUNET_assert (0 ==
                 json_array_append_new (cls->products[0].product.taxes,
                                        json_string ("2% sales tax")));
  cls->products[0].product.total_stock = 100;
  cls->products[0].product.total_sold = 0; /* will be ignored! */
  cls->products[0].product.total_lost = 7;
  GNUNET_free (cls->products[0].product.image);
  cls->products[0].product.image = GNUNET_strdup ("image");
  GNUNET_assert (0 ==
                 json_array_append_new (cls->products[0].product.address,
                                        json_string ("444 Some Street")));
  cls->products[0].product.next_restock = GNUNET_TIME_timestamp_get ();
  TEST_RET_ON_FAIL (test_update_product (
                      &cls->instance,
                      &cls->products[0],
                      0,
                      NULL,
                      GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                      false,
                      false,
                      false,
                      false,
                      false,
                      -1));

  {
    struct ProductData stock_dec = cls->products[0];

    stock_dec.product.total_stock = 40;
    TEST_RET_ON_FAIL (test_update_product (
                        &cls->instance,
                        &stock_dec,
                        0,
                        NULL,
                        GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                        false,
                        false,
                        false,
                        false,
                        true,
                        -1));
  }
  {
    struct ProductData lost_dec = cls->products[0];

    lost_dec.product.total_lost = 1;
    TEST_RET_ON_FAIL (test_update_product (
                        &cls->instance,
                        &lost_dec,
                        0,
                        NULL,
                        GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                        false,
                        false,
                        true,
                        false,
                        false,
                        -1));
  }
  TEST_RET_ON_FAIL (test_lookup_product (&cls->instance,
                                         &cls->products[0]));
  TEST_RET_ON_FAIL (test_update_product (
                      &cls->instance,
                      &cls->products[1],
                      0,
                      NULL,
                      GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                      false,
                      true,
                      false,
                      false,
                      false,
                      -1));
  /* Test collective product lookup */
  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
                                         &cls->products[1],
                                         0,
                                         NULL,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                                         false,
                                         false,
                                         -1));
  TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
                                          2,
                                          cls->products));
  /* Test locking */
  uuid.value[0] = 0x1287346a;
  if (0 != plugin->lock_product (plugin->cls,
                                 cls->instance.instance.id,
                                 cls->products[0].id,
                                 &uuid,
                                 256,
                                 0,
                                 refund_deadline))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lock product failed\n");
    return 1;
  }
  if (1 != plugin->lock_product (plugin->cls,
                                 cls->instance.instance.id,
                                 cls->products[0].id,
                                 &uuid,
                                 1,
                                 0,
                                 refund_deadline))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lock product failed\n");
    return 1;
  }
  /* Test product deletion */
  TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
                                         &cls->products[1],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double deletion fails */
  TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
                                         &cls->products[1],
                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
                                          1,
                                          cls->products));
  /* Test unlocking */
  if (1 != plugin->unlock_inventory (plugin->cls,
                                     &uuid))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unlock inventory failed\n");
    return 1;
  }
  if (0 != plugin->unlock_inventory (plugin->cls,
                                     &uuid))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unlock inventory failed\n");
    return 1;
  }
  TEST_RET_ON_FAIL (test_delete_product (&cls->instance,
                                         &cls->products[0],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_products (&cls->instance,
                                          0,
                                          NULL));
  return 0;
}


/**
 * Takes care of product testing.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_products (void)
{
  struct TestProducts_Closure test_cls;
  int test_result;

  pre_test_products (&test_cls);
  test_result = run_test_products (&test_cls);
  post_test_products (&test_cls);
  return test_result;
}


/* ********** Orders ********** */


/**
 * Container for order data
 */
struct OrderData
{
  /**
   * The id of the order
   */
  const char *id;

  /**
   * The pay deadline for the order
   */
  struct GNUNET_TIME_Timestamp pay_deadline;

  /**
   * The contract of the order
   */
  json_t *contract;

  /**
   * The claim token for the order.
   */
  struct TALER_ClaimTokenP claim_token;
};


/**
 * Builds an order for testing.
 *
 * @param order_id the identifier to use for the order.
 * @param order the container to fill with data.
 */
static void
make_order (const char *order_id,
            struct OrderData *order)
{
  struct GNUNET_TIME_Timestamp refund_deadline;

  order->id = order_id;
  order->contract = json_object ();
  GNUNET_assert (NULL != order->contract);
  order->pay_deadline = GNUNET_TIME_relative_to_timestamp (
    GNUNET_TIME_UNIT_DAYS);
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                              &order->claim_token,
                              sizeof (order->claim_token));
  refund_deadline = GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_WEEKS);
  GNUNET_assert (0 ==
                 json_object_set_new (order->contract,
                                      "fulfillment_url",
                                      json_string ("a")));
  GNUNET_assert (0 ==
                 json_object_set_new (order->contract,
                                      "summary",
                                      json_string ("Test order")));
  GNUNET_assert (0 ==
                 json_object_set_new (order->contract,
                                      "order_id",
                                      json_string (order_id)));
  GNUNET_assert (0 ==
                 json_object_set_new (
                   order->contract,
                   "pay_deadline",
                   GNUNET_JSON_from_timestamp (order->pay_deadline))
                 );
  GNUNET_assert (0 ==
                 json_object_set_new (order->contract,
                                      "refund_deadline",
                                      GNUNET_JSON_from_timestamp (
                                        refund_deadline)));
}


/**
 * Frees memory associated with an order.
 *
 * @param the order to free.
 */
static void
free_order_data (struct OrderData *order)
{
  json_decref (order->contract);
}


/**
 * Tests inserting an order into the database.
 *
 * @param instance the instance to insert the order for.
 * @param order the order to insert.
 * @param expected_result the value we expect the db to return.
 * @return 0 on success, 1 otherwise.
 */
static int
test_insert_order (const struct InstanceData *instance,
                   const struct OrderData *order,
                   enum GNUNET_DB_QueryStatus expected_result)
{
  struct TALER_MerchantPostDataHashP h_post;

  memset (&h_post,
          42,
          sizeof (h_post));
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_order (plugin->cls,
                                               instance->instance.id,
                                               order->id,
                                               NULL, /* session_id */
                                               &h_post,
                                               order->pay_deadline,
                                               &order->claim_token,
                                               order->contract,
                                               NULL,
                                               0),
                         "Insert order failed\n");
  return 0;
}


/**
 * Tests looking up an order in the database.
 *
 * @param instance the instance to lookup from.
 * @param order the order that should be looked up.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_order (const struct InstanceData *instance,
                   const struct OrderData *order)
{
  struct TALER_ClaimTokenP ct;
  json_t *lookup_terms = NULL;
  struct TALER_MerchantPostDataHashP oh;
  struct TALER_MerchantPostDataHashP wh;

  memset (&wh,
          42,
          sizeof (wh));
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
      plugin->lookup_order (plugin->cls,
                            instance->instance.id,
                            order->id,
                            &ct,
                            &oh,
                            &lookup_terms))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup order failed\n");
    if (NULL != lookup_terms)
      json_decref (lookup_terms);
    return 1;
  }
  if ( (1 != json_equal (order->contract,
                         lookup_terms)) ||
       (0 != GNUNET_memcmp (&order->claim_token,
                            &ct)) ||
       (0 != GNUNET_memcmp (&oh,
                            &wh)) )
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup order failed: incorrect order returned\n");
    if (NULL != lookup_terms)
      json_decref (lookup_terms);
    return 1;
  }
  json_decref (lookup_terms);
  return 0;
}


/**
 * Closure for testing order lookup
 */
struct TestLookupOrders_Closure
{
  /**
   * Number of orders to compare to
   */
  unsigned int orders_to_cmp_length;

  /**
   * Pointer to (ordered) array of order ids
   */
  const struct OrderData *orders_to_cmp;

  /**
   * Pointer to array of bools indicating matches in the correct index
   */
  bool *results_match;

  /**
   * Total number of results returned
   */
  unsigned int results_length;
};


/**
 * Called after @e test_lookup_orders.
 *
 * @param cls the lookup closure.
 * @param order_id the identifier of the order found.
 * @param order_serial the row number of the order found.
 * @param timestamp when the order was added to the database.
 */
static void
lookup_orders_cb (void *cls,
                  const char *order_id,
                  uint64_t order_serial,
                  struct GNUNET_TIME_Timestamp timestamp)
{
  struct TestLookupOrders_Closure *cmp = cls;
  unsigned int i;

  if (NULL == cmp)
    return;
  i = cmp->results_length;
  cmp->results_length += 1;
  if (cmp->orders_to_cmp_length > i)
  {
    /* Compare the orders */
    if (0 == strcmp (cmp->orders_to_cmp[i].id,
                     order_id))
      cmp->results_match[i] = true;
    else
      cmp->results_match[i] = false;
  }
}


/**
 * Tests looking up orders for an instance.
 *
 * @param instance the instance.
 * @param filter the filters applied on the lookup.
 * @param orders_length the number of orders we expect to find.
 * @param orders the orders we expect to find.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_orders (const struct InstanceData *instance,
                    const struct TALER_MERCHANTDB_OrderFilter *filter,
                    unsigned int orders_length,
                    const struct OrderData *orders)
{
  bool results_match[GNUNET_NZL (orders_length)];
  struct TestLookupOrders_Closure cls = {
    .orders_to_cmp_length = orders_length,
    .orders_to_cmp = orders,
    .results_match = results_match,
    .results_length = 0
  };
  memset (results_match,
          0,
          sizeof (bool) * orders_length);
  if (0 > plugin->lookup_orders (plugin->cls,
                                 instance->instance.id,
                                 filter,
                                 &lookup_orders_cb,
                                 &cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup orders failed\n");
    return 1;
  }
  if (orders_length != cls.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup orders failed: incorrect number of results (%d)\n",
                cls.results_length);
    return 1;
  }
  for (unsigned int i = 0; orders_length > i; ++i)
  {
    if (false == cls.results_match[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup orders failed: mismatched data (index %d)\n",
                  i);
      return 1;
    }
  }
  return 0;
}


/**
 * Container for data used for looking up the row number of an order.
 */
struct LookupOrderSerial_Closure
{
  /**
   * The order that is being looked up.
   */
  const struct OrderData *order;

  /**
   * The serial of the order that was found.
   */
  uint64_t serial;
};


/**
 * Called after @e test_lookup_orders.
 *
 * @param cls the lookup closure.
 * @param order_id the identifier of the order found.
 * @param order_serial the row number of the order found.
 * @param timestamp when the order was added to the database.
 */
static void
get_order_serial_cb (void *cls,
                     const char *order_id,
                     uint64_t order_serial,
                     struct GNUNET_TIME_Timestamp timestamp)
{
  struct LookupOrderSerial_Closure *lookup_cls = cls;
  if (NULL == lookup_cls)
    return;
  if (0 == strcmp (lookup_cls->order->id,
                   order_id))
    lookup_cls->serial = order_serial;
}


/**
 * Convenience function for getting the row number of an order.
 *
 * @param instance the instance to look up from.
 * @param order the order to lookup the serial for.
 * @return the row number of the order.
 */
static uint64_t
get_order_serial (const struct InstanceData *instance,
                  const struct OrderData *order)
{
  struct LookupOrderSerial_Closure lookup_cls = {
    .order = order,
    .serial = 0
  };
  struct TALER_MERCHANTDB_OrderFilter filter = {
    .paid = TALER_EXCHANGE_YNA_ALL,
    .refunded = TALER_EXCHANGE_YNA_ALL,
    .wired = TALER_EXCHANGE_YNA_ALL,
    .date = GNUNET_TIME_UNIT_ZERO_TS,
    .start_row = 0,
    .delta = 256
  };

  GNUNET_assert (0 < plugin->lookup_orders (plugin->cls,
                                            instance->instance.id,
                                            &filter,
                                            &get_order_serial_cb,
                                            &lookup_cls));
  GNUNET_assert (0 != lookup_cls.serial);

  return lookup_cls.serial;
}


/**
 * Tests deleting an order from the database.
 *
 * @param instance the instance to delete the order from.
 * @param order the order to delete.
 * @param expected_result the result we expect to receive.
 * @return 0 on success, 1 otherwise.
 */
static int
test_delete_order (const struct InstanceData *instance,
                   const struct OrderData *order,
                   enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->delete_order (plugin->cls,
                                               instance->instance.id,
                                               order->id,
                                               false),
                         "Delete order failed\n");
  return 0;
}


/**
 * Test inserting contract terms for an order.
 *
 * @param instance the instance.
 * @param order the order containing the contract terms.
 * @param expected_result the result we expect to receive.
 * @return 0 on success, 1 otherwise.
 */
static int
test_insert_contract_terms (const struct InstanceData *instance,
                            const struct OrderData *order,
                            enum GNUNET_DB_QueryStatus expected_result)
{
  uint64_t os;

  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_contract_terms (plugin->cls,
                                                        instance->instance.id,
                                                        order->id,
                                                        order->contract,
                                                        &os),
                         "Insert contract terms failed\n");
  return 0;
}


/**
 * Test updating contract terms for an order.
 *
 * @param instance the instance.
 * @param order the order containing the contract terms.
 * @param expected_result the result we expect to receive.
 * @return 0 on success, 1 otherwise.
 */
static int
test_update_contract_terms (const struct InstanceData *instance,
                            const struct OrderData *order,
                            enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->update_contract_terms (plugin->cls,
                                                        instance->instance.id,
                                                        order->id,
                                                        order->contract),
                         "Update contract terms failed\n");
  return 0;
}


/**
 * Tests lookup of contract terms
 *
 * @param instance the instance to lookup from.
 * @param order the order to lookup for.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_contract_terms (const struct InstanceData *instance,
                            const struct OrderData *order)
{
  json_t *contract = NULL;
  uint64_t order_serial;

  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
      plugin->lookup_contract_terms (plugin->cls,
                                     instance->instance.id,
                                     order->id,
                                     &contract,
                                     &order_serial,
                                     NULL))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup contract terms failed\n");
    GNUNET_assert (NULL == contract);
    return 1;
  }
  if (1 != json_equal (order->contract,
                       contract))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup contract terms failed: mismatched data\n");
    json_decref (contract);
    return 1;
  }
  json_decref (contract);
  return 0;
}


/**
 * Tests deleting contract terms for an order.
 *
 * @param instance the instance to delete from.
 * @param order the order whose contract terms we should delete.
 * @param legal_expiration how long we must wait after creating an order to delete it
 * @param expected_result the result we expect to receive.
 * @return 0 on success, 1 otherwise.
 */
static int
test_delete_contract_terms (const struct InstanceData *instance,
                            const struct OrderData *order,
                            struct GNUNET_TIME_Relative legal_expiration,
                            enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->delete_contract_terms (plugin->cls,
                                                        instance->instance.id,
                                                        order->id,
                                                        legal_expiration),
                         "Delete contract terms failed\n");
  return 0;
}


/**
 * Test marking a contract as paid in the database.
 *
 * @param instance the instance to use.
 * @param order the order whose contract to use.
 * @param expected_result the result we expect to receive.
 * @return 0 on success, 1 otherwise.
 */
static int
test_mark_contract_paid (const struct InstanceData *instance,
                         const struct OrderData *order,
                         enum GNUNET_DB_QueryStatus expected_result)
{
  struct TALER_PrivateContractHashP h_contract_terms;

  GNUNET_assert (GNUNET_OK ==
                 TALER_JSON_contract_hash (order->contract,
                                           &h_contract_terms));
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->mark_contract_paid (plugin->cls,
                                                     instance->instance.id,
                                                     &h_contract_terms,
                                                     "test_orders_session",
                                                     -1),
                         "Mark contract paid failed\n");
  return 0;
}


/**
 * Tests looking up the status of an order.
 *
 * @param instance the instance to lookup from.
 * @param order the order to lookup.
 * @param expected_paid whether the order was paid or not.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_order_status (const struct InstanceData *instance,
                          const struct OrderData *order,
                          bool expected_paid)
{
  struct TALER_PrivateContractHashP h_contract_terms_expected;
  struct TALER_PrivateContractHashP h_contract_terms;
  bool order_paid = false;

  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
      plugin->lookup_order_status (plugin->cls,
                                   instance->instance.id,
                                   order->id,
                                   &h_contract_terms,
                                   &order_paid))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup order status failed\n");
    return 1;
  }
  GNUNET_assert (GNUNET_OK ==
                 TALER_JSON_contract_hash (order->contract,
                                           &h_contract_terms_expected));
  if ((expected_paid != order_paid) ||
      (0 != GNUNET_memcmp (&h_contract_terms,
                           &h_contract_terms_expected)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup order status/deposit failed: mismatched data\n");
    return 1;
  }
  return 0;
}


/**
 * Test looking up an order by its fulfillment.
 *
 * @param instance the instance to lookup from.
 * @param order the order to lookup.
 * @param the session id associated with the payment.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_order_by_fulfillment (const struct InstanceData *instance,
                                  const struct OrderData *order,
                                  const char *session_id)
{
  char *order_id;
  const char *fulfillment_url =
    json_string_value (json_object_get (order->contract,
                                        "fulfillment_url"));
  GNUNET_assert (NULL != fulfillment_url);
  if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
      plugin->lookup_order_by_fulfillment (plugin->cls,
                                           instance->instance.id,
                                           fulfillment_url,
                                           session_id,
                                           false,
                                           &order_id))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup order by fulfillment failed\n");
    GNUNET_free (order_id);
    return 1;
  }
  if (0 != strcmp (order->id,
                   order_id))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup order by fulfillment failed\n");
    GNUNET_free (order_id);
    return 1;
  }
  GNUNET_free (order_id);
  return 0;
}


/**
 * Test looking up the status of an order.
 *
 * @param order_id the row of the order in the database.
 * @param session_id the session id associated with the payment.
 * @param expected_paid whether the order was paid or not.
 * @param expected_wired whether the order was wired or not.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_payment_status (const char *instance_id,
                            const char *order_id,
                            const char *session_id,
                            bool expected_paid,
                            bool expected_wired)
{
  bool paid;
  bool wired;
  bool matches;
  uint64_t os;
  int16_t choice_index;

  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
                         plugin->lookup_contract_terms3 (plugin->cls,
                                                         instance_id,
                                                         order_id,
                                                         session_id,
                                                         NULL,
                                                         &os,
                                                         &paid,
                                                         &wired,
                                                         &matches,
                                                         NULL,
                                                         &choice_index),
                         "Lookup payment status failed\n");
  if ( (NULL != session_id) && (! matches) )
  {
    paid = false;
    wired = false;
  }
  if (expected_wired != wired)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup payment status failed: wired status is wrong\n");
    return 1;
  }
  if (expected_paid != paid)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup payment status failed: paid status is wrong\n");
    return 1;
  }
  return 0;
}


/**
 * Test marking an order as being wired.
 *
 * @param order_id the row of the order in the database.
 * @param expected_result the result we expect the plugin to return.
 * @return 0 on success, 1 otherwise.
 */
static int
test_mark_order_wired (uint64_t order_id,
                       enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->mark_order_wired (plugin->cls,
                                                   order_id),
                         "Mark order wired failed\n");
  return 0;
}


/**
 * Closure for order tests.
 */
struct TestOrders_Closure
{
  /**
   * The instance to use for the order tests.
   */
  struct InstanceData instance;

  /**
   * A product to use for the order tests.
   */
  struct ProductData product;

  /**
   * The array of orders
   */
  struct OrderData orders[3];
};


/**
 * Initializes order test data.
 *
 * @param cls the order test closure.
 */
static void
pre_test_orders (struct TestOrders_Closure *cls)
{
  /* Instance */
  make_instance ("test_inst_orders",
                 &cls->instance);

  /* Product */
  make_product ("test_orders_pd_0",
                &cls->product);

  /* Orders */
  make_order ("test_orders_od_0",
              &cls->orders[0]);
  make_order ("test_orders_od_1",
              &cls->orders[1]);
  make_order ("test_orders_od_2",
              &cls->orders[2]);

  GNUNET_assert (0 ==
                 json_object_set_new (cls->orders[1].contract,
                                      "other_field",
                                      json_string ("Second contract")));

  cls->orders[2].pay_deadline = GNUNET_TIME_UNIT_ZERO_TS;
  GNUNET_assert (0 ==
                 json_object_set_new (
                   cls->orders[2].contract,
                   "pay_deadline",
                   GNUNET_JSON_from_timestamp (cls->orders[2].pay_deadline)));
}


/**
 * Frees memory after order tests.
 *
 * @param cls the order test closure.
 */
static void
post_test_orders (struct TestOrders_Closure *cls)
{
  free_instance_data (&cls->instance);
  free_product_data (&cls->product);
  free_order_data (&cls->orders[0]);
  free_order_data (&cls->orders[1]);
  free_order_data (&cls->orders[2]);
}


/**
 * Run the tests for orders.
 *
 * @param cls the order test closure.
 * @return 0 on success, 1 on failure.
 */
static int
run_test_orders (struct TestOrders_Closure *cls)
{
  struct TALER_MERCHANTDB_OrderFilter filter = {
    .paid = TALER_EXCHANGE_YNA_ALL,
    .refunded = TALER_EXCHANGE_YNA_ALL,
    .wired = TALER_EXCHANGE_YNA_ALL,
    .date = GNUNET_TIME_UNIT_ZERO_TS,
    .start_row = 0,
    .delta = 8
  };
  uint64_t serial;

  /* Insert the instance */
  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test inserting an order */
  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                       &cls->orders[0],
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double insert fails */
  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                       &cls->orders[0],
                                       GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test lookup order */
  TEST_RET_ON_FAIL (test_lookup_order (&cls->instance,
                                       &cls->orders[0]));
  /* Make sure it fails correctly for nonexistent orders */
  {
    struct TALER_MerchantPostDataHashP unused;

    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
        plugin->lookup_order (plugin->cls,
                              cls->instance.instance.id,
                              cls->orders[1].id,
                              NULL,
                              &unused,
                              NULL))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup order failed\n");
      return 1;
    }
  }
  /* Test lookups on multiple orders */
  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                       &cls->orders[1],
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  serial = get_order_serial (&cls->instance,
                             &cls->orders[0]);
  TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
                                        &filter,
                                        2,
                                        cls->orders));
  /* Test inserting contract terms */
  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
                                                &cls->orders[0],
                                                GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double insert fails */
  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
                                                &cls->orders[0],
                                                GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test order lock */
  TEST_RET_ON_FAIL (test_insert_product (&cls->instance,
                                         &cls->product,
                                         0,
                                         NULL,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                                         false,
                                         false,
                                         -1));
  if (1 != plugin->insert_order_lock (plugin->cls,
                                      cls->instance.instance.id,
                                      cls->orders[0].id,
                                      cls->product.id,
                                      5,
                                      0))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Insert order lock failed\n");
    return 1;
  }
  /* Test lookup contract terms */
  TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance,
                                                &cls->orders[0]));
  /* Test lookup fails for nonexistent contract terms */
  {
    json_t *lookup_contract = NULL;
    uint64_t lookup_order_serial;

    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
        plugin->lookup_contract_terms (plugin->cls,
                                       cls->instance.instance.id,
                                       cls->orders[1].id,
                                       &lookup_contract,
                                       &lookup_order_serial,
                                       NULL))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup contract terms failed\n");
      GNUNET_assert (NULL == lookup_contract);
      return 1;
    }
  }
  /* Test update contract terms */
  GNUNET_assert (0 ==
                 json_object_set_new (cls->orders[0].contract,
                                      "some_new_field",
                                      json_string ("another value")));
  TEST_RET_ON_FAIL (test_update_contract_terms (&cls->instance,
                                                &cls->orders[0],
                                                GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_contract_terms (&cls->instance,
                                                &cls->orders[0]));
  /* Test lookup order status */
  TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance,
                                              &cls->orders[0],
                                              false));
  {
    struct TALER_PrivateContractHashP h_contract_terms;
    bool order_paid = false;

    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
        plugin->lookup_order_status (plugin->cls,
                                     cls->instance.instance.id,
                                     cls->orders[1].id,
                                     &h_contract_terms,
                                     &order_paid))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup order status failed\n");
      return 1;
    }
  }
  /* Test lookup payment status */
  TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
                                                cls->orders[0].id,
                                                NULL,
                                                false,
                                                false));
  /* Test lookup order status fails for nonexistent order */
  {
    struct TALER_PrivateContractHashP h_contract_terms;
    bool order_paid;

    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
        plugin->lookup_order_status (plugin->cls,
                                     cls->instance.instance.id,
                                     cls->orders[1].id,
                                     &h_contract_terms,
                                     &order_paid))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup order status failed\n");
      return 1;
    }
  }
  /* Test marking contracts as paid */
  TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
                                             &cls->orders[0],
                                             GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
                                                cls->orders[0].id,
                                                NULL,
                                                true,
                                                false));
  TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
                                                cls->orders[0].id,
                                                "test_orders_session",
                                                true,
                                                false));
  TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
                                                cls->orders[0].id,
                                                "bad_session",
                                                false,
                                                false));
  /* Test lookup order by fulfillment */
  TEST_RET_ON_FAIL (test_lookup_order_by_fulfillment (&cls->instance,
                                                      &cls->orders[0],
                                                      "test_orders_session"));
  {
    char *order_id;
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
        plugin->lookup_order_by_fulfillment (plugin->cls,
                                             cls->instance.instance.id,
                                             "fulfillment_url",
                                             "test_orders_session",
                                             false,
                                             &order_id))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup order by fulfillment failed\n");
      GNUNET_free (order_id);
      return 1;
    }
  }
  /* Test mark as paid fails for nonexistent order */
  TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
                                             &cls->orders[1],
                                             GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  TEST_RET_ON_FAIL (test_lookup_order_status (&cls->instance,
                                              &cls->orders[0],
                                              true));
  filter.paid = TALER_EXCHANGE_YNA_YES;
  TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
                                        &filter,
                                        1,
                                        cls->orders));
  /* Test marking orders as wired */
  TEST_RET_ON_FAIL (test_mark_order_wired (serial,
                                           GNUNET_DB_STATUS_SUCCESS_ONE_RESULT))
  ;
  TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
                                                cls->orders[0].id,
                                                NULL,
                                                true,
                                                true));
  TEST_RET_ON_FAIL (test_mark_order_wired (1007,
                                           GNUNET_DB_STATUS_SUCCESS_NO_RESULTS))
  ;
  /* If an order has been claimed and we aren't past
     the pay deadline, we can't delete it. */
  TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
                                       &cls->orders[0],
                                       GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test we can't delete before the legal expiration */
  TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
                                                &cls->orders[0],
                                                GNUNET_TIME_UNIT_MONTHS,
                                                GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test deleting contract terms */
  TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
                                                &cls->orders[0],
                                                GNUNET_TIME_UNIT_ZERO,
                                                GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test we can't delete something that doesn't exist */
  TEST_RET_ON_FAIL (test_delete_contract_terms (&cls->instance,
                                                &cls->orders[0],
                                                GNUNET_TIME_UNIT_ZERO,
                                                GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test delete order where we aren't past
     the deadline, but the order is unclaimed. */
  TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
                                       &cls->orders[1],
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
                                        &filter,
                                        0,
                                        NULL));
  /* Test we can't delete something that doesn't exist */
  TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
                                       &cls->orders[1],
                                       GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));

  /* Test we can also delete a claimed order that's past the pay deadline. */
  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                       &cls->orders[2],
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
                                                &cls->orders[2],
                                                GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_delete_order (&cls->instance,
                                       &cls->orders[2],
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  return 0;
}


/**
 * Does all tasks for testing orders.
 *
 * @return 0 when successful, 1 otherwise.
 */
static int
test_orders (void)
{
  struct TestOrders_Closure test_cls;
  int test_result;

  pre_test_orders (&test_cls);
  test_result = run_test_orders (&test_cls);
  post_test_orders (&test_cls);
  return test_result;
}


/* ********** Deposits ********** */


/**
 * A container for exchange signing key data.
 */
struct ExchangeSignkeyData
{
  /**
   * The master private key of the exchange.
   */
  struct TALER_MasterPrivateKeyP master_priv;

  /**
   * The master public key of the exchange.
   */
  struct TALER_MasterPublicKeyP master_pub;

  /**
   * A signature made with the master keys.
   */
  struct TALER_MasterSignatureP master_sig;

  /**
   * The private key of the exchange.
   */
  struct TALER_ExchangePrivateKeyP exchange_priv;

  /**
   * The public key of the exchange.
   */
  struct TALER_ExchangePublicKeyP exchange_pub;

  /**
   * When the signing key becomes valid.
   */
  struct GNUNET_TIME_Timestamp start_date;

  /**
   * When the signing key stops being used.
   */
  struct GNUNET_TIME_Timestamp expire_date;

  /**
   * When the signing key becomes invalid for proof.
   */
  struct GNUNET_TIME_Timestamp end_date;
};


/**
 * Creates an exchange signing key.
 *
 * @param signkey the signing key data to fill.
 */
static void
make_exchange_signkey (struct ExchangeSignkeyData *signkey)
{
  struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();

  GNUNET_CRYPTO_eddsa_key_create (&signkey->exchange_priv.eddsa_priv);
  GNUNET_CRYPTO_eddsa_key_get_public (&signkey->exchange_priv.eddsa_priv,
                                      &signkey->exchange_pub.eddsa_pub);
  GNUNET_CRYPTO_eddsa_key_create (&signkey->master_priv.eddsa_priv);
  GNUNET_CRYPTO_eddsa_key_get_public (&signkey->master_priv.eddsa_priv,
                                      &signkey->master_pub.eddsa_pub);
  signkey->start_date = now;
  signkey->expire_date = now;
  signkey->end_date = now;
  TALER_exchange_offline_signkey_validity_sign (
    &signkey->exchange_pub,
    signkey->start_date,
    signkey->expire_date,
    signkey->end_date,
    &signkey->master_priv,
    &signkey->master_sig);
}


/**
 * A container for deposit data.
 */
struct DepositData
{
  /**
   * When the deposit was made.
   */
  struct GNUNET_TIME_Timestamp timestamp;

  /**
   * Hash of the associated order's contract terms.
   */
  struct TALER_PrivateContractHashP h_contract_terms;

  /**
   * Public key of the coin that has been deposited.
   */
  struct TALER_CoinSpendPublicKeyP coin_pub;

  /**
   * Signature of the coin that has been deposited.
   */
  struct TALER_CoinSpendSignatureP coin_sig;

  /**
   * URL of the exchange.
   */
  const char *exchange_url;

  /**
   * Value of the coin with fees applied.
   */
  struct TALER_Amount amount_with_fee;

  /**
   * Fee charged for deposit.
   */
  struct TALER_Amount deposit_fee;

  /**
   * Fee to be charged in case of a refund.
   */
  struct TALER_Amount refund_fee;

  /**
   * Fee charged after the money is wired.
   */
  struct TALER_Amount wire_fee;

  /**
   * Hash of the wire details.
   */
  struct TALER_MerchantWireHashP h_wire;

  /**
   * Signature the exchange made on this deposit.
   */
  struct TALER_ExchangeSignatureP exchange_sig;

};


/**
 * Generates deposit data for an order.
 *
 * @param instance the instance to make the deposit to.
 * @param account the merchant account to use.
 * @param order the order this deposit is for.
 * @param signkey the signing key to use.
 * @param deposit the deposit data to fill.
 */
static void
make_deposit (const struct InstanceData *instance,
              const struct TALER_MERCHANTDB_AccountDetails *account,
              const struct OrderData *order,
              const struct ExchangeSignkeyData *signkey,
              struct DepositData *deposit)
{
  struct TALER_CoinSpendPrivateKeyP coin_priv;
  struct GNUNET_TIME_Timestamp now;
  struct TALER_Amount amount_without_fee;

  now = GNUNET_TIME_timestamp_get ();
  deposit->timestamp = now;
  GNUNET_assert (GNUNET_OK ==
                 TALER_JSON_contract_hash (order->contract,
                                           &deposit->h_contract_terms));
  GNUNET_CRYPTO_eddsa_key_create (&coin_priv.eddsa_priv);
  GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv.eddsa_priv,
                                      &deposit->coin_pub.eddsa_pub);
  deposit->exchange_url = "https://test-exchange/";
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:50.00",
                                         &deposit->amount_with_fee));
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:1.00",
                                         &deposit->deposit_fee));
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:1.50",
                                         &deposit->refund_fee));
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:2.00",
                                         &deposit->wire_fee));
  GNUNET_assert (0 <=
                 TALER_amount_subtract (&amount_without_fee,
                                        &deposit->amount_with_fee,
                                        &deposit->deposit_fee));
  deposit->h_wire = account->h_wire;
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                              &deposit->exchange_sig,
                              sizeof (deposit->exchange_sig));
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                              &deposit->coin_sig,
                              sizeof (deposit->coin_sig));
}


/**
 * Tests inserting an exchange signing key into the database.
 *
 * @param signkey the signing key to insert.
 * @param expected_result the result we expect the database to return.
 * @return 0 on success, 1 otherwise.
 */
static int
test_insert_exchange_signkey (const struct ExchangeSignkeyData *signkey,
                              enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_exchange_signkey (plugin->cls,
                                                          &signkey->master_pub,
                                                          &signkey->exchange_pub
                                                          ,
                                                          signkey->start_date,
                                                          signkey->expire_date,
                                                          signkey->end_date,
                                                          &signkey->master_sig),
                         "Insert exchange signkey failed\n");
  return 0;
}


/**
 * Tests inserting a deposit into the database.
 *
 * @param instance the instance the deposit was made to.
 * @param signkey the signing key used.
 * @param deposit the deposit information to insert.
 * @param expected_result the result we expect the database to return.
 * @return 0 on success, 1 otherwise.
 */
static int
test_insert_deposit (const struct InstanceData *instance,
                     const struct ExchangeSignkeyData *signkey,
                     const struct DepositData *deposit,
                     enum GNUNET_DB_QueryStatus expected_result)
{
  uint64_t row;
  struct TALER_Amount awf;

  GNUNET_assert (0 <=
                 TALER_amount_subtract (&awf,
                                        &deposit->amount_with_fee,
                                        &deposit->deposit_fee));
  TEST_COND_RET_ON_FAIL (
    expected_result ==
    plugin->insert_deposit_confirmation (plugin->cls,
                                         instance->instance.id,
                                         deposit->timestamp,
                                         &deposit->h_contract_terms,
                                         deposit->exchange_url,
                                         deposit->timestamp,
                                         &awf,
                                         &deposit->wire_fee,
                                         &deposit->h_wire,
                                         &deposit->exchange_sig,
                                         &signkey->exchange_pub,
                                         &row),
    "Insert deposit confirmation failed\n");
  TEST_COND_RET_ON_FAIL (
    expected_result ==
    plugin->insert_deposit (plugin->cls,
                            0,
                            row,
                            &deposit->coin_pub,
                            &deposit->coin_sig,
                            &deposit->amount_with_fee,
                            &deposit->deposit_fee,
                            &deposit->refund_fee,
                            GNUNET_TIME_absolute_get ()),
    "Insert deposit failed\n");
  return 0;
}


/**
 * Closure for testing deposit lookup
 */
struct TestLookupDeposits_Closure
{
  /**
   * Number of deposits to compare to
   */
  unsigned int deposits_to_cmp_length;

  /**
   * Pointer to array of deposit data
   */
  const struct DepositData *deposits_to_cmp;

  /**
   * Pointer to array of number of matches per deposit
   */
  unsigned int *results_matching;

  /**
   * Total number of results returned
   */
  unsigned int results_length;
};


/**
 * Called after 'test_lookup_deposits'.
 *
 * @param cls pointer to the test lookup closure.
 * @param coin_pub public key of the coin deposited.
 * @param amount_with_fee amount of the deposit with fees.
 * @param deposit_fee fee charged for the deposit.
 * @param refund_fee fee charged in case of a refund.
 */
static void
lookup_deposits_cb (void *cls,
                    const char *exchange_url,
                    const struct TALER_CoinSpendPublicKeyP *coin_pub,
                    const struct TALER_Amount *amount_with_fee,
                    const struct TALER_Amount *deposit_fee,
                    const struct TALER_Amount *refund_fee)
{
  struct TestLookupDeposits_Closure *cmp = cls;
  if (NULL == cmp)
    return;
  cmp->results_length += 1;
  for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i)
  {
    if ((GNUNET_OK ==
         TALER_amount_cmp_currency (
           &cmp->deposits_to_cmp[i].amount_with_fee,
           amount_with_fee)) &&
        (0 ==
         TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee,
                           amount_with_fee)) &&
        (GNUNET_OK ==
         TALER_amount_cmp_currency (
           &cmp->deposits_to_cmp[i].deposit_fee,
           deposit_fee)) &&
        (0 ==
         TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee,
                           deposit_fee)) &&
        (GNUNET_OK ==
         TALER_amount_cmp_currency (
           &cmp->deposits_to_cmp[i].refund_fee,
           refund_fee)) &&
        (0 ==
         TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee,
                           refund_fee)))
    {
      cmp->results_matching[i] += 1;
    }

  }
}


/**
 * Tests looking up deposits from the database.
 *
 * @param instance the instance to lookup deposits from.
 * @param h_contract_terms the contract terms that the deposits should have.
 * @param deposits_length length of @e deposits.
 * @param deposits the deposits we expect to be found.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_deposits (const struct InstanceData *instance,
                      const struct TALER_PrivateContractHashP *h_contract_terms,
                      unsigned int deposits_length,
                      const struct DepositData *deposits)
{
  unsigned int results_matching[GNUNET_NZL (deposits_length)];
  struct TestLookupDeposits_Closure cmp = {
    .deposits_to_cmp_length = deposits_length,
    .deposits_to_cmp = deposits,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching,
          0,
          sizeof (unsigned int) * deposits_length);
  TEST_COND_RET_ON_FAIL (0 <=
                         plugin->lookup_deposits (plugin->cls,
                                                  instance->instance.id,
                                                  h_contract_terms,
                                                  &lookup_deposits_cb,
                                                  &cmp),
                         "Lookup deposits failed\n");
  if (deposits_length != cmp.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup deposits failed: incorrect number of results returned (%d)\n",
                cmp.results_length);
    return 1;
  }
  for (unsigned int i = 0; deposits_length > i; ++i)
  {
    if (cmp.results_matching[i] != 1)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup deposits failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Called after 'test_lookup_deposits_contract_and_coin'.
 *
 * @param cls pointer to the test lookup closure.
 * @param exchange_url URL to the exchange
 * @param amount_with_fee amount of the deposit with fees.
 * @param deposit_fee fee charged for the deposit.
 * @param refund_fee fee charged in case of a refund.
 * @param wire_fee fee charged when the money is wired.
 * @param h_wire hash of the wire transfer details.
 * @param deposit_timestamp when the deposit was made.
 * @param refund_deadline deadline for refunding the deposit.
 * @param exchange_sig signature the exchange made on the deposit.
 * @param exchange_pub public key of the exchange.
 */
static void
lookup_deposits_contract_coin_cb (
  void *cls,
  const char *exchange_url,
  const struct TALER_Amount *amount_with_fee,
  const struct TALER_Amount *deposit_fee,
  const struct TALER_Amount *refund_fee,
  const struct TALER_Amount *wire_fee,
  const struct TALER_MerchantWireHashP *h_wire,
  struct GNUNET_TIME_Timestamp deposit_timestamp,
  struct GNUNET_TIME_Timestamp refund_deadline,
  const struct TALER_ExchangeSignatureP *exchange_sig,
  const struct TALER_ExchangePublicKeyP *exchange_pub)
{
  struct TestLookupDeposits_Closure *cmp = cls;

  if (NULL == cmp)
    return;
  cmp->results_length++;
  for (unsigned int i = 0; cmp->deposits_to_cmp_length > i; ++i)
  {
    if ((GNUNET_TIME_timestamp_cmp (cmp->deposits_to_cmp[i].timestamp,
                                    ==,
                                    deposit_timestamp)) &&
        (0 == strcmp (cmp->deposits_to_cmp[i].exchange_url,
                      exchange_url)) &&
        (GNUNET_OK == TALER_amount_cmp_currency (
           &cmp->deposits_to_cmp[i].amount_with_fee,
           amount_with_fee)) &&
        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee,
                                amount_with_fee)) &&
        (GNUNET_OK == TALER_amount_cmp_currency (
           &cmp->deposits_to_cmp[i].deposit_fee,
           deposit_fee)) &&
        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee,
                                deposit_fee)) &&
        (GNUNET_OK == TALER_amount_cmp_currency (
           &cmp->deposits_to_cmp[i].refund_fee,
           refund_fee)) &&
        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].refund_fee,
                                refund_fee)) &&
        (GNUNET_OK == TALER_amount_cmp_currency (
           &cmp->deposits_to_cmp[i].wire_fee,
           wire_fee)) &&
        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].wire_fee,
                                wire_fee)) &&
        (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire,
                             h_wire)) &&
        (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].exchange_sig,
                             exchange_sig)))
    {
      cmp->results_matching[i]++;
    }
  }
}


/**
 * Tests lookup of deposits by contract and coin.
 *
 * @param instance the instance to lookup from.
 * @param h_contract the contract terms the deposits should have.
 * @param coin_pub the public key of the coin the deposits should have.
 * @param deposits_length length of @e deposits.
 * @param deposits the deposits the db is expected to find.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_deposits_contract_and_coin (
  const struct InstanceData *instance,
  const struct TALER_PrivateContractHashP *h_contract,
  const struct TALER_CoinSpendPublicKeyP *coin_pub,
  unsigned int deposits_length,
  const struct DepositData *deposits)
{
  unsigned int results_matching[deposits_length];
  struct TestLookupDeposits_Closure cmp = {
    .deposits_to_cmp_length = deposits_length,
    .deposits_to_cmp = deposits,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching,
          0,
          sizeof (unsigned int) * deposits_length);
  TEST_COND_RET_ON_FAIL (
    GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
    plugin->lookup_deposits_by_contract_and_coin (
      plugin->cls,
      instance->instance.id,
      h_contract,
      coin_pub,
      &lookup_deposits_contract_coin_cb,
      &cmp),
    "Lookup deposits by contract and coin failed\n");
  if (deposits_length != cmp.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup deposits failed: incorrect number of results returned\n");
    return 1;
  }
  for (unsigned int i = 0; deposits_length > i; ++i)
  {
    if (cmp.results_matching[i] != 1)
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup deposits failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Called after 'test_lookup_deposits_by_order'.
 *
 * @param cls pointer to the test lookup closure.
 * @param deposit_serial row number of the deposit in the database.
 * @param exchange_url URL to the exchange
 * @param h_wire hash of the wire transfer details.
 * @param deposit_timestamp when was the deposit made
 * @param amount_with_fee amount of the deposit with fees.
 * @param deposit_fee fee charged for the deposit.
 * @param coin_pub public key of the coin deposited.
 */
static void
lookup_deposits_order_cb (void *cls,
                          uint64_t deposit_serial,
                          const char *exchange_url,
                          const struct TALER_MerchantWireHashP *h_wire,
                          struct GNUNET_TIME_Timestamp deposit_timestamp,
                          const struct TALER_Amount *amount_with_fee,
                          const struct TALER_Amount *deposit_fee,
                          const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
  struct TestLookupDeposits_Closure *cmp = cls;

  if (NULL == cmp)
    return;
  cmp->results_length += 1;
  for (unsigned int i = 0; i < cmp->deposits_to_cmp_length; ++i)
  {
    if ((0 == strcmp (cmp->deposits_to_cmp[i].exchange_url,
                      exchange_url)) &&
        (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].h_wire,
                             h_wire)) &&
        (GNUNET_OK == TALER_amount_cmp_currency (
           &cmp->deposits_to_cmp[i].amount_with_fee,
           amount_with_fee)) &&
        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].amount_with_fee,
                                amount_with_fee)) &&
        (GNUNET_OK == TALER_amount_cmp_currency (
           &cmp->deposits_to_cmp[i].deposit_fee,
           deposit_fee)) &&
        (0 == TALER_amount_cmp (&cmp->deposits_to_cmp[i].deposit_fee,
                                deposit_fee)) &&
        (0 == GNUNET_memcmp (&cmp->deposits_to_cmp[i].coin_pub,
                             coin_pub)))
      cmp->results_matching[i] += 1;
  }
}


/**
 * Tests looking up deposits by associated order.
 *
 * @param order_serial row number of the order to lookup for.
 * @param deposits_length length of @e deposits_length.
 * @param deposits the deposits we expect to be found.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_deposits_by_order (uint64_t order_serial,
                               unsigned int deposits_length,
                               const struct DepositData *deposits)
{
  unsigned int results_matching[deposits_length];
  struct TestLookupDeposits_Closure cmp = {
    .deposits_to_cmp_length = deposits_length,
    .deposits_to_cmp = deposits,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching,
          0,
          sizeof (unsigned int) * deposits_length);
  if (deposits_length !=
      plugin->lookup_deposits_by_order (plugin->cls,
                                        order_serial,
                                        &lookup_deposits_order_cb,
                                        &cmp))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup deposits by order failed\n");
    return 1;
  }
  if (deposits_length != cmp.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup deposits by order failed: incorrect number of results\n");
    return 1;
  }
  for (unsigned int i = 0; i < deposits_length; ++i)
  {
    if (1 != cmp.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup deposits by order failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Container for information for looking up the row number of a deposit.
 */
struct LookupDepositSerial_Closure
{
  /**
   * The deposit we're looking for.
   */
  const struct DepositData *deposit;

  /**
   * The serial found.
   */
  uint64_t serial;
};


/**
 * Called after 'get_deposit_serial'.
 *
 * @param cls pointer to the test lookup closure.
 * @param deposit_serial row number of the deposit in the database.
 * @param exchange_url URL to the exchange
 * @param h_wire hash of the wire transfer details.
 * @param deposit_timestamp when was the deposit made.
 * @param amount_with_fee amount of the deposit with fees.
 * @param deposit_fee fee charged for the deposit.
 * @param coin_pub public key of the coin deposited.
 */
static void
get_deposit_serial_cb (void *cls,
                       uint64_t deposit_serial,
                       const char *exchange_url,
                       const struct TALER_MerchantWireHashP *h_wire,
                       struct GNUNET_TIME_Timestamp deposit_timestamp,
                       const struct TALER_Amount *amount_with_fee,
                       const struct TALER_Amount *deposit_fee,
                       const struct TALER_CoinSpendPublicKeyP *coin_pub)
{
  struct LookupDepositSerial_Closure *lookup_cls = cls;

  (void) deposit_timestamp;
  if (NULL == lookup_cls)
    return;
  if ((0 == strcmp (lookup_cls->deposit->exchange_url,
                    exchange_url)) &&
      (0 == GNUNET_memcmp (&lookup_cls->deposit->h_wire,
                           h_wire)) &&
      (GNUNET_OK == TALER_amount_cmp_currency (
         &lookup_cls->deposit->amount_with_fee,
         amount_with_fee)) &&
      (0 == TALER_amount_cmp (&lookup_cls->deposit->amount_with_fee,
                              amount_with_fee)) &&
      (GNUNET_OK == TALER_amount_cmp_currency (
         &lookup_cls->deposit->deposit_fee,
         deposit_fee)) &&
      (0 == TALER_amount_cmp (&lookup_cls->deposit->deposit_fee,
                              deposit_fee)) &&
      (0 == GNUNET_memcmp (&lookup_cls->deposit->coin_pub,
                           coin_pub)))
    lookup_cls->serial = deposit_serial;
}


/**
 * Convenience function to retrieve the row number of a deposit in the database.
 *
 * @param instance the instance to get deposits from.
 * @param order the order associated with the deposit.
 * @param deposit the deposit to lookup the serial for.
 * @return the row number of the deposit.
 */
static uint64_t
get_deposit_serial (const struct InstanceData *instance,
                    const struct OrderData *order,
                    const struct DepositData *deposit)
{
  uint64_t order_serial = get_order_serial (instance,
                                            order);
  struct LookupDepositSerial_Closure lookup_cls = {
    .deposit = deposit,
    .serial = 0
  };

  GNUNET_assert (0 <
                 plugin->lookup_deposits_by_order (plugin->cls,
                                                   order_serial,
                                                   &get_deposit_serial_cb,
                                                   &lookup_cls));
  GNUNET_assert (0 != lookup_cls.serial);

  return lookup_cls.serial;
}


/**
 * Closure for deposit tests.
 */
struct TestDeposits_Closure
{
  /**
   * The instance settings
   */
  struct InstanceData instance;

  /**
   * The merchant account
   */
  struct TALER_MERCHANTDB_AccountDetails account;

  /**
   * The exchange signing key
   */
  struct ExchangeSignkeyData signkey;

  /**
   * The order data
   */
  struct OrderData orders[2];

  /**
   * The array of deposits
   */
  struct DepositData deposits[3];
};


/**
 * Initializes data for testing deposits.
 *
 * @param cls the test closure to initialize.
 */
static void
pre_test_deposits (struct TestDeposits_Closure *cls)
{
  /* Instance */
  make_instance ("test_inst_deposits",
                 &cls->instance);

  /* Account */
  make_account (&cls->account);
  cls->account.instance_id = cls->instance.instance.id;
  /* Signing key */
  make_exchange_signkey (&cls->signkey);

  /* Order */
  make_order ("test_deposits_od_1",
              &cls->orders[0]);
  make_order ("test_deposits_od_2",
              &cls->orders[1]);

  /* Deposit */
  make_deposit (&cls->instance,
                &cls->account,
                &cls->orders[0],
                &cls->signkey,
                &cls->deposits[0]);
  make_deposit (&cls->instance,
                &cls->account,
                &cls->orders[0],
                &cls->signkey,
                &cls->deposits[1]);
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:29.00",
                                         &cls->deposits[1].amount_with_fee));
  make_deposit (&cls->instance,
                &cls->account,
                &cls->orders[1],
                &cls->signkey,
                &cls->deposits[2]);
}


/**
 * Cleans up memory after testing deposits.
 *
 * @param cls the closure containing memory to free.
 */
static void
post_test_deposits (struct TestDeposits_Closure *cls)
{
  free_instance_data (&cls->instance);
  json_decref (cls->orders[0].contract);
  json_decref (cls->orders[1].contract);
}


/**
 * Runs tests for deposits.
 *
 * @param cls the closure containing test data.
 * @return 0 on success, 1 otherwise.
 */
static int
run_test_deposits (struct TestDeposits_Closure *cls)
{
  /* Insert the instance */
  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert an account */
  TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
                                         &cls->account,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert a signing key */
  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
                                                  GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
                                                  GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Insert an order */
  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                       &cls->orders[0],
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert contract terms */
  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
                                                &cls->orders[0],
                                                GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test inserting a deposit */
  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
                                         &cls->signkey,
                                         &cls->deposits[0],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double inserts fail */
  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
                                         &cls->signkey,
                                         &cls->deposits[0],
                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test lookup deposits */
  TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
                                          &cls->deposits[0].h_contract_terms,
                                          1,
                                          cls->deposits));
  TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
                                          &cls->deposits[2].h_contract_terms,
                                          0,
                                          NULL));
  /* Test lookup deposits by contract and coins */
  TEST_RET_ON_FAIL (test_lookup_deposits_contract_and_coin (
                      &cls->instance,
                      &cls->deposits[0].h_contract_terms,
                      &cls->deposits[0].coin_pub,
                      1,
                      cls->deposits));
  /* Test multiple deposits */
  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
                                         &cls->signkey,
                                         &cls->deposits[1],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                       &cls->orders[1],
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
                                                &cls->orders[1],
                                                GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
                                         &cls->signkey,
                                         &cls->deposits[2],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
                                          &cls->deposits[0].h_contract_terms,
                                          2,
                                          cls->deposits));
  TEST_RET_ON_FAIL (test_lookup_deposits (&cls->instance,
                                          &cls->deposits[2].h_contract_terms,
                                          1,
                                          &cls->deposits[2]));
  /* Test lookup deposits by order */
  {
    uint64_t order_serial = get_order_serial (&cls->instance,
                                              &cls->orders[0]);
    TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial,
                                                     2,
                                                     cls->deposits));
    order_serial = get_order_serial (&cls->instance,
                                     &cls->orders[1]);
    TEST_RET_ON_FAIL (test_lookup_deposits_by_order (order_serial,
                                                     1,
                                                     &cls->deposits[2]));
  }
  return 0;
}


/**
 * Handles functionality for testing deposits.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_deposits (void)
{
  struct TestDeposits_Closure test_cls;
  int test_result;

  pre_test_deposits (&test_cls);
  test_result = run_test_deposits (&test_cls);
  post_test_deposits (&test_cls);
  return test_result;
}


/* *********** Transfers ********** */


/**
 * Container for wire fee data for an exchange.
 */
struct WireFeeData
{
  /**
   * The method used.
   */
  const char *wire_method;

  /**
   * Hash of the wire method.
   */
  struct GNUNET_HashCode h_wire_method;

  /**
   * Wire fees charged.
   */
  struct TALER_WireFeeSet fees;

  /**
   * Start date of the wire fee.
   */
  struct GNUNET_TIME_Timestamp wire_fee_start;

  /**
   * End date of the wire fee.
   */
  struct GNUNET_TIME_Timestamp wire_fee_end;

  /**
   * Signature on the wire fee.
   */
  struct TALER_MasterSignatureP fee_sig;
};


/**
 * Creates data for an exchange wire fee.
 *
 * @param signkey the exchange signing key data.
 * @param wire_fee where to store the wire fee data.
 */
static void
make_wire_fee (const struct ExchangeSignkeyData *signkey,
               struct WireFeeData *wire_fee)
{
  wire_fee->wire_method = "wire-method";
  GNUNET_CRYPTO_hash (wire_fee->wire_method,
                      strlen (wire_fee->wire_method) + 1,
                      &wire_fee->h_wire_method);
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:0.49",
                                         &wire_fee->fees.wire));
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:0.49",
                                         &wire_fee->fees.closing));
  wire_fee->wire_fee_start = GNUNET_TIME_timestamp_get ();
  wire_fee->wire_fee_end = GNUNET_TIME_relative_to_timestamp (
    GNUNET_TIME_UNIT_MONTHS);
  TALER_exchange_offline_wire_fee_sign (
    wire_fee->wire_method,
    wire_fee->wire_fee_start,
    wire_fee->wire_fee_end,
    &wire_fee->fees,
    &signkey->master_priv,
    &wire_fee->fee_sig);
}


/**
 * Container for wire transfer data.
 */
struct TransferData
{
  /**
   * Id of the transfer.
   */
  struct TALER_WireTransferIdentifierRawP wtid;

  /**
   * The main data for the transfer.
   */
  struct TALER_EXCHANGE_TransferData data;

  /**
   * URL to the exchange the transfer was made through.
   */
  const char *exchange_url;

  /**
   * How much the fee for the deposit was.
   */
  struct TALER_Amount deposit_fee;

  /**
   * Whether the transfer has been confirmed.
   */
  bool confirmed;

  /**
   * Whether the transfer has been verified.
   */
  bool verified;
};


/**
 * Creates a transfer for use with testing.
 *
 * @param deposits_length length of @e deposits.
 * @param deposits list of deposits to combine into one transfer.
 * @param transfer where to write the transfer data.
 */
static void
make_transfer (const struct ExchangeSignkeyData *signkey,
               unsigned int deposits_length,
               const struct DepositData deposits[static deposits_length],
               struct TransferData *transfer)
{
  struct TALER_TrackTransferDetails *details = NULL;

  GNUNET_CRYPTO_seed_weak_random (585);
  GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
                              &transfer->wtid,
                              sizeof (struct TALER_WireTransferIdentifierRawP));
  transfer->exchange_url = deposits[0].exchange_url;
  transfer->verified = false;
  transfer->confirmed = false;
  transfer->data.details_length = 0;
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (deposits[0].amount_with_fee.currency,
                                        &transfer->data.total_amount));
  GNUNET_assert (GNUNET_OK ==
                 TALER_amount_set_zero (deposits[0].amount_with_fee.currency,
                                        &transfer->deposit_fee));
  for (unsigned int i = 0; i < deposits_length; ++i)
  {
    GNUNET_array_grow (details,
                       transfer->data.details_length,
                       i + 1);
    details[i].h_contract_terms = deposits[i].h_contract_terms;
    details[i].coin_pub = deposits[i].coin_pub;
    details[i].coin_value = deposits[i].amount_with_fee;
    details[i].coin_fee = deposits[i].deposit_fee;
    GNUNET_assert (0 <=
                   TALER_amount_add (&transfer->data.total_amount,
                                     &transfer->data.total_amount,
                                     &deposits[i].amount_with_fee));
    GNUNET_assert (0 <=
                   TALER_amount_add (&transfer->deposit_fee,
                                     &transfer->deposit_fee,
                                     &deposits[i].deposit_fee));
  }
  transfer->data.exchange_pub = signkey->exchange_pub;
  transfer->data.execution_time = GNUNET_TIME_timestamp_get ();
  transfer->data.details = details;
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:0.50",
                                         &transfer->data.wire_fee));
}


/**
 * Closure for testing 'lookup_transfer_summary'
 */
struct TestLookupTransferSummary_Closure
{
  /**
   * Id of the order the transfer was made for.
   */
  const char *order_id;

  /**
   * The value of the deposit made.
   */
  const struct TALER_Amount *deposit_value;

  /**
   * The fee on the deposit made.
   */
  const struct TALER_Amount *deposit_fee;

  /**
   * 0 if the comparison is true, 1 if false.
   */
  int result;
};


/**
 * Called after 'test_lookup_transfer_summary'.
 *
 * @param cls pointer to 'TestLookupTransferSummary_Closure'.
 * @param order_id id of the order the transfer was made for.
 * @param deposit_value the value of the deposit made.
 * @param deposit_fee the fee on the deposit made.
 */
static void
lookup_transfer_summary_cb (void *cls,
                            const char *order_id,
                            const struct TALER_Amount *deposit_value,
                            const struct TALER_Amount *deposit_fee)
{
  struct TestLookupTransferSummary_Closure *cmp = cls;
  if (NULL == cmp)
    return;
  if ((0 == strcmp (cmp->order_id,
                    order_id)) &&
      (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_value,
                                               deposit_value)) &&
      (0 == TALER_amount_cmp (cmp->deposit_value,
                              deposit_value)) &&
      (GNUNET_OK == TALER_amount_cmp_currency (cmp->deposit_fee,
                                               deposit_fee)) &&
      (0 == TALER_amount_cmp (cmp->deposit_fee,
                              deposit_fee)))
    cmp->result = 0;
  else
    cmp->result = 1;
}


/**
 * Tests looking up a transfer's summary.
 *
 * @param exchange_url url to the exchange for the transfer.
 * @param wtid identifier of the transfer.
 * @param expected_order_id the id of the order associated with the transfer.
 * @param expected_deposit_value the amount of the deposit made for the transfer.
 * @param expected_deposit_fee the fee on the deposit made for the transfer.
 * @return 1 on success, 0 otherwise.
 */
static int
test_lookup_transfer_summary (
  const char *exchange_url,
  const struct TALER_WireTransferIdentifierRawP *wtid,
  const char *expected_order_id,
  const struct TALER_Amount *expected_deposit_value,
  const struct TALER_Amount *expected_deposit_fee)
{
  struct TestLookupTransferSummary_Closure cmp = {
    .order_id = expected_order_id,
    .deposit_value = expected_deposit_value,
    .deposit_fee = expected_deposit_fee,
    .result = 0
  };
  if (1 != plugin->lookup_transfer_summary (plugin->cls,
                                            exchange_url,
                                            wtid,
                                            &lookup_transfer_summary_cb,
                                            &cmp))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup transfer summary failed\n");
    return 1;
  }
  if (0 != cmp.result)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup transfer summary failed: mismatched data\n");
    return 1;
  }
  return 0;
}


/**
 * Closure for testing 'lookup_transfer_details'.
 */
struct TestLookupTransferDetails_Closure
{
  /**
   * Length of @e details_to_cmp.
   */
  unsigned int details_to_cmp_length;

  /**
   * The details we expect to find.
   */
  const struct TALER_TrackTransferDetails *details_to_cmp;

  /**
   * Number of results matching each detail in @e details_to_cmp.
   */
  unsigned int *results_matching;

  /**
   * Total number of results found.
   */
  unsigned int results_length;
};


/**
 * Called after 'test_lookup_transfer_details'.
 *
 * @param cls pointer to 'TestLookupTransferDetails_Closure'.
 * @param current_offset offset within transfer details.
 * @param details the details that were found.
 */
static void
lookup_transfer_details_cb (void *cls,
                            unsigned int current_offset,
                            const struct TALER_TrackTransferDetails *details)
{
  struct TestLookupTransferDetails_Closure *cmp = cls;
  if (NULL == cmp)
    return;
  for (unsigned int i = 0; cmp->details_to_cmp_length > i; ++i)
  {
    if ((0 == GNUNET_memcmp (&cmp->details_to_cmp[i].h_contract_terms,
                             &details->h_contract_terms)) &&
        (0 == GNUNET_memcmp (&cmp->details_to_cmp[i].coin_pub,
                             &details->coin_pub)) &&
        (GNUNET_OK == TALER_amount_cmp_currency (
           &cmp->details_to_cmp[i].coin_value,
           &details->coin_value)) &&
        (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_value,
                                &details->coin_value)) &&
        (GNUNET_OK == TALER_amount_cmp_currency (
           &cmp->details_to_cmp[i].coin_fee,
           &details->coin_fee)) &&
        (0 == TALER_amount_cmp (&cmp->details_to_cmp[i].coin_fee,
                                &details->coin_fee)))
    {
      cmp->results_matching[i] += 1;
    }
  }
  cmp->results_length += 1;
}


/**
 * Tests looking up details for a wire transfer.
 *
 * @param exchange_url url to the exchange.
 * @param wtid id of the transfer.
 * @param details_length the length of @e details.
 * @param details the details we expect to be returned.
 * @return 1 on success, 0 otherwise.
 */
static int
test_lookup_transfer_details (
  const char *exchange_url,
  const struct TALER_WireTransferIdentifierRawP *wtid,
  unsigned int details_length,
  const struct TALER_TrackTransferDetails *details)
{
  unsigned int results_matching[details_length];
  struct TestLookupTransferDetails_Closure cmp = {
    .details_to_cmp_length = details_length,
    .details_to_cmp = details,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching,
          0,
          sizeof (unsigned int) * details_length);
  if (1 != plugin->lookup_transfer_details (plugin->cls,
                                            exchange_url,
                                            wtid,
                                            &lookup_transfer_details_cb,
                                            &cmp))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup transfer details failed\n");
    return 1;
  }
  if (details_length != cmp.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup transfer details failed: incorrect number of results (%d)\n",
                cmp.results_length);
    return 1;
  }
  for (unsigned int i = 0; details_length > i; ++i)
  {
    if (1 != cmp.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup transfer details failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Closure for 'lookup_transfer_details_by_order'.
 */
struct TestLookupTransferDetailsByOrder_Closure
{
  /**
   * Length of @e transfers_to_cmp.
   */
  unsigned int transfers_to_cmp_length;

  /**
   * List of transfers that we expect to find.
   */
  const struct TransferData *transfers_to_cmp;

  /**
   * How many results match the corresponding element of @e transfers_to_cmp.
   */
  unsigned int *results_matching;

  /**
   * Total number of results found.
   */
  unsigned int results_length;
};


/**
 * Called after 'test_lookup_transfer_details_by_order'.
 *
 * @param cls pointer to 'TestLookupTransferDetailsByOrder_Closure'.
 * @param wtid identifier of the transfer found.
 * @param exchange_url exchange url of the transfer found.
 * @param execution_time when the transfer found occurred.
 * @param deposit_value amount of the deposit for the transfer found.
 * @param deposit_fee amount of the fee for the deposit of the transfer.
 * @param transfer_confirmed did the merchant confirm that a wire transfer with
 *        @a wtid over the total amount happened?
 */
static void
lookup_transfer_details_order_cb (
  void *cls,
  const struct TALER_WireTransferIdentifierRawP *wtid,
  const char *exchange_url,
  struct GNUNET_TIME_Timestamp execution_time,
  const struct TALER_Amount *deposit_value,
  const struct TALER_Amount *deposit_fee,
  bool transfer_confirmed)
{
  struct TestLookupTransferDetailsByOrder_Closure *cmp = cls;

  if (NULL == cmp)
    return;
  cmp->results_length += 1;
  for (unsigned int i = 0; i < cmp->transfers_to_cmp_length; ++i)
  {
    /* Right now lookup_transfer_details_by_order leaves execution_time
       uninitialized */
    if ((0 == GNUNET_memcmp (&cmp->transfers_to_cmp[i].wtid,
                             wtid)) &&
        (0 == strcmp (cmp->transfers_to_cmp[i].exchange_url,
                      exchange_url)) &&
        (GNUNET_OK ==
         TALER_amount_cmp_currency (
           &cmp->transfers_to_cmp[i].data.total_amount,
           deposit_value)) &&
        (0 ==
         TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
                           deposit_value)) &&
        (GNUNET_OK ==
         TALER_amount_cmp_currency (
           &cmp->transfers_to_cmp[i].deposit_fee,
           deposit_fee)) &&
        (0 ==
         TALER_amount_cmp (&cmp->transfers_to_cmp[i].deposit_fee,
                           deposit_fee)) )
      cmp->results_matching[i] += 1;
  }
}


/**
 * Tests looking up wire transfers associated with an order.
 *
 * @param order_serial the order to be queried.
 * @param transfers_length length of @e transfers.
 * @param transfers the transfers we expect to be found.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_transfer_details_by_order (
  uint64_t order_serial,
  unsigned int transfers_length,
  const struct TransferData *transfers)
{
  unsigned int results_matching[transfers_length];
  struct TestLookupTransferDetailsByOrder_Closure cmp = {
    .transfers_to_cmp_length = transfers_length,
    .transfers_to_cmp = transfers,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching,
          0,
          sizeof (unsigned int) * transfers_length);
  if (transfers_length !=
      plugin->lookup_transfer_details_by_order (
        plugin->cls,
        order_serial,
        &lookup_transfer_details_order_cb,
        &cmp))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup transfer details by order failed\n");
    return 1;
  }
  if (transfers_length != cmp.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup transfer details by order failed: incorrect number of results\n");
    return 1;
  }
  for (unsigned int i = 0; i < transfers_length; ++i)
  {
    if (1 != cmp.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup transfer details by order failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Tests inserting wire fee data for an exchange.
 *
 * @param signkey the signing key for the exchange.
 * @param wire_fee the wire fee data.
 * @param expected_result what the database should return.
 * @return 0 on success, 1 otherwise.
 */
static int
test_insert_wire_fee (const struct ExchangeSignkeyData *signkey,
                      const struct WireFeeData *wire_fee,
                      enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->store_wire_fee_by_exchange (
                           plugin->cls,
                           &signkey->master_pub,
                           &wire_fee->h_wire_method,
                           &wire_fee->fees,
                           wire_fee->wire_fee_start,
                           wire_fee->wire_fee_end,
                           &wire_fee->fee_sig),
                         "Store wire fee by exchange failed\n");
  return 0;
}


/**
 * Tests looking up wire fee data for an exchange.
 *
 * @param signkey the signing key to use for lookup.
 * @param wire_fee_data the wire fee data we expect to find.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_wire_fee (const struct ExchangeSignkeyData *signkey,
                      const struct WireFeeData *wire_fee_data)
{
  struct TALER_WireFeeSet fees;
  struct GNUNET_TIME_Timestamp start_date;
  struct GNUNET_TIME_Timestamp end_date;
  struct TALER_MasterSignatureP master_sig;
  if (1 != plugin->lookup_wire_fee (plugin->cls,
                                    &signkey->master_pub,
                                    wire_fee_data->wire_method,
                                    GNUNET_TIME_timestamp_get (),
                                    &fees,
                                    &start_date,
                                    &end_date,
                                    &master_sig))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup wire fee failed\n");
    return 1;
  }
  if ((0 !=
       TALER_wire_fee_set_cmp (&wire_fee_data->fees,
                               &fees)) ||
      (GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_start,
                                  !=,
                                  start_date)) ||
      (GNUNET_TIME_timestamp_cmp (wire_fee_data->wire_fee_end,
                                  !=,
                                  end_date)) ||
      (0 != GNUNET_memcmp (&wire_fee_data->fee_sig,
                           &master_sig)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup wire fee failed: mismatched data\n");
    return 1;
  }
  return 0;
}


/**
 * Closure for 'lookup_transfers'.
 */
struct TestLookupTransfers_Closure
{
  /**
   * Length of @e transfers_to_cmp.
   */
  unsigned int transfers_to_cmp_length;

  /**
   * The transfers we expect to find.
   */
  const struct TransferData *transfers_to_cmp;

  /**
   * Number of results matching each transfer.
   */
  unsigned int *results_matching;

  /**
   * Total number of results found.
   */
  unsigned int results_length;
};


/**
 * Function called after 'test_lookup_transfers'.
 *
 * @param cls pointer to 'TestLookupTransfers_Closure'.
 * @param credit_amount how much was wired to the merchant (minus fees)
 * @param wtid wire transfer identifier
 * @param payto_uri target account that received the wire transfer
 * @param exchange_url base URL of the exchange that made the wire transfer
 * @param transfer_serial_id serial number identifying the transfer in the backend
 * @param execution_time when did the exchange make the transfer, #GNUNET_TIME_UNIT_FOREVER_TS
 *           if it did not yet happen
 * @param confirmed true if the merchant confirmed this wire transfer
 *                 false if it is so far only claimed to have been made by the exchange
 */
static void
lookup_transfers_cb (void *cls,
                     const struct TALER_Amount *credit_amount,
                     const struct TALER_WireTransferIdentifierRawP *wtid,
                     struct TALER_FullPayto payto_uri,
                     const char *exchange_url,
                     uint64_t transfer_serial_id,
                     struct GNUNET_TIME_Absolute execution_time,
                     bool confirmed)
{
  struct TestLookupTransfers_Closure *cmp = cls;
  if (NULL == cmp)
    return;
  for (unsigned int i = 0; cmp->transfers_to_cmp_length > i; ++i)
  {
    if ( (GNUNET_OK ==
          TALER_amount_cmp_currency (
            &cmp->transfers_to_cmp[i].data.total_amount,
            credit_amount)) &&
         (0 == TALER_amount_cmp (&cmp->transfers_to_cmp[i].data.total_amount,
                                 credit_amount)) )
    {
      cmp->results_matching[i]++;
    }
  }
  cmp->results_length++;
}


/**
 * Tests looking up transfers from the database.
 *
 * @param instance the instance to lookup from.
 * @param account the account the transfer was made to.
 * @param before do not return transfers before this time.
 * @param after do not return transfers after this time.
 * @param limit maximum number of transfers to return.
 * @param offset row in the database to start with.
 * @param filter_verified how to filter verified transfers.
 * @param transfers_length length of @e transfers.
 * @param transfers the transfers we expect to find.
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_transfers (const struct InstanceData *instance,
                       const struct TALER_MERCHANTDB_AccountDetails *account,
                       struct GNUNET_TIME_Timestamp before,
                       struct GNUNET_TIME_Timestamp after,
                       int64_t limit,
                       uint64_t offset,
                       unsigned int transfers_length,
                       const struct TransferData *transfers)
{
  unsigned int results_matching[transfers_length];
  struct TestLookupTransfers_Closure cmp = {
    .transfers_to_cmp_length = transfers_length,
    .transfers_to_cmp = transfers,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching,
          0,
          sizeof (unsigned int) * transfers_length);
  if (1 != plugin->lookup_transfers (plugin->cls,
                                     instance->instance.id,
                                     account->payto_uri,
                                     before,
                                     after,
                                     limit,
                                     offset,
                                     TALER_EXCHANGE_YNA_ALL,
                                     &lookup_transfers_cb,
                                     &cmp))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup transfers failed\n");
    return 1;
  }
  if (transfers_length != cmp.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup transfers failed: incorrect number of results\n");
    return 1;
  }
  for (unsigned int i = 0; transfers_length > i; ++i)
  {
    if (1 != cmp.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup transfers failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Tests inserting a transfer into the database.
 *
 * @param instance the instance to use.
 * @param account the account to transfer to.
 * @param transfer the transfer to insert.
 * @param expected_result the result we expect the db to return.
 * @return 0 on success, 1 otherwise.
 */
static int
test_insert_transfer (const struct InstanceData *instance,
                      const struct TALER_MERCHANTDB_AccountDetails *account,
                      const struct TransferData *transfer,
                      enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_transfer (plugin->cls,
                                                  instance->instance.id,
                                                  transfer->exchange_url,
                                                  &transfer->wtid,
                                                  &transfer->data.total_amount,
                                                  account->payto_uri,
                                                  transfer->confirmed),
                         "Insert transfer failed\n");
  return 0;
}


/**
 * Tests linking a deposit to a transfer.
 *
 * @param instance the instance that the deposit and transfer are for.
 * @param signkey the signing used on the deposit.
 * @param order the order the deposit and transfer were made for.
 * @param transfer the transfer.
 * @param expected_result the result the database should return.
 * @param expected_cleared clearance status the database should return.
 * @return 0 on success, 1 otherwise.
 */
static int
test_insert_deposit_to_transfer (const struct InstanceData *instance,
                                 const struct ExchangeSignkeyData *signkey,
                                 const struct OrderData *order,
                                 const struct DepositData *deposit,
                                 const struct TransferData *transfer,
                                 enum GNUNET_DB_QueryStatus expected_result,
                                 bool expect_cleared)
{
  const struct TALER_EXCHANGE_DepositData deposit_data = {
    .exchange_pub = signkey->exchange_pub,
    .exchange_sig = deposit->exchange_sig,
    .wtid = transfer->wtid,
    .execution_time = transfer->data.execution_time,
    .coin_contribution = deposit->amount_with_fee
  };
  uint64_t deposit_serial = get_deposit_serial (instance,
                                                order,
                                                deposit);

  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_deposit_to_transfer (
                           plugin->cls,
                           deposit_serial,
                           &deposit->h_wire,
                           deposit->exchange_url,
                           &deposit_data),
                         "insert deposit to transfer failed\n");
  return 0;
}


/**
 * Inserts details for a transfer into the database.
 *
 * @param instance the instance the transfer is in.
 * @param account the destination account for the transfer.
 * @param transfer the transfer we are adding details to.
 * @param expected_result the result expected from the db.
 * @return 0 on success, 1 otherwise.
 */
static int
test_insert_transfer_details (
  const struct InstanceData *instance,
  const struct TALER_MERCHANTDB_AccountDetails *account,
  const struct TransferData *transfer,
  enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_transfer_details (
                           plugin->cls,
                           instance->instance.id,
                           transfer->exchange_url,
                           account->payto_uri,
                           &transfer->wtid,
                           &transfer->data),
                         "Insert transfer details failed\n");
  return 0;
}


/**
 * Container for data used when testing transfers.
 */
struct TestTransfers_Closure
{
  /**
   * The instance.
   */
  struct InstanceData instance;

  /**
   * The account.
   */
  struct TALER_MERCHANTDB_AccountDetails account;

  /**
   * The exchange signing key.
   */
  struct ExchangeSignkeyData signkey;

  /**
   * The order data.
   */
  struct OrderData order;

  /**
   * The deposit data.
   */
  struct DepositData deposit;

  /**
   * Wire fee data.
   */
  struct WireFeeData wire_fee[2];

  /**
   * The transfers.
   */
  struct TransferData transfers[1];
};


/**
 * Prepares for testing transfers.
 *
 * @param cls the test data.
 */
static void
pre_test_transfers (struct TestTransfers_Closure *cls)
{
  /* Instance */
  make_instance ("test_inst_transfers",
                 &cls->instance);

  /* Account */
  make_account (&cls->account);
  cls->account.instance_id = cls->instance.instance.id;
  /* Order */
  make_order ("test_transfers_od_1",
              &cls->order);

  /* Signing key */
  make_exchange_signkey (&cls->signkey);

  /* Deposit */
  make_deposit (&cls->instance,
                &cls->account,
                &cls->order,
                &cls->signkey,
                &cls->deposit);

  /* Wire fee */
  make_wire_fee (&cls->signkey,
                 &cls->wire_fee[0]);
  make_wire_fee (&cls->signkey,
                 &cls->wire_fee[1]);
  cls->wire_fee[1].wire_method = "wire-method-2";
  GNUNET_CRYPTO_hash (cls->wire_fee[1].wire_method,
                      strlen (cls->wire_fee[1].wire_method) + 1,
                      &cls->wire_fee[1].h_wire_method);

  /* Transfers */
  make_transfer (&cls->signkey,
                 1,
                 &cls->deposit,
                 &cls->transfers[0]);
  cls->transfers[0].confirmed = true;
}


/**
 * Cleans up after testing transfers.
 *
 * @param cls the test data.
 */
static void
post_test_transfers (struct TestTransfers_Closure *cls)
{
  GNUNET_array_grow (cls->transfers->data.details,
                     cls->transfers->data.details_length,
                     0);
  free_instance_data (&cls->instance);
  free_order_data (&cls->order);
}


/**
 * Runs the tests for transfers.
 *
 * @param cls the test data.
 * @return 0 on success, 1 otherwise.
 */
static int
run_test_transfers (struct TestTransfers_Closure *cls)
{
  uint64_t order_serial;
  struct TALER_WireFeeSet fees;

  /* Test lookup wire fee fails when it isn't in the db */
  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
                         plugin->lookup_wire_fee (plugin->cls,
                                                  &cls->signkey.master_pub,
                                                  cls->wire_fee[0].wire_method,
                                                  GNUNET_TIME_timestamp_get (),
                                                  &fees,
                                                  NULL,
                                                  NULL,
                                                  NULL),
                         "Lookup wire fee failed\n");
  /* Test store wire fee by exchange */
  TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
                                          &cls->wire_fee[0],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double insertion fails */
  TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
                                          &cls->wire_fee[0],
                                          GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test lookup wire fee by exchange */
  TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey,
                                          &cls->wire_fee[0]));
  /* Test different wire fees for different methods. */
  TEST_RET_ON_FAIL (test_insert_wire_fee (&cls->signkey,
                                          &cls->wire_fee[1],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_wire_fee (&cls->signkey,
                                          &cls->wire_fee[1]));
  /* Insert the instance */
  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert the account */
  TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
                                         &cls->account,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert a signing key */
  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
                                                  GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert an order */
  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                       &cls->order,
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert contract terms */
  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
                                                &cls->order,
                                                GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  order_serial = get_order_serial (&cls->instance,
                                   &cls->order);
  /* Insert the deposit */
  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
                                         &cls->signkey,
                                         &cls->deposit,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert the transfer */
  TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance,
                                          &cls->account,
                                          &cls->transfers[0],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_transfer (&cls->instance,
                                          &cls->account,
                                          &cls->transfers[0],
                                          GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
                                                     &cls->signkey,
                                                     &cls->order,
                                                     &cls->deposit,
                                                     &cls->transfers[0],
                                                     GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                                                     false));
  TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
                                                     &cls->signkey,
                                                     &cls->order,
                                                     &cls->deposit,
                                                     &cls->transfers[0],
                                                     GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                                                     false));
  TEST_RET_ON_FAIL (test_insert_transfer_details (&cls->instance,
                                                  &cls->account,
                                                  &cls->transfers[0],
                                                  GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_payment_status (cls->instance.instance.id,
                                                cls->order.id,
                                                NULL,
                                                false,
                                                false));
  TEST_RET_ON_FAIL (test_insert_deposit_to_transfer (&cls->instance,
                                                     &cls->signkey,
                                                     &cls->order,
                                                     &cls->deposit,
                                                     &cls->transfers[0],
                                                     GNUNET_DB_STATUS_SUCCESS_ONE_RESULT,
                                                     true));

  TEST_RET_ON_FAIL (test_lookup_transfer_summary (cls->deposit.exchange_url,
                                                  &cls->transfers[0].wtid,
                                                  cls->order.id,
                                                  &cls->deposit.amount_with_fee,
                                                  &cls->deposit.deposit_fee));
  TEST_RET_ON_FAIL (test_lookup_transfer_details (cls->deposit.exchange_url,
                                                  &cls->transfers[0].wtid,
                                                  1,
                                                  &cls->transfers[0].data.
                                                  details[0]));
  TEST_RET_ON_FAIL (test_lookup_transfer_details_by_order (order_serial,
                                                           1,
                                                           &cls->transfers[0]));
  TEST_RET_ON_FAIL (test_lookup_transfers (&cls->instance,
                                           &cls->account,
                                           GNUNET_TIME_UNIT_FOREVER_TS,
                                           GNUNET_TIME_UNIT_ZERO_TS,
                                           8,
                                           0,
                                           1,
                                           &cls->transfers[0]));
  return 0;
}


/**
 * Takes care of all work for testing transfers.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_transfers (void)
{
  struct TestTransfers_Closure test_cls;
  int test_result;

  pre_test_transfers (&test_cls);
  test_result = run_test_transfers (&test_cls);
  post_test_transfers (&test_cls);
  return test_result;
}


/**
 * Closure for testing lookup_refunds.
 */
struct TestLookupRefunds_Closure
{
  /**
   * Length of @e coin_pub_to_cmp and @e refund_amount_to_cmp.
   */
  unsigned int refunds_to_cmp_length;

  /**
   * Public keys of the refunded coins.
   */
  const struct TALER_CoinSpendPublicKeyP *coin_pub_to_cmp;

  /**
   * Amount of each refund.
   */
  const struct TALER_Amount *refund_amount_to_cmp;

  /**
   * Number of results matching each refund provided.
   */
  unsigned int *results_matching;

  /**
   * Total number of results returned;
   */
  unsigned int results_length;
};


/**
 * Called after test_lookup_refunds.
 * @param cls pointer to a TestLookupRefunds_Closure.
 * @param coin_pub the public key of the coin for the refund found.
 * @param refund_amount the amount of the refund found.
 */
static void
lookup_refunds_cb (void *cls,
                   const struct TALER_CoinSpendPublicKeyP *coin_pub,
                   const struct TALER_Amount *refund_amount)
{
  struct TestLookupRefunds_Closure *cmp = cls;
  if (NULL == cmp)
    return;
  cmp->results_length += 1;
  for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i)
  {
    if ((0 == GNUNET_memcmp (&cmp->coin_pub_to_cmp[i],
                             coin_pub)) &&
        (GNUNET_OK == TALER_amount_cmp_currency (&cmp->refund_amount_to_cmp[i],
                                                 refund_amount)) &&
        (0 == TALER_amount_cmp (&cmp->refund_amount_to_cmp[i],
                                refund_amount)))
    {
      cmp->results_matching[i] += 1;
    }
  }
}


/**
 * Tests looking up refunds from the database.
 * @param instance the instance to look up refunds for.
 * @param h_contract_terms hash of the contract terms the refunds are for.
 * @param refunds_length length of @e coin_pubs and @e refund_amounts.
 * @param coin_pubs the public keys of the coins that were refunded.
 * @param refund_amounts the amounts of the coins that were refunded.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_refunds (const struct InstanceData *instance,
                     const struct TALER_PrivateContractHashP *h_contract_terms,
                     unsigned int refunds_length,
                     const struct TALER_CoinSpendPublicKeyP *coin_pubs,
                     const struct TALER_Amount *refund_amounts)
{
  unsigned int results_matching[refunds_length];
  struct TestLookupRefunds_Closure cmp = {
    .refunds_to_cmp_length = refunds_length,
    .coin_pub_to_cmp = coin_pubs,
    .refund_amount_to_cmp = refund_amounts,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
  if (1 != plugin->lookup_refunds (plugin->cls,
                                   instance->instance.id,
                                   h_contract_terms,
                                   &lookup_refunds_cb,
                                   &cmp))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup refunds failed\n");
    return 1;
  }
  if (refunds_length != cmp.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup refunds failed: incorrect number of results returned\n")
    ;
    return 1;
  }
  for (unsigned int i = 0; refunds_length > i; ++i)
  {
    if (1 != cmp.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup refunds failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Container for refund data.
 */
struct RefundData
{
  /**
   * When the refund occurred.
   */
  struct GNUNET_TIME_Timestamp timestamp;

  /**
   * Reason for the refund.
   */
  const char *reason;

  /**
   * Amount of the refund.
   */
  struct TALER_Amount refund_amount;

  /**
   * Public key of the coin that was refunded.
   */
  const struct TALER_CoinSpendPublicKeyP *coin_pub;

  /**
   * URL to the exchange that did the refund.
   */
  const char *exchange_url;
};


/**
 * Creates a refund for testing with.
 * @param deposit the deposit being refunded.
 * @param refund the data to set.
 */
static void
make_refund (const struct DepositData *deposit,
             struct RefundData *refund)
{
  refund->timestamp = GNUNET_TIME_timestamp_get ();
  refund->reason = "some reason";
  refund->refund_amount = deposit->amount_with_fee;
  refund->coin_pub = &deposit->coin_pub;
  refund->exchange_url = deposit->exchange_url;
}


/**
 * Container for proof of a refund.
 */
struct RefundProofData
{
  /**
   * Fee charged for the refund.
   */
  struct TALER_Amount refund_fee;

  /**
   * The exchange's signature on the refund.
   */
  struct TALER_ExchangeSignatureP exchange_sig;
};


/**
 * Closure for testing lookup_refunds_detailed.
 */
struct TestLookupRefundsDetailed_Closure
{
  /**
   * Length of @e refunds_to_cmp.
   */
  unsigned int refunds_to_cmp_length;

  /**
   * The refunds we expect to find.
   */
  const struct RefundData *refunds_to_cmp;

  /**
   * Whether to compare the timestamps or not (if we don't have direct control
   * of the timestamps, there will be differences on the order of microseconds
   * that can be ignored).
   */
  bool cmp_timestamps;

  /**
   * The number of results matching each refund.
   */
  unsigned int *results_matching;

  /**
   * The total number of results from the db.
   */
  unsigned int results_length;
};


/**
 * Called after test_lookup_refunds_detailed.
 * @param cls pointer to a TestLookupRefundsDetailed_Closure.
 * @param refund_serial unique serial number of the refund
 * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
 * @param coin_pub public coin from which the refund comes from
 * @param exchange_url URL of the exchange that issued @a coin_pub
 * @param rtransaction_id identificator of the refund
 * @param reason human-readable explanation of the refund
 * @param refund_amount refund amount which is being taken from @a coin_pub
 * @param pending true if this refund has not been processed by the wallet/exchange
 */
static void
lookup_refunds_detailed_cb (void *cls,
                            uint64_t refund_serial,
                            struct GNUNET_TIME_Timestamp timestamp,
                            const struct TALER_CoinSpendPublicKeyP *coin_pub,
                            const char *exchange_url,
                            uint64_t rtransaction_id,
                            const char *reason,
                            const struct TALER_Amount *refund_amount,
                            bool pending)
{
  struct TestLookupRefundsDetailed_Closure *cmp = cls;
  if (NULL == cmp)
    return;
  cmp->results_length += 1;
  for (unsigned int i = 0; cmp->refunds_to_cmp_length > i; ++i)
  {
    if (((GNUNET_TIME_timestamp_cmp (cmp->refunds_to_cmp[i].timestamp,
                                     ==,
                                     timestamp)) ||
         ! cmp->cmp_timestamps) &&
        (0 == GNUNET_memcmp (cmp->refunds_to_cmp[i].coin_pub,
                             coin_pub)) &&
        (0 == strcmp (cmp->refunds_to_cmp[i].exchange_url,
                      exchange_url)) &&
        (0 == strcmp (cmp->refunds_to_cmp[i].reason,
                      reason)) &&
        (GNUNET_OK ==
         TALER_amount_cmp_currency (
           &cmp->refunds_to_cmp[i].refund_amount,
           refund_amount)) &&
        (0 == TALER_amount_cmp (&cmp->refunds_to_cmp[i].refund_amount,
                                refund_amount)))
    {
      cmp->results_matching[i] += 1;
    }
  }
}


/**
 * Tests looking up refunds with details from the database.
 * @param instance the instance to lookup from.
 * @param h_contract_terms the contract terms the refunds were made for.
 * @param cmp_timestamps whether to compare timestamps or not.
 * @param refunds_length length of @e refunds.
 * @param refunds the refunds we expect to be returned.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_refunds_detailed (
  const struct InstanceData *instance,
  const struct TALER_PrivateContractHashP *h_contract_terms,
  bool cmp_timestamps,
  unsigned int refunds_length,
  const struct RefundData *refunds)
{
  unsigned int results_matching[refunds_length];
  struct TestLookupRefundsDetailed_Closure cmp = {
    .refunds_to_cmp_length = refunds_length,
    .refunds_to_cmp = refunds,
    .cmp_timestamps = cmp_timestamps,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching, 0, sizeof (unsigned int) * refunds_length);
  if (0 > plugin->lookup_refunds_detailed (plugin->cls,
                                           instance->instance.id,
                                           h_contract_terms,
                                           &lookup_refunds_detailed_cb,
                                           &cmp))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup refunds detailed failed\n");
    return 1;
  }
  if (refunds_length != cmp.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup refunds detailed failed: incorrect number of results\n")
    ;
    return 1;
  }
  for (unsigned int i = 0; refunds_length > i; ++i)
  {
    if (1 != cmp.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup refunds detailed failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Closure for get_refund_serial.
 */
struct LookupRefundSerial_Closure
{
  /**
   * The refund we are looking up the id for.
   */
  const struct RefundData *refund;

  /**
   * The row number found.
   */
  uint64_t serial;
};


/**
 * Called after get_refund_serial.
 * @param cls pointer to a LookupRefundSerial_Closure.
 * @param refund_serial unique serial number of the refund
 * @param timestamp time of the refund (for grouping of refunds in the wallet UI)
 * @param coin_pub public coin from which the refund comes from
 * @param exchange_url URL of the exchange that issued @a coin_pub
 * @param rtransaction_id identificator of the refund
 * @param reason human-readable explanation of the refund
 * @param refund_amount refund amount which is being taken from @a coin_pub
 * @param pending true if this refund has not been processed by the wallet/exchange
 */
static void
get_refund_serial_cb (void *cls,
                      uint64_t refund_serial,
                      struct GNUNET_TIME_Timestamp timestamp,
                      const struct TALER_CoinSpendPublicKeyP *coin_pub,
                      const char *exchange_url,
                      uint64_t rtransaction_id,
                      const char *reason,
                      const struct TALER_Amount *refund_amount,
                      bool pending)
{
  struct LookupRefundSerial_Closure *lookup_cls = cls;
  if (NULL == lookup_cls)
    return;
  if ((GNUNET_TIME_timestamp_cmp (lookup_cls->refund->timestamp,
                                  ==,
                                  timestamp)) &&
      (0 == GNUNET_memcmp (lookup_cls->refund->coin_pub,
                           coin_pub)) &&
      (0 == strcmp (lookup_cls->refund->exchange_url,
                    exchange_url)) &&
      (0 == strcmp (lookup_cls->refund->reason,
                    reason)) &&
      (GNUNET_OK ==
       TALER_amount_cmp_currency (
         &lookup_cls->refund->refund_amount,
         refund_amount)) &&
      (0 == TALER_amount_cmp (&lookup_cls->refund->refund_amount,
                              refund_amount)))
    lookup_cls->serial = refund_serial;
}


/**
 * Utility function for getting the database row number of a refund.
 * @param instance the instance associated with the refund.
 * @param h_contract_terms the contract terms the refund was made with.
 * @param refund the refund we are querying the row number of.
 *
 * @return the row number of the refund.
 */
static uint64_t
get_refund_serial (const struct InstanceData *instance,
                   const struct TALER_PrivateContractHashP *h_contract_terms,
                   const struct RefundData *refund)
{
  struct LookupRefundSerial_Closure lookup_cls = {
    .refund = refund,
    .serial = 0
  };

  GNUNET_assert (0 < plugin->lookup_refunds_detailed (plugin->cls,
                                                      instance->instance.id,
                                                      h_contract_terms,
                                                      &get_refund_serial_cb,
                                                      &lookup_cls));
  GNUNET_assert (0 != lookup_cls.serial);

  return lookup_cls.serial;
}


/**
 * Tests looking up proof of a refund.
 * @param refund_serial the row number of the refund.
 * @param expected_exchange_sig the exchange signature we are expecting.
 * @param expected_exchange_pub the exchange public key we are expecting.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_refund_proof (uint64_t refund_serial,
                          const struct
                          TALER_ExchangeSignatureP *expected_exchange_sig,
                          const struct
                          TALER_ExchangePublicKeyP *expected_exchange_pub)
{
  struct TALER_ExchangeSignatureP exchange_sig;
  struct TALER_ExchangePublicKeyP exchange_pub;
  if (1 != plugin->lookup_refund_proof (plugin->cls,
                                        refund_serial,
                                        &exchange_sig,
                                        &exchange_pub))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup refund proof failed\n");
    return 1;
  }
  if ((0 != GNUNET_memcmp (expected_exchange_sig,
                           &exchange_sig)) ||
      (0 != GNUNET_memcmp (expected_exchange_pub,
                           &exchange_pub)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup refund proof failed: mismatched data\n");
    return 1;
  }
  return 0;
}


/**
 * Closure for testing refund functionality.
 */
struct TestRefunds_Closure
{
  /**
   * The instance.
   */
  struct InstanceData instance;

  /**
   * The merchant account.
   */
  struct TALER_MERCHANTDB_AccountDetails account;

  /**
   * The exchange signing key.
   */
  struct ExchangeSignkeyData signkey;

  /**
   * The order data.
   */
  struct OrderData orders[2];

  /**
   * The deposit data.
   */
  struct DepositData deposits[3];

  /**
   * The refund data.
   */
  struct RefundData refunds[3];

  /**
   * The refund proof data.
   */
  struct RefundProofData refund_proof;
};


/**
 * Prepares for testing refunds.
 * @param cls the closure to initialize.
 */
static void
pre_test_refunds (struct TestRefunds_Closure *cls)
{
  /* Instance */
  make_instance ("test_inst_refunds",
                 &cls->instance);

  /* Account */
  make_account (&cls->account);
  cls->account.instance_id = cls->instance.instance.id;
  /* Signing key */
  make_exchange_signkey (&cls->signkey);

  /* Order */
  make_order ("test_refunds_od_0",
              &cls->orders[0]);
  make_order ("test_refunds_od_1",
              &cls->orders[1]);

  /* Deposit */
  make_deposit (&cls->instance,
                &cls->account,
                &cls->orders[0],
                &cls->signkey,
                &cls->deposits[0]);
  make_deposit (&cls->instance,
                &cls->account,
                &cls->orders[0],
                &cls->signkey,
                &cls->deposits[1]);
  make_deposit (&cls->instance,
                &cls->account,
                &cls->orders[1],
                &cls->signkey,
                &cls->deposits[2]);

  /* Refund */
  make_refund (&cls->deposits[0],
               &cls->refunds[0]);
  make_refund (&cls->deposits[2],
               &cls->refunds[1]);
  make_refund (&cls->deposits[2],
               &cls->refunds[2]);
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:10.00",
                                         &cls->refunds[1].refund_amount));
  cls->refunds[1].reason = "refund 1";
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:10.00",
                                         &cls->refunds[2].refund_amount));
  cls->refunds[2].reason = "refund 2";

  /* Refund proof */
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:0.02",
                                         &cls->refund_proof.refund_fee));
  memset (&cls->refund_proof.exchange_sig,
          42,
          sizeof (cls->refund_proof.exchange_sig));
}


/**
 * Cleans up after testing refunds.
 * @param cls the closure.
 */
static void
post_test_refunds (struct TestRefunds_Closure *cls)
{
  free_instance_data (&cls->instance);
  free_order_data (&cls->orders[0]);
  free_order_data (&cls->orders[1]);
}


/**
 * Runs the refund tests.
 * @param cls the closure.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
run_test_refunds (struct TestRefunds_Closure *cls)
{
  struct TALER_Amount inc;
  uint64_t refund_serial;

  /* Insert an instance */
  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert an account */
  TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
                                         &cls->account,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert an order */
  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                       &cls->orders[0],
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert contract terms */
  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
                                                &cls->orders[0],
                                                GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert a signing key */
  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
                                                  GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Insert a deposit */
  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
                                         &cls->signkey,
                                         &cls->deposits[0],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
                                         &cls->signkey,
                                         &cls->deposits[1],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Mark as paid */
  TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
                                             &cls->orders[0],
                                             GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test refund coin */
  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
                         plugin->refund_coin (plugin->cls,
                                              cls->instance.instance.id,
                                              &cls->deposits[0].h_contract_terms
                                              ,
                                              cls->refunds[0].timestamp,
                                              cls->refunds[0].coin_pub,
                                              cls->refunds[0].reason),
                         "Refund coin failed\n");
  refund_serial = get_refund_serial (&cls->instance,
                                     &cls->deposits[0].h_contract_terms,
                                     &cls->refunds[0]);
  /* Test double refund fails */
  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
                         plugin->refund_coin (plugin->cls,
                                              cls->instance.instance.id,
                                              &cls->deposits[0].h_contract_terms
                                              ,
                                              cls->refunds[0].timestamp,
                                              cls->refunds[0].coin_pub,
                                              cls->refunds[0].reason),
                         "Refund coin failed\n");
  /* Test lookup refunds */
  TEST_RET_ON_FAIL (test_lookup_refunds (&cls->instance,
                                         &cls->deposits[0].h_contract_terms,
                                         1,
                                         cls->refunds[0].coin_pub,
                                         &cls->refunds[0].refund_amount));
  /* Test lookup refunds detailed */
  TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance,
                                                  &cls->deposits[0].
                                                  h_contract_terms,
                                                  true,
                                                  1,
                                                  &cls->refunds[0]));
  /* Test insert refund proof */
  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
                         plugin->insert_refund_proof (plugin->cls,
                                                      refund_serial,
                                                      &cls->refund_proof.
                                                      exchange_sig,
                                                      &cls->signkey.exchange_pub
                                                      ),
                         "Insert refund proof failed\n");
  TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS ==
                         plugin->insert_refund_proof (plugin->cls,
                                                      refund_serial,
                                                      &cls->refund_proof.
                                                      exchange_sig,
                                                      &cls->signkey.exchange_pub
                                                      ),
                         "Insert refund proof failed\n");
  /* Test that we can't give too much in refunds */
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:1000.00",
                                         &inc));
  TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_TOO_HIGH ==
                         plugin->increase_refund (plugin->cls,
                                                  cls->instance.instance.id,
                                                  cls->orders[0].id,
                                                  &inc,
                                                  NULL,
                                                  NULL,
                                                  "more"),
                         "Increase refund failed\n");
  /* Test increase refund */
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:1.00",
                                         &inc));
  TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
                         plugin->increase_refund (plugin->cls,
                                                  cls->instance.instance.id,
                                                  cls->orders[0].id,
                                                  &inc,
                                                  NULL,
                                                  NULL,
                                                  "more"),
                         "Increase refund failed\n");
  /* Test lookup refund proof */
  TEST_RET_ON_FAIL (test_lookup_refund_proof (1,
                                              &cls->refund_proof.exchange_sig,
                                              &cls->signkey.exchange_pub));
  TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                       &cls->orders[1],
                                       GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
                                                &cls->orders[1],
                                                GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
                                         &cls->signkey,
                                         &cls->deposits[2],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
                                             &cls->orders[1],
                                             GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test refunding a small amount of the coin, then increasing it */
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:10.00",
                                         &inc));
  TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
                         plugin->increase_refund (plugin->cls,
                                                  cls->instance.instance.id,
                                                  cls->orders[1].id,
                                                  &inc,
                                                  NULL,
                                                  NULL,
                                                  cls->refunds[1].reason),
                         "Increase refund failed\n");
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount ("EUR:20.00",
                                         &inc));
  TEST_COND_RET_ON_FAIL (TALER_MERCHANTDB_RS_SUCCESS ==
                         plugin->increase_refund (plugin->cls,
                                                  cls->instance.instance.id,
                                                  cls->orders[1].id,
                                                  &inc,
                                                  NULL,
                                                  NULL,
                                                  cls->refunds[2].reason),
                         "Increase refund failed\n");
  TEST_RET_ON_FAIL (test_lookup_refunds_detailed (&cls->instance,
                                                  &cls->deposits[2].
                                                  h_contract_terms,
                                                  false,
                                                  2,
                                                  &cls->refunds[1]));
  return 0;
}


/**
 * All logic for testing refunds.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_refunds (void)
{
  struct TestRefunds_Closure test_cls;
  int test_result;

  pre_test_refunds (&test_cls);
  test_result = run_test_refunds (&test_cls);
  post_test_refunds (&test_cls);
  return test_result;
}


/**
 * Convenience function that reverses an array of orders.
 * @param orders_length length of @e orders.
 * @param orders the array to reverse.
 */
static void
reverse_order_data_array (unsigned int orders_length,
                          struct OrderData *orders)
{
  struct OrderData tmp[orders_length];
  for (unsigned int i = 0; i < orders_length; ++i)
    tmp[i] = orders[orders_length - 1 - i];
  for (unsigned int i = 0; i < orders_length; ++i)
    orders[i] = tmp[i];
}


/**
 * Closure for testing all the filters for looking up orders.
 */
struct TestLookupOrdersAllFilters_Closure
{
  /**
   * The instance.
   */
  struct InstanceData instance;

  /**
   * The merchant account.
   */
  struct TALER_MERCHANTDB_AccountDetails account;

  /**
   * The exchange signing key.
   */
  struct ExchangeSignkeyData signkey;

  /**
   * The array of order ids.
   */
  char *order_ids[64];

  /**
   * The array of orders.
   */
  struct OrderData orders[64];

  /**
   * The array of deposits.
   */
  struct DepositData deposits[64];

  /**
   * The array of refunds.
   */
  struct RefundData refunds[64];
};


/**
 * Sets up for testing lookup order filters.
 * @param cls the closure.
 */
static void
pre_test_lookup_orders_all_filters (
  struct TestLookupOrdersAllFilters_Closure *cls)
{
  make_instance ("test_inst_lookup_orders_all_filters",
                 &cls->instance);
  make_account (&cls->account);
  cls->account.instance_id = cls->instance.instance.id;
  make_exchange_signkey (&cls->signkey);
  for (unsigned int i = 0; i < 64; ++i)
  {
    (void) GNUNET_asprintf (&cls->order_ids[i],
                            "test_orders_filter_od_%u",
                            i);
    make_order (cls->order_ids[i],
                &cls->orders[i]);
    GNUNET_assert (0 ==
                   json_object_set_new (cls->orders[i].contract,
                                        "order_id",
                                        json_string (cls->order_ids[i])));
    make_deposit (&cls->instance,
                  &cls->account,
                  &cls->orders[i],
                  &cls->signkey,
                  &cls->deposits[i]);
    make_refund (&cls->deposits[i],
                 &cls->refunds[i]);
  }
}


/**
 * Cleans up after testing lookup order filters.
 * @param cls the closure.
 */
static void
post_test_lookup_orders_all_filters (
  struct TestLookupOrdersAllFilters_Closure *cls)
{
  free_instance_data (&cls->instance);
  for (unsigned int i = 0; i < 64; ++i)
  {
    free_order_data (&cls->orders[i]);
    GNUNET_free (cls->order_ids[i]);
  }
}


/**
 * Runs the tests for lookup order filters.
 * @param cls the closure.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
run_test_lookup_orders_all_filters (
  struct TestLookupOrdersAllFilters_Closure *cls)
{
  /* Order filter extravaganza */
  struct
  {
    bool claimed;
    bool paid;
    bool refunded;
    bool wired;
  } order_status[64];
  unsigned int *permutation;

  /* Pseudorandomly generate variations for the filter to differentiate */
  GNUNET_CRYPTO_seed_weak_random (1);
  permutation = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_WEAK,
                                              64);
  for (unsigned int i = 0; i < 64; ++i)
  {
    unsigned int dest = permutation[i];
    order_status[dest].claimed = (i & 1) ? true : false;
    order_status[dest].paid = (3 == (i & 3)) ? true : false;
    order_status[dest].refunded = (5 == (i & 5)) ? true : false;
    order_status[dest].wired = (9 == (i & 9)) ? true : false;
  }
  GNUNET_free (permutation);


  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_account (&cls->instance,
                                         &cls->account,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_exchange_signkey (&cls->signkey,
                                                  GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  for (unsigned int i = 0; i < 64; ++i)
  {
    uint64_t order_serial;

    TEST_RET_ON_FAIL (test_insert_order (&cls->instance,
                                         &cls->orders[i],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
    order_serial = get_order_serial (&cls->instance,
                                     &cls->orders[i]);

    if (order_status[i].claimed)
    {
      TEST_RET_ON_FAIL (test_insert_contract_terms (&cls->instance,
                                                    &cls->orders[i],
                                                    GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
    }
    else
    {
      continue;
    }

    if (order_status[i].paid)
      TEST_RET_ON_FAIL (test_mark_contract_paid (&cls->instance,
                                                 &cls->orders[i],
                                                 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
    if (order_status[i].refunded)
    {
      TEST_RET_ON_FAIL (test_insert_deposit (&cls->instance,
                                             &cls->signkey,
                                             &cls->deposits[i],
                                             GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
      TEST_COND_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
                             plugin->refund_coin (plugin->cls,
                                                  cls->instance.instance.id,
                                                  &cls->deposits[i].
                                                  h_contract_terms,
                                                  cls->refunds[i].timestamp,
                                                  cls->refunds[i].coin_pub,
                                                  cls->refunds[i].reason),
                             "Refund coin failed\n");
    }

    if (order_status[i].wired)
      TEST_RET_ON_FAIL (test_mark_order_wired (order_serial,
                                               GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  }

  /* There are 3^3 = 27 possibilities here, not counting inc/dec and start row */
  for (unsigned int i = 0; i < 27; ++i)
  {
    struct TALER_MERCHANTDB_OrderFilter filter = {
      .paid = (i % 3) + 1,
      .refunded = ((i / 3) % 3) + 1,
      .wired = ((i / 9) % 3) + 1,
      .date = GNUNET_TIME_UNIT_ZERO_TS,
      .start_row = 0,
      .delta = 64
    };
    unsigned int orders_length = 0;
    struct OrderData orders[64];

    /* Now figure out which orders should make it through the filter */
    for (unsigned int j = 0; j < 64; ++j)
    {
      if (((TALER_EXCHANGE_YNA_YES == filter.paid) &&
           (true != order_status[j].paid)) ||
          ((TALER_EXCHANGE_YNA_NO == filter.paid) &&
           (false != order_status[j].paid)) ||
          ((TALER_EXCHANGE_YNA_YES == filter.refunded) &&
           (true != order_status[j].refunded)) ||
          ((TALER_EXCHANGE_YNA_NO == filter.refunded) &&
           (false != order_status[j].refunded)) ||
          ((TALER_EXCHANGE_YNA_YES == filter.wired) &&
           (true != order_status[j].wired)) ||
          ((TALER_EXCHANGE_YNA_NO == filter.wired) &&
           (false != order_status[j].wired)))
        continue;
      orders[orders_length] = cls->orders[j];
      orders_length += 1;
    }

    /* Test the lookup */
    TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
                                          &filter,
                                          orders_length,
                                          orders));

    /* Now test decreasing */
    filter.start_row = 256;
    filter.date = GNUNET_TIME_UNIT_FOREVER_TS;
    filter.delta = -64;

    reverse_order_data_array (orders_length,
                              orders);

    TEST_RET_ON_FAIL (test_lookup_orders (&cls->instance,
                                          &filter,
                                          orders_length,
                                          orders));
  }

  return 0;
}


/**
 * Handles all logic for testing lookup order filters.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_lookup_orders_all_filters (void)
{
  struct TestLookupOrdersAllFilters_Closure test_cls;
  int test_result;

  memset (&test_cls,
          0,
          sizeof (test_cls));
  pre_test_lookup_orders_all_filters (&test_cls);
  test_result = run_test_lookup_orders_all_filters (&test_cls);
  post_test_lookup_orders_all_filters (&test_cls);
  return test_result;
}


static void
kyc_status_ok (
  void *cls,
  const struct TALER_MerchantWireHashP *h_wire,
  struct TALER_FullPayto payto_uri,
  const char *exchange_url,
  struct GNUNET_TIME_Timestamp last_check,
  bool kyc_ok,
  const struct TALER_AccountAccessTokenP *access_token,
  unsigned int last_http_status,
  enum TALER_ErrorCode last_ec,
  bool in_aml_review,
  const json_t *jlimits)
{
  bool *fail = cls;

  if (kyc_ok)
    *fail = false;
}


static void
kyc_status_fail (
  void *cls,
  const struct TALER_MerchantWireHashP *h_wire,
  struct TALER_FullPayto payto_uri,
  const char *exchange_url,
  struct GNUNET_TIME_Timestamp last_check,
  bool kyc_ok,
  const struct TALER_AccountAccessTokenP *access_token,
  unsigned int last_http_status,
  enum TALER_ErrorCode last_ec,
  bool in_aml_review,
  const json_t *jlimits)
{
  bool *fail = cls;

  if (! kyc_ok)
    *fail = false;
}


/**
 * Function that tests the KYC table.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_kyc (void)
{
  struct InstanceData instance;
  struct TALER_MERCHANTDB_AccountDetails account;
  bool fail;
  struct GNUNET_TIME_Timestamp now;

  make_instance ("test_kyc",
                 &instance);
  make_account (&account);
  account.instance_id = instance.instance.id;
  TEST_RET_ON_FAIL (test_insert_instance (&instance,
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_insert_account (&instance,
                                         &account,
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  now = GNUNET_TIME_timestamp_get ();
  TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
                    plugin->account_kyc_set_status (plugin->cls,
                                                    instance.instance.id,
                                                    &account.h_wire,
                                                    "https://exchange.net/",
                                                    now,
                                                    MHD_HTTP_OK,
                                                    TALER_EC_NONE,
                                                    42,
                                                    NULL,
                                                    NULL,
                                                    false,
                                                    false));
  TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
                    plugin->account_kyc_set_status (plugin->cls,
                                                    instance.instance.id,
                                                    &account.h_wire,
                                                    "https://exchange2.com/",
                                                    now,
                                                    MHD_HTTP_OK,
                                                    TALER_EC_NONE,
                                                    42,
                                                    NULL,
                                                    NULL,
                                                    false,
                                                    false));
  TEST_RET_ON_FAIL (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
                    plugin->account_kyc_set_status (plugin->cls,
                                                    instance.instance.id,
                                                    &account.h_wire,
                                                    "https://exchange.net/",
                                                    now,
                                                    MHD_HTTP_OK,
                                                    TALER_EC_NONE,
                                                    42,
                                                    NULL,
                                                    NULL,
                                                    false,
                                                    true));
  fail = true;
  TEST_RET_ON_FAIL (1 !=
                    plugin->account_kyc_get_status (plugin->cls,
                                                    instance.instance.id,
                                                    &account.h_wire,
                                                    "https://exchange.net/",
                                                    &kyc_status_ok,
                                                    &fail));
  TEST_RET_ON_FAIL (fail);
  fail = true;
  TEST_RET_ON_FAIL (1 !=
                    plugin->account_kyc_get_status (plugin->cls,
                                                    instance.instance.id,
                                                    NULL,
                                                    "https://exchange2.com/",
                                                    &kyc_status_fail,
                                                    &fail));
  TEST_RET_ON_FAIL (fail);
  fail = true;
  TEST_RET_ON_FAIL (2 !=
                    plugin->account_kyc_get_status (plugin->cls,
                                                    instance.instance.id,
                                                    NULL,
                                                    NULL,
                                                    &kyc_status_fail,
                                                    &fail));
  TEST_RET_ON_FAIL (fail);
  fail = true;
  TEST_RET_ON_FAIL (2 !=
                    plugin->account_kyc_get_status (plugin->cls,
                                                    instance.instance.id,
                                                    NULL,
                                                    NULL,
                                                    &kyc_status_ok,
                                                    &fail));
  TEST_RET_ON_FAIL (fail);
  json_decref (instance.instance.address);
  json_decref (instance.instance.jurisdiction);
  return 0;
}


/* *********** Templates ********** */

/**
 * A container for data relevant to a template.
 */
struct TemplateData
{
  /**
   * The identifier of the template.
   */
  const char *id;

  /**
   * The details of the template.
   */
  struct TALER_MERCHANTDB_TemplateDetails template;
};


/**
 * Creates a template for testing with.
 *
 * @param id the id of the template.
 * @param template the template data to fill.
 */
static void
make_template (const char *id,
               struct TemplateData *template)
{
  template->id = id;
  template->template.template_description = "This is a test template";
  template->template.otp_id = NULL;
  template->template.template_contract = json_array ();
  GNUNET_assert (NULL != template->template.template_contract);
}


/**
 * Frees memory associated with @e TemplateData.
 *
 * @param template the container to free.
 */
static void
free_template_data (struct TemplateData *template)
{
  GNUNET_free (template->template.otp_id);
  json_decref (template->template.template_contract);
}


/**
 * Compare two templates for equality.
 *
 * @param a the first template.
 * @param b the second template.
 * @return 0 on equality, 1 otherwise.
 */
static int
check_templates_equal (const struct TALER_MERCHANTDB_TemplateDetails *a,
                       const struct TALER_MERCHANTDB_TemplateDetails *b)
{
  if ((0 != strcmp (a->template_description,
                    b->template_description)) ||
      ( (NULL == a->otp_id) && (NULL != b->otp_id)) ||
      ( (NULL != a->otp_id) && (NULL == b->otp_id)) ||
      ( (NULL != a->otp_id) && (0 != strcmp (a->otp_id,
                                             b->otp_id))) ||
      (1 != json_equal (a->template_contract,
                        b->template_contract)))
    return 1;
  return 0;
}


/**
 * Tests inserting template data into the database.
 *
 * @param instance the instance to insert the template for.
 * @param template the template data to insert.
 * @param expected_result the result we expect the db to return.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_insert_template (const struct InstanceData *instance,
                      const struct TemplateData *template,
                      enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_template (plugin->cls,
                                                  instance->instance.id,
                                                  template->id,
                                                  0,
                                                  &template->template),
                         "Insert template failed\n");
  return 0;
}


/**
 * Tests updating template data in the database.
 *
 * @param instance the instance to update the template for.
 * @param template the template data to update.
 * @param expected_result the result we expect the db to return.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_update_template (const struct InstanceData *instance,
                      const struct TemplateData *template,
                      enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->update_template (plugin->cls,
                                                  instance->instance.id,
                                                  template->id,
                                                  &template->template),
                         "Update template failed\n");
  return 0;
}


/**
 * Tests looking up a template from the db.
 *
 * @param instance the instance to query from.
 * @param template the template to query and compare to.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_lookup_template (const struct InstanceData *instance,
                      const struct TemplateData *template)
{
  const struct TALER_MERCHANTDB_TemplateDetails *to_cmp
    = &template->template;
  struct TALER_MERCHANTDB_TemplateDetails lookup_result;

  if (0 > plugin->lookup_template (plugin->cls,
                                   instance->instance.id,
                                   template->id,
                                   &lookup_result))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup template failed\n");
    TALER_MERCHANTDB_template_details_free (&lookup_result);
    return 1;
  }
  if (0 != check_templates_equal (&lookup_result,
                                  to_cmp))
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup template failed: incorrect template returned\n");
    TALER_MERCHANTDB_template_details_free (&lookup_result);
    return 1;
  }
  TALER_MERCHANTDB_template_details_free (&lookup_result);
  return 0;
}


/**
 * Closure for testing template lookup
 */
struct TestLookupTemplates_Closure
{
  /**
   * Number of template ids to compare to
   */
  unsigned int templates_to_cmp_length;

  /**
   * Pointer to array of template ids
   */
  const struct TemplateData *templates_to_cmp;

  /**
   * Pointer to array of number of matches for each template
   */
  unsigned int *results_matching;

  /**
   * Total number of results returned
   */
  unsigned int results_length;
};


/**
 * Function called after calling @e test_lookup_templates
 *
 * @param cls a pointer to the lookup closure.
 * @param template_id the identifier of the template found.
 */
static void
lookup_templates_cb (void *cls,
                     const char *template_id,
                     const char *template_description)
{
  struct TestLookupTemplates_Closure *cmp = cls;

  if (NULL == cmp)
    return;
  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)) &&
         (0 == strcmp (cmp->templates_to_cmp[i].template.template_description,
                       template_description)) )
      cmp->results_matching[i] += 1;
  }
}


/**
 * Tests looking up all templates for an instance.
 *
 * @param instance the instance to query from.
 * @param templates_length the number of templates we are expecting.
 * @param templates the list of templates that we expect to be found.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_lookup_templates (const struct InstanceData *instance,
                       unsigned int templates_length,
                       const struct TemplateData *templates)
{
  unsigned int results_matching[templates_length];
  struct TestLookupTemplates_Closure cls = {
    .templates_to_cmp_length = templates_length,
    .templates_to_cmp = templates,
    .results_matching = results_matching,
    .results_length = 0
  };

  memset (results_matching,
          0,
          sizeof (unsigned int) * templates_length);
  if (0 > plugin->lookup_templates (plugin->cls,
                                    instance->instance.id,
                                    &lookup_templates_cb,
                                    &cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup templates failed\n");
    return 1;
  }
  if (templates_length != cls.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup templates failed: incorrect number of results\n");
    return 1;
  }
  for (unsigned int i = 0; templates_length > i; ++i)
  {
    if (1 != cls.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup templates failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * Tests deleting a template.
 *
 * @param instance the instance to delete the template from.
 * @param template the template 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_template (const struct InstanceData *instance,
                      const struct TemplateData *template,
                      enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->delete_template (plugin->cls,
                                                  instance->instance.id,
                                                  template->id),
                         "Delete template failed\n");
  return 0;
}


/**
 * Closure for template tests.
 */
struct TestTemplates_Closure
{
  /**
   * The instance to use for this test.
   */
  struct InstanceData instance;

  /**
   * The array of templates.
   */
  struct TemplateData templates[2];
};


/**
 * Sets up the data structures used in the template tests.
 *
 * @param cls the closure to fill with test data.
 */
static void
pre_test_templates (struct TestTemplates_Closure *cls)
{
  /* Instance */
  make_instance ("test_inst_templates",
                 &cls->instance);

  /* Templates */
  make_template ("test_templates_pd_0",
                 &cls->templates[0]);

  make_template ("test_templates_pd_1",
                 &cls->templates[1]);
  cls->templates[1].template.template_description =
    "This is a another test template";
}


/**
 * Handles all teardown after testing.
 *
 * @param cls the closure containing memory to be freed.
 */
static void
post_test_templates (struct TestTemplates_Closure *cls)
{
  free_instance_data (&cls->instance);
  free_template_data (&cls->templates[0]);
  free_template_data (&cls->templates[1]);
}


/**
 * Runs the tests for templates.
 *
 * @param cls the container of the test data.
 * @return 0 on success, 1 otherwise.
 */
static int
run_test_templates (struct TestTemplates_Closure *cls)
{

  /* Test that insert without an instance fails */
  TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
                                          &cls->templates[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 template */
  TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
                                          &cls->templates[0],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test that double insert fails */
  TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
                                          &cls->templates[0],
                                          GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test lookup of individual templates */
  TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
                                          &cls->templates[0]));
  /* Make sure it fails correctly for templates that don't exist */
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
      plugin->lookup_template (plugin->cls,
                               cls->instance.instance.id,
                               "nonexistent_template",
                               NULL))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup template failed\n");
    return 1;
  }
  /* Test template update */
  cls->templates[0].template.template_description =
    "This is a test template that has been updated!";
  GNUNET_free (cls->templates[0].template.otp_id);
  cls->templates[0].template.otp_id = GNUNET_strdup ("otp_id");
  {
    /* ensure OTP device exists */
    struct TALER_MERCHANTDB_OtpDeviceDetails td = {
      .otp_description = "my otp",
      .otp_key = "my key",
      .otp_algorithm = 1,
      .otp_ctr = 42
    };
    GNUNET_assert (0 <=
                   plugin->insert_otp (plugin->cls,
                                       cls->instance.instance.id,
                                       "otp_id",
                                       &td));
  }
  GNUNET_assert (0 ==
                 json_array_append_new (
                   cls->templates[0].template.template_contract,
                   json_string ("This is a test. 3CH.")));
  TEST_RET_ON_FAIL (test_update_template (&cls->instance,
                                          &cls->templates[0],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_template (&cls->instance,
                                          &cls->templates[0]));
  TEST_RET_ON_FAIL (test_update_template (&cls->instance,
                                          &cls->templates[1],
                                          GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test collective template lookup */
  TEST_RET_ON_FAIL (test_insert_template (&cls->instance,
                                          &cls->templates[1],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
                                           2,
                                           cls->templates));

  /* Test template deletion */
  TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
                                          &cls->templates[1],
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double deletion fails */
  TEST_RET_ON_FAIL (test_delete_template (&cls->instance,
                                          &cls->templates[1],
                                          GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  TEST_RET_ON_FAIL (test_lookup_templates (&cls->instance,
                                           1,
                                           cls->templates));
  return 0;
}


/**
 * Takes care of template testing.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_templates (void)
{
  struct TestTemplates_Closure test_cls;
  int test_result;

  memset (&test_cls,
          0,
          sizeof (test_cls));
  pre_test_templates (&test_cls);
  test_result = run_test_templates (&test_cls);
  post_test_templates (&test_cls);
  return test_result;
}


/* *********** Webhooks ********** */

/**
 * A container for data relevant to a webhook.
 */
struct WebhookData
{

  /**
   * The identifier of the webhook.
   */
  const char *id;

  /**
   * The details of the webhook.
   */
  struct TALER_MERCHANTDB_WebhookDetails webhook;
};


/**
 * Creates a webhook for testing with.
 *
 * @param id the id of the webhook.
 * @param webhook the webhook data to fill.
 */
static void
make_webhook (const char *id,
              struct WebhookData *webhook)
{
  webhook->id = id;
  webhook->webhook.event_type = "Paid";
  webhook->webhook.url = "https://exampletest.com";
  webhook->webhook.http_method = "POST";
  webhook->webhook.header_template = "Authorization:XYJAORKJEO";
  webhook->webhook.body_template = "$Amount";
}


/**
 * Compare two webhooks for equality.
 *
 * @param a the first webhook.
 * @param b the second webhook.
 * @return 0 on equality, 1 otherwise.
 */
static int
check_webhooks_equal (const struct TALER_MERCHANTDB_WebhookDetails *a,
                      const struct TALER_MERCHANTDB_WebhookDetails *b)
{
  if ((0 != strcmp (a->event_type,
                    b->event_type)) ||
      (0 != strcmp (a->url,
                    b->url)) ||
      (0 != strcmp (a->http_method,
                    b->http_method)) ||
      (0 != strcmp (a->header_template,
                    b->header_template)) ||
      (0 != strcmp (a->body_template,
                    b->body_template)))
    return 1;
  return 0;
}


/**
 * Tests inserting webhook data into the database.
 *
 * @param instance the instance to insert the webhook for.
 * @param webhook the webhook data to insert.
 * @param expected_result the result we expect the db to return.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_insert_webhook (const struct InstanceData *instance,
                     const struct WebhookData *webhook,
                     enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->insert_webhook (plugin->cls,
                                                 instance->instance.id,
                                                 webhook->id,
                                                 &webhook->webhook),
                         "Insert webhook failed\n");
  return 0;
}


/**
 * Tests updating webhook data in the database.
 *
 * @param instance the instance to update the webhook for.
 * @param webhook the webhook data to update.
 * @param expected_result the result we expect the db to return.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_update_webhook (const struct InstanceData *instance,
                     const struct WebhookData *webhook,
                     enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->update_webhook (plugin->cls,
                                                 instance->instance.id,
                                                 webhook->id,
                                                 &webhook->webhook),
                         "Update webhook failed\n");
  return 0;
}


/**
 * Tests looking up a webhook from the db.
 *
 * @param instance the instance to query from.
 * @param webhook the webhook to query and compare to.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_lookup_webhook (const struct InstanceData *instance,
                     const struct WebhookData *webhook)
{
  const struct TALER_MERCHANTDB_WebhookDetails *to_cmp = &webhook->webhook;
  struct TALER_MERCHANTDB_WebhookDetails lookup_result;

  if (0 > plugin->lookup_webhook (plugin->cls,
                                  instance->instance.id,
                                  webhook->id,
                                  &lookup_result))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup webhook failed\n");
    TALER_MERCHANTDB_webhook_details_free (&lookup_result);
    return 1;
  }
  if (0 != check_webhooks_equal (&lookup_result,
                                 to_cmp))
  {
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup webhook failed: incorrect webhook returned\n");
    TALER_MERCHANTDB_webhook_details_free (&lookup_result);
    return 1;
  }
  TALER_MERCHANTDB_webhook_details_free (&lookup_result);
  return 0;
}


/**
 * Closure for testing webhook lookup
 */
struct TestLookupWebhooks_Closure
{
  /**
   * Number of webhook ids to compare to
   */
  unsigned int webhooks_to_cmp_length;

  /**
   * Pointer to array of webhook ids
   */
  const struct WebhookData *webhooks_to_cmp;

  /**
   * Pointer to array of number of matches for each webhook
   */
  unsigned int *results_matching;

  /**
   * Total number of results returned
   */
  unsigned int results_length;
};


/**
 * Function called after calling @e test_lookup_webhooks
 *
 * @param cls a pointer to the lookup closure.
 * @param webhook_id the identifier of the webhook found.
 */
static void
lookup_webhooks_cb (void *cls,
                    const char *webhook_id,
                    const char *event_type)
{
  struct TestLookupWebhooks_Closure *cmp = cls;
  if (NULL == cmp)
    return;
  cmp->results_length += 1;
  for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
  {
    if ((0 == strcmp (cmp->webhooks_to_cmp[i].id,
                      webhook_id)) &&
        (0 == strcmp (cmp->webhooks_to_cmp[i].webhook.event_type,
                      event_type)) )
      cmp->results_matching[i] += 1;
  }
}


/**
 * Tests looking up all webhooks for an instance.
 *
 * @param instance the instance to query from.
 * @param webhooks_length the number of webhooks we are expecting.
 * @param webhooks the list of webhooks that we expect to be found.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_lookup_webhooks (const struct InstanceData *instance,
                      unsigned int webhooks_length,
                      const struct WebhookData *webhooks)
{
  unsigned int results_matching[webhooks_length];
  struct TestLookupWebhooks_Closure cls = {
    .webhooks_to_cmp_length = webhooks_length,
    .webhooks_to_cmp = webhooks,
    .results_matching = results_matching,
    .results_length = 0
  };
  memset (results_matching, 0, sizeof (unsigned int) * webhooks_length);
  if (0 > plugin->lookup_webhooks (plugin->cls,
                                   instance->instance.id,
                                   &lookup_webhooks_cb,
                                   &cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup webhooks failed\n");
    return 1;
  }
  if (webhooks_length != cls.results_length)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup webhooks failed: incorrect number of results\n");
    return 1;
  }
  for (unsigned int i = 0; webhooks_length > i; ++i)
  {
    if (1 != cls.results_matching[i])
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Lookup webhooks failed: mismatched data\n");
      return 1;
    }
  }
  return 0;
}


/**
 * 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.
 *
 * @param instance the instance to delete the webhook from.
 * @param webhook the webhook that should be deleted.
 * @param expected_result the result that we expect the plugin to return.
 * @return 0 when successful, 1 otherwise.
 */
static int
test_delete_webhook (const struct InstanceData *instance,
                     const struct WebhookData *webhook,
                     enum GNUNET_DB_QueryStatus expected_result)
{
  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->delete_webhook (plugin->cls,
                                                 instance->instance.id,
                                                 webhook->id),
                         "Delete webhook failed\n");
  return 0;
}


/**
 * Closure for webhook tests.
 */
struct TestWebhooks_Closure
{
  /**
   * The instance to use for this test.
   */
  struct InstanceData instance;

  /**
   * The array of webhooks.
   */
  struct WebhookData webhooks[3];
};


/**
 * Sets up the data structures used in the webhook tests.
 *
 * @param cls the closure to fill with test data.
 */
static void
pre_test_webhooks (struct TestWebhooks_Closure *cls)
{
  /* Instance */
  make_instance ("test_inst_webhooks",
                 &cls->instance);

  /* Webhooks */
  make_webhook ("test_webhooks_wb_0",
                &cls->webhooks[0]);

  make_webhook ("test_webhooks_wb_1",
                &cls->webhooks[1]);
  cls->webhooks[1].webhook.event_type = "Test paid";
  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";
}


/**
 * Handles all teardown after testing.
 *
 * @param cls the closure containing memory to be freed.
 */
static void
post_test_webhooks (struct TestWebhooks_Closure *cls)
{
  free_instance_data (&cls->instance);
}


/**
 * Runs the tests for webhooks.
 *
 * @param cls the container of the test data.
 * @return 0 on success, 1 otherwise.
 */
static int
run_test_webhooks (struct TestWebhooks_Closure *cls)
{

  /* Test that insert without an instance fails */
  TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
                                         &cls->webhooks[0],
                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Insert the instance */
  TEST_RET_ON_FAIL (test_insert_instance (&cls->instance,
                                          GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test inserting a webhook */
  TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
                                         &cls->webhooks[0],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test that double insert fails */
  TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
                                         &cls->webhooks[0],
                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test lookup of individual webhooks */
  TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
                                         &cls->webhooks[0]));
  /* Make sure it fails correctly for webhooks that don't exist */
  if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
      plugin->lookup_webhook (plugin->cls,
                              cls->instance.instance.id,
                              "nonexistent_webhook",
                              NULL))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Lookup webhook failed\n");
    return 1;
  }
  /* Test webhook update */
  cls->webhooks[0].webhook.event_type =
    "Test paid";
  cls->webhooks[0].webhook.url =
    "example.com";
  cls->webhooks[0].webhook.http_method =
    "POST";
  cls->webhooks[0].webhook.header_template =
    "Authorization:WEKFOEKEXZ";
  cls->webhooks[0].webhook.body_template =
    "$Amount";
  TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
                                         &cls->webhooks[0],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));

  TEST_RET_ON_FAIL (test_lookup_webhook (&cls->instance,
                                         &cls->webhooks[0]));
  TEST_RET_ON_FAIL (test_update_webhook (&cls->instance,
                                         &cls->webhooks[1],
                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  /* Test collective webhook lookup */
  TEST_RET_ON_FAIL (test_insert_webhook (&cls->instance,
                                         &cls->webhooks[1],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_webhooks (&cls->instance,
                                          2,
                                          cls->webhooks));
  TEST_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,
                                         &cls->webhooks[1],
                                         GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double deletion fails */
  TEST_RET_ON_FAIL (test_delete_webhook (&cls->instance,
                                         &cls->webhooks[1],
                                         GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  cls->webhooks[1] = cls->webhooks[2];
  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));
  return 0;
}


/**
 * Takes care of webhook testing.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
test_webhooks (void)
{
  struct TestWebhooks_Closure test_cls;
  int test_result;

  pre_test_webhooks (&test_cls);
  test_result = run_test_webhooks (&test_cls);
  post_test_webhooks (&test_cls);
  return test_result;
}


/* *********** 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.next_attempt = GNUNET_TIME_UNIT_ZERO_ABS;
  pwebhook->pwebhook.retries = 0;
  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)
{
  pwebhook->pwebhook.next_attempt = GNUNET_TIME_relative_to_absolute (
    GNUNET_TIME_UNIT_HOURS);
  pwebhook->pwebhook.retries++;
  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;
}


/**
 * Container for information for looking up the row number of a deposit.
 */
struct LookupPendingWebhookSerial_Closure
{
  /**
   * The pending webhook we're looking for.
   */
  const struct PendingWebhookData *pwebhook;

  /**
   * The serial found.
   */
  uint64_t webhook_pending_serial;
};


/**
 * 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
get_pending_serial_cb (void *cls,
                       uint64_t webhook_pending_serial,
                       struct GNUNET_TIME_Absolute next_attempt,
                       uint32_t retries,
                       const char *url,
                       const char *http_method,
                       const char *header,
                       const char *body)
{
  struct LookupPendingWebhookSerial_Closure *lpw = cls;

  if ((0 == strcmp (lpw->pwebhook->pwebhook.url,
                    url)) &&
      (0 == strcmp (lpw->pwebhook->pwebhook.http_method,
                    http_method)) &&
      (0 == strcmp (lpw->pwebhook->pwebhook.header,
                    header)) &&
      (0 == strcmp (lpw->pwebhook->pwebhook.body,
                    body)) )
  {
    lpw->webhook_pending_serial = webhook_pending_serial;
  }
  /* else
    {
      fprintf(stdout, "next_attempt: %lu vs %lu\n", lpw->pwebhook->pwebhook.next_attempt.abs_value_us, next_attempt.abs_value_us);
      fprintf(stdout, "retries: %d vs %d\n", lpw->pwebhook->pwebhook.retries, retries);
      fprintf(stdout, "url: %s vs %s\n", lpw->pwebhook->pwebhook.url, url);
      fprintf(stdout, "http_method: %s vs %s\n", lpw->pwebhook->pwebhook.http_method, http_method);
      fprintf(stdout, "header: %s vs %s\n", lpw->pwebhook->pwebhook.header, header);
      fprintf(stdout, "body: %s vs %s\n", lpw->pwebhook->pwebhook.body, body);
      }*/
}


/**
 * Convenience function to retrieve the row number of a webhook pending in the database.
 *
 * @param instance the instance to get webhook pending(wp) from.
 * @param webhook pending the wp to lookup the serial for.
 * @return the row number of the deposit.
 */
static uint64_t
get_pending_serial (const struct InstanceData *instance,
                    const struct PendingWebhookData *pwebhook)
{
  struct LookupPendingWebhookSerial_Closure lpw = {
    .pwebhook = pwebhook,
    .webhook_pending_serial = 0
  };

  GNUNET_assert (0 <
                 plugin->lookup_all_webhooks (plugin->cls,
                                              instance->instance.id,
                                              0,
                                              INT_MAX,
                                              &get_pending_serial_cb,
                                              &lpw));
  GNUNET_assert (0 != lpw.webhook_pending_serial);

  return lpw.webhook_pending_serial;
}


/**
 * 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_pending_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;

  cmp->results_length++;
  for (unsigned int i = 0; cmp->webhooks_to_cmp_length > i; ++i)
  {
    if ((0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.url,
                      url)) &&
        (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.http_method,
                      http_method)) &&
        (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.header,
                      header)) &&
        (0 == strcmp (cmp->webhooks_to_cmp[i].pwebhook.body,
                      body)) )
    {
      cmp->results_matching[i]++;
    }
  }
}


/**
 * 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_webhooks (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 (results_matching));
  if (0 > plugin->lookup_pending_webhooks (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; i < pwebhooks_length; 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 (results_matching));
  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)
{
  uint64_t max_results = 2;
  uint64_t min_row = 0;
  unsigned int results_matching[GNUNET_NZL (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 (results_matching));
  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 (uint64_t webhooks_pending_serial,
                             enum GNUNET_DB_QueryStatus expected_result)
{

  TEST_COND_RET_ON_FAIL (expected_result ==
                         plugin->delete_pending_webhook (plugin->cls,
                                                         webhooks_pending_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)
{
  uint64_t webhook_pending_serial0;
  uint64_t webhook_pending_serial1;

  /* 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_RET_ON_FAIL (test_insert_pending_webhook (&cls->instance,
                                                 &cls->pwebhooks[1],
                                                 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test collective pending webhook lookup */
  TEST_RET_ON_FAIL (test_lookup_pending_webhooks (&cls->instance,
                                                  2,
                                                  cls->pwebhooks));
  /* Test pending webhook update */
  TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
                                                 &cls->pwebhooks[0],
                                                 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  TEST_RET_ON_FAIL (test_lookup_future_webhook (&cls->instance,
                                                1,
                                                &cls->pwebhooks[1]));
  TEST_RET_ON_FAIL (test_update_pending_webhook (&cls->instance,
                                                 &cls->pwebhooks[1],
                                                 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  // ???
  TEST_RET_ON_FAIL (test_lookup_all_webhooks (&cls->instance,
                                              2,
                                              cls->pwebhooks));

  webhook_pending_serial0 = get_pending_serial (&cls->instance,
                                                &cls->pwebhooks[0]);
  webhook_pending_serial1 = get_pending_serial (&cls->instance,
                                                &cls->pwebhooks[1]);

  /* Test webhook deletion */
  TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
                                                 GNUNET_DB_STATUS_SUCCESS_ONE_RESULT));
  /* Test double deletion fails */
  TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial1,
                                                 GNUNET_DB_STATUS_SUCCESS_NO_RESULTS));
  TEST_RET_ON_FAIL (test_delete_pending_webhook (webhook_pending_serial0,
                                                 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;
  int test_result;

  pre_test_pending_webhooks (&test_cls);
  test_result = run_test_pending_webhooks (&test_cls);
  post_test_pending_webhooks (&test_cls);
  return test_result;
}


/**
 * Function that runs all tests.
 *
 * @return 0 on success, 1 otherwise.
 */
static int
run_tests (void)
{
  TEST_RET_ON_FAIL (test_instances ());
  TEST_RET_ON_FAIL (test_products ());
  TEST_RET_ON_FAIL (test_orders ());
  TEST_RET_ON_FAIL (test_deposits ());
  TEST_RET_ON_FAIL (test_transfers ());
  TEST_RET_ON_FAIL (test_refunds ());
  TEST_RET_ON_FAIL (test_lookup_orders_all_filters ());
  TEST_RET_ON_FAIL (test_kyc ());
  TEST_RET_ON_FAIL (test_templates ());
  TEST_RET_ON_FAIL (test_webhooks ());
  TEST_RET_ON_FAIL (test_pending_webhooks ());
  return 0;
}


/**
 * Main function that will be run by the scheduler.
 *
 * @param cls closure with config
 */
static void
run (void *cls)
{
  struct GNUNET_CONFIGURATION_Handle *cfg = cls;
  /* Data for 'store_payment()' */

  /* Drop the tables to cleanup anything that might cause issues */
  if (NULL == (plugin = TALER_MERCHANTDB_plugin_load (cfg)))
  {
    result = 77;
    return;
  }
  (void) plugin->drop_tables (plugin->cls);
  if (GNUNET_OK !=
      plugin->create_tables (plugin->cls))
  {
    result = 77;
    return;
  }
  if (GNUNET_OK !=
      plugin->connect (plugin->cls))
  {
    GNUNET_break (0);
    result = 17;
    return;
  }

  /* Run the preflight */
  plugin->preflight (plugin->cls);

  result = run_tests ();
  if (0 == result)
  /** result = run_test_templates ();
            if (0 == result)*/
  {
    /* Test dropping tables */
    if (GNUNET_OK != plugin->drop_tables (plugin->cls))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "Dropping tables failed\n");
      result = 77;
      return;
    }
  }

  TALER_MERCHANTDB_plugin_unload (plugin);
  plugin = NULL;
}


/**
 * Entry point for the tests.
 */
int
main (int argc,
      char *const argv[])
{
  const char *plugin_name;
  char *config_filename;
  char *testname;
  struct GNUNET_CONFIGURATION_Handle *cfg;

  result = -1;
  if (NULL == (plugin_name = strrchr (argv[0],
                                      (int) '-')))
  {
    GNUNET_break (0);
    return -1;
  }
  GNUNET_log_setup (argv[0], "DEBUG", NULL);
  plugin_name++;
  (void) GNUNET_asprintf (&testname,
                          "test-merchantdb-%s",
                          plugin_name);
  (void) GNUNET_asprintf (&config_filename,
                          "%s.conf",
                          testname);
  fprintf (stdout, "Using %s\n", config_filename);
  cfg = GNUNET_CONFIGURATION_create (TALER_MERCHANT_project_data ());
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_parse (cfg,
                                  config_filename))
  {
    GNUNET_break (0);
    GNUNET_free (config_filename);
    GNUNET_free (testname);
    return 2;
  }
  GNUNET_SCHEDULER_run (&run,
                        cfg);
  GNUNET_CONFIGURATION_destroy (cfg);
  GNUNET_free (config_filename);
  GNUNET_free (testname);
  return result;
}


/* end of test_merchantdb.c */
