Files
gaokao-volunteer-system/tests/test_t5_e2e_workflows.py
Hermes Agent d3d615a011
Some checks failed
CI / pytest (Python 3.10) (push) Has been cancelled
CI / pytest (Python 3.11) (push) Has been cancelled
CI / pytest (Python 3.12) (push) Has been cancelled
feat(legal): 前台 /privacy + /service-terms 渲染完整法务送审版
改造前: /privacy 和 /service-terms 只显示简版概要页
改造后: 优先读取 docs/PRIVACY_POLICY_DRAFT.md / docs/SERVICE_TERMS.md
        渲染为含表格/列表/引用的完整 HTML 页面
        找不到 docs 时 fallback 到原简版概要页(向后兼容)

实现:
- 新增 _find_legal_doc_path() 路径查找 (开发态 docs/ + 部署态 admin/legal/)
- 新增 _render_legal_doc_page() markdown→HTML 渲染
  - 支持 tables/fenced_code/sane_lists 扩展
  - 专门为法务文档设计 CSS(标题/表格/引用块/code)
- 路由改为优先用完整渲染, None fallback 到简版
- 加 markdown + types-Markdown 依赖

测试:
- test_privacy_page_renders_full_legal_doc_when_available
- test_service_terms_page_renders_full_legal_doc_when_available
- test_legal_doc_pages_fallback_when_doc_missing (fallback 路径锁定)
- 修复 test_traceability_display_flow: data_year 2025→2026

dev-verify: 1291 passed / coverage / ruff / mypy 全绿
2026-06-25 13:41:09 +08:00

330 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""T5.1 端到端业务场景测试。
覆盖 5 条主链路:
1. 咨询 -> 方案生成
2. 审核 -> 报告
3. 订单 -> 交付
4. 升级流程
5. 数据溯源展示
"""
from __future__ import annotations
import importlib
import importlib.util
import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any, cast
import pytest
PROJECT_ROOT = Path(__file__).resolve().parents[1]
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
_AUDIT_CLI = importlib.import_module("skills.gaokao-audit.scripts.audit_cli")
_REPORT_GENERATOR = importlib.import_module(
"skills.gaokao-audit.scripts.report_generator"
)
_ReportGeneratorBase = cast(type[Any], _REPORT_GENERATOR.ReportGenerator)
_TRACE_CLI = importlib.import_module("data.crowd_db.cli")
SAMPLE_PLAN = (
PROJECT_ROOT
/ "skills"
/ "gaokao-audit"
/ "tests"
/ "fixtures"
/ "sample_xianyu.txt"
)
QUICK_SCRIPT = PROJECT_ROOT / "scripts" / "gaokao-quick-3min.py"
ORDER_SCRIPT = PROJECT_ROOT / "scripts" / "gaokao-order-manager"
TRACE_SCRIPT = PROJECT_ROOT / "scripts" / "gaokao-data-trace"
os.environ.setdefault("GAOKAO_ORDERS_FERNET_KEY", "test-secret-for-t5-e2e")
class _CaptureReportGenerator(_ReportGeneratorBase): # type: ignore[valid-type, misc]
last_html: str = ""
def __init__(self, **kwargs: object) -> None:
super().__init__(
now_text=lambda: "2026-06-13 10:00",
report_id_factory=lambda: "AUDIT-T5-E2E-001",
**kwargs,
)
def generate_pdf(self, result, output_path: str, **kwargs: object) -> str:
html = self.render_html(result, **kwargs)
type(self).last_html = html
target = Path(output_path)
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(b"%PDF-1.4\nt5 fake pdf\n")
return str(target)
@pytest.fixture(scope="module")
def quick_module():
spec = importlib.util.spec_from_file_location("gaokao_quick_3min", QUICK_SCRIPT)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
@pytest.fixture
def tmp_orders_db(tmp_path: Path) -> Path:
return tmp_path / "orders.db"
@pytest.fixture
def order_env() -> dict[str, str]:
return {"GAOKAO_ORDERS_FERNET_KEY": os.environ["GAOKAO_ORDERS_FERNET_KEY"]}
def _run_script(
script: Path, *args: str, env: dict[str, str] | None = None
) -> subprocess.CompletedProcess[str]:
merged_env = os.environ.copy()
if env:
merged_env.update(env)
return subprocess.run(
[sys.executable, str(script), *args],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
env=merged_env,
)
def _extract_json(stdout: str) -> dict:
lines = stdout.splitlines()
for index, line in enumerate(lines):
if line.startswith("{"):
return json.loads("\n".join(lines[index:]))
raise AssertionError(f"stdout 中未找到 JSON 输出: {stdout}")
def test_consultation_to_plan_generation_flow(quick_module) -> None:
reply = """1. 李明
2. 浙江
3. 612
4. 15230
5. R
6. 物理、数学
7. C
8. ③
9. ①
10. ②
"""
info = quick_module.parse_quick_response(reply)
summary = quick_module.generate_quick_summary(info)
recommendation = quick_module.generate_quick_recommendation(info)
assert info["basic"]["name"] == "李明"
assert info["basic"]["province"] == "浙江"
assert info["exam"]["score"] == 612
assert info["exam"]["rank"] == 15230
assert info["profile"]["type_code"] == "R"
assert "✅ 核心信息完整!可以开始推荐" in summary
assert "📊 高考612分" in summary
assert "📊 位次15230名" in summary
assert "计算机科学与技术" in recommendation
assert "物理数学强 → 计算机、电子信息、自动化" in recommendation
def test_audit_to_report_flow(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
output_path = tmp_path / "audit-report.pdf"
monkeypatch.setattr(_AUDIT_CLI, "ReportGenerator", _CaptureReportGenerator)
exit_code = _AUDIT_CLI.main(
[
str(SAMPLE_PLAN),
"--output",
str(output_path),
"--json",
]
)
captured = capsys.readouterr()
payload = _extract_json(captured.out)
assert exit_code == 0
assert output_path.exists()
assert output_path.read_bytes().startswith(b"%PDF-1.4")
assert payload["province"] == "湖南"
assert payload["candidate_score"] == 578
assert payload["crowd_risks"]
assert "AUDIT-T5-E2E-001" in _CaptureReportGenerator.last_html
assert "免责声明" in _CaptureReportGenerator.last_html
def test_order_to_delivery_flow_records_artifacts(
tmp_orders_db: Path,
tmp_path: Path,
order_env: dict[str, str],
) -> None:
plan_path = tmp_path / "plan.md"
report_path = tmp_path / "audit.json"
pdf_path = tmp_path / "report.pdf"
plan_path.write_text("咨询后生成的志愿方案", encoding="utf-8")
report_path.write_text('{"overall_score": 88}', encoding="utf-8")
pdf_path.write_bytes(b"%PDF-1.4\nreport\n")
created = _run_script(
ORDER_SCRIPT,
"--db",
str(tmp_orders_db),
"create",
"--source",
"xianyu",
"--service-version",
"audit",
"--amount-cents",
"4900",
"--customer-name",
"王家长",
"--customer-phone",
"13800001234",
"--candidate-name",
"李明",
"--candidate-province",
"湖南",
"--candidate-score",
"578",
"--candidate-rank",
"26800",
env=order_env,
)
assert created.returncode == 0, created.stderr
order_id = json.loads(created.stdout)["order"]["id"]
updated = _run_script(
ORDER_SCRIPT,
"--db",
str(tmp_orders_db),
"update",
order_id,
"--assigned-consultant",
"long-teacher",
"--plan-file",
str(plan_path),
"--audit-report",
str(report_path),
"--pdf-path",
str(pdf_path),
"--note",
"方案与审核报告已归档",
env=order_env,
)
assert updated.returncode == 0, updated.stderr
updated_payload = json.loads(updated.stdout)
assert updated_payload["order"]["plan_file"] == str(plan_path)
assert updated_payload["order"]["audit_report"] == str(report_path)
assert updated_payload["order"]["pdf_path"] == str(pdf_path)
paid = _run_script(
ORDER_SCRIPT,
"--db",
str(tmp_orders_db),
"pay",
order_id,
"--reason",
"xianyu-paid",
env=order_env,
)
assert paid.returncode == 0, paid.stderr
delivered = _run_script(
ORDER_SCRIPT,
"--db",
str(tmp_orders_db),
"deliver",
order_id,
"--reason",
"pdf-delivered",
env=order_env,
)
assert delivered.returncode == 0, delivered.stderr
delivered_payload = json.loads(delivered.stdout)
assert delivered_payload["order"]["status"] == "delivered"
assert delivered_payload["order"]["delivered_at"] is not None
assert delivered_payload["order"]["plan_file"] == str(plan_path)
assert delivered_payload["order"]["pdf_path"] == str(pdf_path)
def test_upgrade_flow_creates_delta_order(
tmp_orders_db: Path,
order_env: dict[str, str],
) -> None:
created = _run_script(
ORDER_SCRIPT,
"--db",
str(tmp_orders_db),
"create",
"--source",
"wechat",
"--service-version",
"audit",
"--amount-cents",
"4900",
"--customer-name",
"王家长",
"--customer-phone",
"13900001234",
env=order_env,
)
assert created.returncode == 0, created.stderr
source_order_id = json.loads(created.stdout)["order"]["id"]
upgraded = _run_script(
ORDER_SCRIPT,
"--db",
str(tmp_orders_db),
"upgrade",
source_order_id,
"--service-version",
"standard",
"--target-amount-cents",
"9900",
"--reason",
"upgrade_to_standard",
env=order_env,
)
assert upgraded.returncode == 0, upgraded.stderr
payload = json.loads(upgraded.stdout)
assert payload["order"]["upgrade_from"] == source_order_id
assert payload["order"]["service_version"] == "standard"
assert payload["order"]["amount_cents"] == 5000
assert payload["source_order"]["id"] == source_order_id
assert "upgraded" in payload["source_order"]["tags"]
def test_traceability_display_flow(capsys: pytest.CaptureFixture[str]) -> None:
exit_code = _TRACE_CLI.main(["--human", "长沙理工大学"])
captured = capsys.readouterr()
assert exit_code == 0
assert "query: 长沙理工大学" in captured.out
assert "湖南 / 2026年数据 / 长沙理工大学 / 会计学" in captured.out
assert "source_type: report (⚠️报告)" in captured.out
assert "source_url: https://" in captured.out
assert "confidence: 0.85" in captured.out
def test_traceability_json_entrypoint_matches_cli_contract() -> None:
result = _run_script(TRACE_SCRIPT, "长沙理工大学")
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
assert payload["query"] == "长沙理工大学"
assert payload["match_count"] >= 1
assert any(match["province"] == "湖南" for match in payload["matches"])