NoteStack-Test / backend /tests /test_billing.py
Arslan1997's picture
LETS GOOOOOOOOOOOOOOOOOOOOOOOOO
a7da6ce
Raw History Blame
3.87 kB
"""Upgrade flow: status meters, nudges, checkout guards and the 402 payload behind the out of fuel popup."""
import pytest
from app.config import settings
from app.models import Artifact, Notebook, Workspace
from app.services.billing import apply_subscription
from tests.conftest import last_code
@pytest.fixture()
def auth(client):
client.post("/api/auth/email/register/start", json={"email": "ada@example.com", "password": "stardust-42"})
r = client.post("/api/auth/email/register/verify", json={"email": "ada@example.com", "code": last_code()})
return {"Authorization": f"Bearer {r.json()['access_token']}"}
@pytest.fixture()
def billing_on(monkeypatch):
monkeypatch.setattr(settings, "billing_enabled", True)
def test_status_without_billing_has_no_upgrade_nudges(client, auth):
body = client.get("/api/billing/status", headers=auth).json()
assert body["plan"]["id"] == "studio" and body["can_upgrade"] is False
assert {m["key"] for m in body["meters"]} == {"audio_minutes", "video_minutes", "launch_kits", "sources"}
assert all(n["tone"] == "action" for n in body["nudges"])
assert body["nudges"][0]["id"] == "act-source"
def test_free_plan_nudges_and_out_of_fuel_402(client, auth, billing_on, db_session):
ws = db_session.query(Workspace).one()
for _ in range(2):
db_session.add(Artifact(workspace_id=ws.id, type="launch_kit", status="ready"))
db_session.commit()
body = client.get("/api/billing/status", headers=auth).json()
assert body["plan"]["id"] == "free" and body["can_upgrade"] and body["next_plan"] == "writer"
kits = next(m for m in body["meters"] if m["key"] == "launch_kits")
assert kits["used"] == 2 and kits["pct"] == 1
ids = [n["id"] for n in body["nudges"]]
assert ids[0].startswith("empty-launch_kits") and "perk-voice" in ids
assert "—" not in str(body)
nb = Notebook(workspace_id=ws.id, title="Pricing")
db_session.add(nb)
db_session.commit()
r = client.post("/api/artifacts/generate", headers=auth,
json={"type": "audio_overview", "notebook_id": str(nb.id), "minutes": 6})
detail = r.json()["detail"]
assert r.status_code == 402 and detail["kind"] == "audio_minutes" and detail["upgrade_to"] == "writer"
def test_checkout_guards(client, auth, billing_on, db_session):
r = client.post("/api/billing/checkout", headers=auth, json={"plan": "writer"})
assert r.status_code == 503 and r.json()["detail"]["code"] == "stripe_not_configured" # no STRIPE_SECRET_KEY
assert client.post("/api/billing/checkout", headers=auth, json={"plan": "free"}).status_code == 422
ws = db_session.query(Workspace).one()
apply_subscription(db_session, ws.id, plan="studio", status="active", customer_id="cus_1")
r = client.post("/api/billing/checkout", headers=auth, json={"plan": "writer", "cycle": "annual"})
assert r.status_code == 409 and r.json()["detail"]["code"] == "already_on_plan"
body = client.get("/api/billing/status", headers=auth).json()
assert body["plan"]["id"] == "studio" and body["has_billing_account"] and not body["can_upgrade"]
def test_checkout_disabled_without_billing(client, auth):
r = client.post("/api/billing/checkout", headers=auth, json={"plan": "writer"})
assert r.status_code == 409 and r.json()["detail"]["code"] == "billing_disabled"
def test_plan_limit_error_names_the_upgrade():
from app.services.plans import PLANS, plan_limit_error, upgrade_for
assert upgrade_for(PLANS["free"], "voice_cloning") == "writer"
assert upgrade_for(PLANS["writer"], "launch_kits") == "studio"
assert upgrade_for(PLANS["studio"], "audio_minutes") is None
detail = plan_limit_error(PLANS["free"], "audio_minutes", "x").detail
assert detail["code"] == "plan_limit" and detail["upgrade_to"] == "writer" and detail["kind"] == "audio_minutes"