/*
 * Copyright (C) 2014-2026 CZ.NIC
 *
 * This program 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 of the License, or
 * (at your option) any later version.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations including
 * the two.
 */

#include <sys/time.h> /* struct timeval */
#include <QCoreApplication>
#include <QDir>
#include <QtTest/QtTest>

#include "src/common.h"
#include "src/datovka_shared/isds/message_interface.h"
#include "src/datovka_shared/log/log.h"
#include "src/datovka_shared/settings/prefs.h"
#include "src/global.h"
#include "src/io/message_db_set.h"
#include "tests/test_message_db_set.h"

class DatabaseIdentifier {
public:
	DatabaseIdentifier(const QString &k, bool t,
	    enum MessageDbSet::Organisation o);

	QString primaryKey; /*!< Part of the database file name. */
	bool testing; /*!< Part of the database name. */
	enum MessageDbSet::Organisation organisation; /*!< Part of the database file name. */
};

DatabaseIdentifier::DatabaseIdentifier(const QString &k, bool t,
    enum MessageDbSet::Organisation o)
    : primaryKey(k),
    testing(t),
    organisation(o)
{
}

class TestMessageDbSet : public QObject {
	Q_OBJECT

public:
	TestMessageDbSet(void);
	~TestMessageDbSet(void);

private slots:
	void initTestCase(void);

	void cleanupTestCase(void);

	void accessEmpty(void);

	void writeEnvelope(void);

	void openEmptyLocation(void);

	void reopenLocation(void);

	void cleanup01(void);

	void accessExistent01(void);

	void copyToLocation(void);

	void deleteLocation(void);

	void cleanup02(void);

	void accessExistent02(void);

	void moveToLocation(void);

	void changePrimaryKey(void);

private:
	static inline
	QStringList dbFileNameList(const QDir &dbDir)
	{
		return dbDir.entryList(QDir::NoDotAndDotDot | QDir::AllEntries);
	}

	static
	void dateTimeToTimeval(struct timeval &tv, const QDateTime &dateTime);

	void cleanup(bool deleteFiles);

	void accessExistent(void);

	/*
	 * An application object must exist before any QObject.
	 * See https://doc.qt.io/qt-6/threads-qobject.html ,
	 * also see QTEST_GUILESS_MAIN().
	 */
	int m_argc;
	char **m_argv;
	QCoreApplication m_app;

	const QString m_connectionPrefix; /*!< SQL connection prefix. */

	const QString m_dbLocationPathSrc; /*!< Directory path. */
	QDir m_dbDirSrc;  /*!< Directory containing testing databases. */

	const QString m_dbLocationPathDst; /*!< Directory path. */
	QDir m_dbDirDst;  /*!< Directory containing testing databases. */

	const DatabaseIdentifier m_dbId01; /*!< Database identifier. */
	const DatabaseIdentifier m_dbId02; /*!< Database identifier. */
	const DatabaseIdentifier m_dbId03; /*!< Database identifier. */
	const DatabaseIdentifier m_dbId04; /*!< Database identifier. */

	MessageDbSet *m_dbSet01;
	MessageDbSet *m_dbSet02;
	MessageDbSet *m_dbSet03;
	MessageDbSet *m_dbSet04;
};

TestMessageDbSet::TestMessageDbSet(void)
    : m_argc(0), m_argv(NULL), m_app(m_argc, m_argv),
    m_connectionPrefix(QLatin1String("GLOBALDBS")),
    m_dbLocationPathSrc(QDir::currentPath() + QDir::separator() + QLatin1String("_db_sets_src")),
    m_dbDirSrc(m_dbLocationPathSrc),
    m_dbLocationPathDst(QDir::currentPath() + QDir::separator() + QLatin1String("_db_sets_dst")),
    m_dbDirDst(m_dbLocationPathDst),
    m_dbId01(QLatin1String("user001"), true, MessageDbSet::DO_SINGLE_FILE),
    m_dbId02(QLatin1String("user002"), true, MessageDbSet::DO_YEARLY),
    m_dbId03(QLatin1String("user003"), true, MessageDbSet::DO_SINGLE_FILE),
    m_dbId04(QLatin1String("user004"), true, MessageDbSet::DO_YEARLY),
    m_dbSet01(Q_NULLPTR),
    m_dbSet02(Q_NULLPTR),
    m_dbSet03(Q_NULLPTR),
    m_dbSet04(Q_NULLPTR)
{
	/* Remove and check that is not present. */
	QVERIFY(m_dbDirSrc.removeRecursively());
	QVERIFY(!m_dbDirSrc.exists());

	QVERIFY(m_dbDirDst.removeRecursively());
	QVERIFY(!m_dbDirDst.exists());
}

TestMessageDbSet::~TestMessageDbSet(void)
{
	/* Just in case. */
	cleanup(false);

	delete m_dbSet04; m_dbSet04 = Q_NULLPTR;
}

void TestMessageDbSet::initTestCase(void)
{
	bool onDisk = true;

	QVERIFY(GlobInstcs::logPtr == Q_NULLPTR);
	GlobInstcs::logPtr = new (::std::nothrow) LogDevice;
	QVERIFY(GlobInstcs::logPtr != Q_NULLPTR);

	QVERIFY(GlobInstcs::prefsPtr == Q_NULLPTR);
	GlobInstcs::prefsPtr = new (::std::nothrow) Prefs;
	QVERIFY(GlobInstcs::prefsPtr != Q_NULLPTR);

	/* Try accessing a non-existent one. */
	m_dbSet01 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId01.primaryKey, m_dbId01.testing, m_dbId01.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_MUST_EXIST);
	QVERIFY(m_dbSet01 == Q_NULLPTR);
	QVERIFY(!m_dbDirSrc.exists());

	m_dbSet02 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId02.primaryKey, m_dbId02.testing, m_dbId02.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_MUST_EXIST);
	QVERIFY(m_dbSet02 == Q_NULLPTR);
	QVERIFY(!m_dbDirSrc.exists());
}

void TestMessageDbSet::cleanupTestCase(void)
{
	delete GlobInstcs::prefsPtr; GlobInstcs::prefsPtr = Q_NULLPTR;

	delete GlobInstcs::logPtr; GlobInstcs::logPtr = Q_NULLPTR;

	cleanup(true);
}

void TestMessageDbSet::accessEmpty(void)
{
	bool onDisk = true;
	MessageDb *dbA, *dbB, *dbC;

	QDateTime dateTimeA(QDate(2014, 1, 31), QTime(0, 0, 0, 0), Qt::UTC);
	QDateTime dateTimeB(QDate(2015, 3, 31), QTime(0, 0, 0, 0), Qt::UTC);
	QDateTime dateTimeC(QDate(2016, 8, 31), QTime(0, 0, 0, 0), Qt::UTC);

	/* Accessing a single file database. */
	m_dbSet01 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId01.primaryKey, m_dbId01.testing, m_dbId01.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_CREATE_EMPTY_CURRENT);
	QVERIFY(m_dbSet01 != Q_NULLPTR);
	QVERIFY(m_dbDirSrc.exists());
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 1);

	QVERIFY(m_dbSet01->fileNames().size() == 1);
	dbA = m_dbSet01->constAccessMessageDb(dateTimeA);
	QVERIFY(dbA != Q_NULLPTR);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	dbB = m_dbSet01->accessMessageDb(dateTimeB, false);
	QVERIFY(dbB != Q_NULLPTR);
	QVERIFY(dbA == dbB);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	dbC = m_dbSet01->accessMessageDb(dateTimeC, true);
	QVERIFY(dbC != Q_NULLPTR);
	QVERIFY(dbA == dbC);
	QVERIFY(m_dbSet01->fileNames().size() == 1);

	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 1);

	/* Accessing a multi-file database. */
	m_dbSet02 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId02.primaryKey, m_dbId02.testing, m_dbId02.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_CREATE_ON_DEMAND);
	QVERIFY(m_dbSet02 != Q_NULLPTR);
	QVERIFY(m_dbSet01 != m_dbSet02);
	QVERIFY(m_dbDirSrc.exists());
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 1);

	QVERIFY(m_dbSet02->fileNames().size() == 0);
	dbA = m_dbSet02->constAccessMessageDb(dateTimeA);
	QVERIFY(dbA == Q_NULLPTR);
	QVERIFY(m_dbSet02->fileNames().size() == 0);
	dbB = m_dbSet02->accessMessageDb(dateTimeB, false);
	QVERIFY(dbB == Q_NULLPTR);
	QVERIFY(m_dbSet02->fileNames().size() == 0);
	dbB = m_dbSet02->accessMessageDb(dateTimeB, true);
	QVERIFY(dbB != Q_NULLPTR);
	QVERIFY(m_dbSet02->fileNames().size() == 1);
	dbC = m_dbSet02->accessMessageDb(dateTimeC, true);
	QVERIFY(dbC != Q_NULLPTR);
	QVERIFY(dbB != dbC); /* Must be different databases. */
	QVERIFY(m_dbSet02->fileNames().size() == 2);

	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 3);

	/* Create followinf database sts in memory. */
	onDisk = false;

	/* Access databases in memory. */
	m_dbSet03 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId03.primaryKey, m_dbId03.testing, m_dbId03.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_CREATE_EMPTY_CURRENT);
	QVERIFY(m_dbSet03 != Q_NULLPTR);
	QVERIFY(m_dbSet01 != m_dbSet03);
	QVERIFY(m_dbSet02 != m_dbSet03);
	QVERIFY(m_dbDirSrc.exists());
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 3);

	QVERIFY(m_dbSet03->fileNames().size() == 1);
	dbA = m_dbSet03->constAccessMessageDb(dateTimeA);
	QVERIFY(dbA != Q_NULLPTR);
	QVERIFY(m_dbSet03->fileNames().size() == 1);
	dbB = m_dbSet03->accessMessageDb(dateTimeB, false);
	QVERIFY(dbB != Q_NULLPTR);
	QVERIFY(dbA == dbB);
	QVERIFY(m_dbSet03->fileNames().size() == 1);
	dbC = m_dbSet03->accessMessageDb(dateTimeC, true);
	QVERIFY(dbC != Q_NULLPTR);
	QVERIFY(dbA == dbC);
	QVERIFY(m_dbSet03->fileNames().size() == 1);

	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 3);

	m_dbSet04 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId04.primaryKey, m_dbId04.testing, m_dbId04.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_CREATE_ON_DEMAND);
	QVERIFY(m_dbSet04 != Q_NULLPTR);
	QVERIFY(m_dbSet01 != m_dbSet04);
	QVERIFY(m_dbSet02 != m_dbSet04);
	QVERIFY(m_dbSet03 != m_dbSet04);
	QVERIFY(m_dbDirSrc.exists());
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 3);

	QVERIFY(m_dbSet04->fileNames().size() == 0);
	dbA = m_dbSet04->constAccessMessageDb(dateTimeA);
	QVERIFY(dbA == Q_NULLPTR);
	QVERIFY(m_dbSet04->fileNames().size() == 0);
	dbB = m_dbSet04->accessMessageDb(dateTimeB, false);
	QVERIFY(dbB == Q_NULLPTR);
	QVERIFY(m_dbSet04->fileNames().size() == 0);
	dbB = m_dbSet04->accessMessageDb(dateTimeB, true);
	QVERIFY(dbB != Q_NULLPTR);
	QVERIFY(m_dbSet04->fileNames().size() == 1);
	dbC = m_dbSet04->accessMessageDb(dateTimeC, true);
	QVERIFY(dbC != Q_NULLPTR);
	QVERIFY(dbB == dbC); /* In memory it is a single database. */
	QVERIFY(m_dbSet04->fileNames().size() == 1);

	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 3);
}

void TestMessageDbSet::writeEnvelope(void)
{
#define TRANSACTION true
	/* Write some data into the databases. */

	QDateTime dateTime(QDate(2014, 1, 31), QTime(0, 0, 0, 0), Qt::UTC);

	MessageDb *db;
	struct timeval tv;
	bool ret;

	dateTimeToTimeval(tv, dateTime);

	Isds::Envelope env;
	env.setDmId(1);
	env.setDbIDSender(QLatin1String("dbIDSender"));
	env.setDmSender(QLatin1String("dmSender"));
	env.setDmSenderAddress(QLatin1String("dmSenderAddress"));
	env.setDmSenderType(Isds::Type::BT_SYSTEM);
	env.setDmRecipient(QLatin1String("dmRecipient"));
	env.setDmRecipientAddress(QLatin1String("dmRecipientAddress"));
	env.setDmAmbiguousRecipient(Isds::Type::BOOL_NULL);
	env.setDmSenderOrgUnit(QLatin1String("dmSenderOrgUnit"));
	env.setDmSenderOrgUnitNum(2);
	env.setDbIDRecipient(QLatin1String("dbIDRecipient"));
	env.setDmRecipientOrgUnit(QLatin1String("dmRecipientOrgUnit"));
	env.setDmRecipientOrgUnitNum(3);
	env.setDmToHands(QLatin1String("dmToHands"));
	env.setDmAnnotation(QLatin1String("dmAnnotation"));
	env.setDmRecipientRefNumber(QLatin1String("dmRecipientRefNumber"));
	env.setDmSenderRefNumber(QLatin1String("dmSenderRefNumber"));
	env.setDmRecipientIdent(QLatin1String("dmRecipientIdent"));
	env.setDmSenderIdent(QLatin1String("dmSenderIdent"));
	env.setDmLegalTitleLaw(4);
	env.setDmLegalTitleYear(5);
	env.setDmLegalTitleSect(QLatin1String("dmLegalTitleSect"));
	env.setDmLegalTitlePar(QLatin1String("dmLegalTitlePar"));
	env.setDmLegalTitlePoint(QLatin1String("dmLegalTitlePoint"));
	env.setDmPersonalDelivery(Isds::Type::BOOL_NULL);
	env.setDmAllowSubstDelivery(Isds::Type::BOOL_NULL);
	//env.setDmQTimestamp(QByteArray());
	env.setDmDeliveryTime(dateTime);
	env.setDmAcceptanceTime(dateTime);
	env.setDmMessageStatus(Isds::Type::MS_POSTED);
	//env.setDmAttachmentSize(-1);
	env.setDmType(Isds::Envelope::dmType2Char(Isds::Type::MT_K));

	db = m_dbSet01->accessMessageDb(dateTime, true);
	QVERIFY(db != Q_NULLPTR);

	ret = db->insertMessageEnvelope(env, QLatin1String("_origin"),
	    MSG_RECEIVED, TRANSACTION);
	QVERIFY(ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 3);

	db = m_dbSet02->accessMessageDb(dateTime, true);
	QVERIFY(db != Q_NULLPTR);

	ret = db->insertMessageEnvelope(env, QLatin1String("_origin"),
	    MSG_RECEIVED, TRANSACTION);
	QVERIFY(ret);
	QVERIFY(m_dbSet02->fileNames().size() == 3);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);

	db = m_dbSet03->accessMessageDb(dateTime, true);
	QVERIFY(db != Q_NULLPTR);

	ret = db->insertMessageEnvelope(env, QLatin1String("_origin"),
	    MSG_RECEIVED, TRANSACTION);
	QVERIFY(ret);
	QVERIFY(m_dbSet03->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);

	db = m_dbSet04->accessMessageDb(dateTime, true);
	QVERIFY(db != Q_NULLPTR);

	ret = db->insertMessageEnvelope(env, QLatin1String("_origin"),
	    MSG_RECEIVED, TRANSACTION);
	QVERIFY(ret);
	QVERIFY(m_dbSet04->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
#undef TRANSACTION
}

void TestMessageDbSet::openEmptyLocation(void)
{
	bool ret;

	QVERIFY(!m_dbDirDst.exists());

	/* Opening existent location must fail because organised wrongly. */
	ret = m_dbSet01->openLocation(m_dbLocationPathSrc,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);

	/* Opening non-existent location. */
	ret = m_dbSet01->openLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet01->openLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_ON_DEMAND, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet01->openLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_EMPTY_CURRENT, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	/* Create the directory. */
	m_dbDirDst.mkpath(".");
	QVERIFY(m_dbDirDst.exists());

	/* Opening existent but empty location must fail. */
	ret = m_dbSet01->openLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet01->openLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_ON_DEMAND, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet01->openLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_EMPTY_CURRENT, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	/* Opening location for databases in memory must fail. */
	ret = m_dbSet03->openLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet03->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet03->openLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_ON_DEMAND, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet03->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet03->openLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_EMPTY_CURRENT, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet03->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);
}

void TestMessageDbSet::reopenLocation(void)
{
	bool ret;

	m_dbDirDst.removeRecursively();
	QVERIFY(!m_dbDirDst.exists());

	/* Reopening database in non-existent location must fail. */
	ret = m_dbSet01->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_SINGLE_FILE, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);

	ret = m_dbSet01->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_SINGLE_FILE, MessageDbSet::CM_CREATE_ON_DEMAND, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);

	ret = m_dbSet01->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_SINGLE_FILE, MessageDbSet::CM_CREATE_EMPTY_CURRENT, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);

	ret = m_dbSet02->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);

	ret = m_dbSet02->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_ON_DEMAND, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);

	ret = m_dbSet02->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_EMPTY_CURRENT, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);

	ret = m_dbSet03->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);

	ret = m_dbSet03->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_ON_DEMAND, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);

	ret = m_dbSet03->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_EMPTY_CURRENT, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);

	m_dbDirDst.mkpath(".");
	QVERIFY(m_dbDirDst.exists());

	/* Must exist parameter is ignored. */
	ret = m_dbSet01->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_SINGLE_FILE, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(ret);
	QVERIFY(m_dbSet01->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet02->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(ret);
	QVERIFY(m_dbSet02->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet03->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_SINGLE_FILE, MessageDbSet::CM_MUST_EXIST, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet03->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	/* Create a new file. */
	ret = m_dbSet01->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_SINGLE_FILE, MessageDbSet::CM_CREATE_EMPTY_CURRENT, MessageDbSet::AM_LEAVE);
	QVERIFY(ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 1);

	ret = m_dbSet02->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_EMPTY_CURRENT, MessageDbSet::AM_LEAVE);
	QVERIFY(ret);
	QVERIFY(m_dbSet02->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 2);

	ret = m_dbSet03->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_SINGLE_FILE, MessageDbSet::CM_CREATE_EMPTY_CURRENT, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet03->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 2);

	/* All possible targets must be deleted. */
	ret = m_dbSet01->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_SINGLE_FILE, MessageDbSet::CM_CREATE_ON_DEMAND, MessageDbSet::AM_LEAVE);
	QVERIFY(ret);
	QVERIFY(m_dbSet01->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 1);

	ret = m_dbSet02->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_YEARLY, MessageDbSet::CM_CREATE_ON_DEMAND, MessageDbSet::AM_LEAVE);
	QVERIFY(ret);
	QVERIFY(m_dbSet02->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet03->reopenLocation(m_dbLocationPathDst,
	    MessageDbSet::DO_SINGLE_FILE, MessageDbSet::CM_CREATE_ON_DEMAND, MessageDbSet::AM_LEAVE);
	QVERIFY(!ret);
	QVERIFY(m_dbSet03->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);
}

void TestMessageDbSet::cleanup01(void)
{
	cleanup(false);
}

void TestMessageDbSet::accessExistent01(void)
{
	accessExistent();
}

void TestMessageDbSet::copyToLocation(void)
{
	bool ret;

	m_dbDirDst.removeRecursively();
	QVERIFY(!m_dbDirDst.exists());

	QVERIFY(m_dbSet01 != Q_NULLPTR);
	QVERIFY(m_dbSet02 != Q_NULLPTR);
	QVERIFY(m_dbSet03 != Q_NULLPTR);
	QVERIFY(m_dbSet04 != Q_NULLPTR);

	/* Copy to non-existent location must fail. */
	ret = m_dbSet01->copyToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(!m_dbDirDst.exists());

	ret = m_dbSet02->copyToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(!m_dbDirDst.exists());

	ret = m_dbSet03->copyToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(!m_dbDirDst.exists());

	ret = m_dbSet04->copyToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(!m_dbDirDst.exists());

	m_dbDirDst.mkpath(".");
	QVERIFY(m_dbDirDst.exists());

	/* Copy to existent location. */
	ret = m_dbSet01->copyToLocation(m_dbLocationPathDst);
	QVERIFY(ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 1);

	ret = m_dbSet02->copyToLocation(m_dbLocationPathDst);
	QVERIFY(ret);
	QVERIFY(m_dbSet02->fileNames().size() == 3);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);

	ret = m_dbSet03->copyToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(m_dbSet03->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);

	ret = m_dbSet04->copyToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(m_dbSet04->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);
}

void TestMessageDbSet::deleteLocation(void)
{
	bool ret;

	QVERIFY(m_dbDirDst.exists());
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);

	ret = m_dbSet03->deleteLocation();
	QVERIFY(ret);
	QVERIFY(m_dbSet03->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);

	ret = m_dbSet04->deleteLocation();
	QVERIFY(ret);
	QVERIFY(m_dbSet04->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);

	ret = m_dbSet01->deleteLocation();
	QVERIFY(ret);
	QVERIFY(m_dbSet01->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 3);

	ret = m_dbSet02->deleteLocation();
	QVERIFY(ret);
	QVERIFY(m_dbSet02->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	/* Repeated deletion must fail. */
	ret = m_dbSet01->deleteLocation();
	QVERIFY(!ret);
	QVERIFY(m_dbSet01->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet02->deleteLocation();
	QVERIFY(!ret);
	QVERIFY(m_dbSet02->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet03->deleteLocation();
	QVERIFY(!ret);
	QVERIFY(m_dbSet03->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);

	ret = m_dbSet04->deleteLocation();
	QVERIFY(!ret);
	QVERIFY(m_dbSet04->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 0);
}

void TestMessageDbSet::cleanup02(void)
{
	cleanup(false);
}

void TestMessageDbSet::accessExistent02(void)
{
	accessExistent();
}

void TestMessageDbSet::moveToLocation(void)
{
	bool ret;

	m_dbDirDst.removeRecursively();
	QVERIFY(!m_dbDirDst.exists());

	QVERIFY(m_dbSet01 != Q_NULLPTR);
	QVERIFY(m_dbSet02 != Q_NULLPTR);
	QVERIFY(m_dbSet03 != Q_NULLPTR);
	QVERIFY(m_dbSet04 != Q_NULLPTR);

	/* Copy to non-existent location must fail. */
	ret = m_dbSet01->moveToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(!m_dbDirDst.exists());

	ret = m_dbSet02->moveToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(!m_dbDirDst.exists());

	ret = m_dbSet03->moveToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(!m_dbDirDst.exists());

	ret = m_dbSet04->moveToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);
	QVERIFY(!m_dbDirDst.exists());

	m_dbDirDst.mkpath(".");
	QVERIFY(m_dbDirDst.exists());

	/* Copy to existent location. */
	ret = m_dbSet01->moveToLocation(m_dbLocationPathDst);
	QVERIFY(ret);
	QVERIFY(m_dbSet01->fileNames().size() == 1);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 3);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 1);

	ret = m_dbSet02->moveToLocation(m_dbLocationPathDst);
	QVERIFY(ret);
	QVERIFY(m_dbSet02->fileNames().size() == 3);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 0);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);

	ret = m_dbSet03->moveToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(m_dbSet03->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 0);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);

	ret = m_dbSet04->moveToLocation(m_dbLocationPathDst);
	QVERIFY(!ret);
	QVERIFY(m_dbSet04->fileNames().size() == 0);
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 0);
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);
}

void TestMessageDbSet::changePrimaryKey(void)
{
	QStringList fileNames;

	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);
	{
		fileNames = m_dbDirDst.entryList(QStringList("user001*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 1);
		fileNames = m_dbDirDst.entryList(QStringList("user002*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 3);
		fileNames = m_dbDirDst.entryList(QStringList("user003*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 0);
		fileNames = m_dbDirDst.entryList(QStringList("user004*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 0);
	}

	m_dbSet01->changePrimaryKey("user003");
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);
	{
		fileNames = m_dbDirDst.entryList(QStringList("user001*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 0);
		fileNames = m_dbDirDst.entryList(QStringList("user002*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 3);
		fileNames = m_dbDirDst.entryList(QStringList("user003*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 1);
		fileNames = m_dbDirDst.entryList(QStringList("user004*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 0);
	}

	m_dbSet02->changePrimaryKey("user004");
	QVERIFY(dbFileNameList(m_dbDirDst).size() == 4);
	{
		fileNames = m_dbDirDst.entryList(QStringList("user001*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 0);
		fileNames = m_dbDirDst.entryList(QStringList("user002*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 0);
		fileNames = m_dbDirDst.entryList(QStringList("user003*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 1);
		fileNames = m_dbDirDst.entryList(QStringList("user004*.db"), QDir::NoDotAndDotDot | QDir::AllEntries);
		QVERIFY(fileNames.size() == 3);
	}
}

void TestMessageDbSet::dateTimeToTimeval(struct timeval &tv,
    const QDateTime &dateTime)
{
#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
	tv.tv_sec = dateTime.toSecsSinceEpoch();
#else /* < Qt-5.8 */
	/* Don't use QDateTime::toTime_t() because it returns uint. */
	tv.tv_sec = dateTime.toMSecsSinceEpoch() / 1000;
#endif /* >= Qt-5.8 */
	tv.tv_usec = dateTime.time().msec() * 1000;
}

void TestMessageDbSet::cleanup(bool deleteFiles)
{
	delete m_dbSet01; m_dbSet01 = Q_NULLPTR;
	delete m_dbSet02; m_dbSet02 = Q_NULLPTR;
	delete m_dbSet03; m_dbSet03 = Q_NULLPTR;
#if 0
	/* Don't delete 04. */
	delete m_dbSet04; m_dbSet04 = Q_NULLPTR;
#endif

	if (deleteFiles) {
		QVERIFY(m_dbDirSrc.removeRecursively());
		QVERIFY(!m_dbDirSrc.exists());

		QVERIFY(m_dbDirDst.removeRecursively());
		QVERIFY(!m_dbDirDst.exists());
	}
}

void TestMessageDbSet::accessExistent(void)
{
	bool onDisk = true;

	QVERIFY(m_dbDirSrc.exists());
	QVERIFY(dbFileNameList(m_dbDirSrc).size() == 4);

	QVERIFY(m_dbSet01 == Q_NULLPTR);
	QVERIFY(m_dbSet02 == Q_NULLPTR);
	QVERIFY(m_dbSet03 == Q_NULLPTR);
#if 0
	QVERIFY(m_dbSet04 == Q_NULLPTR);
#endif

	m_dbSet01 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId01.primaryKey, m_dbId01.testing, m_dbId01.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_MUST_EXIST);
	QVERIFY(m_dbSet01 != Q_NULLPTR);
	QVERIFY(m_dbSet01->fileNames().size() == 1);

	m_dbSet02 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId02.primaryKey, m_dbId02.testing, m_dbId02.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_MUST_EXIST);
	QVERIFY(m_dbSet02 != Q_NULLPTR);
	QVERIFY(m_dbSet02->fileNames().size() == 3);

	onDisk = false;

	m_dbSet03 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId03.primaryKey, m_dbId03.testing, m_dbId03.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_MUST_EXIST);
	QVERIFY(m_dbSet03 != Q_NULLPTR);
	QVERIFY(m_dbSet03->fileNames().size() == 0);

#if 0
	/* Don't delete 04. */
	m_dbSet04 = MessageDbSet::createNew(m_dbLocationPathSrc,
	    m_dbId04.primaryKey, m_dbId04.testing, m_dbId04.organisation,
	    onDisk, m_connectionPrefix, MessageDbSet::CM_MUST_EXIST);
	QVERIFY(m_dbSet04 != Q_NULLPTR);
	QVERIFY(m_dbSet04->fileNames().size() == 0);
#endif
}

QObject *newTestMessageDbSet(void)
{
	return new (::std::nothrow) TestMessageDbSet();
}

//QTEST_MAIN(TestMessageDbSet)
#include "test_message_db_set.moc"
