1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
// SPDX-FileCopyrightText: 2024 Linus Jahn <lnj@kaidan.im>
//
// SPDX-License-Identifier: LGPL-2.1-or-later
// std
#include <ranges>
// Qt
#include <QSqlDatabase>
#include <QSqlField>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QTest>
// Kaidan
#include "Algorithms.h"
#include "Database.h"
#include "Settings.h"
#include "SqlUtils.h"
#include "Test.h"
using namespace SqlUtils;
using std::ranges::sort;
using std::views::iota;
using std::views::zip;
static auto dbTableRecords(QSqlDatabase &db)
{
auto tables = db.tables();
std::sort(tables.begin(), tables.end());
auto records = transform(tables, [&](const auto &tableName) {
return db.record(tableName);
});
return std::tuple{std::move(tables), std::move(records)};
}
static auto fieldNames(const QSqlRecord &record)
{
QStringList fieldNames;
for (int i : iota(0, record.count())) {
fieldNames.push_back(record.fieldName(i));
}
return fieldNames;
}
class DatabaseTest : public Test
{
Q_OBJECT
private:
Q_SLOT void conversion();
};
void DatabaseTest::conversion()
{
new Settings(this);
Database db;
auto sqlDb = db.currentDatabase();
// create database using conversion functions
db.createV3Database();
db.createTables();
// dump tables
auto [tableNamesConversion, recordsConversion] = dbTableRecords(sqlDb);
// remove all created tables
QSqlQuery query(sqlDb);
for (const auto &tableName : sqlDb.tables()) {
execQuery(query, QStringLiteral("DROP TABLE ") + tableName);
}
for (const auto &viewName : sqlDb.tables(QSql::Views)) {
execQuery(query, QStringLiteral("DROP VIEW ") + viewName);
}
// create tables directly (without conversion functions)
db.createNewDatabase();
// dump tables
auto [tableNamesNew, recordsNew] = dbTableRecords(sqlDb);
// check the same tables exist in both cases
QCOMPARE(tableNamesConversion, tableNamesNew);
Q_ASSERT(tableNamesConversion.size() == recordsConversion.size());
Q_ASSERT(tableNamesNew.size() == recordsNew.size());
// check records of each table
for (const auto &[tableName, recordC, recordN] : zip(tableNamesConversion, recordsConversion, recordsNew)) {
auto fieldNamesC = fieldNames(recordC);
auto fieldNamesN = fieldNames(recordN);
bool orderMatches = (fieldNamesC == fieldNamesN);
// compare without order
std::sort(fieldNamesC.begin(), fieldNamesC.end());
std::sort(fieldNamesN.begin(), fieldNamesN.end());
QCOMPARE(fieldNamesC.join(u", "), fieldNamesN.join(u", "));
// print warning if order does not match
if (!orderMatches) {
qWarning() << "Order of fields of" << tableName << "does not match between conversion/new variant.";
qWarning() << " " << fieldNames(recordC);
qWarning() << " " << fieldNames(recordN);
}
// check optional/required property of each field
for (const auto &fieldName : fieldNamesC) {
auto fieldC = recordC.field(recordC.indexOf(fieldName));
auto fieldN = recordN.field(recordN.indexOf(fieldName));
// check 'NOT NULL'
if (fieldC.requiredStatus() != fieldN.requiredStatus()) {
qDebug() << tableName << fieldName << "has wrong required status (NOT NULL)!";
qDebug() << " Conversion variant is:" << fieldC.requiredStatus();
qDebug() << " New variant is:" << fieldN.requiredStatus();
QFAIL("Field required status of conversion/new variant does not match.");
}
// check data type
if (fieldC.metaType() != fieldN.metaType()) {
qDebug() << tableName << fieldName << "data type mismatch!";
qDebug() << " Conversion variant:" << fieldC.metaType();
qDebug() << " New variant is:" << fieldN.metaType();
QFAIL("Field data type of conversion/new variant does not match.");
}
}
}
}
QTEST_GUILESS_MAIN(DatabaseTest)
#include "DatabaseTest.moc"
|