Files
radiantwave/tests/test_db.cpp
T
2026-08-07 14:51:41 -05:00

323 lines
10 KiB
C++

#include "db/database_manager.h"
#include <gtest/gtest.h>
#include <filesystem>
using namespace radiantwave::db;
TEST(DatabaseManagerTest, InitializeAndMigrate) {
std::string temp_path = (std::filesystem::temp_directory_path() / "test_rw_db1.db").string();
if (std::filesystem::exists(temp_path)) {
std::filesystem::remove(temp_path);
}
DatabaseManager db;
EXPECT_TRUE(db.initialize(temp_path));
EXPECT_TRUE(std::filesystem::exists(temp_path));
db.close();
std::filesystem::remove(temp_path);
}
TEST(DatabaseManagerTest, KeyValueStoreOperations) {
std::string temp_path = (std::filesystem::temp_directory_path() / "test_rw_db2.db").string();
if (std::filesystem::exists(temp_path)) {
std::filesystem::remove(temp_path);
}
DatabaseManager db;
ASSERT_TRUE(db.initialize(temp_path));
EXPECT_FALSE(db.hasKV("device_token"));
EXPECT_EQ(db.getKV("device_token", "default_token"), "default_token");
EXPECT_TRUE(db.setKV("device_token", "ABC-123-XYZ"));
EXPECT_TRUE(db.hasKV("device_token"));
EXPECT_EQ(db.getKV("device_token"), "ABC-123-XYZ");
// Upsert test
EXPECT_TRUE(db.setKV("device_token", "NEW-TOKEN-789"));
EXPECT_EQ(db.getKV("device_token"), "NEW-TOKEN-789");
// Delete test
EXPECT_TRUE(db.deleteKV("device_token"));
EXPECT_FALSE(db.hasKV("device_token"));
db.close();
std::filesystem::remove(temp_path);
}
TEST(DatabaseManagerTest, ProclamationsAndDownloads) {
std::string temp_path = (std::filesystem::temp_directory_path() / "test_rw_db3.db").string();
if (std::filesystem::exists(temp_path)) {
std::filesystem::remove(temp_path);
}
DatabaseManager db;
ASSERT_TRUE(db.initialize(temp_path));
ProclamationRecord proc;
proc.id = "p1";
proc.title = "Peace";
proc.content = "I am peace.";
proc.solfeggio_freq_hz = 528.0;
EXPECT_TRUE(db.saveProclamation(proc));
auto active = db.getActiveProclamations();
ASSERT_EQ(active.size(), 1u);
EXPECT_EQ(active[0].title, "Peace");
EXPECT_DOUBLE_EQ(active[0].solfeggio_freq_hz, 528.0);
AssetDownloadRecord dl;
dl.id = "a1";
dl.filename = "audio1.wav";
dl.url = "https://example.com/audio1.wav";
dl.status = "pending";
EXPECT_TRUE(db.updateAssetDownload(dl));
auto pending = db.getPendingAssetDownloads();
ASSERT_EQ(pending.size(), 1u);
EXPECT_EQ(pending[0].filename, "audio1.wav");
EXPECT_TRUE(db.logEvent("INFO", "Test", "All systems operational"));
db.close();
std::filesystem::remove(temp_path);
}
namespace {
// Helper: build a fresh in-memory-less temp DatabaseManager.
struct DbFixture {
std::string path;
DatabaseManager db;
DbFixture(const char* tag) {
path = (std::filesystem::temp_directory_path() / tag).string();
if (std::filesystem::exists(path)) {
std::filesystem::remove(path);
}
db.initialize(path);
}
~DbFixture() {
db.close();
std::filesystem::remove(path);
}
};
ProclamationRecord makeProc(const std::string& id, const std::string& title, int cat) {
ProclamationRecord p;
p.id = id;
p.title = title;
p.content = "content of " + title;
p.category_id = cat;
return p;
}
} // namespace
TEST(DatabaseManagerTest, SyncProclamationsPreservesSelectionAndDeletesRemoved) {
const std::string defId = "default-1";
DbFixture fx("test_rw_db_sync.db");
ProclamationRecord a = makeProc("a", "Alpha", 1);
ProclamationRecord b = makeProc("b", "Beta", 1);
// First sync inserts both. The incoming `selected` flag is never trusted.
ASSERT_TRUE(fx.db.syncProclamations({a, b}, defId));
// Simulate the user picking "a".
ASSERT_TRUE(fx.db.setProclamationSelected("a", true));
// Re-sync: "a" stays selected, "b" is removed because the API no longer
// returns it.
ASSERT_TRUE(fx.db.syncProclamations({a}, defId));
auto all = fx.db.getAllProclamations();
ASSERT_EQ(all.size(), 1u);
EXPECT_EQ(all[0].id, "a");
EXPECT_TRUE(all[0].selected);
}
TEST(DatabaseManagerTest, RequiredLevelIdRoundTripsThroughSync) {
const std::string defId = "default-1";
DbFixture fx("test_rw_db_level.db");
ProclamationRecord def = makeProc(defId, "Default", 1);
def.required_level_id = 1;
ProclamationRecord elite = makeProc("elite", "Elite Only", 2);
elite.required_level_id = 3;
ASSERT_TRUE(fx.db.syncProclamations({def, elite}, defId));
auto all = fx.db.getAllProclamations();
ASSERT_EQ(all.size(), 2u);
for (const auto& p : all) {
EXPECT_EQ(p.required_level_id, p.id == "elite" ? 3 : 1);
}
// A re-sync with the same records keeps the level (and selection).
ASSERT_TRUE(fx.db.syncProclamations({def, elite}, defId));
auto again = fx.db.getAllProclamations();
ASSERT_EQ(again.size(), 2u);
for (const auto& p : again) {
EXPECT_EQ(p.required_level_id, p.id == "elite" ? 3 : 1);
}
}
TEST(DatabaseManagerTest, SyncForcesDefaultProclamationSelected) {
const std::string defId = "default-1";
DbFixture fx("test_rw_db_def.db");
ProclamationRecord def = makeProc(defId, "Default", 3);
ProclamationRecord other = makeProc("x", "Other", 3);
def.selected = false;
ASSERT_TRUE(fx.db.syncProclamations({def, other}, defId));
ASSERT_TRUE(fx.db.ensureDefaultProclamationSelected(defId));
auto all = fx.db.getAllProclamations();
for (const auto& p : all) {
if (p.id == defId) {
EXPECT_TRUE(p.selected);
}
}
}
TEST(DatabaseManagerTest, SaveSelectedProclamationsClearsOthers) {
const std::string defId = "default-1";
DbFixture fx("test_rw_db_save.db");
ProclamationRecord def = makeProc(defId, "Default", 1);
ProclamationRecord a = makeProc("a", "A", 1);
ProclamationRecord b = makeProc("b", "B", 1);
ASSERT_TRUE(fx.db.syncProclamations({def, a, b}, defId));
ASSERT_TRUE(fx.db.setProclamationSelected("a", true));
// Deselect everything except a; the default must still end up selected.
ASSERT_TRUE(fx.db.saveSelectedProclamations({"a"}, defId));
auto all = fx.db.getAllProclamations();
for (const auto& p : all) {
bool expected = (p.id == "a" || p.id == defId);
EXPECT_EQ(p.selected, expected) << "proclamation " << p.id;
}
}
TEST(DatabaseManagerTest, ReplaceAndReadCategories) {
DbFixture fx("test_rw_db_cats.db");
std::vector<ProclamationCategoryRecord> cats;
cats.push_back({3, "Root", 0, 1});
cats.push_back({7, "Child", 3, 2});
ASSERT_TRUE(fx.db.replaceCategories(cats));
auto all = fx.db.getAllCategories();
ASSERT_EQ(all.size(), 2u);
EXPECT_EQ(all[0].id, 3);
EXPECT_EQ(all[0].parent_id, 0);
EXPECT_EQ(all[1].id, 7);
EXPECT_EQ(all[1].parent_id, 3);
// Replacing wipes previous categories.
ASSERT_TRUE(fx.db.replaceCategories({}));
EXPECT_EQ(fx.db.getAllCategories().size(), 0u);
}
TEST(DatabaseManagerTest, SyncAudioFilesUpsertsAndDeletesMissing) {
DbFixture fx("test_rw_db_audio.db");
std::vector<AudioFileRecord> first = {
{0, "174 Hz Solfeggio", "174Hz.wav", 1, 1},
{0, "Original Audio", "OriginalAudio.wav", 2, 0},
{0, "Alpha Waves", "AlphaWaves.wav", 3, 13},
};
ASSERT_TRUE(fx.db.syncAudioFiles(first));
auto rows = fx.db.getAudioFiles();
ASSERT_EQ(rows.size(), 3u);
EXPECT_EQ(rows[0].filename, "174Hz.wav");
EXPECT_EQ(rows[0].display_name, "174 Hz Solfeggio");
EXPECT_EQ(rows[0].display_order, 1);
EXPECT_EQ(rows[0].solfeggio_frequency_id, 1);
EXPECT_EQ(rows[1].filename, "OriginalAudio.wav");
EXPECT_EQ(rows[1].solfeggio_frequency_id, 0);
EXPECT_EQ(rows[2].filename, "AlphaWaves.wav");
EXPECT_EQ(rows[2].solfeggio_frequency_id, 13);
// Manifest update: renamed display name, reordered, one file removed.
std::vector<AudioFileRecord> second = {
{0, "174 Hz Solfeggio (Updated)", "174Hz.wav", 2, 1},
{0, "Alpha Waves", "AlphaWaves.wav", 1, 13},
};
ASSERT_TRUE(fx.db.syncAudioFiles(second));
rows = fx.db.getAudioFiles();
ASSERT_EQ(rows.size(), 2u);
EXPECT_EQ(rows[0].filename, "AlphaWaves.wav");
EXPECT_EQ(rows[0].display_order, 1);
EXPECT_EQ(rows[1].filename, "174Hz.wav");
EXPECT_EQ(rows[1].display_name, "174 Hz Solfeggio (Updated)");
EXPECT_EQ(rows[1].display_order, 2);
}
TEST(DatabaseManagerTest, SyncAudioFilesClearsEverythingWhenEmpty) {
DbFixture fx("test_rw_db_audio_empty.db");
ASSERT_TRUE(fx.db.syncAudioFiles({{0, "Original Audio", "OriginalAudio.wav", 1, 0}}));
ASSERT_EQ(fx.db.getAudioFiles().size(), 1u);
ASSERT_TRUE(fx.db.syncAudioFiles({}));
EXPECT_EQ(fx.db.getAudioFiles().size(), 0u);
}
TEST(DatabaseManagerTest, PalettesSeededAndRead) {
DbFixture fx("test_rw_db_palette.db");
auto palette = fx.db.getPaletteForFrequencyId(5); // 528 Hz
ASSERT_EQ(palette.size(), 7u);
EXPECT_EQ(palette[0].r, 0x32);
EXPECT_EQ(palette[0].g, 0xCD);
EXPECT_EQ(palette[0].b, 0x32);
EXPECT_EQ(palette[0].a, 255u);
EXPECT_EQ(palette[6].r, 0xF0);
EXPECT_EQ(palette[6].g, 0xFF);
EXPECT_EQ(palette[6].b, 0xF0);
auto red = fx.db.getPaletteForFrequencyId(2); // 396 Hz
ASSERT_EQ(red.size(), 7u);
EXPECT_EQ(red[0].r, 0x8B);
EXPECT_EQ(red[0].g, 0x00);
EXPECT_EQ(red[0].b, 0x00);
}
TEST(DatabaseManagerTest, UnknownFrequencyFallsBackTo528) {
DbFixture fx("test_rw_db_palette_fb.db");
auto palette = fx.db.getPaletteForFrequencyId(9999);
ASSERT_EQ(palette.size(), 7u);
EXPECT_EQ(palette[0].r, 0x32);
EXPECT_EQ(palette[0].g, 0xCD);
EXPECT_EQ(palette[0].b, 0x32);
}
TEST(DatabaseManagerTest, PaletteSeedIsIdempotent) {
DbFixture fx("test_rw_db_palette_seed.db");
// Re-initialize on the same file; seeding must not duplicate rows.
fx.db.close();
ASSERT_TRUE(fx.db.initialize(fx.path));
auto palette = fx.db.getPaletteForFrequencyId(9); // 963 Hz
ASSERT_EQ(palette.size(), 7u);
EXPECT_EQ(palette[0].r, 0x94);
EXPECT_EQ(palette[0].g, 0x00);
EXPECT_EQ(palette[0].b, 0xD3);
}