Covers exact product idea: admin create group -> analyze -> personas; role/IP splits (super_admin sees recipe, admin stripped, trainee revealable-only); trainee picks scenario (social=customer opens) -> chat -> persona decides (buy->won) -> readable debrief -> one-shot enforced. All pass.
89 lines
4.4 KiB
Python
89 lines
4.4 KiB
Python
"""Full user-journey flow test (mock LLM) covering the exact 'idea' flow end-to-end:
|
|
admin create group -> analyze -> personas -> trainee picks scenario -> chat ->
|
|
persona decides -> debrief readable. Verifies no 500s and debrief is user-readable.
|
|
"""
|
|
import io, os, sys, tempfile, warnings
|
|
from pathlib import Path
|
|
|
|
warnings.filterwarnings("ignore")
|
|
BACKEND = str(Path(__file__).resolve().parents[1])
|
|
sys.path.insert(0, BACKEND)
|
|
from app.factory import create_app
|
|
from app.config import Config
|
|
|
|
td = tempfile.mkdtemp(); Config.DATA_DIR = Path(td)
|
|
sys.path.insert(0, BACKEND + "/scripts")
|
|
from mock_llm import MockLLM
|
|
|
|
app = create_app(); app.extensions["llm"] = MockLLM()
|
|
C = app.test_client()
|
|
|
|
def tok(u, p): return C.post("/api/auth/login", json={"username": u, "password": p}).get_json()["token"]
|
|
|
|
AT = tok("admin", "1234"); AH = {"Authorization": f"Bearer {AT}"}
|
|
C.post("/api/auth/setup", headers=AH, json={"username":"admin","email":"a@b.co","password":"newpass","accepted_terms":True})
|
|
AT = tok("admin", "newpass"); AH = {"Authorization": f"Bearer {AT}"}
|
|
|
|
# 1. admin creates group
|
|
r = C.post("/api/groups", headers=AH, json={"product":"Point-of-Sale CRM","segment":"SME restaurants","channel":"line","language":"th"})
|
|
assert r.status_code == 201, r.get_json()
|
|
gid = r.get_json()["group"]["id"]
|
|
print("[ok] admin created group", gid[:8])
|
|
|
|
# 2. analyze
|
|
r = C.post(f"/api/groups/{gid}/analyze", headers=AH)
|
|
assert r.status_code == 200, r.get_json()
|
|
g = C.get(f"/api/groups/{gid}", headers=AH).get_json()["group"]
|
|
assert g.get("status") in ("ready", "draft"), g.get("status")
|
|
print("[ok] analyze produced", g.get("status"))
|
|
|
|
# 3. super_admin sees personas WITH secret fields (recipe), tiers populated
|
|
ps = C.get(f"/api/groups/{gid}/personas", headers=AH).get_json()["personas"]
|
|
assert ps and len(ps) >= 3
|
|
assert "pains" in ps[0], "super_admin should see recipe fields"
|
|
print("[ok] super_admin sees", len(ps), "personas incl. recipe (pains)")
|
|
|
|
# 4. create a trainee (user) in the same org, login
|
|
C.post("/api/admin/users", headers=AH, json={"username":"trainee","password":"pppp","role":"user"})
|
|
TT = tok("trainee", "pppp"); TH = {"Authorization": f"Bearer {TT}"}
|
|
|
|
# 5. trainee picks scenario and starts chat with persona 0
|
|
pid = ps[0]["id"]
|
|
r = C.post(f"/api/chat/{gid}/personas/{pid}/chat/start", headers=TH, json={"scenario":"social","locale":"th"})
|
|
assert r.status_code == 200, r.get_json()
|
|
assert any(m.get("role") == "customer" for m in r.get_json()["session"]["messages"]), "social = customer opens"
|
|
print("[ok] trainee started social scenario; customer opens")
|
|
|
|
# 6. trainee sends messages until persona decides (mock decides buy on first send)
|
|
r = C.post(f"/api/chat/{gid}/personas/{pid}/chat/send", headers=TH, json={"text":"สวัสดีครับ ช่วยแนะนำหน่อยได้ไหม"})
|
|
assert r.status_code == 200, r.get_json()
|
|
b = r.get_json()
|
|
assert b.get("finished") is True and b.get("outcome") == "won", b
|
|
db = b.get("debrief") or {}
|
|
# 7. debrief must be user-readable: no raw nested JSON blobs leaking internals unexpectedly
|
|
assert db.get("outcome") == "won"
|
|
assert isinstance(db.get("coaching"), list) and db.get("coaching")
|
|
assert isinstance(db.get("why"), str) and db.get("why")
|
|
print("[ok] chat ended (persona decided buy); debrief readable")
|
|
|
|
# 8. one-shot: trainee cannot start again on same persona
|
|
r = C.post(f"/api/chat/{gid}/personas/{pid}/chat/start", headers=TH, json={"scenario":"social"})
|
|
assert r.status_code in (400, 409), r.get_json()
|
|
print("[ok] one-shot enforced: cannot restart finished persona")
|
|
|
|
# 9. admin (non-super) sees persona WITHOUT recipe (IP protection)
|
|
C.post("/api/admin/users", headers=AH, json={"username":"adm","password":"pppp","role":"admin"})
|
|
ADM = tok("adm","pppp"); ADH = {"Authorization": f"Bearer {ADM}"}
|
|
ps2 = C.get(f"/api/groups/{gid}/personas", headers=ADH).get_json()["personas"]
|
|
assert "pains" not in ps2[0] and "tolerance" not in ps2[0], "admin must NOT see recipe"
|
|
print("[ok] admin sees personas but recipe fields stripped (IP)")
|
|
|
|
# 10. trainee sees a minimal revealable persona (no hidden signals)
|
|
pt = C.get(f"/api/groups/{gid}/personas", headers=TH).get_json()["personas"]
|
|
pfirst = pt[0]
|
|
for hidden in ("pains","tolerance","negotiation_levers","opener","income","background"):
|
|
assert hidden not in pfirst, f"trainee should not see '{hidden}' before chat"
|
|
print("[ok] trainee sees only revealable persona (latent hidden)")
|
|
|
|
print("ALL USER-JOURNEY FLOW TESTS PASSED")
|