/* Настоящие SQLite/DbManager/Qt-страница: optimize без строк, строгие integrity-проверки и SQL-отказы. */ #include "../db/db_manager.h" #include "../src/databasesettingspage.h" #include #include 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()) 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("operation"); QTest::addColumn("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("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("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("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("operation"); QTest::addColumn("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(); 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()->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"