Files
sales-trainer/backend/scripts/test_resume_decision.py
Macky 94e75238a3 fix(chat): resume unfinished session without re-picking scenario; detect win/loss from real-LLM text
- start_session now RESUMES an existing ACTIVE session for the persona instead of creating
  a new one / forcing re-pick of the scenario (keep original scenario+messages).
- send_message falls back to a text-based decision detector (_detect_customer_decision)
  because real LLMs rarely emit structured meta.decision — so a customer who says
  'ซื้อไม่ไหว'/'no thanks' now actually ENDS the chat as lost (was stuck active forever).
  Fragments handled in TH + EN; buy + walk.
All 11 backend suites pass. Rebuilt dist.
2026-08-09 13:12:08 +07:00

51 lines
2.7 KiB
Python

"""Test: chat RESUME (no re-pick scenario) + decision detection for real-LLM text."""
import 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}"}
C.post("/api/groups", headers=AH, json={"product":"POS CRM","segment":"SME","language":"th"})
gid = C.get("/api/groups", headers=AH).get_json()["groups"][0]["id"]
C.post(f"/api/groups/{gid}/analyze", headers=AH)
pid = C.get(f"/api/groups/{gid}/personas", headers=AH).get_json()["personas"][0]["id"]
# 1. start chat
r1 = C.post(f"/api/chat/{gid}/personas/{pid}/chat/start", headers=AH, json={"scenario":"social","locale":"th"})
assert r1.status_code == 200, r1.get_json()
sid1 = r1.get_json()["session"]["id"]
# 2. call start AGAIN (as if re-entering) -> should RESUME the same session, not create new
r2 = C.post(f"/api/chat/{gid}/personas/{pid}/chat/start", headers=AH, json={"scenario":"f2f_call","locale":"th"})
assert r2.status_code == 200, r2.get_json()
sid2 = r2.get_json()["session"]["id"]
assert sid1 == sid2, f"resume must return same session (got {sid2}, want {sid1})"
assert r2.get_json()["scenario"] == "social", "resume keeps ORIGINAL scenario (not re-pick)"
print("[ok] resume returns same active session + keeps original scenario")
# 3. resume endpoint also returns the active session
rr = C.get(f"/api/chat/{gid}/personas/{pid}/chat/resume", headers=AH)
assert rr.status_code == 200 and rr.get_json()["session"]["id"] == sid1
print("[ok] /chat/resume returns active session")
# 4. decision detection: unit-test the text detector
from app.api.chat_routes import _detect_customer_decision
assert _detect_customer_decision("ผมซื้อไม่ไหวแล้วครับ ขอตัวก่อน") == "walk"
assert _detect_customer_decision("สวัสดีครับ ผมสนใจสินค้าครับ") is None
assert _detect_customer_decision("ok ผมเอาครับ รับเลย") == "buy"
print("[ok] _detect_customer_decision: walk/buy/None cases correct")
print("ALL RESUME+DECISION TESTS PASSED")