feat: add GET /auth/me/feature-flags resolution endpoint

Resolves feature flags for the current user using:
account override > plan default > false

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-02 15:16:26 +00:00
parent a2749104f4
commit a60c19b305
2 changed files with 164 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
"""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