Files
sales-trainer/backend/tests/test_group_report.py
Macky 3c22d88bcd feat: demo SaaS + training flow security hardening (8/8 review gate passed)
- Demo accounts: super_admin-only provisioning into isolated DEMO_ORG_ID tenant,
  30-day UTC trial on first login, revocable, one-time credential delivery via
  optional SES/webhook (never persisted). Adds boto3 dependency.
- Analytics/report/export/privacy: shared bounded scan budget across users/groups/
  sessions, tenant-consistent session/user/group joins, scalar-only CSV export
  (no nested persisted-value stringification).
- Ownership/tenant isolation: canonical owner-tenant predicate for list/read/chat;
  client sees is_owned only, never owner_user_id.
- Lifecycle/races: status transition validation, analyzing is an in-progress gate
  (no duplicate reanalysis), structured-ready publication, stale-variant revalidation.
- Auth/setup/consent/JWT/OAuth/config: fail-closed consent, bounded JWT lifetime,
  provider-subject atomic OAuth identity, repeated-secret rejection, strict Persona
  trait validation.
- Chat/session/privacy: pre-seller opener redaction, corrupt-session recovery,
  role-aware completed-chat dashboard routing.
- Frontend: Training→product→personas→practice flow, demo/role/demo guards,
  is_owned-based ownership display, 320×568 and 500×768 responsive E2E.
- 8 independent exact-five-key review scopes passed; backend 509, frontend 26,
  production build 1775 modules, isolated E2E 15.
2026-08-25 06:39:06 +07:00

84 lines
2.9 KiB
Python

"""Human-readable redacted report contract tests."""
from __future__ import annotations
from scripts.mock_llm import MockLLM
def _headers(token: str) -> dict[str, str]:
return {"Authorization": f"Bearer {token}"}
def _setup(user_store, login):
user_store.complete_setup(
"admin",
"admin@example.com",
"admin-ready-password",
accepted_terms=True,
accepted_terms_at="2026-08-13T00:00:00Z",
)
return login("admin", "admin-ready-password")["token"]
def _ready(client, token):
client.application.extensions["llm"] = MockLLM()
created = client.post("/api/groups", json={"product": "CRM"}, headers=_headers(token))
gid = created.get_json()["group"]["id"]
analyzed = client.post(f"/api/groups/{gid}/analyze", headers=_headers(token))
assert analyzed.status_code == 200
return gid
def test_admin_report_is_redacted_and_downloadable(client, user_store, login):
super_token = _setup(user_store, login)
gid = _ready(client, super_token)
admin = user_store.create_user(
org_id="org-default",
username="report-admin",
password="report-admin-password",
name="Report Admin",
role="admin",
must_setup=False,
)
token = login(admin["username"], "report-admin-password")["token"]
headers = _headers(token)
json_response = client.get(f"/api/groups/{gid}/report?format=json", headers=headers)
assert json_response.status_code == 200, json_response.get_json()
report = json_response.get_json()
assert report["title"]
assert "CRM" in report["markdown"]
for forbidden in ("raw_personas", "negotiation_levers", "failurePoints", "private", "rootCause"):
assert forbidden not in report["markdown"]
download = client.get(f"/api/groups/{gid}/report", headers=headers)
assert download.status_code == 200
assert download.mimetype == "text/markdown"
assert "attachment" in download.headers["Content-Disposition"]
def test_report_is_tenant_and_role_guarded(client, user_store, login):
super_token = _setup(user_store, login)
gid = _ready(client, super_token)
user = user_store.create_user(
org_id="org-default",
username="report-user",
password="report-user-password",
name="Report User",
role="user",
must_setup=False,
)
user_token = login(user["username"], "report-user-password")["token"]
assert client.get(f"/api/groups/{gid}/report", headers=_headers(user_token)).status_code == 403
other_org = user_store.create_org("Other")
other_admin = user_store.create_user(
org_id=other_org["id"],
username="other-report-admin",
password="other-report-password",
name="Other Report Admin",
role="admin",
must_setup=False,
)
other_token = login(other_admin["username"], "other-report-password")["token"]
assert client.get(f"/api/groups/{gid}/report", headers=_headers(other_token)).status_code == 404