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) -> 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'\nstatus: active\nrules:\n max_volunteers:\n title: 志愿上限\n severity: fatal\n value:\n max_volunteers: 45\n source_evidence_id: hunan-2026-max-volunteers\n effective_date: '2026-01-01'\n status: active\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"] == []