merge: integrate ZSY customer service and profile capabilities
This commit is contained in:
@@ -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)
|
||||
Reference in New Issue
Block a user