/** * Unit tests for Supabase utility classes. * * Covers: * - SupabaseQueryBuilder — PostgREST filter / pagination / sort / list query assembly * - SupabaseAuthManager — token management and authentication state * - SupabaseRlsManager — tenant header building * * All pure-logic, no network required. */ #include #include #include #include "adapters/supabase/supabase_query_builder.hpp" #include "adapters/supabase/supabase_auth_manager.hpp" #include "adapters/supabase/supabase_rls_manager.hpp" using namespace dbal::adapters::supabase; using Json = nlohmann::json; // --------------------------------------------------------------------------- // SupabaseQueryBuilder — buildFilterQuery // --------------------------------------------------------------------------- TEST(SupabaseQueryBuilderTest, BuildFilterQuery_SingleString) { Json filter; filter["status"] = "active"; auto q = SupabaseQueryBuilder::buildFilterQuery(filter); EXPECT_EQ(q, "status=eq.active"); } TEST(SupabaseQueryBuilderTest, BuildFilterQuery_MultipleKeys) { Json filter; filter["a"] = "1"; filter["b"] = "2"; auto q = SupabaseQueryBuilder::buildFilterQuery(filter); EXPECT_NE(q.find("a=eq.1"), std::string::npos); EXPECT_NE(q.find("b=eq.2"), std::string::npos); EXPECT_NE(q.find("&"), std::string::npos); } TEST(SupabaseQueryBuilderTest, BuildFilterQuery_IntegerValue) { Json filter; filter["age"] = 25; auto q = SupabaseQueryBuilder::buildFilterQuery(filter); EXPECT_NE(q.find("age=eq.25"), std::string::npos); } TEST(SupabaseQueryBuilderTest, BuildFilterQuery_BooleanValue) { Json filter; filter["enabled"] = true; auto q = SupabaseQueryBuilder::buildFilterQuery(filter); EXPECT_NE(q.find("enabled=eq.true"), std::string::npos); } TEST(SupabaseQueryBuilderTest, BuildFilterQuery_FloatValue) { Json filter; filter["score"] = 3.14; auto q = SupabaseQueryBuilder::buildFilterQuery(filter); EXPECT_NE(q.find("score=eq."), std::string::npos); } TEST(SupabaseQueryBuilderTest, BuildFilterQuery_Empty_ReturnsEmpty) { auto q = SupabaseQueryBuilder::buildFilterQuery(Json::object()); EXPECT_TRUE(q.empty()); } // --------------------------------------------------------------------------- // SupabaseQueryBuilder — buildPaginationQuery // --------------------------------------------------------------------------- TEST(SupabaseQueryBuilderTest, BuildPaginationQuery_FirstPage) { auto q = SupabaseQueryBuilder::buildPaginationQuery(10, 1); EXPECT_EQ(q, "limit=10&offset=0"); } TEST(SupabaseQueryBuilderTest, BuildPaginationQuery_SecondPage) { auto q = SupabaseQueryBuilder::buildPaginationQuery(20, 2); EXPECT_EQ(q, "limit=20&offset=20"); } TEST(SupabaseQueryBuilderTest, BuildPaginationQuery_ThirdPage) { auto q = SupabaseQueryBuilder::buildPaginationQuery(5, 3); EXPECT_EQ(q, "limit=5&offset=10"); } // --------------------------------------------------------------------------- // SupabaseQueryBuilder — buildSortQuery // --------------------------------------------------------------------------- TEST(SupabaseQueryBuilderTest, BuildSortQuery_Ascending) { std::map sort = {{"name", "asc"}}; auto q = SupabaseQueryBuilder::buildSortQuery(sort); EXPECT_EQ(q, "order=name.asc"); } TEST(SupabaseQueryBuilderTest, BuildSortQuery_Descending) { std::map sort = {{"createdAt", "desc"}}; auto q = SupabaseQueryBuilder::buildSortQuery(sort); EXPECT_EQ(q, "order=createdAt.desc"); } TEST(SupabaseQueryBuilderTest, BuildSortQuery_DefaultsToAsc) { std::map sort = {{"age", "invalid"}}; auto q = SupabaseQueryBuilder::buildSortQuery(sort); EXPECT_NE(q.find("order=age.asc"), std::string::npos); } // --------------------------------------------------------------------------- // SupabaseQueryBuilder — buildListQuery // --------------------------------------------------------------------------- TEST(SupabaseQueryBuilderTest, BuildListQuery_NoFilter) { dbal::ListOptions opts; opts.limit = 10; opts.page = 1; auto q = SupabaseQueryBuilder::buildListQuery("users", opts); EXPECT_EQ(q.substr(0, 5), "users"); EXPECT_NE(q.find("limit=10"), std::string::npos); EXPECT_EQ(q.find("eq."), std::string::npos); // no filter } TEST(SupabaseQueryBuilderTest, BuildListQuery_WithFilter) { dbal::ListOptions opts; opts.filter["role"] = "admin"; opts.limit = 5; opts.page = 1; auto q = SupabaseQueryBuilder::buildListQuery("users", opts); EXPECT_NE(q.find("role=eq.admin"), std::string::npos); EXPECT_NE(q.find("limit=5"), std::string::npos); } TEST(SupabaseQueryBuilderTest, BuildListQuery_WithSort) { dbal::ListOptions opts; opts.sort["name"] = "asc"; opts.limit = 20; auto q = SupabaseQueryBuilder::buildListQuery("products", opts); EXPECT_NE(q.find("order=name.asc"), std::string::npos); } TEST(SupabaseQueryBuilderTest, BuildListQuery_DefaultLimit20) { dbal::ListOptions opts; // default limit == 20 auto q = SupabaseQueryBuilder::buildListQuery("things", opts); EXPECT_NE(q.find("limit=20"), std::string::npos); } // --------------------------------------------------------------------------- // SupabaseQueryBuilder — buildReadQuery / buildIdFilterQuery // --------------------------------------------------------------------------- TEST(SupabaseQueryBuilderTest, BuildReadQuery) { auto q = SupabaseQueryBuilder::buildReadQuery("orders", "ord-123"); EXPECT_EQ(q, "orders?id=eq.ord-123"); } TEST(SupabaseQueryBuilderTest, BuildIdFilterQuery_SameAsReadQuery) { auto read = SupabaseQueryBuilder::buildReadQuery("items", "x"); auto id = SupabaseQueryBuilder::buildIdFilterQuery("items", "x"); EXPECT_EQ(read, id); } // --------------------------------------------------------------------------- // SupabaseQueryBuilder — escapeValue // --------------------------------------------------------------------------- TEST(SupabaseQueryBuilderTest, EscapeValue_Null_DumpsNull) { Json nullVal = nullptr; // null is escaped via the Json::dump() path Json filter; filter["x"] = nullVal; auto q = SupabaseQueryBuilder::buildFilterQuery(filter); EXPECT_NE(q.find("x=eq."), std::string::npos); } // --------------------------------------------------------------------------- // SupabaseAuthManager // --------------------------------------------------------------------------- TEST(SupabaseAuthManagerTest, InitialToken_IsApiKey) { SupabaseAuthManager mgr("https://project.supabase.co", "my-api-key"); EXPECT_EQ(mgr.getAuthToken(), "my-api-key"); } TEST(SupabaseAuthManagerTest, IsAuthenticated_TrueByDefault) { SupabaseAuthManager mgr("https://project.supabase.co", "my-api-key"); EXPECT_TRUE(mgr.isAuthenticated()); } TEST(SupabaseAuthManagerTest, SetAuthToken_UpdatesToken) { SupabaseAuthManager mgr("https://project.supabase.co", "api-key"); mgr.setAuthToken("user-jwt-token"); EXPECT_EQ(mgr.getAuthToken(), "user-jwt-token"); EXPECT_TRUE(mgr.isAuthenticated()); } TEST(SupabaseAuthManagerTest, SetAuthToken_EmptyToken_SetsNotAuthenticated) { SupabaseAuthManager mgr("https://project.supabase.co", "api-key"); mgr.setAuthToken(""); EXPECT_FALSE(mgr.isAuthenticated()); } TEST(SupabaseAuthManagerTest, ClearAuth_RevertsToApiKey) { SupabaseAuthManager mgr("https://project.supabase.co", "original-key"); mgr.setAuthToken("temp-token"); mgr.clearAuth(); EXPECT_EQ(mgr.getAuthToken(), "original-key"); EXPECT_TRUE(mgr.isAuthenticated()); } // --------------------------------------------------------------------------- // SupabaseRlsManager // --------------------------------------------------------------------------- TEST(SupabaseRlsManagerTest, BuildTenantHeaders_ContainsTenantId) { SupabaseRlsManager mgr; auto headers = mgr.buildTenantHeaders("tenant-abc"); ASSERT_TRUE(headers.contains("X-Tenant-Id")); EXPECT_EQ(headers["X-Tenant-Id"].get(), "tenant-abc"); } TEST(SupabaseRlsManagerTest, BuildTenantHeaders_EmptyTenantId_NoHeader) { // Empty tenant id: implementation omits the header (no RLS context to inject) SupabaseRlsManager mgr; auto headers = mgr.buildTenantHeaders(""); EXPECT_FALSE(headers.contains("X-Tenant-Id")); }