Files
sales-trainer/backend/tests/conftest.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

70 lines
2.1 KiB
Python

"""Shared isolated fixtures for backend tests."""
from __future__ import annotations
from pathlib import Path
import pytest
@pytest.fixture()
def app(monkeypatch: pytest.MonkeyPatch, tmp_path: Path):
"""Create a Flask app against a fresh temporary data directory."""
monkeypatch.setenv("DATA_DIR", str(tmp_path))
monkeypatch.setenv("JWT_SECRET", "pytest-only-secret-0123456789abcdef")
monkeypatch.setenv("APP_ENV", "test")
monkeypatch.setenv("BOOTSTRAP_ADMIN_PASSWORD", "pytest-bootstrap-password")
monkeypatch.setenv("FLASK_DEBUG", "false")
from app.config import Config
Config.DATA_DIR = tmp_path
Config.SECRET_KEY = "pytest-only-secret-0123456789abcdef"
Config.APP_ENV = "test"
Config.BOOTSTRAP_ADMIN_PASSWORD = "pytest-bootstrap-password"
Config.FLASK_DEBUG = False
from app.factory import create_app
from app.services import rate_limit
rate_limit._mem.clear()
application = create_app()
application.config.update(TESTING=True)
return application
@pytest.fixture()
def client(app):
return app.test_client()
@pytest.fixture()
def user_store(app):
store = app.extensions["user_store"]
original_create_user = store.create_user
def create_test_user(*args, **kwargs):
# Existing route tests create already-authorized identities directly;
# explicit accepted_terms=False remains available for consent tests.
kwargs.setdefault("accepted_terms", True)
return original_create_user(*args, **kwargs)
store.create_user = create_test_user
return store
def auth_headers(token: str) -> dict[str, str]:
"""Build an Authorization header without exposing the token in assertions."""
return {"Authorization": f"Bearer {token}"}
@pytest.fixture()
def login(client):
def _login(username: str, password: str) -> dict:
response = client.post(
"/api/auth/login",
json={"username": username, "password": password},
)
assert response.status_code == 200, response.get_json()
return response.get_json()
return _login