Files
sales-trainer/backend/tests/test_final_review_regressions.py

188 lines
6.2 KiB
Python

"""Regression tests for findings from the final adversarial review."""
from __future__ import annotations
from concurrent.futures import ThreadPoolExecutor
from scripts.mock_llm import MockLLM
def _headers(token: str) -> dict[str, str]:
return {"Authorization": f"Bearer {token}"}
def _setup_admin(user_store, login) -> str:
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 test_trainee_cannot_read_draft_group_by_id(client, user_store, login):
_setup_admin(user_store, login)
group_store = client.application.extensions["group_store"]
group = group_store.create(
org_id="org-default", creator_id="admin", title="Draft group"
)
trainee = user_store.create_user(
org_id="org-default",
username="draft-reader",
password="draft-reader-password",
name="Draft Reader",
role="user",
must_setup=False,
)
token = login(trainee["username"], "draft-reader-password")["token"]
response = client.get(f"/api/groups/{group['id']}", headers=_headers(token))
assert response.status_code == 403, response.get_json()
def test_analysis_publishes_ready_group_as_one_complete_record(
client, user_store, login, monkeypatch
):
token = _setup_admin(user_store, login)
client.application.extensions["llm"] = MockLLM()
group_store = client.application.extensions["group_store"]
group = group_store.create(
org_id="org-default", creator_id="admin", title="Atomic analysis"
)
group_store.update(
group["id"],
input={
"product": "CRM",
"segment": "SME",
"description": "sales tool",
"channel": "facebook",
"language": "th",
},
)
observed_ready = []
original_replace = group_store.groups.replace
def checked_replace(key, value):
if value.get("status") == "ready":
observed_ready.append(value)
assert value.get("personas")
assert value.get("report")
assert value.get("sales_kit")
return original_replace(key, value)
monkeypatch.setattr(group_store.groups, "replace", checked_replace)
response = client.post(
f"/api/groups/{group['id']}/analyze", headers=_headers(token)
)
assert response.status_code == 200, response.get_json()
assert len(observed_ready) == 1
def test_group_delete_holds_each_session_record_lock(
client, user_store, login, monkeypatch
):
token = _setup_admin(user_store, login)
group_store = client.application.extensions["group_store"]
group = group_store.create(
org_id="org-default", creator_id="admin", title="Delete group"
)
group_id = group["id"]
session_store = client.application.extensions["session_store"]
session_store.sessions.create(
{
"id": "session-to-delete",
"org_id": "org-default",
"user_id": "admin",
"group_id": group_id,
"persona_id": "persona-1",
"mode": "preview",
"status": "active",
},
key="session-to-delete",
)
from app.storage import store as storage_module
original_delete = session_store.sessions.delete
def checked_delete(key):
path = str(session_store.sessions._path(key))
assert path in storage_module._held_records.get()
return original_delete(key)
monkeypatch.setattr(session_store.sessions, "delete", checked_delete)
response = client.delete(f"/api/groups/{group_id}", headers=_headers(token))
assert response.status_code == 200, response.get_json()
assert session_store.sessions.get_or_none("session-to-delete") is None
def test_bootstrap_admin_is_process_safe_for_concurrent_first_start(
monkeypatch, tmp_path
):
from app.auth.users import UserStore
from app.config import Config
from app.factory import bootstrap_admin
monkeypatch.setattr(Config, "DATA_DIR", tmp_path)
monkeypatch.setattr(Config, "APP_ENV", "test")
monkeypatch.setattr(Config, "SECRET_KEY", "pytest-secret-0123456789abcdef0123456789")
monkeypatch.setattr(Config, "BOOTSTRAP_ADMIN_PASSWORD", "bootstrap-password")
stores = [UserStore(tmp_path), UserStore(tmp_path)]
with ThreadPoolExecutor(max_workers=2) as pool:
results = list(pool.map(bootstrap_admin, stores))
assert results == [None, None]
assert len(UserStore(tmp_path).users.all()) == 1
assert UserStore(tmp_path).get_user("admin")["role"] == "super_admin"
def test_analytics_hardest_personas_prioritize_losses_then_low_score(
client, user_store, login
):
token = _setup_admin(user_store, login)
trainee = user_store.create_user(
org_id="org-default",
username="hardest-personas-trainee",
password="hardest-personas-trainee-password",
name="Hardest Personas Trainee",
role="user",
must_setup=False,
)
session_store = client.application.extensions["session_store"]
analytics_group = client.application.extensions["group_store"].create(
org_id="org-default", creator_id="admin", title="Analytics group"
)
rows = [
("hard-1", "Hard persona", "lost", 20),
("hard-2", "Hard persona", "lost", 30),
("easy-1", "Easy persona", "won", 95),
]
for sid, persona_name, outcome, score in rows:
session_store.sessions.create(
{
"id": sid,
"org_id": "org-default",
"user_id": trainee["id"],
"group_id": analytics_group["id"],
"persona_id": persona_name.lower().replace(" ", "-"),
"persona_name": persona_name,
"mode": "trainee",
"status": "finished",
"outcome": outcome,
"debrief": {"score": score},
},
key=sid,
)
response = client.get("/api/analytics", headers=_headers(token))
assert response.status_code == 200, response.get_json()
assert [item["persona_name"] for item in response.get_json()["hardest_personas"][:2]] == [
"Hard persona",
"Easy persona",
]