Files
utun2/tools/chatgui/tests/test_database_maintenance.cpp
T

137 lines
6.7 KiB
C++

/* Настоящие SQLite/DbManager/Qt-страница: optimize без строк, строгие integrity-проверки и SQL-отказы. */
#include "../db/db_manager.h"
#include "../src/databasesettingspage.h"
#include <QtTest>
#include <QTemporaryDir>
extern "C" {
#include "debug_config.h"
}
using Operation = IntegrityResult (DbManager::*)() const;
static const Operation operations[] = {&DbManager::integrityCheckQuick, &DbManager::integrityCheckFull, &DbManager::integrityOptimize};
static const char* titles[] = {"Quick Check", "Full Check", "Optimize"};
static const char* actionSlots[] = {"onQuickCheck", "onFullCheck", "onOptimize"};
struct Database {
QTemporaryDir directory;
sqlite3* handle = nullptr;
DbManager manager;
~Database() {
manager.setDb(nullptr);
if (handle && sqlite3_close(handle) != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "test DB close failed: statements remain");
}
bool execute(const char* sql) {
char* error = nullptr;
const int rc = sqlite3_exec(handle, sql, nullptr, nullptr, &error);
if (rc != SQLITE_OK) DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "test SQL failed rc=%d: %s", rc, error ? error : sqlite3_errmsg(handle));
sqlite3_free(error);
return rc == SQLITE_OK;
}
bool open(bool disk = false) {
if (!directory.isValid()) return false;
const QByteArray path = disk ? directory.filePath("test.db").toUtf8() : QByteArray(":memory:");
const int rc = sqlite3_open(path.constData(), &handle);
if (rc != SQLITE_OK) { DEBUG_ERROR(DEBUG_CATEGORY_CONFIG, "test DB open failed rc=%d", rc); return false; }
manager.setDb(handle);
return execute("CREATE TABLE items(value INTEGER CHECK(value>0)); CREATE INDEX item_value ON items(value);"
"INSERT INTO items VALUES(1),(2),(3); SELECT value FROM items WHERE value=2;");
}
};
static int denyPragma(void*, int action, const char*, const char*, const char*, const char*) {
return action == SQLITE_PRAGMA ? SQLITE_DENY : SQLITE_OK;
}
static QString statusText(DatabaseSettingsPage& page) {
for (auto* label : page.findChildren<QLabel*>())
for (const auto* title : titles) if (label->text().startsWith(QString::fromLatin1(title) + ':')) return label->text();
return {};
}
class DatabaseMaintenanceTest : public QObject {
Q_OBJECT
private slots:
void initTestCase() {
debug_config_init(); debug_set_level(DEBUG_LEVEL_WARN);
debug_set_category_level(DEBUG_CATEGORY_CONFIG, DEBUG_LEVEL_DEBUG);
}
void healthyOperations_data() {
QTest::addColumn<int>("operation"); QTest::addColumn<bool>("disk");
for (int operation = 0; operation < 3; ++operation)
for (bool disk : {false, true})
QTest::newRow(qPrintable(QString("%1-%2").arg(titles[operation]).arg(disk ? "file" : "memory"))) << operation << disk;
}
void healthyOperations() {
QFETCH(int, operation); QFETCH(bool, disk);
Database db; QVERIFY(db.open(disk));
for (int repeat = 0; repeat < 2; ++repeat) {
const auto result = (db.manager.*operations[operation])();
QVERIFY2(result.ok, qPrintable(result.message));
QCOMPARE(result.message, QString("ok")); QVERIFY(result.errors.isEmpty()); QVERIFY(result.elapsedMs >= 0);
QVERIFY(!sqlite3_next_stmt(db.handle, nullptr));
}
}
void missingDatabase_data() {
QTest::addColumn<int>("operation");
for (int operation = 0; operation < 3; ++operation) QTest::newRow(titles[operation]) << operation;
}
void missingDatabase() {
QFETCH(int, operation);
DbManager manager;
const auto result = (manager.*operations[operation])();
QVERIFY(!result.ok); QVERIFY(!result.message.isEmpty()); QCOMPARE(result.elapsedMs, 0);
}
void sqlFailure_data() {
QTest::addColumn<int>("operation");
for (int operation = 0; operation < 3; ++operation) QTest::newRow(titles[operation]) << operation;
}
void sqlFailure() {
QFETCH(int, operation);
Database db; QVERIFY(db.open());
QCOMPARE(sqlite3_set_authorizer(db.handle, denyPragma, nullptr), SQLITE_OK);
const auto result = (db.manager.*operations[operation])();
QVERIFY(!result.ok); QVERIFY2(result.message.contains("authorized"), qPrintable(result.message));
QVERIFY(!sqlite3_next_stmt(db.handle, nullptr));
QCOMPARE(sqlite3_set_authorizer(db.handle, nullptr, nullptr), SQLITE_OK);
const auto retry = (db.manager.*operations[operation])();
QVERIFY2(retry.ok, qPrintable(retry.message));
}
void integrityViolation_data() {
QTest::addColumn<int>("operation");
QTest::newRow("quick") << 0; QTest::newRow("full") << 1;
}
void integrityViolation() {
QFETCH(int, operation);
Database db; QVERIFY(db.open());
QVERIFY(db.execute("PRAGMA ignore_check_constraints=ON; INSERT INTO items VALUES(-1),(-2); PRAGMA ignore_check_constraints=OFF;"));
const auto result = (db.manager.*operations[operation])();
QVERIFY(!result.ok); QVERIFY2(result.message.contains("CHECK constraint failed"), qPrintable(result.message));
QVERIFY(!result.errors.isEmpty()); QVERIFY(result.errors.first().contains("CHECK constraint failed"));
}
void pageShowsResult_data() {
QTest::addColumn<int>("operation"); QTest::addColumn<bool>("denied");
for (int operation = 0; operation < 3; ++operation)
for (bool denied : {false, true})
QTest::newRow(qPrintable(QString("%1-%2").arg(titles[operation]).arg(denied ? "denied" : "success")))
<< operation << denied;
}
void pageShowsResult() {
QFETCH(int, operation); QFETCH(bool, denied);
Database db; QVERIFY(db.open());
DatabaseSettingsPage page(&db.manager);
if (denied) QCOMPARE(sqlite3_set_authorizer(db.handle, denyPragma, nullptr), SQLITE_OK);
QVERIFY(QMetaObject::invokeMethod(&page, actionSlots[operation], Qt::DirectConnection));
const auto buttons = page.findChildren<QPushButton*>(); QCOMPARE(buttons.size(), 3);
for (auto* button : buttons) QVERIFY(!button->isEnabled());
QTRY_VERIFY_WITH_TIMEOUT(!statusText(page).isEmpty(), 1000);
QVERIFY2(statusText(page).contains(denied ? ": FAILED" : ": OK"), qPrintable(statusText(page)));
const auto details = page.findChild<QPlainTextEdit*>()->toPlainText();
if (denied) QVERIFY2(details.contains("authorized"), qPrintable(details));
else QCOMPARE(details, QString("ok"));
for (auto* button : buttons) QVERIFY(button->isEnabled());
}
};
QTEST_MAIN(DatabaseMaintenanceTest)
#include "test_database_maintenance.moc"