/*
  This file is part of TALER
  (C) 2016-2020 Taler Systems SA
  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.
  TALER is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
  You should have received a copy of the GNU General Public License along with
  TALER; see the file COPYING.  If not, see 
*/
/**
 * @file testing/test_taler_exchange_aggregator.c
 * @brief Tests for taler-exchange-aggregator logic
 * @author Christian Grothoff 
 * @author Marcello Stanisci
 */
#include "platform.h"
#include "taler_util.h"
#include 
#include "taler_json_lib.h"
#include "taler_exchangedb_lib.h"
#include 
#include "taler_fakebank_lib.h"
#include "taler_testing_lib.h"
/**
 * Helper structure to keep exchange configuration values.
 */
static struct TALER_TESTING_ExchangeConfiguration ec;
/**
 * Bank configuration data.
 */
static struct TALER_TESTING_BankConfiguration bc;
/**
 * Contains plugin.
 */
static struct TALER_TESTING_DatabaseConnection dbc;
/**
 * Return value from main().
 */
static int result;
/**
 * Name of the configuration file to use.
 */
static char *config_filename;
#define USER42_ACCOUNT "42"
/**
 * Execute the taler-exchange-aggregator, closer and transfer commands with
 * our configuration file.
 *
 * @param label label to use for the command.
 * @param cfg_fn configuration file to use
 */
#define CMD_EXEC_AGGREGATOR(label, cfg_fn)                                 \
  TALER_TESTING_cmd_exec_aggregator (label "-aggregator", cfg_fn), \
  TALER_TESTING_cmd_exec_transfer (label "-transfer", cfg_fn)
/**
 * Function run on shutdown to unload the DB plugin.
 *
 * @param cls NULL
 */
static void
unload_db (void *cls)
{
  (void) cls;
  if (NULL != dbc.plugin)
  {
    dbc.plugin->drop_tables (dbc.plugin->cls);
    TALER_EXCHANGEDB_plugin_unload (dbc.plugin);
    dbc.plugin = NULL;
  }
}
/**
 * Collects all the tests.
 */
static void
run (void *cls,
     struct TALER_TESTING_Interpreter *is)
{
  struct TALER_TESTING_Command all[] = {
    TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees",
                                              config_filename,
                                              "EUR:0.01",
                                              "EUR:0.01",
                                              "EUR:0.01"),
    // check no aggregation happens on a empty database
    CMD_EXEC_AGGREGATOR ("run-aggregator-on-empty-db",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-on-start"),
    /* check aggregation happens on the simplest case:
       one deposit into the database. */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-1",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:1",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-on-deposit-1",
                         config_filename),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-1",
                                           ec.exchange_url,
                                           "EUR:0.89",
                                           bc.exchange_payto,
                                           bc.user42_payto),
    TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-after-1"),
    /* check aggregation accumulates well. */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-2a",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:1",
                                      "EUR:0.1"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-2b",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:1",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-2",
                         config_filename),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-2",
                                           ec.exchange_url,
                                           "EUR:1.79",
                                           bc.exchange_payto,
                                           bc.user42_payto),
    TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-after-2"),
    /* check that different merchants stem different aggregations. */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-3a",
                                      &dbc,
                                      "bob",
                                      "4",
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:1",
                                      "EUR:0.1"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-3b",
                                      &dbc,
                                      "bob",
                                      "5",
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:1",
                                      "EUR:0.1"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-3c",
                                      &dbc,
                                      "alice",
                                      "4",
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:1",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-3",
                         config_filename),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3a",
                                           ec.exchange_url,
                                           "EUR:0.89",
                                           bc.exchange_payto,
                                           "payto://x-taler-bank/localhost/4?receiver-name=4"),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3b",
                                           ec.exchange_url,
                                           "EUR:0.89",
                                           bc.exchange_payto,
                                           "payto://x-taler-bank/localhost/4?receiver-name=4"),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-3c",
                                           ec.exchange_url,
                                           "EUR:0.89",
                                           bc.exchange_payto,
                                           "payto://x-taler-bank/localhost/5?receiver-name=5"),
    TALER_TESTING_cmd_check_bank_empty ("expect-empty-transactions-after-3"),
    /* checking that aggregator waits for the deadline. */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-4a",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_relative_multiply
                                        (GNUNET_TIME_UNIT_SECONDS,
                                        5),
                                      "EUR:0.2",
                                      "EUR:0.1"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-4b",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_relative_multiply
                                        (GNUNET_TIME_UNIT_SECONDS,
                                        5),
                                      "EUR:0.2",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-4-early",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-4-fast"),
    TALER_TESTING_cmd_sleep ("wait (5s)", 5),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-4-delayed",
                         config_filename),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-4",
                                           ec.exchange_url,
                                           "EUR:0.19",
                                           bc.exchange_payto,
                                           bc.user42_payto),
    // test picking all deposits at earliest deadline
    TALER_TESTING_cmd_insert_deposit ("do-deposit-5a",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_relative_multiply
                                        (GNUNET_TIME_UNIT_SECONDS,
                                        10),
                                      "EUR:0.2",
                                      "EUR:0.1"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-5b",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_relative_multiply
                                        (GNUNET_TIME_UNIT_SECONDS,
                                        5),
                                      "EUR:0.2",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-5-early",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-5-early"),
    TALER_TESTING_cmd_sleep ("wait (5s)", 5),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-5-delayed",
                         config_filename),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-5",
                                           ec.exchange_url,
                                           "EUR:0.19",
                                           bc.exchange_payto,
                                           bc.user42_payto),
    /* Test NEVER running 'tiny' unless they make up minimum unit */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-6a",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.102",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-6a-tiny",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-6a-tiny"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-6b",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.102",
                                      "EUR:0.1"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-6c",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.102",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-6c-tiny",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-6c-tiny"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-6d",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.102",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-6d-tiny",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-6d-tiny"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-6e",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.112",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-6e",
                         config_filename),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-6",
                                           ec.exchange_url,
                                           "EUR:0.01",
                                           bc.exchange_payto,
                                           bc.user42_payto),
    /* Test profiteering if wire deadline is short */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-7a",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.109",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-7a-tiny",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-7a-tiny"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-7b",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.119",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-7-profit",
                         config_filename),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-7",
                                           ec.exchange_url,
                                           "EUR:0.01",
                                           bc.exchange_payto,
                                           bc.user42_payto),
    /* Now check profit was actually taken */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-7c",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.122",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-7-loss",
                         config_filename),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-7",
                                           ec.exchange_url,
                                           "EUR:0.01",
                                           bc.exchange_payto,
                                           bc.user42_payto),
    /* Test that aggregation would happen fully if wire deadline is long */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-8a",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_relative_multiply
                                        (GNUNET_TIME_UNIT_SECONDS,
                                        5),
                                      "EUR:0.109",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-8a-tiny",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-8a-tiny"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-8b",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_relative_multiply
                                        (GNUNET_TIME_UNIT_SECONDS,
                                        5),
                                      "EUR:0.109",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-8b-tiny",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-8b-tiny"),
    /* now trigger aggregate with large transaction and short deadline */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-8c",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.122",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-8",
                         config_filename),
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-8",
                                           ec.exchange_url,
                                           "EUR:0.03",
                                           bc.exchange_payto,
                                           bc.user42_payto),
    /* Test aggregation with fees and rounding profits. */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-9a",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_relative_multiply
                                        (GNUNET_TIME_UNIT_SECONDS,
                                        5),
                                      "EUR:0.104",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-9a-tiny",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-9a-tiny"),
    TALER_TESTING_cmd_insert_deposit ("do-deposit-9b",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_relative_multiply
                                        (GNUNET_TIME_UNIT_SECONDS,
                                        5),
                                      "EUR:0.105",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-9b-tiny",
                         config_filename),
    TALER_TESTING_cmd_check_bank_empty (
      "expect-empty-transactions-after-9b-tiny"),
    /* now trigger aggregate with large transaction and short deadline */
    TALER_TESTING_cmd_insert_deposit ("do-deposit-9c",
                                      &dbc,
                                      "bob",
                                      USER42_ACCOUNT,
                                      GNUNET_TIME_timestamp_get (),
                                      GNUNET_TIME_UNIT_ZERO,
                                      "EUR:0.112",
                                      "EUR:0.1"),
    CMD_EXEC_AGGREGATOR ("run-aggregator-deposit-9",
                         config_filename),
    /* 0.009 + 0.009 + 0.022 - 0.001 - 0.002 - 0.008 = 0.029 => 0.02 */
    TALER_TESTING_cmd_check_bank_transfer ("expect-deposit-9",
                                           ec.exchange_url,
                                           "EUR:0.01",
                                           bc.exchange_payto,
                                           bc.user42_payto),
    TALER_TESTING_cmd_end ()
  };
  TALER_TESTING_run_with_fakebank (is,
                                   all,
                                   bc.exchange_auth.wire_gateway_url);
}
/**
 * Prepare database and launch the test.
 *
 * @param cls unused
 * @param is interpreter to use
 */
static void
prepare_database (void *cls,
                  struct TALER_TESTING_Interpreter *is)
{
  dbc.plugin = TALER_EXCHANGEDB_plugin_load (is->cfg);
  if (NULL == dbc.plugin)
  {
    GNUNET_break (0);
    result = 77;
    TALER_TESTING_interpreter_fail (is);
    return;
  }
  if (GNUNET_OK !=
      dbc.plugin->preflight (dbc.plugin->cls))
  {
    GNUNET_break (0);
    result = 77;
    TALER_TESTING_interpreter_fail (is);
    return;
  }
  GNUNET_SCHEDULER_add_shutdown (&unload_db,
                                 NULL);
  run (NULL,
       is);
}
int
main (int argc,
      char *const argv[])
{
  const char *plugin_name;
  char *testname;
  if (NULL == (plugin_name = strrchr (argv[0], (int) '-')))
  {
    GNUNET_break (0);
    return -1;
  }
  plugin_name++;
  (void) GNUNET_asprintf (&testname,
                          "test-taler-exchange-aggregator-%s",
                          plugin_name);
  (void) GNUNET_asprintf (&config_filename,
                          "%s.conf",
                          testname);
  GNUNET_log_setup ("test_taler_exchange_aggregator",
                    "DEBUG",
                    NULL);
  /* these might get in the way */
  unsetenv ("XDG_DATA_HOME");
  unsetenv ("XDG_CONFIG_HOME");
  TALER_TESTING_cleanup_files (config_filename);
  if (GNUNET_OK !=
      TALER_TESTING_prepare_exchange (config_filename,
                                      GNUNET_YES,
                                      &ec))
  {
    TALER_LOG_WARNING ("Could not prepare the exchange.\n");
    return 77;
  }
  if (GNUNET_OK !=
      TALER_TESTING_prepare_fakebank (config_filename,
                                      "exchange-account-1",
                                      &bc))
  {
    TALER_LOG_WARNING ("Could not prepare the fakebank\n");
    return 77;
  }
  result = GNUNET_OK;
  if (GNUNET_OK !=
      TALER_TESTING_setup_with_exchange (&prepare_database,
                                         NULL,
                                         config_filename))
  {
    TALER_LOG_WARNING ("Could not prepare database for tests.\n");
    return result;
  }
  GNUNET_free (config_filename);
  GNUNET_free (testname);
  return GNUNET_OK == result ? 0 : 1;
}
/* end of test_taler_exchange_aggregator.c */