feat(billing): plan taxonomy reconciliation + Stripe sync + internal-tester allowlist (#164)
Co-authored-by: Michael Chihlas <michael@resolutionflow.com> Co-committed-by: Michael Chihlas <michael@resolutionflow.com>
This commit was merged in pull request #164.
This commit is contained in:
@@ -49,6 +49,58 @@ class TestConfigPublic:
|
||||
assert response.status_code == 200
|
||||
assert response.json()["oauth_providers"] == ["microsoft"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_config_public_returns_true_for_internal_tester(
|
||||
self,
|
||||
client: AsyncClient,
|
||||
auth_headers: dict,
|
||||
test_user: dict,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
"""Authenticated user whose email is on INTERNAL_TESTER_EMAILS sees
|
||||
self_serve_enabled=True even when the global flag is off."""
|
||||
monkeypatch.setattr(settings, "SELF_SERVE_ENABLED", False)
|
||||
monkeypatch.setattr(settings, "GOOGLE_CLIENT_ID", None)
|
||||
monkeypatch.setattr(settings, "MS_CLIENT_ID", None)
|
||||
monkeypatch.setattr(settings, "INTERNAL_TESTER_EMAILS", [test_user["email"].lower()])
|
||||
|
||||
response = await client.get("/api/v1/config/public", headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["self_serve_enabled"] is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_config_public_returns_false_for_non_tester_when_global_off(
|
||||
self,
|
||||
client: AsyncClient,
|
||||
auth_headers: dict,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
"""Authenticated user NOT on the allowlist sees the global flag —
|
||||
prevents accidental opt-in via stale credentials or empty allowlist."""
|
||||
monkeypatch.setattr(settings, "SELF_SERVE_ENABLED", False)
|
||||
monkeypatch.setattr(settings, "GOOGLE_CLIENT_ID", None)
|
||||
monkeypatch.setattr(settings, "MS_CLIENT_ID", None)
|
||||
monkeypatch.setattr(settings, "INTERNAL_TESTER_EMAILS", ["someone-else@example.com"])
|
||||
|
||||
response = await client.get("/api/v1/config/public", headers=auth_headers)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["self_serve_enabled"] is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_config_public_anonymous_ignores_allowlist(
|
||||
self, client: AsyncClient, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""Anonymous callers always see the global flag — the allowlist is
|
||||
keyed on authenticated identity, not request content."""
|
||||
monkeypatch.setattr(settings, "SELF_SERVE_ENABLED", False)
|
||||
monkeypatch.setattr(settings, "GOOGLE_CLIENT_ID", None)
|
||||
monkeypatch.setattr(settings, "MS_CLIENT_ID", None)
|
||||
monkeypatch.setattr(settings, "INTERNAL_TESTER_EMAILS", ["anon-tester@example.com"])
|
||||
|
||||
response = await client.get("/api/v1/config/public")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["self_serve_enabled"] is False
|
||||
|
||||
|
||||
class TestRegisterInviteCodeGate:
|
||||
"""Regression + new-behavior tests for /auth/register vs SELF_SERVE_ENABLED."""
|
||||
@@ -98,3 +150,55 @@ class TestRegisterInviteCodeGate:
|
||||
assert body["email"] == "self-serve@example.com"
|
||||
assert body["account_role"] == "owner"
|
||||
assert "account_id" in body
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_invite_code_optional_for_internal_tester(
|
||||
self, client: AsyncClient, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""SELF_SERVE_ENABLED is False but the registering email is on
|
||||
INTERNAL_TESTER_EMAILS — registration should succeed without an
|
||||
invite code, matching the per-email soft-cutover behavior."""
|
||||
monkeypatch.setattr(settings, "REQUIRE_INVITE_CODE", True)
|
||||
monkeypatch.setattr(settings, "SELF_SERVE_ENABLED", False)
|
||||
monkeypatch.setattr(
|
||||
settings, "INTERNAL_TESTER_EMAILS", ["tester@example.com"]
|
||||
)
|
||||
|
||||
response = await client.post(
|
||||
"/api/v1/auth/register",
|
||||
json={
|
||||
"email": "tester@example.com",
|
||||
"password": "SecurePass123!",
|
||||
"name": "Internal Tester",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 201, response.text
|
||||
body = response.json()
|
||||
assert body["email"] == "tester@example.com"
|
||||
assert body["account_role"] == "owner"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_register_blocked_for_non_tester_when_self_serve_disabled(
|
||||
self, client: AsyncClient, monkeypatch: pytest.MonkeyPatch
|
||||
):
|
||||
"""Registering with an email NOT on the allowlist still 400s when
|
||||
self-serve is off and no invite code is provided. Prevents the
|
||||
allowlist from leaking to public users."""
|
||||
monkeypatch.setattr(settings, "REQUIRE_INVITE_CODE", True)
|
||||
monkeypatch.setattr(settings, "SELF_SERVE_ENABLED", False)
|
||||
monkeypatch.setattr(
|
||||
settings, "INTERNAL_TESTER_EMAILS", ["other@example.com"]
|
||||
)
|
||||
|
||||
response = await client.post(
|
||||
"/api/v1/auth/register",
|
||||
json={
|
||||
"email": "outsider@example.com",
|
||||
"password": "SecurePass123!",
|
||||
"name": "Outsider",
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "invite code is required" in response.json()["detail"].lower()
|
||||
|
||||
Reference in New Issue
Block a user