"""Integration tests for feature flag resolution endpoint.""" import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import text async def _seed_feature_flag(db: AsyncSession, flag_key: str, display_name: str): """Insert a feature flag and return its id.""" result = await db.execute( text( "INSERT INTO feature_flags (id, flag_key, display_name) " "VALUES (gen_random_uuid(), :key, :name) RETURNING id" ), {"key": flag_key, "name": display_name}, ) await db.commit() return result.scalar_one() async def _seed_plan_default(db: AsyncSession, flag_id, plan: str, enabled: bool): """Insert a plan default for a flag.""" await db.execute( text( "INSERT INTO plan_feature_defaults (id, plan, flag_id, enabled) " "VALUES (gen_random_uuid(), :plan, :flag_id, :enabled)" ), {"plan": plan, "flag_id": flag_id, "enabled": enabled}, ) await db.commit() async def _seed_account_override(db: AsyncSession, flag_id, account_id, enabled: bool): """Insert an account override for a flag.""" await db.execute( text( "INSERT INTO account_feature_overrides (id, account_id, flag_id, enabled) " "VALUES (gen_random_uuid(), :account_id, :flag_id, :enabled)" ), {"account_id": account_id, "flag_id": flag_id, "enabled": enabled}, ) await db.commit() async def _get_account_id(db: AsyncSession, user_id: str): """Get account_id for a user.""" result = await db.execute( text("SELECT account_id FROM users WHERE id = :uid"), {"uid": user_id}, ) return result.scalar_one() class TestFeatureFlagResolution: """Tests for GET /auth/me/feature-flags.""" @pytest.mark.asyncio async def test_no_flags_returns_empty(self, client: AsyncClient, auth_headers: dict): """When no flags exist, returns empty dict.""" response = await client.get("/api/v1/auth/me/feature-flags", headers=auth_headers) assert response.status_code == 200 assert response.json() == {} @pytest.mark.asyncio async def test_plan_default_resolves( self, client: AsyncClient, auth_headers: dict, test_user: dict, test_db: AsyncSession ): """Flag with plan default for 'free' plan resolves correctly.""" flag_id = await _seed_feature_flag(test_db, "test_feature", "Test Feature") await _seed_plan_default(test_db, flag_id, "free", True) response = await client.get("/api/v1/auth/me/feature-flags", headers=auth_headers) assert response.status_code == 200 assert response.json()["test_feature"] is True @pytest.mark.asyncio async def test_no_plan_default_resolves_false( self, client: AsyncClient, auth_headers: dict, test_user: dict, test_db: AsyncSession ): """Flag with no plan default for user's plan resolves to false.""" flag_id = await _seed_feature_flag(test_db, "pro_only", "Pro Only") await _seed_plan_default(test_db, flag_id, "pro", True) response = await client.get("/api/v1/auth/me/feature-flags", headers=auth_headers) assert response.status_code == 200 assert response.json()["pro_only"] is False @pytest.mark.asyncio async def test_account_override_beats_plan_default( self, client: AsyncClient, auth_headers: dict, test_user: dict, test_db: AsyncSession ): """Account override takes precedence over plan default.""" flag_id = await _seed_feature_flag(test_db, "overridden", "Overridden Flag") await _seed_plan_default(test_db, flag_id, "free", False) account_id = await _get_account_id(test_db, test_user["user_data"]["id"]) await _seed_account_override(test_db, flag_id, account_id, True) response = await client.get("/api/v1/auth/me/feature-flags", headers=auth_headers) assert response.status_code == 200 assert response.json()["overridden"] is True @pytest.mark.asyncio async def test_unauthenticated_returns_401(self, client: AsyncClient): """Unauthenticated request returns 401.""" response = await client.get("/api/v1/auth/me/feature-flags") assert response.status_code == 401