52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
# requires: langgraph, pydantic, pytest
|
|
from langgraph.checkpoint.memory import InMemorySaver
|
|
from langgraph.types import Command
|
|
|
|
from ticket_flow import FakeLLM, Mailer, build
|
|
|
|
|
|
def run(text, llm=None, mailer=None, tid="t"):
|
|
llm, mailer = llm or FakeLLM(), mailer or Mailer()
|
|
app = build(llm, mailer, InMemorySaver())
|
|
cfg = {"configurable": {"thread_id": tid}}
|
|
out = app.invoke({"ticket_id": tid, "text": text, "status": "new", "events": []}, cfg)
|
|
return app, cfg, out, llm, mailer
|
|
|
|
|
|
def test_small_refund_sent_without_approval():
|
|
_, _, out, _, mailer = run("申请退款 30 元")
|
|
assert out["status"] == "sent" and "t" in mailer.sent
|
|
|
|
|
|
def test_large_refund_waits_then_rejected():
|
|
app, cfg, _, _, mailer = run("申请退款 900 元")
|
|
assert app.get_state(cfg).next == ("approve",)
|
|
out = app.invoke(Command(resume={"ok": False, "note": "超过权限"}), cfg)
|
|
assert out["status"] == "rejected" and not mailer.sent
|
|
|
|
|
|
def test_transient_error_retried():
|
|
_, _, out, llm, _ = run("系统报错 500", llm=FakeLLM(fail_times=2))
|
|
assert out["status"] == "sent" and llm.calls == 3
|
|
|
|
|
|
def test_retry_exhausted_raises_and_can_resume():
|
|
llm = FakeLLM(fail_times=3)
|
|
app = build(llm, Mailer(), InMemorySaver())
|
|
cfg = {"configurable": {"thread_id": "x"}}
|
|
try:
|
|
app.invoke({"ticket_id": "x", "text": "怎么开发票", "status": "new", "events": []}, cfg)
|
|
raise AssertionError("应该抛出异常")
|
|
except Exception as e:
|
|
assert "超时" in str(e)
|
|
assert app.get_state(cfg).next == ("draft",) # 停在失败的节点,分类结果保留
|
|
out = app.invoke(None, cfg) # 故障恢复后续跑
|
|
assert out["status"] == "sent" and out["events"].count("classified:question") == 1
|
|
|
|
|
|
def test_send_is_idempotent():
|
|
mailer = Mailer()
|
|
mailer.send("a", "x")
|
|
mailer.send("a", "y")
|
|
assert mailer.sent == {"a": "x"}
|