#include "db/database_manager.h" #include "network/api_client.h" #include "network/proclamation_sync.h" #include #include #include #include #include using namespace radiantwave::api; namespace { // Reads an env var, returning "" when unset. std::string env(const char* name) { const char* value = std::getenv(name); return value ? std::string(value) : std::string(); } } // namespace // ─── Offline: GET /color-palettes parse ──────────────────────────────── TEST(ColorPalettesTest, ParseColorPalettesJson) { const std::string body = R"([ {"solfeggioFrequencyId":1, "colors":["#8B6914","#B8860B","#DAA520","#E8B64C","#FFD700","#FFE9A8","#FFF4CC"]}, {"solfeggioFrequencyId":5, "colors":["#32CD32","#3CB371","#90EE90","#98FB98","#ADFFA9","#C8FFD2","#F0FFF0"]} ])"; std::vector out; EXPECT_TRUE(parseColorPalettesJson(body, out)); ASSERT_EQ(out.size(), 2u); EXPECT_EQ(out[0].solfeggio_frequency_id, 1); ASSERT_EQ(out[0].colors.size(), 7u); EXPECT_EQ(out[0].colors[0].r, 0x8B); EXPECT_EQ(out[0].colors[0].g, 0x69); EXPECT_EQ(out[0].colors[0].b, 0x14); EXPECT_EQ(out[0].colors[6].b, 0xCC); EXPECT_EQ(out[1].solfeggio_frequency_id, 5); ASSERT_EQ(out[1].colors.size(), 7u); EXPECT_EQ(out[1].colors[0].r, 0x32); EXPECT_EQ(out[1].colors[0].g, 0xCD); EXPECT_EQ(out[1].colors[0].b, 0x32); } TEST(ColorPalettesTest, ParseColorPalettesJsonAcceptsHexWithoutHash) { const std::string body = R"([{"solfeggioFrequencyId":1,"colors":["32CD32"]}])"; std::vector out; EXPECT_TRUE(parseColorPalettesJson(body, out)); ASSERT_EQ(out.size(), 1u); ASSERT_EQ(out[0].colors.size(), 1u); EXPECT_EQ(out[0].colors[0].r, 0x32); EXPECT_EQ(out[0].colors[0].g, 0xCD); EXPECT_EQ(out[0].colors[0].b, 0x32); } TEST(ColorPalettesTest, ParseColorPalettesJsonSkipsMissingFrequencyId) { const std::string body = R"([{"colors":["#32CD32"]},{"solfeggioFrequencyId":5,"colors":["#00FF7F"]}])"; std::vector out; EXPECT_TRUE(parseColorPalettesJson(body, out)); ASSERT_EQ(out.size(), 1u); EXPECT_EQ(out[0].solfeggio_frequency_id, 5); } TEST(ColorPalettesTest, ParseColorPalettesJsonEmptyAndMalformed) { std::vector out; EXPECT_TRUE(parseColorPalettesJson("[]", out)); EXPECT_TRUE(out.empty()); out.clear(); EXPECT_FALSE(parseColorPalettesJson("not json", out)); EXPECT_FALSE(parseColorPalettesJson(R"({"solfeggioFrequencyId":1})", out)); } // ─── Offline: palettes embedded in /proclamations payload ────────────── TEST(ColorPalettesTest, ParseProclamationPalettesJson) { const std::string body = R"PALETTE([ {"id":"p1","name":"One","solfeggioFrequencyId":1, "colorPalette":["#8B6914","#B8860B"]}, {"id":"p2","name":"Two","solfeggioFrequencyId":5, "colorPalette":["#32CD32","#3CB371","#90EE90"]}, {"id":"p3","name":"Also freq 5 (ignored, first wins)", "solfeggioFrequencyId":5, "colorPalette":["#FF0000"]} ])PALETTE"; std::vector out; EXPECT_TRUE(radiantwave::sync::parseProclamationPalettesJson(body, out)); ASSERT_EQ(out.size(), 2u); EXPECT_EQ(out[0].solfeggio_frequency_id, 1); ASSERT_EQ(out[0].colors.size(), 2u); EXPECT_EQ(out[0].colors[0].r, 0x8B); EXPECT_EQ(out[1].solfeggio_frequency_id, 5); ASSERT_EQ(out[1].colors.size(), 3u); EXPECT_EQ(out[1].colors[0].r, 0x32); // not #FF0000: first proclamation wins } TEST(ColorPalettesTest, ParseProclamationPalettesJsonSkipsMissingPalette) { const std::string body = R"([{"id":"p1","name":"No palette","solfeggioFrequencyId":1}])"; std::vector out; EXPECT_TRUE(radiantwave::sync::parseProclamationPalettesJson(body, out)); EXPECT_TRUE(out.empty()); } // ─── Offline: DB round-trip via replaceColorPalettes ─────────────────── TEST(ColorPalettesTest, ReplaceColorPalettesRoundTrip) { const std::string temp_path = (std::filesystem::temp_directory_path() / "test_rw_db_palettesync.db").string(); if (std::filesystem::exists(temp_path)) { std::filesystem::remove(temp_path); } radiantwave::db::DatabaseManager db; ASSERT_TRUE(db.initialize(temp_path)); radiantwave::db::PaletteRecord rec; rec.solfeggio_frequency_id = 1; rec.colors = {{0x8B, 0x69, 0x14, 255}, {0xFF, 0xD7, 0x00, 255}}; ASSERT_TRUE(db.replaceColorPalettes({rec})); auto palette = db.getPaletteForFrequencyId(1); ASSERT_EQ(palette.size(), 2u); EXPECT_EQ(palette[0].r, 0x8B); EXPECT_EQ(palette[0].g, 0x69); EXPECT_EQ(palette[0].b, 0x14); EXPECT_EQ(palette[1].r, 0xFF); EXPECT_EQ(palette[1].g, 0xD7); EXPECT_EQ(palette[1].b, 0x00); db.close(); std::filesystem::remove(temp_path); } TEST(ColorPalettesTest, ReplaceColorPalettesEmptyClearsAndFallsBackTo528) { const std::string temp_path = (std::filesystem::temp_directory_path() / "test_rw_db_paletteclear.db").string(); if (std::filesystem::exists(temp_path)) { std::filesystem::remove(temp_path); } radiantwave::db::DatabaseManager db; ASSERT_TRUE(db.initialize(temp_path)); ASSERT_TRUE(db.replaceColorPalettes({})); // id 5 was cleared, so both the direct lookup and the 528 Hz fallback are empty. EXPECT_TRUE(db.getPaletteForFrequencyId(5).empty()); db.close(); std::filesystem::remove(temp_path); } // ─── Live: GET /color-palettes (route not live yet; opt-in) ──────────── TEST(ColorPalettesLiveTest, FetchColorPalettesLive) { if (env("RW_LIVE_COLOR_PALETTES").empty()) { GTEST_SKIP() << "RW_LIVE_COLOR_PALETTES not set (route not live yet)"; } std::vector palettes; ASSERT_TRUE(fetchColorPalettes(palettes)); EXPECT_FALSE(palettes.empty()); for (const auto& p : palettes) { EXPECT_GE(p.colors.size(), 1u); } }