Files
gaokao-volunteer-system/tests/test_audit_cli_major_validation_phase2.py
Hermes Agent 246f21c7f2
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(review): land unified remediation and rules evidence closure
2026-06-18 10:21:38 +08:00

230 lines
6.9 KiB
Python

from __future__ import annotations
import json
import subprocess
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[1]
SCRIPT_PATH = PROJECT_ROOT / "scripts" / "gaokao-cli"
def _write_truth_and_catalog(
root: Path,
*,
max_volunteers_status: str = "active",
max_volunteers_severity: str = "fatal",
max_volunteers_title: str = "志愿上限",
) -> tuple[Path, Path, Path]:
truth_root = root / "truth"
province_dir = truth_root / "province"
province_dir.mkdir(parents=True, exist_ok=True)
(truth_root / "national.yaml").write_text(
"scope: national\nyear: 2026\nversion: '2026.1'\nrules: {}\n",
encoding="utf-8",
)
(province_dir / "hunan.yaml").write_text(
(
"scope: province\nprovince: 湖南\nyear: 2026\nversion: '2026.1'\n"
"status: active\nrules:\n max_volunteers:\n"
f" title: {max_volunteers_title}\n"
f" severity: {max_volunteers_severity}\n"
" value:\n max_volunteers: 45\n"
" source_evidence_id: hunan-2026-max-volunteers\n"
" effective_date: '2026-01-01'\n"
f" status: {max_volunteers_status}\n"
),
encoding="utf-8",
)
catalog_root = root / "catalog"
national = catalog_root / "national"
national.mkdir(parents=True, exist_ok=True)
payload = {
"year": 2024,
"version": "2024.1",
"coverage_mode": "mvp_subset",
"source": "教育部普通高等学校本科专业目录(人工摘录校对)",
"source_url": "https://www.moe.gov.cn/",
"last_verified_at": "2026-06-17",
"majors": [
{
"code": "080901",
"name": "计算机科学与技术",
"discipline": "工学",
"category": "计算机类",
"degree": "工学学士",
"is_directional": False,
"status": "active",
"year_added": 1998,
"year_removed": None,
"notes": None,
},
{
"code": "120201K",
"name": "工商管理",
"discipline": "管理学",
"category": "工商管理类",
"degree": "管理学学士",
"is_directional": True,
"status": "deprecated",
"year_added": 1998,
"year_removed": 2024,
"notes": "示例停用",
},
],
}
for name in ("2024.json", "latest.json"):
(national / name).write_text(
json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
)
plan_path = root / "plan.json"
plan_path.write_text(
json.dumps(
{
"province": "湖南",
"items": [
{
"school_name": "北京大学",
"major_names": ["计算机科学与技术", "不存在专业", "工商管理"],
}
],
},
ensure_ascii=False,
indent=2,
),
encoding="utf-8",
)
return truth_root, catalog_root, plan_path
def test_audit_run_cli_reports_major_validation_issues(tmp_path: Path) -> None:
truth_root, catalog_root, plan_path = _write_truth_and_catalog(tmp_path)
result = subprocess.run(
[
sys.executable,
str(SCRIPT_PATH),
"audit",
"run",
"--province",
"湖南",
"--plan",
str(plan_path),
"--truth-root",
str(truth_root),
"--catalog-root",
str(catalog_root),
"--json",
],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 1, result.stderr
payload = json.loads(result.stdout)
rule_ids = {issue["rule_id"] for issue in payload["issues"]}
assert payload["overall_pass"] is False
assert "MAJORS.not_found" in rule_ids
assert "MAJORS.non_active" in rule_ids
def test_audit_run_cli_returns_zero_when_plan_majors_are_all_active(
tmp_path: Path,
) -> None:
truth_root, catalog_root, _ = _write_truth_and_catalog(tmp_path)
plan_path = tmp_path / "ok-plan.json"
plan_path.write_text(
json.dumps(
{
"province": "湖南",
"items": [
{"school_name": "北京大学", "major_names": ["计算机科学与技术"]}
],
},
ensure_ascii=False,
indent=2,
),
encoding="utf-8",
)
result = subprocess.run(
[
sys.executable,
str(SCRIPT_PATH),
"audit",
"run",
"--province",
"湖南",
"--plan",
str(plan_path),
"--truth-root",
str(truth_root),
"--catalog-root",
str(catalog_root),
"--json",
],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 0, result.stderr
payload = json.loads(result.stdout)
assert payload["overall_pass"] is True
assert payload["issues"] == []
def test_audit_run_cli_fails_when_volunteer_count_exceeds_province_limit(
tmp_path: Path,
) -> None:
truth_root, catalog_root, _ = _write_truth_and_catalog(tmp_path)
plan_path = tmp_path / "too-many-volunteers.json"
plan_path.write_text(
json.dumps(
{
"province": "湖南",
"items": [
{"school_name": f"学校{i}", "major_names": []}
for i in range(46)
],
},
ensure_ascii=False,
indent=2,
),
encoding="utf-8",
)
result = subprocess.run(
[
sys.executable,
str(SCRIPT_PATH),
"audit",
"run",
"--province",
"湖南",
"--plan",
str(plan_path),
"--truth-root",
str(truth_root),
"--catalog-root",
str(catalog_root),
"--json",
],
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
check=False,
)
assert result.returncode == 1, result.stderr
payload = json.loads(result.stdout)
assert payload["overall_pass"] is False
issue = next(issue for issue in payload["issues"] if issue["rule_id"] == "RULES.max_volunteers")
assert issue["severity"] == "fatal"
assert issue["title"] == "志愿上限"
assert issue["message"] == "当前方案包含 46 个志愿单位,已超过湖南规则上限 45"
assert issue["suggestion"] == "请减少到不超过 45 个志愿单位后重新审计"