/*
 * Copyright (C) 2008 Funambol, Inc.
 * Copyright (C) 2008-2009 Patrick Ohly <patrick.ohly@gmx.de>
 * Copyright (C) 2009 Intel Corporation
 *
 * This library 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 2.1 of the License, or (at your option) version 3.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#ifndef INCL_TESTSYNCCLIENT
#define INCL_TESTSYNCCLIENT

#include <functional>
#include <list>
#include <memory>
#include <string>
#include <vector>

#include <SyncML.h>
#include <TransportAgent.h>
#include <SyncSource.h>
#include <syncevo/SuspendFlags.h>

#include "test.h"
#include "ClientTestAssert.h"

#ifdef ENABLE_INTEGRATION_TESTS

#include <cppunit/TestSuite.h>
#include <cppunit/TestAssert.h>
#include <cppunit/TestFixture.h>

#include <syncevo/Logging.h>
#include <syncevo/util.h>

#include <boost/utility.hpp>

#include <syncevo/declarations.h>
SE_BEGIN_CXX

class SyncContext;
class EvolutionSyncSource;
class TransportWrapper;
class TestingSyncSource;

/**
 * This class encapsulates logging and checking of a SyncReport.
 * When constructed with default parameters, no checking will be done.
 * Otherwise the sync report has to contain exactly the expected result.
 * When multiple sync sources are active, @b all of them have to behave
 * alike (which is how the tests are constructed).
 *
 * No item is ever supposed to fail.
 */
class CheckSyncReport {
  public:
    CheckSyncReport(int clAdded = -1, int clUpdated = -1, int clDeleted = -1,
                    int srAdded = -1, int srUpdated = -1, int srDeleted = -1,
                    bool mstSucceed = true, SyncMode mode = SYNC_NONE) :
        clientAdded(clAdded),
        clientUpdated(clUpdated),
        clientDeleted(clDeleted),
        serverAdded(srAdded),
        serverUpdated(srUpdated),
        serverDeleted(srDeleted),
        restarts(0),
        mustSucceed(mstSucceed),
        syncMode(mode),
        m_report(nullptr)
        {}

    int clientAdded, clientUpdated, clientDeleted,
        serverAdded, serverUpdated, serverDeleted;
    int restarts;
    bool mustSucceed;
    SyncMode syncMode;

    // if set, then the report is copied here
    SyncReport *m_report;

    CheckSyncReport &setMode(SyncMode mode) { syncMode = mode; return *this; }
    CheckSyncReport &setReport(SyncReport *report) { m_report = report; return *this; }
    CheckSyncReport &setRestarts(int r) { restarts = r; return *this; }

    /**
     * checks that the sync completed as expected and throws
     * CPPUnit exceptions if something is wrong
     *
     * @param res     return code from SyncClient::sync()
     * @param report  the sync report stored in the SyncClient
     */
    void check(SyncMLStatus status, SyncReport &report) const;

    /**
     * checks that the source report matches with expectations
     */
    void check(const std::string &name, const SyncSourceReport &report) const;
};

/**
 * parameters for running a sync
 */
struct SyncOptions {
    /** default maximum message size */
    static const long DEFAULT_MAX_MSG_SIZE = 128 * 1024;
    /** default maximum object size */
    static const long DEFAULT_MAX_OBJ_SIZE = 1024 * 1024 * 1024;
    /** sync mode chosen by client */
    SyncMode m_syncMode;
    /**
     * has to be called after a successful or unsuccessful sync,
     * will dump the report and (optionally) check the result;
     * beware, the later may throw exceptions inside CPPUNIT macros
     */
    CheckSyncReport m_checkReport;

    /** maximum message size supported by client */
    long m_maxMsgSize;
    /** maximum object size supported by client */
    long m_maxObjSize;
    /** enabled large object support */
    bool m_loSupport;
    /** enabled WBXML (default) */
    bool m_isWBXML;
    /** overrides resend properties */
    int m_retryDuration;
    int m_retryInterval;

    std::shared_ptr<SuspendFlags::StateBlocker> m_isSuspended;
    std::shared_ptr<SuspendFlags::StateBlocker> m_isAborted;

    /**
     * Callback to be invoked after setting up local sources, but
     * before running the engine. May throw exception to indicate
     * error and return true to stop sync without error.
     */
    typedef std::function<bool (SyncContext &,
                                  SyncOptions &)> Callback_t;
    Callback_t m_startCallback;

    /**
     * called while configuration is prepared for sync, see
     * SyncContext::prepare()
     */
    Callback_t m_prepareCallback;

    std::shared_ptr<TransportAgent> m_transport;

    SyncOptions(SyncMode syncMode = SYNC_NONE,
                const CheckSyncReport &checkReport = CheckSyncReport(),
                long maxMsgSize = DEFAULT_MAX_MSG_SIZE, // 128KB = large enough that normal tests should run with a minimal number of messages
                long maxObjSize = DEFAULT_MAX_OBJ_SIZE, // 1GB = basically unlimited...
                bool loSupport = false,
                bool isWBXML = defaultWBXML(),
                const Callback_t &startCallback = [] (SyncContext &, SyncOptions &) { return false; },
                const std::shared_ptr<TransportAgent> &transport = {}) :
        m_syncMode(syncMode),
        m_checkReport(checkReport),
        m_maxMsgSize(maxMsgSize),
        m_maxObjSize(maxObjSize),
        m_loSupport(loSupport),
        m_isWBXML(isWBXML),
        m_retryDuration(300),
        m_retryInterval(60),
        m_startCallback(startCallback),
        m_transport (transport)
    {}

    SyncOptions &setSyncMode(SyncMode syncMode) { m_syncMode = syncMode; return *this; }
    SyncOptions &setCheckReport(const CheckSyncReport &checkReport) { m_checkReport = checkReport; return *this; }
    SyncOptions &setMaxMsgSize(long maxMsgSize) { m_maxMsgSize = maxMsgSize; return *this; }
    SyncOptions &setMaxObjSize(long maxObjSize) { m_maxObjSize = maxObjSize; return *this; }
    SyncOptions &setLOSupport(bool loSupport) { m_loSupport = loSupport; return *this; }
    SyncOptions &setWBXML(bool isWBXML) { m_isWBXML = isWBXML; return *this; }
    SyncOptions &setRetryDuration(int retryDuration) { m_retryDuration = retryDuration; return *this; }
    SyncOptions &setRetryInterval(int retryInterval) { m_retryInterval = retryInterval; return *this; }
    SyncOptions &setStartCallback(const Callback_t &callback) { m_startCallback = callback; return *this; }
    SyncOptions &setPrepareCallback(const Callback_t &callback) { m_prepareCallback = callback; return *this; }
    SyncOptions &setTransportAgent(const std::shared_ptr<TransportAgent> &transport)
                                  {m_transport = transport; return *this;}

    /** if CLIENT_TEST_XML=1, then XML, otherwise WBXML */
    static bool defaultWBXML();
};

class LocalTests;
class SyncTests;

/**
 * This is the interface expected by the testing framework for sync
 * clients.  It defines several methods that a derived class must
 * implement if it wants to use that framework. Note that this class
 * itself is not derived from SyncClient. This gives a user of this
 * framework the freedom to implement it in two different ways:
 * - implement a class derived from both SyncClient and ClientTest
 * - add testing of an existing subclass of SyncClient by implementing
 *   a ClientTest which uses that subclass
 *
 * The client is expected to support change tracking for multiple
 * servers. Although the framework always always tests against the
 * same server, for most tests it is necessary to access the database
 * without affecting the next synchronization with the server. This is
 * done by asking the client for two different sync sources via
 * Config::createSourceA and Config::createSourceB which have to
 * create them in a suitable way - pretty much as if the client was
 * synchronized against different server. A third, different change
 * tracking is needed for real synchronizations of the data.
 *
 * Furthermore the client is expected to support multiple data sources
 * of the same kind, f.i. two different address books. This is used to
 * test full client A <-> server <-> client B synchronizations in some
 * tests or to check server modifications done by client A with a
 * synchronization against client B. In those tests client A is mapped
 * to the first data source and client B to the second one.
 *
 * Handling configuration and creating classes is entirely done by the
 * subclass of ClientTest, the frameworks makes no assumptions
 * about how this is done. Instead it queries the ClientTest for
 * properties (like available sync sources) and then creates several
 * tests.
 */
class ClientTest : private boost::noncopyable {
  public:
    ClientTest(int serverSleepSec = 0, const std::string &serverLog= "");
    virtual ~ClientTest();

    /** a unique string - "1" or "2" in practice */
    virtual std::string getClientID() const = 0;

    /** set up before running a test */
    virtual void setup() { }

    /** cleanup function to be called when shutting down testing */
    typedef void (*Cleanup_t)(void);

    /**
     * Call this to register another shutdown cleanup functions.
     * Every unique function will be called exactly once.
     */
    static void registerCleanup(Cleanup_t cleanup);

    /**
     * Call cleanup functions.
     */
    static void shutdown();

    /**
     * This function registers tests using this instance of ClientTest for
     * later use during a test run.
     *
     * The instance must remain valid until after the tests were
     * run. To run them use a separate test runner, like the one from
     * client-test-main.cpp.
     */
    virtual void registerTests();

    typedef ClientTestConfig Config;

    /**
     * Creates an instance of LocalTests (default implementation) or a
     * class derived from it.  LocalTests provides tests which cover
     * the SyncSource interface and can be executed without a SyncML
     * server. It also contains utility functions for working with
     * SyncSources.
     *
     * A ClientTest implementation can, but doesn't have to extend
     * these tests by instantiating a derived class here.
     */
    virtual LocalTests *createLocalTests(const std::string &name, int sourceParam, ClientTest::Config &co);

    /**
     * Creates an instance of SyncTests (default) or a class derived
     * from it.  SyncTests provides tests which cover the actual
     * interaction with a SyncML server.
     *
     * A ClientTest implementation can, but doesn't have to extend
     * these tests by instantiating a derived class here.
     */
    virtual SyncTests *createSyncTests(const std::string &name, std::vector<int> sourceIndices, bool isClientA = true);

    /**
     * utility function for dumping items which are C strings with blank lines as separator
     */
    static int dump(ClientTest &client, TestingSyncSource &source, const std::string &file);

    /**
     * utility function for splitting file into items with blank lines as separator
     *
     * @retval realfile       If <file>.<server>.tem exists, then it is used instead
     *                        of the generic version. The caller gets the name of the
     *                        file that was opened here.
     */
    static void getItems(const std::string &file, std::list<std::string> &items, std::string &realfile);

    /**
     * utility function for importing items with blank lines as separator,
     * for ClientTestConfig::m_import
     */
    static std::string import(ClientTest &client, TestingSyncSource &source,
                              const ClientTestConfig &config,
                              const std::string &file, std::string &realfile,
                              std::list<std::string> *luids);

    /**
     * utility function for comparing vCard and iCal files with the external
     * synccompare.pl Perl script
     */
    static bool compare(ClientTest &client, const std::string &fileA, const std::string &fileB);

    /**
     * utility function: update a vCard or iCalendar item by inserting "MOD-" into
     * FN, N, resp. SUMMARY; used for ClientTestConfig::update
     */
    static void update(std::string &item);

    struct ClientTestConfig config;

    /**
     * A derived class can use this call to get default test
     * cases, but still has to add callbacks which create sources
     * and execute a sync session.
     *
     * Some of the test cases are compiled into the library, other
     * depend on the auxiliary files from the "test" directory.
     * Currently supported types:
     * - eds_contact = vCard 3.0 contacts, with Evolution extensions
     * - eds_event = iCalendar 2.0 events, as used by Evolution
     * - eds_task = iCalendar 2.0 tasks, as used by Evolution
     * - eds_memo = iCalendar 2.0 journals, as used by Evolution
     */
    static void getTestData(const char *type, Config &config);

    /**
     * Data sources are enumbered from 0 to n-1 for the purpose of
     * testing. This call returns n.
     */
    virtual int getNumLocalSources() = 0;
    virtual int getNumSyncSources() = 0;

    /**
     * Called to fill the given test source config with information
     * about a sync source identified by its index. It's okay to only
     * fill in the available pieces of information and set everything
     * else to zero.
     * Two kinds of source config indexs are maintained, used for localSources
     * and SyncSources, this is because virtual datasoures should be visible as
     * a whole to the synccontext while should be viewed as a list of sub
     * datasoures for Localtests.
     */
    virtual void getLocalSourceConfig(int source, Config &config) = 0;
    virtual void getSyncSourceConfig(int source, Config &config) = 0;

    /**
     * Find the correspoding test source config via config name.
     */
    virtual void getSourceConfig(const string &configName, Config &config) =0;
    /*
     * Give me a test source config name, return the index in localSyncSources.
     * */
    virtual int getLocalSourcePosition (const string &configName) =0;

    /**
     * The instance to use as second client. Returning nullptr disables
     * all checks which require a second client. The returned pointer
     * must remain valid throughout the life time of the tests.
     *
     * The second client must be configured to access the same server
     * and have data sources which match the ones from the primary
     * client.
     */
    virtual ClientTest *getClientB() = 0;

    /**
     * Execute a synchronization with the selected sync sources
     * and the selected synchronization options. The log file
     * in LOG has been set up already for the synchronization run
     * and should not be changed by the client.
     *
     * @param activeSources a -1 terminated array of sync source indices
     * @param logbase      basename for logging: can be used for directory or as file (by adding .log suffix)
     * @param options      sync options to be used
     * @return return code of SyncClient::sync()
     */
    virtual SyncMLStatus doSync(
        const int *activeSources,
        const std::string &logbase,
        const SyncOptions &options) = 0;


    /**
     * This is called after successful sync() calls (res == 0) as well
     * as after unsuccessful ones (res != 1). The default implementation
     * sleeps for the number of seconds specified when constructing this
     * instance and copies the server log if one was named.
     *
     * @param res       result of sync()
     * @param logname   base name of the current sync log (without ".client.[AB].log" suffix)
     */
    virtual void postSync(int res, const std::string &logname);

  protected:
    /**
     * time to sleep in postSync()
     */
    int serverSleepSeconds;

    /**
     * server log file which is copied by postSync() and then
     * truncated (Unix only, Windows does not allow such access
     * to an open file)
     */
    std::string serverLogFileName;

  private:
    /**
     * really a CppUnit::TestFactory, but declared as void * to avoid
     * dependencies on the CPPUnit header files: created by
     * registerTests() and remains valid until the client is deleted
     */
    void *factory;
    void freeFactory();
};

/**
 * helper class to encapsulate ClientTest::Config::createsource_t
 * pointer and the corresponding parameters
 */
class CreateSource {
public:
     CreateSource(const ClientTest::Config::createsource_t &createSourceParam, ClientTest &clientParam, int sourceParam, bool isSourceAParam) :
        createSource(createSourceParam),
        client(clientParam),
        source(sourceParam),
        isSourceA(isSourceAParam) {}

    std::unique_ptr<TestingSyncSource> operator() () const {
        CPPUNIT_ASSERT(createSource);
        return createSource(client, client.getClientID(), source, isSourceA);
    }

    const ClientTest::Config::createsource_t createSource;
    ClientTest &client;
    const int source;
    const bool isSourceA;
};


/**
 * local test of one sync source and utility functions also used by
 * sync tests
 */
class LocalTests : public CppUnit::TestSuite, public CppUnit::TestFixture {
public:
    /** the client we are testing */
    ClientTest &client;

    /** number of the source we are testing in that client */
    const int source;

    /** configuration that corresponds to source */
    const ClientTest::Config config;

    /** shortcut for config.m_sourceName */
    const std::string &getSourceName() const { return config.m_sourceName; }

    /**
     * A list of config pointers which share the same
     * database. Normally, sources are tested in isolation, but for
     * such linked sources we also need to test interdependencies, in
     * particular regarding change tracking and item listing.
     *
     * This includes *all* configs, not just the other ones.
     */
    std::list<LocalTests *> m_linkedSources;

    /** helper funclets to create sources */
    CreateSource createSourceA, createSourceB;

    LocalTests(const std::string &name, ClientTest &cl, int sourceParam, ClientTest::Config &co) :
        CppUnit::TestSuite(name),
        client(cl),
        source(sourceParam),
        config(co),
        createSourceA(co.m_createSourceA, cl, sourceParam, true),
        createSourceB(co.m_createSourceB, cl, sourceParam, false)
        {}

    /** set up before running a test */
    virtual void setUp() { client.setup(); }

    /**
     * adds the supported tests to the instance itself;
     * this is the function that a derived class can override
     * to add additional tests
     */
    virtual void addTests();

    /**
     * opens source and inserts the given item; can be called
     * regardless whether the data source already contains items or not
     *
     * @param relaxed   if true, then disable some of the additional checks after adding the item
     * @retval inserted    actual data that was inserted, optional
     * @param uniqueUIDSuffix    gets added to UID of the inserted item if unique UIDs are necessary
     * @return the LUID of the inserted item
     */
    virtual std::string insert(const CreateSource &createSource, const std::string &data, bool relaxed = false, std::string *inserted = nullptr, const std::string &uniqueUIDSuffix = "");

    /**
     * assumes that exactly one element is currently inserted and updates it with the given item
     *
     * @param check     if true, then reopen the source and verify that the reported items are as expected
     * @param uniqueUIDSuffix    same UID suffix as when creating the item
     */
    virtual void update(const CreateSource &createSource, const std::string &data, bool check = true, const std::string &uniqueUIDSuffix = "");

    /**
     * updates one item identified by its LUID with the given item
     *
     * The type of the item is cleared, as in insert() above.
     */
    virtual void update(const CreateSource &createSource, const std::string &data, const std::string &luid);

    /** deletes all items locally via sync source */
    virtual void deleteAll(const CreateSource &createSource);

    /**
     * takes two databases, exports them,
     * then compares them using synccompare
     *
     * @param refFile      existing file with source reference items, nullptr uses a dump of sync source A instead
     * @param copy         a sync source which contains the copied items, begin/endSync will be called
     * @param raiseAssert  raise assertion if comparison yields differences (defaults to true)
     * @return true if the two databases are equal
     */
    virtual bool compareDatabases(const char *refFile, TestingSyncSource &copy, bool raiseAssert = true);

    /**
     * compares data using synccompare
     *
     * @param refFile      existing file with source reference items
     * @param actualFile   existing file with actual items which are expected to match
     * @param raiseAssert  raise assertion if comparison yields differences (defaults to true)
     * @return true if the two databases are equal
     */
    bool compareDatabases(const std::string &refFile, const std::string &actualFile, bool raiseAssert = true);

    /**
     * compare data in source with set of items
     */
    void compareDatabasesRef(TestingSyncSource &copy,
                             const std::list<std::string> &items);

    /**
     * compare data in source with vararg list of std::string pointers, nullptr terminated
     */
    void compareDatabases(TestingSyncSource *copy, ...);

    /**
     * insert artificial items, number of them determined by TEST_EVOLUTION_NUM_ITEMS
     * unless passed explicitly
     *
     * @param createSource    a factory for the sync source that is to be used
     * @param startIndex      IDs are generated starting with this value
     * @param numItems        number of items to be inserted if non-null, otherwise TEST_EVOLUTION_NUM_ITEMS is used
     * @param size            minimum size for new items
     * @return number of items inserted
     */
    virtual std::list<std::string> insertManyItems(const CreateSource &createSource, int startIndex = 1, int numItems = 0, int size = -1);
    virtual std::list<std::string> insertManyItems(TestingSyncSource *source, int startIndex = 1, int numItems = 0, int size = -1);

    /**
     * Update existing items. Must match a corresponding previous call to
     * insertManyItems().
     *
     * @param revision    revision number, used to distinguish different generations of each item
     * @param luids       result from corresponding insertManyItems() call
     * @param offset      skip that many items at the start of luids before updating the following ones
     */
    void updateManyItems(const CreateSource &createSource, int startIndex, int numItems, int size,
                         int revision,
                         std::list<std::string> &luids,
                         int offset);

    /**
     * Delete items. Skips offset items in luids before deleting numItems.
     */
    void removeManyItems(const CreateSource &createSource, int numItems,
                         std::list<std::string> &luids,
                         int offset);

    /**
     * update every single item, using config.update
     */
    virtual void updateData(const CreateSource &createSource);

    /**
     * create an artificial item for the current database
     *
     * @param item      item number: items with different number should be
     *                  recognized as different by SyncML servers
     * @param revision  differentiates items with the same item number (= updates of an older item)
     * @param size      if > 0, then create items at least that large (in bytes)
     * @return created item
     */
    std::string createItem(int item, const std::string &revision, int size);
    std::string createItem(int item, int revision, int size) {
        char buffer[32];
        sprintf(buffer, "%d", revision);
        return createItem(item, std::string(buffer), size);
    }

    /* for more information on the different tests see their implementation */

    virtual void testOpen();
    virtual void testIterateTwice();
    virtual void testDelete404();
    virtual void testReadItem404();
    void doInsert(bool withUID = true);
    virtual void testSimpleInsert();
    virtual void testLocalDeleteAll();
    virtual void testComplexInsert();
    virtual void testInsertTwice();
    virtual void testLocalUpdate();
    void doChanges(bool restart);
    virtual void testChanges();
    virtual void testChangesMultiCycles();
    virtual void testLinkedSources();
    void doImport(const std::string &testcases);
    virtual void testImport();
    virtual void testImportDelete();
    virtual void testRemoveProperties();
    virtual void testManyChanges();
    virtual void testLinkedItemsParent();
    virtual void testLinkedItemsChild();
    virtual void testLinkedItemsParentChild();
    virtual void testLinkedItemsChildParent();
    virtual void testLinkedItemsChildChangesParent();
    virtual void testLinkedItemsRemoveParentFirst();
    virtual void testLinkedItemsRemoveNormal();
    virtual void testLinkedItemsInsertParentTwice();
    virtual void testLinkedItemsInsertChildTwice();
    virtual void testLinkedItemsParentUpdate();
    virtual void testLinkedItemsUpdateChild();
    virtual void testLinkedItemsInsertBothUpdateChild();
    virtual void testLinkedItemsInsertBothUpdateParent();
    virtual void testLinkedItemsInsertBothUpdateChildNoIDs();
    virtual void testLinkedItemsUpdateChildNoIDs();
    virtual void testLinkedItemsSingle404();
    virtual void testLinkedItemsMany404();

    virtual void testSubset();

    /** retrieve right set of items for running test */
    ClientTestConfig::LinkedItems_t getParentChildData();
};

std::list<std::string> listItemsOfType(TestingSyncSource *source, int state);

/**
 * Tests synchronization with one or more sync sources enabled.
 * When testing multiple sources at once only the first config
 * is checked to see which tests can be executed.
 */
class SyncTests : public CppUnit::TestSuite, public CppUnit::TestFixture {
public:
    /** the client we are testing */
    ClientTest &client;

    SyncTests(const std::string &name, ClientTest &cl, std::vector<int> sourceIndices, bool isClientA = true);
    ~SyncTests();

    /**
     * adds the supported tests to the instance itself
     * @param isFirstSource     the tests are getting generated for a single source,
     *                          the one which was listed first; some tests are the
     *                          same for all sources and should only be run once
     */
    virtual void addTests(bool isFirstSource = false);

    /** set up before running a test */
    virtual void setUp() { client.setup(); }

protected:
    /** list with all local test classes for manipulating the sources and their index in the client */
    typedef std::vector< std::pair<int, LocalTests *> > source_array_t;
    source_array_t sources;
    typedef source_array_t::iterator source_it;

    /**
     * Stack of log file prefixes which are to be appended to the base name,
     * which already contains the current test name. Add a new prefix by
     * instantiating SyncPrefix. Its destructor takes care of popping
     * the prefix.
     */
    std::list<std::string> logPrefixes;

    class SyncPrefix {
        SyncTests &m_tests;
    public:
        SyncPrefix(const std::string &prefix, SyncTests &tests) :
        m_tests(tests) {
            tests.logPrefixes.push_back(prefix);
        }
        ~SyncPrefix() {
            m_tests.logPrefixes.pop_back();
        }
    };
    friend class SyncPrefix;

    /** the indices from sources, terminated by -1 (for sync()) */
    int *sourceArray;

    /** utility functions for second client */
    SyncTests *accessClientB;

    enum DeleteAllMode {
        DELETE_ALL_SYNC,   /**< make sure client and server are in sync,
                              delete locally,
                              sync again */
        DELETE_ALL_REFRESH /**< delete locally, refresh server */
    };

    /**
     * Compare databases second client with either reference file(s)
     * or first client.  The reference file(s) must follow the naming
     * scheme <reFileBase><source name>.dat
     */
    virtual bool compareDatabases(const char *refFileBase = nullptr,
                                  bool raiseAssert = true);

    /** deletes all items locally and on server */
    virtual void deleteAll(DeleteAllMode mode = DELETE_ALL_SYNC);

    /** get both clients in sync with empty server, then copy one item from client A to B */
    virtual void doCopy();

    /**
     * replicate server database locally: same as SYNC_REFRESH_FROM_SERVER,
     * but done with explicit local delete and then a SYNC_SLOW because some
     * servers do no support SYNC_REFRESH_FROM_SERVER
     */
    virtual void refreshClient(SyncOptions options = SyncOptions());

    /* for more information on the different tests see their implementation */

    virtual void testTwoWaySync();
    virtual void testSlowSync();
    virtual void testRefreshFromServerSync();
    virtual void testRefreshFromClientSync();
    virtual void testRefreshFromRemoteSync();
    virtual void testRefreshFromLocalSync();

    virtual void testDeleteAllSync();

    virtual void testDeleteAllRefresh();
    virtual void testRefreshFromClientSemantic();
    virtual void testRefreshFromServerSemantic();
    virtual void testRefreshStatus();

    void doRestartSync(SyncMode mode);
    void testTwoWayRestart();
    void testSlowRestart();
    void testRefreshFromLocalRestart();
    void testOneWayFromLocalRestart();
    void testRefreshFromRemoteRestart();
    void testOneWayFromRemoteRestart();
    void testManyRestarts();

    void testCopy();

    virtual void testUpdate();
    virtual void testComplexUpdate();
    virtual void testDelete();
    virtual void testMerge();
    virtual void testTwinning();
    void doOneWayFromRemote(SyncMode oneWayFromRemote);
    void testOneWayFromServer();
    void testOneWayFromRemote();
    void doOneWayFromLocal(SyncMode oneWayFromLocal);
    void testOneWayFromClient();
    void testOneWayFromLocal();
    virtual void testConversion();
    virtual void testItems();
    virtual void testItemsXML();
    virtual void testExtensions();
    virtual void testAddUpdate();

    void testMaxMsg();
    void testLargeObject();

    virtual void testManyItems();
    virtual void testManyDeletes();
    virtual void testSlowSyncSemantic();
    virtual void testComplexRefreshFromServerSemantic();
    virtual void testDeleteBothSides();
    virtual void testAddBothSides();
    virtual void testAddBothSidesRefresh();
    virtual void testLinkedItemsParentChild();
    virtual void testLinkedItemsChild();
    virtual void testLinkedItemsChildParent();

    virtual void doInterruptResume(int changes,
                  std::shared_ptr<TransportWrapper> wrapper); 

    /**
     * CLIENT_ = change made on client B before interrupting
     * SERVER_ = change made on client A and applied to server before interrupting
     *           while sending to B
     * _ADD = new item added
     * _REMOVE = existing item deleted
     * _UPDATE = existing item replaced
     * BIG = when adding or updating, make the new item so large that it does
     *       not fit into a single message
     */
    enum {
        CLIENT_ADD = (1<<0),
        CLIENT_REMOVE = (1<<1),
        CLIENT_UPDATE = (1<<2),
        SERVER_ADD = (1<<3),
        SERVER_REMOVE = (1<<4),
        SERVER_UPDATE = (1<<5),
        BIG = (1<<6)
    };
    virtual void testInterruptResumeClientAdd();
    virtual void testInterruptResumeClientRemove();
    virtual void testInterruptResumeClientUpdate();
    virtual void testInterruptResumeServerAdd();
    virtual void testInterruptResumeServerRemove();
    virtual void testInterruptResumeServerUpdate();
    virtual void testInterruptResumeClientAddBig();
    virtual void testInterruptResumeClientUpdateBig();
    virtual void testInterruptResumeServerAddBig();
    virtual void testInterruptResumeServerUpdateBig();
    virtual void testInterruptResumeFull();

    virtual void testUserSuspendClientAdd();
    virtual void testUserSuspendClientRemove();
    virtual void testUserSuspendClientUpdate();
    virtual void testUserSuspendServerAdd();
    virtual void testUserSuspendServerRemove();
    virtual void testUserSuspendServerUpdate();
    virtual void testUserSuspendClientAddBig();
    virtual void testUserSuspendClientUpdateBig();
    virtual void testUserSuspendServerAddBig();
    virtual void testUserSuspendServerUpdateBig();
    virtual void testUserSuspendFull();

    virtual void testResendClientAdd();
    virtual void testResendClientRemove();
    virtual void testResendClientUpdate();
    virtual void testResendServerAdd();
    virtual void testResendServerRemove();
    virtual void testResendServerUpdate();
    virtual void testResendFull();

    virtual void testResendProxyClientAdd();
    virtual void testResendProxyClientRemove();
    virtual void testResendProxyClientUpdate();
    virtual void testResendProxyServerAdd();
    virtual void testResendProxyServerRemove();
    virtual void testResendProxyServerUpdate();
    virtual void testResendProxyFull();

    virtual void testTimeout();

    virtual void testUpload();
    virtual void testDownload();

    void doUpdateConflict(const std::string &testname, bool localWins);
    virtual void testUpdateLocalWins();
    virtual void testUpdateRemoteWins();

    /**
     * implements testMaxMsg(), testLargeObject(), testLargeObjectEncoded()
     * using a sequence of items with varying sizes
     */
    virtual void doVarSizes(bool withMaxMsgSize,
                            bool withLargeObject);

    /**
     * executes a sync with the given options,
     * checks the result and (optionally) the sync report
     */
    virtual void doSync(const SyncOptions &options);
    virtual void doSync(const char *logPrefix,
                        const SyncOptions &options) {
        SyncPrefix prefix(logPrefix, *this);
        doSync(options);
    }
    void doSync(const char *file, int line,
                const char *logPrefix,
                const SyncOptions &options) {
        CT_WRAP_ASSERT(file, line, doSync(logPrefix, options), CT_ASSERT_EXE_CLANG_BUILD(doSync(logPrefix, options)));
    }
    void doSync(const char *file, int line,
                const SyncOptions &options) {
        CT_WRAP_ASSERT(file, line, doSync(options), CT_ASSERT_EXE_CLANG_BUILD(doSync(options)));
    }
    virtual void postSync(int res, const std::string &logname);

 private:
    void allSourcesInsert(bool withUID = true);
    void allSourcesUpdate();
    void allSourcesDeleteAll();
    void allSourcesInsertMany(int startIndex, int numItems,
                              std::map<int, std::list<std::string> > &luids);
    void allSourcesUpdateMany(int startIndex, int numItems,
                              int revision,
                              std::map<int, std::list<std::string> > &luids,
                              int offset);
    void allSourcesRemoveMany(int numItems,
                              std::map<int, std::list<std::string> > &luids,
                              int offset);
};

/*
 * A transport wraper wraps a real transport impl and gives user 
 * possibility to do additional work before/after transport operation.
 * We use TransportFaultInjector to emulate a network failure;
 * We use UserSuspendInjector to emulate a user suspend after receving
 * a response.
 */
class TransportWrapper : public TransportAgent {
protected:
    int m_interruptAtMessage, m_messageCount;
    std::shared_ptr<TransportAgent> m_wrappedAgent;
    Status m_status;
    SyncOptions *m_options;
public:
    TransportWrapper() {
        m_messageCount = 0;
        m_interruptAtMessage = -1;
        m_status = INACTIVE;
        m_options = nullptr;
    }
    ~TransportWrapper() {
    }

    /**
     * -1 for wrappers which are meant to be used without message resending,
     * otherwise the number x for which "interrupt" <= x will lead to
     * an aborted sync (0 for TransportResendInjector, 2 for TransportResendProxy)
     */
    virtual int getResendFailureThreshold() { return -1; }

    virtual int getMessageCount() { return m_messageCount; }

    virtual void setURL(const std::string &url) { m_wrappedAgent->setURL(url); }
    virtual void setContentType(const std::string &type) { m_wrappedAgent->setContentType(type); }
    virtual void setAgent(std::shared_ptr<TransportAgent> agent) {m_wrappedAgent = agent;}
    virtual void setSyncOptions(SyncOptions *options) {m_options = options;}
    virtual void setInterruptAtMessage (int interrupt) {m_interruptAtMessage = interrupt;}
    virtual void cancel() { m_wrappedAgent->cancel(); }
    virtual void shutdown() { m_wrappedAgent->shutdown(); }

    virtual void rewind() {
        m_messageCount = 0;
        m_interruptAtMessage = -1;
        m_status = INACTIVE;
        m_options = nullptr;
        m_wrappedAgent.reset();
    }
    virtual Status wait(bool noReply = false) { return m_status; }
    virtual void setTimeout(int seconds) { m_wrappedAgent->setTimeout(seconds); }
};

/** write log message into *.log file of a test */
#define CLIENT_TEST_LOG(_format, _args...) \
    SE_LOG_DEBUG(NULL, "\n%s:%d *** " _format, \
                 getBasename(__FILE__).c_str(), __LINE__, \
                 ##_args)

/** assert equality, include string in message if unequal */
#define CLIENT_TEST_EQUAL( _prefix, \
                           _expected, \
                           _actual ) \
    CT_ASSERT_EQUAL_MESSAGE( std::string(_prefix) + ": " + #_expected + " == " + #_actual, \
                             _expected, \
                             _actual )

/** execute _x and then check the status of the _source pointer */
#define SOURCE_ASSERT_NO_FAILURE(_source, _x) \
{ \
    CT_ASSERT_NO_THROW(_x); \
    CT_ASSERT((_source)); \
}

/** check _x for true and then the status of the _source pointer */
#define SOURCE_ASSERT(_source, _x) \
{ \
    CT_ASSERT(_x); \
    CT_ASSERT((_source)); \
}

/** check that _x evaluates to a specific value and then the status of the _source pointer */
#define SOURCE_ASSERT_EQUAL(_source, _value, _x) \
{ \
    CT_ASSERT_EQUAL(_value, _x); \
    CT_ASSERT((_source)); \
}

/** same as SOURCE_ASSERT() with a specific failure message */
#define SOURCE_ASSERT_MESSAGE(_message, _source, _x)     \
{ \
    CT_ASSERT_MESSAGE((_message), (_x)); \
    CT_ASSERT((_source)); \
}

/**
 * convenience macro for adding a test name like a function,
 * to be used inside addTests() of an instance of that class
 *
 * @param _class      class which contains the function
 * @param _function   a function without parameters in that class
 */
#define ADD_TEST(_class, _function) \
    ADD_TEST_TO_SUITE(this, _class, _function)

#define ADD_TEST_TO_SUITE(_suite, _class, _function) \
    _suite->addTest(FilterTest(new CppUnit::TestCaller<_class>(_suite->getName() + "::" #_function, &_class::_function, *this)))

#define ADD_TEST_TO_SUITE_SUFFIX(_suite, _class, _function, _suffix) \
    _suite->addTest(FilterTest(new CppUnit::TestCaller<_class>(_suite->getName() + "::" #_function + _suffix, &_class::_function, *this)))

SE_END_CXX

#endif // ENABLE_INTEGRATION_TESTS
#endif // INCL_TESTSYNCCLIENT
