Files
saas_backend/tests/test_operations.py
T
2026-08-31 20:39:41 -04:00

246 lines
8.0 KiB
Python

"""What the background work has been doing, made answerable.
Three jobs run unattended and every one reported only into a log file. "How many
customers lapsed this month and how many we could not reach" was answerable in
principle and, in practice, never answered.
These are read-only summaries. The tests care about two things: the numbers are
right, and they are superadmin-only — every one of them is a platform-wide view,
which is precisely what a workspace must not have.
"""
from __future__ import annotations
from datetime import date, datetime, timedelta, timezone
import pytest
from app.core.tenant_context import scoped_to, unscoped
from .conftest import requires_db, utc_today
pytestmark = requires_db
TODAY = utc_today()
@pytest.fixture
def superadmin_token(client, user_factory):
user_factory(tenant=None, email="ops@example.com", is_superadmin=True)
token = client.post(
"/api/auth/signin",
json={"email": "ops@example.com", "password": "CorrectHorse!9"},
).json()["access_token"]
return {"Authorization": f"Bearer {token}"}
@pytest.fixture
def member_token(client, tenant_factory, user_factory):
tenant = tenant_factory()
user_factory(tenant=tenant, email="member@example.com")
token = client.post(
"/api/auth/signin",
json={"email": "member@example.com", "password": "CorrectHorse!9"},
).json()["access_token"]
return {"Authorization": f"Bearer {token}"}
@pytest.mark.parametrize(
"path",
["subscription-notices", "outbox", "sessions"],
)
def test_a_workspace_member_cannot_see_the_platform_view(client, member_token, path):
"""Every one of these counts across all customers. That is the whole point,
and the reason a workspace must not reach it."""
assert client.get(
f"/api/admin/operations/{path}", headers=member_token
).status_code == 403
@pytest.mark.parametrize(
"path",
["subscription-notices", "outbox", "sessions"],
)
def test_a_superadmin_may(client, superadmin_token, path):
assert client.get(
f"/api/admin/operations/{path}", headers=superadmin_token
).status_code == 200
def test_notices_are_summarised_by_kind(client, db, superadmin_token,
tenant_factory, plan_factory):
from app.services.auth import subscription_notices
plan = plan_factory(grace_period_days=5)
tenant = tenant_factory(
plan_id=plan.id, end_date=TODAY + timedelta(days=2),
billing_email="pays@example.com",
)
with unscoped():
subscription_notices.record(
db,
subscription_notices.Notice(
tenant_id=tenant.id, tenant_name=tenant.tenant_name,
kind=subscription_notices.EXPIRING_SOON,
for_end_date=tenant.end_date, to="pays@example.com",
),
)
db.commit()
body = client.get(
"/api/admin/operations/subscription-notices", headers=superadmin_token
).json()
assert body["by_kind"].get(subscription_notices.EXPIRING_SOON) == 1
assert body["total"] >= 1
assert any(r["tenant_name"] == tenant.tenant_name for r in body["recent"])
def test_workspaces_nobody_can_be_warned_about_are_counted(
client, db, superadmin_token, tenant_factory, plan_factory
):
"""The number that matters. A workspace with no billing address gets no
warning at all before it lapses, and the only previous sign was a log line."""
plan = plan_factory()
tenant_factory(
plan_id=plan.id, end_date=TODAY + timedelta(days=60), billing_email=None
)
with unscoped():
db.commit()
body = client.get(
"/api/admin/operations/subscription-notices", headers=superadmin_token
).json()
assert body["workspaces_with_no_billing_contact"] >= 1
def test_a_workspace_with_no_end_date_is_not_counted_as_unreachable(
client, db, superadmin_token, tenant_factory, plan_factory
):
"""It will never be chased, so having nobody to chase is not a gap."""
plan = plan_factory()
tenant_factory(plan_id=plan.id, end_date=None, billing_email=None)
with unscoped():
db.commit()
before = client.get(
"/api/admin/operations/subscription-notices", headers=superadmin_token
).json()["workspaces_with_no_billing_contact"]
tenant_factory(plan_id=plan.id, end_date=None, billing_email=None)
db.commit()
after = client.get(
"/api/admin/operations/subscription-notices", headers=superadmin_token
).json()["workspaces_with_no_billing_contact"]
assert after == before
def test_a_module_that_keeps_refusing_shows_up(client, db, superadmin_token,
tenant_factory, module_factory,
environment_factory,
tenant_module_factory,
monkeypatch):
"""`stuck` is the number worth an alert: pending, overdue, already retried.
A module refusing deliveries for a day appears here long before anyone
notices its data is stale.
"""
from app.services.auth import event_service
from app.services.auth.event_service import EventService
class Refuses:
status_code = 503
text = "unavailable"
monkeypatch.setattr(
event_service.httpx, "post", lambda *a, **kw: Refuses()
)
tenant = tenant_factory()
module = module_factory()
environment_factory(module, slug="prod", is_default=True)
tenant_module_factory(tenant, module)
with unscoped():
EventService.emit_event(
db, "TENANT_UPDATED", {"tenant_id": str(tenant.id)}, tenant_id=tenant.id
)
from app.models.system.event_log_model import EventLog
row = db.query(EventLog).order_by(EventLog.created_at.desc()).first()
EventService.process_queue_item(db, str(row.event_id))
row.next_retry_at = datetime.now(timezone.utc) - timedelta(minutes=1)
db.commit()
body = client.get(
"/api/admin/operations/outbox", headers=superadmin_token
).json()
assert body["stuck"] >= 1
assert any("503" in (t["last_error"] or "") for t in body["failing_targets"])
def test_a_healthy_outbox_reports_nothing_stuck(client, db, superadmin_token):
body = client.get(
"/api/admin/operations/outbox", headers=superadmin_token
).json()
assert body["stuck"] == 0
assert body["failing_targets"] == []
def test_a_detected_token_reuse_is_surfaced(client, db, superadmin_token,
tenant_factory, user_factory):
"""A security signal, not a capacity one.
Non-zero means somebody presented a refresh token the legitimate client had
already spent — which is what a copied token looks like. Nothing surfaced it
before; it was a revocation reason in a column nobody queried.
"""
from app.services.auth import session_service
tenant = tenant_factory()
user = user_factory(tenant=tenant)
with unscoped():
session = session_service.start(db, user)
spent = session.current_jti
session_service.rotate(db, spent)
with pytest.raises(session_service.SessionRevoked):
session_service.rotate(db, spent)
db.commit()
body = client.get(
"/api/admin/operations/sessions", headers=superadmin_token
).json()
assert body["reuse_detected"] >= 1
assert body["ended_by_reason"].get("reuse_detected", 0) >= 1
def test_live_sessions_are_counted(client, db, superadmin_token, tenant_factory,
user_factory):
from app.services.auth import session_service
tenant = tenant_factory()
user = user_factory(tenant=tenant)
with unscoped():
before = client.get(
"/api/admin/operations/sessions", headers=superadmin_token
).json()["active"]
session_service.start(db, user)
db.commit()
after = client.get(
"/api/admin/operations/sessions", headers=superadmin_token
).json()["active"]
assert after == before + 1