merge: integrate ZSY customer service and profile capabilities

This commit is contained in:
张胜宇
2026-09-11 22:31:51 +08:00
94 changed files with 7933 additions and 77 deletions
@@ -0,0 +1,95 @@
"""验证迁移前状态证据采集器只读取 Git 元数据。"""
# 导入测试所需的标准库 JSON、路径和子进程结果类型。
import json
from pathlib import Path
from subprocess import CompletedProcess
# 导入 pytest 补丁类型以及待验证的预检工具接口。
from pytest import MonkeyPatch
from tools.foundation_migration_preflight import (
collect_workspace_state,
run_git,
write_preflight_report,
)
# 验证未跟踪客服文件会被记录,且采集结果不含环境变量名称或值。
def test_collect_workspace_state_records_untracked_paths_without_environment_values(
tmp_path: Path,
) -> None:
# 构造确定性的 Git 命令替身,不调用真实 Git 或环境变量。
def fake_git_runner(_worktree: Path, *args: str) -> str:
# 为分支查询返回客服功能分支名称。
if args == ("branch", "--show-current"):
return "feature/customer-service-rag\n"
# 为提交查询返回固定的非敏感提交标识。
if args == ("rev-parse", "HEAD"):
return "abc123\n"
# 为状态查询返回一个未跟踪客服文件。
if args == ("status", "--short"):
return "?? app/service/agent/customer_service_agent.py\n"
# 防止测试静默接受未定义的 Git 查询。
raise AssertionError(f"unexpected git arguments: {args}")
# 使用替身采集临时工作区的只读状态。
state = collect_workspace_state(tmp_path, runner=fake_git_runner)
# 断言采集到预期的分支名称。
assert state["branch"] == "feature/customer-service-rag"
# 断言未跟踪客服文件完整保留在状态清单中。
assert state["status"] == ["?? app/service/agent/customer_service_agent.py"]
# 断言序列化结果不包含任何环境变量敏感字段。
assert "MYSQL_PASSWORD" not in json.dumps(state)
# 验证报告写入器只输出传入的非敏感 Git 状态结构。
def test_write_preflight_report_persists_utf8_json(tmp_path: Path) -> None:
# 指定临时报告文件,避免写入任何真实工作目录。
report_path = tmp_path / "preflight.json"
# 构造只含安全 Git 元数据的状态条目。
states = [{"path": "D:/workspace", "branch": "develop", "head": "abc123", "status": []}]
# 写入迁移前报告。
write_preflight_report(report_path, states)
# 以 UTF-8 读取并解析报告正文。
report = json.loads(report_path.read_text(encoding="utf-8"))
# 断言报告保留原始状态条目。
assert report == states
# 验证每次 Git 查询只信任当前显式工作区,而不写入全局 Git 配置。
def test_run_git_scopes_safe_directory_to_the_requested_worktree(
tmp_path: Path,
monkeypatch: MonkeyPatch,
) -> None:
# 保存被测函数交给子进程层的命令参数。
captured_commands: list[list[str]] = []
# 构造返回固定分支名的无副作用子进程替身。
def fake_run(command: list[str], **_kwargs: object) -> CompletedProcess[str]:
# 记录命令以便后续断言安全目录范围。
captured_commands.append(command)
# 返回模拟的 Git 成功结果。
return CompletedProcess(command, 0, "develop\n", "")
# 让测试不执行真实 Git。
monkeypatch.setattr("tools.foundation_migration_preflight.subprocess.run", fake_run)
# 执行一个受限 Git 分支查询。
output = run_git(tmp_path, "branch", "--show-current")
# 断言调用仍返回 Git 输出。
assert output == "develop\n"
# 断言命令仅为该临时工作区附加安全目录。
assert captured_commands == [[
"git",
"-c",
f"safe.directory={tmp_path.resolve().as_posix()}",
"-C",
str(tmp_path.resolve()),
"branch",
"--show-current",
]]
@@ -0,0 +1,75 @@
import json
import pytest
from tools.knowledge_import_preflight import build_import_manifest
def vector_candidate() -> dict[str, object]:
return {
"qa_id": "RAG-PUB-001",
"title": "基金交易确认时间说明",
"question": "基金什么时候确认?",
"paraphrases": ["申购何时确认", "赎回多久确认"],
"answer": "交易确认时间以产品规则和实际交易日为准。",
"scope": "public",
"intent": "policy_explain",
"collection": "fin_policy_collection",
"execution_mode": "vector_search",
"retrieval_status": "approved_candidate",
"audience": ["visitor", "authenticated_user"],
"agent_data_access": "none",
"tags": ["交易规则", "确认"],
"phase": "phase_1",
"source_type": "qa_pair",
"source_file": "qa-v5.8.txt",
"source_url": None,
"source_version": "v5.8",
"review_status": "approved_candidate",
"status": "active",
"effective_date": None,
"expire_date": None,
}
def rule_only_record() -> dict[str, object]:
return {
"qa_id": "RAG-SEC-001",
"scope": "security_notice",
"collection": None,
"execution_mode": "fixed_route",
"retrieval_status": "rule_only",
}
def test_manifest_contains_only_eligible_public_records_pending_admin_review() -> None:
manifest = build_import_manifest(
[vector_candidate(), rule_only_record()], source_name="qa-v5.8.jsonl"
)
assert manifest["summary"] == {
"total_records": 2,
"eligible_records": 1,
"excluded_rule_records": 1,
"publication_state": "pending_review",
}
entry = manifest["records"][0]
assert entry["qa_id"] == "RAG-PUB-001"
assert entry["knowledge_type"] == "policy_explain"
assert entry["milvus_collection"] == "fin_policy_collection"
assert entry["review_status"] == "pending_review"
assert entry["status"] == "active"
assert entry["retrieval_text"] == (
"标准问题:基金什么时候确认?\n"
"相似问法:申购何时确认;赎回多久确认\n"
"标签:交易规则、确认"
)
assert json.loads(entry["content_text"])["answer"] == "交易确认时间以产品规则和实际交易日为准。"
def test_invalid_public_record_is_rejected_instead_of_silently_entering_manifest() -> None:
invalid = vector_candidate()
invalid["agent_data_access"] = "account"
with pytest.raises(ValueError, match="RAG-PUB-001"):
build_import_manifest([invalid], source_name="qa-v5.8.jsonl")
@@ -0,0 +1,40 @@
import pytest
from tools.publish_customer_service_knowledge import load_pending_manifest
def manifest() -> dict[str, object]:
return {
"summary": {"eligible_records": 1, "publication_state": "pending_review"},
"records": [{
"qa_id": "FAQ-001",
"milvus_collection": "fin_faq_collection",
"retrieval_text": "标准问题:基金开户",
"title": "基金开户",
"snippet": "基金开户",
"tags": ["开户"],
"version": "v5.8",
"content_text": "{\"answer\":\"请在官方页面开户。\"}",
"source_file": "qa-v5.8.txt",
"effective_date": None,
"expire_date": None,
"review_status": "pending_review",
"status": "active",
}],
}
def test_pending_manifest_is_converted_to_a_publishable_administrator_payload() -> None:
records = load_pending_manifest(manifest())
assert len(records) == 1
assert records[0].qa_id == "FAQ-001"
assert records[0].metadata["content_text"] == "{\"answer\":\"请在官方页面开户。\"}"
def test_manifest_that_claims_to_be_published_is_rejected() -> None:
invalid = manifest()
invalid["summary"] = {"eligible_records": 1, "publication_state": "published"}
with pytest.raises(ValueError, match="pending_review"):
load_pending_manifest(invalid)