#include <gtest/gtest.h>
#include <mw/database.hpp>
#include "db/migrations.hpp"
namespace
{
bool tableExists(mw::SQLite& db, const std::string& name)
{
auto stmt = db.statementFromStr(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?;");
EXPECT_TRUE(stmt.has_value());
EXPECT_TRUE(stmt->bind(name).has_value());
auto rows = db.eval<int64_t>(std::move(*stmt));
EXPECT_TRUE(rows.has_value());
return !rows->empty();
}
int64_t userVersion(mw::SQLite& db)
{
auto v = db.evalToValue<int64_t>("PRAGMA user_version;");
EXPECT_TRUE(v.has_value());
return *v;
}
} // namespace
TEST(Migrations, V0ToV1AppliesSchema)
{
auto db_r = mw::SQLite::connectMemory();
ASSERT_TRUE(db_r.has_value());
auto& db = **db_r;
ASSERT_TRUE(overseer::db::migrateDB0To1(db).has_value());
// The init migration creates a known set of tables.
EXPECT_TRUE(tableExists(db, "storage"));
EXPECT_TRUE(tableExists(db, "stuff"));
EXPECT_TRUE(tableExists(db, "stuff_move"));
EXPECT_TRUE(tableExists(db, "attachment"));
}
TEST(Migrations, MigrateIfNeededSetsUserVersion)
{
auto db_r = mw::SQLite::connectMemory();
ASSERT_TRUE(db_r.has_value());
auto& db = **db_r;
// Empty path -> skip backup, which is what we want in-memory.
ASSERT_TRUE(overseer::db::migrateIfNeeded(db, "").has_value());
EXPECT_EQ(userVersion(db), overseer::db::HIGHEST_KNOWN_VERSION);
}
TEST(Migrations, MigrateIfNeededIsIdempotent)
{
auto db_r = mw::SQLite::connectMemory();
ASSERT_TRUE(db_r.has_value());
auto& db = **db_r;
ASSERT_TRUE(overseer::db::migrateIfNeeded(db, "").has_value());
const auto v1 = userVersion(db);
// Running it again is a no-op: should succeed and the version
// should not move.
ASSERT_TRUE(overseer::db::migrateIfNeeded(db, "").has_value());
EXPECT_EQ(userVersion(db), v1);
}
TEST(Migrations, RefusesNewerThanBinary)
{
auto db_r = mw::SQLite::connectMemory();
ASSERT_TRUE(db_r.has_value());
auto& db = **db_r;
ASSERT_TRUE(db.execute("PRAGMA user_version = 9999;").has_value());
auto r = overseer::db::migrateIfNeeded(db, "");
ASSERT_FALSE(r.has_value());
}