feat(deps): add require_active_subscription guard with allowlist

Mounts on Pro routers (trees, sessions, scripts, FlowPilot, etc.) and
returns 402 with structured detail when an account's subscription is
missing or locked. Allowlist bypasses billing/account/auth flows so
users can recover from a lapsed subscription.

Conftest now seeds a default Pro/active Subscription on test_user and
test_admin (delete-then-insert because the register endpoint already
creates a free/active sub by default). Two existing tests adapted to
the new seeded plan; tenant-isolation tests seed Subscription rows for
the accounts they create directly.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 14:35:59 -04:00
parent cfe0e6cae6
commit 9ec208f6e7
7 changed files with 245 additions and 29 deletions

View File

@@ -10,8 +10,15 @@ class TestSubscriptionLimits:
"""Test suite for subscription plan limits."""
@pytest.mark.asyncio
async def test_free_plan_tree_limit(self, client: AsyncClient, auth_headers: dict):
async def test_free_plan_tree_limit(
self, client: AsyncClient, auth_headers: dict, test_db: AsyncSession
):
"""Test that free plan has tree creation limit of 3."""
from app.models.subscription import Subscription
sub = (await test_db.execute(select(Subscription))).scalar_one()
sub.plan = "free"
await test_db.commit()
tree_template = {
"name": "Limit Test Tree",
"tree_structure": {
@@ -90,8 +97,15 @@ class TestSubscriptionLimits:
assert response.status_code == 201
@pytest.mark.asyncio
async def test_free_plan_limits_correct(self, client: AsyncClient, auth_headers: dict):
async def test_free_plan_limits_correct(
self, client: AsyncClient, auth_headers: dict, test_db: AsyncSession
):
"""Test that free plan limits are correct."""
from app.models.subscription import Subscription
sub = (await test_db.execute(select(Subscription))).scalar_one()
sub.plan = "free"
await test_db.commit()
response = await client.get("/api/v1/accounts/me/subscription", headers=auth_headers)
assert response.status_code == 200
limits = response.json()["limits"]