15.1.911. tablet_qt/tests/auto/db/databasemanager/testdatabasemanager.cpp

/*
    Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
    Created by Rudolf Cardinal (rnc1001@cam.ac.uk).

    This file is part of CamCOPS.

    CamCOPS 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.

    CamCOPS 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 CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/

#include <QDir>
#include <QFileInfo>
#include <QTemporaryFile>
#include <QtTest/QtTest>

#include "db/databasemanager.h"
#include "db/sqlcipherdriver.h"
#include "db/whichdb.h"

const QString PLAIN_CONNECTION_NAME("plain");
const QString ENCRYPTED_CONNECTION_NAME("encrypted");
const QString RIGHT_PASSWORD("password");
const QString WRONG_PASSWORD("wrongpassword");
const QString
    V3_ENCRYPTED_TEST_DATABASE("encrypted_test_database_v3.20.1.sqlite");
const QString
    V4_ENCRYPTED_TEST_DATABASE("encrypted_test_database_v4.5.5.sqlite");

class TestDatabaseManager : public QObject
{
    Q_OBJECT

private:
    QString m_fixtures_dir;
    bool openFixture(const QString filename, QTemporaryFile& test_db);
    // Call this to run a test with DatabaseManager threaded or not from
    // the method <name of original test method>_data().
    // See corresponding QFETCH() in the test itself.
    void threadedData();

private slots:
    void initTestCase();
    // All tests are run twice: threaded and not threaded
    void testCanEncryptPlainDatabase();
    void testCanEncryptPlainDatabase_data();
    void testCanConnectToEncryptedDatabase();
    void testCanConnectToEncryptedDatabase_data();
    void testCanConnectToEncryptedDatabaseOnSecondAttempt();
    void testCanConnectToEncryptedDatabaseOnSecondAttempt_data();
    void testCanConnectToEncryptedV3Database();
    void testCanConnectToEncryptedV3Database_data();
};

void TestDatabaseManager::initTestCase()
{
    QSqlDatabase::
        registerSqlDriver(whichdb::SQLCIPHER, new QSqlDriverCreator<SQLCipherDriver>);

    const QString this_dir = QFileInfo(__FILE__).dir().absolutePath();
    m_fixtures_dir = this_dir + QDir::separator() + "fixtures";
}

void TestDatabaseManager::testCanEncryptPlainDatabase()
{
    QFETCH(bool, threaded);

    auto plain_file = QTemporaryFile();
    plain_file.open();
    auto plain_manager = DatabaseManager(
        plain_file.fileName(), PLAIN_CONNECTION_NAME, whichdb::DBTYPE, threaded
    );
    QVERIFY(plain_manager.canReadDatabase());

    auto encrypted_file = QTemporaryFile();
    encrypted_file.open();
    plain_manager.encryptToAnother(encrypted_file.fileName(), RIGHT_PASSWORD);

    auto encrypted_manager = DatabaseManager(
        encrypted_file.fileName(),
        ENCRYPTED_CONNECTION_NAME,
        whichdb::DBTYPE,
        threaded
    );
    QVERIFY(!encrypted_manager.canReadDatabase());

    QVERIFY(encrypted_manager.decrypt(RIGHT_PASSWORD));
}

void TestDatabaseManager::testCanEncryptPlainDatabase_data()
{
    threadedData();
}

void TestDatabaseManager::testCanConnectToEncryptedDatabase()
{
    QFETCH(bool, threaded);

    QTemporaryFile v4_test_file;
    QVERIFY(openFixture(V4_ENCRYPTED_TEST_DATABASE, v4_test_file));

    auto manager = DatabaseManager(
        v4_test_file.fileName(),
        ENCRYPTED_CONNECTION_NAME,
        whichdb::DBTYPE,
        threaded
    );
    QVERIFY(!manager.canReadDatabase());
    QVERIFY(manager.decrypt(RIGHT_PASSWORD));
}

void TestDatabaseManager::testCanConnectToEncryptedDatabase_data()
{
    threadedData();
}

void TestDatabaseManager::testCanConnectToEncryptedDatabaseOnSecondAttempt()
{
    QFETCH(bool, threaded);

    QTemporaryFile v4_test_file;
    QVERIFY(openFixture(V4_ENCRYPTED_TEST_DATABASE, v4_test_file));

    auto manager = DatabaseManager(
        v4_test_file.fileName(),
        ENCRYPTED_CONNECTION_NAME,
        whichdb::DBTYPE,
        threaded
    );
    QVERIFY(!manager.canReadDatabase());
    QVERIFY(!manager.decrypt(WRONG_PASSWORD));
    QVERIFY(manager.decrypt(RIGHT_PASSWORD));
}

void TestDatabaseManager::
    testCanConnectToEncryptedDatabaseOnSecondAttempt_data()
{
    threadedData();
}

void TestDatabaseManager::testCanConnectToEncryptedV3Database()
{
    QFETCH(bool, threaded);
    // Should fail initially with key and then migrate to V4
    // before trying again.
    QTemporaryFile v3_test_file;

    QVERIFY(openFixture(V3_ENCRYPTED_TEST_DATABASE, v3_test_file));

    auto manager = DatabaseManager(
        v3_test_file.fileName(),
        ENCRYPTED_CONNECTION_NAME,
        whichdb::DBTYPE,
        threaded
    );
    QVERIFY(!manager.canReadDatabase());
    QVERIFY(manager.decrypt(RIGHT_PASSWORD));
}

void TestDatabaseManager::testCanConnectToEncryptedV3Database_data()
{
    threadedData();
}

bool TestDatabaseManager::openFixture(
    const QString filename, QTemporaryFile& test_db
)
{
    // Copy fixture to temporary file so we don't change the original
    QFile original_test_db
        = QFile(m_fixtures_dir + QDir::separator() + filename);
    if (!original_test_db.open(QIODevice::ReadOnly)) {
        qDebug() << "Failed to open " << original_test_db.fileName();

        return false;
    }

    if (!test_db.open()) {
        qDebug() << "Failed to open temporary file";

        return false;
    }

    QByteArray data = original_test_db.readAll();
    const int num_written = test_db.write(data);
    if (num_written <= 0) {
        qDebug() << "Failed to write anything to temporary file";

        return false;
    }
    test_db.close();
    original_test_db.close();

    return true;
}

void TestDatabaseManager::threadedData()
{
    QTest::addColumn<bool>("threaded");
    QTest::newRow("threaded") << true;
    QTest::newRow("not threaded") << false;
}


QTEST_MAIN(TestDatabaseManager)

#include "testdatabasemanager.moc"