- fields_registry: 24 business keys in acd/human1/human2 groups with
value_type, writable and SensitiveLevel metadata; strict bool/int
parsing (no bool('0') trap); INTERNAL_FIELD_KEYS protection list
- repository: SessionRepository protocol + InMemorySessionRepository
(idempotent delete; PostgreSQL swap-in is assignment 7)
- AccidentAppService: chat/set_info/get_info/delete_session share one
business state - chat injects latest repository fields into the graph
and writes back validated field diffs; set/get/delete make zero LLM
calls and zero helper conversations (includeInputInfo accepted but
unused); unknown get keys keep the '' JSON-empty-string contract,
unknown set keys are rejected by whitelist
- ChatInput.injected_fields neutral contract extension; LangGraphBackend
merges injected fields into graph input (set_info visible next chat)
- endpoints: thin layer - langgraph mode routes business state through
the app service (fastgpt mode keeps the legacy path); delete cleans
repository + graph checkpoint, idempotent
- tests: registry completeness (sfzmwh/sjwh/csbw included), app service
combos with LLM-call spy, HTTP acceptance flow (set -> get -> chat ->
groups -> delete -> no leak, idempotent re-delete)
- docs/assignment6-compat.md: behavior diff list vs FastGPT status quo
and the audit backlog (arbitrary set_info key usage, stream formUpdate
write-back, sensitive-field display boundary)
Verified: 200 unit tests (172 existing + 28 new)
107 lines
3.5 KiB
Python
107 lines
3.5 KiB
Python
"""作业 6 验收演示的 HTTP 层组合测试。
|
||
|
||
链路(对应计划作业 6):创建会话 -> /set_info 写车牌 -> /get_info 读回
|
||
-> /chat 继续核实 -> 分组读值 -> /delete_session -> 再读与再聊不泄漏。
|
||
|
||
fake LLM(LLM_PROVIDER=fake)保证本测试离线可跑、零真实模型调用。
|
||
"""
|
||
|
||
import json
|
||
|
||
import pytest
|
||
from fastapi.testclient import TestClient
|
||
|
||
from src.agent.llm import workflow_fake_llm
|
||
from src.core.config import Config
|
||
from src.main import app
|
||
|
||
|
||
@pytest.fixture()
|
||
def client(monkeypatch):
|
||
"""langgraph 后端 + fake LLM 的测试客户端(lifespan 完整初始化)。"""
|
||
monkeypatch.setattr(Config, "AGENT_BACKEND", "langgraph")
|
||
monkeypatch.setattr(Config, "LLM_PROVIDER", "fake")
|
||
|
||
import src.agent.llm as llm_module
|
||
|
||
monkeypatch.setattr(
|
||
llm_module, "build_llm_service", lambda *a, **k: workflow_fake_llm()
|
||
)
|
||
with TestClient(app) as test_client:
|
||
yield test_client
|
||
|
||
|
||
TS = "20261022120000"
|
||
|
||
|
||
def test_assignment6_acceptance_flow(client):
|
||
session = "a6-acceptance-001"
|
||
|
||
# set_info 写入车牌(零 LLM:fake 未注入任何 responder,调用即报错)
|
||
response = client.post("/set_info", json={
|
||
"sessionId": session, "timeStamp": TS,
|
||
"key": "hphm1", "value": "苏B12345",
|
||
})
|
||
assert response.status_code == 200
|
||
assert response.json()["code"] == "200"
|
||
|
||
# get_info 单键读回
|
||
response = client.post("/get_info", json={
|
||
"sessionId": session, "timeStamp": TS, "key": "hphm1",
|
||
})
|
||
assert response.json()["value"] == '"苏B12345"'
|
||
|
||
# chat 使用业务状态继续(非流式,协议层解析 state 前缀)
|
||
response = client.post("/chat", json={
|
||
"sessionId": session, "timeStamp": TS,
|
||
"text": "我是刚才报警的驾驶员", "clientMode": "direct",
|
||
})
|
||
body = response.json()
|
||
assert body["code"] == "200"
|
||
assert body["nextStageCode"] in ("1001", "1002")
|
||
|
||
# 分组读值:三个分组齐全,未填字段为空串
|
||
response = client.post("/get_info", json={
|
||
"sessionId": session, "timeStamp": TS, "key": "all",
|
||
})
|
||
groups = json.loads(response.json()["value"])
|
||
assert set(groups) == {"acdinfo", "acdhuman1", "acdhuman2"}
|
||
assert groups["acdhuman1"]["hphm1"] == "苏B12345"
|
||
assert groups["acdhuman1"]["xm1"] == ""
|
||
|
||
# 未知 key 兼容:JSON 空串
|
||
response = client.post("/get_info", json={
|
||
"sessionId": session, "timeStamp": TS, "key": "no_such_key",
|
||
})
|
||
assert response.json()["value"] == '""'
|
||
|
||
# set_info 未知 key:白名单拒绝
|
||
response = client.post("/set_info", json={
|
||
"sessionId": session, "timeStamp": TS,
|
||
"key": "no_such_key", "value": "x",
|
||
})
|
||
assert response.json()["code"] == "500"
|
||
|
||
# 删除 -> 再读为空
|
||
response = client.request("DELETE", "/delete_session", json={
|
||
"sessionId": session, "timeStamp": TS,
|
||
})
|
||
assert response.json()["code"] == "200"
|
||
response = client.post("/get_info", json={
|
||
"sessionId": session, "timeStamp": TS, "key": "hphm1",
|
||
})
|
||
assert response.json()["value"] == '""'
|
||
|
||
# 重复删除幂等成功
|
||
response = client.request("DELETE", "/delete_session", json={
|
||
"sessionId": session, "timeStamp": TS,
|
||
})
|
||
assert response.json()["code"] == "200"
|
||
|
||
# 删除后再聊天:不注入旧业务数据,服务正常应答
|
||
response = client.post("/chat", json={
|
||
"sessionId": session, "timeStamp": TS,
|
||
"text": "新对话", "clientMode": "direct",
|
||
})
|
||
assert response.json()["code"] == "200"
|