Files
ZQYY.FQ-ETL/tests/unit/test_audit_run.py

178 lines
6.0 KiB
Python
Raw Permalink 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.
# -*- coding: utf-8 -*-
"""
run_audit 主入口的单元测试。
验证:
- docs/audit/ 目录自动创建
- 三份报告文件正确生成
- 报告头部包含时间戳和仓库路径
- 目录创建失败时抛出 RuntimeError
"""
from __future__ import annotations
import os
import re
from pathlib import Path
import pytest
class TestEnsureReportDir:
"""测试 _ensure_report_dir 目录创建逻辑。"""
def test_creates_dir_when_missing(self, tmp_path: Path):
from scripts.audit.run_audit import _ensure_report_dir
result = _ensure_report_dir(tmp_path)
expected = tmp_path / "docs" / "audit"
assert result == expected
assert expected.is_dir()
def test_returns_existing_dir(self, tmp_path: Path):
from scripts.audit.run_audit import _ensure_report_dir
audit_dir = tmp_path / "docs" / "audit"
audit_dir.mkdir(parents=True)
result = _ensure_report_dir(tmp_path)
assert result == audit_dir
def test_raises_on_creation_failure(self, tmp_path: Path):
from scripts.audit.run_audit import _ensure_report_dir
# 在 docs/audit 位置放一个文件,使 mkdir 失败
docs = tmp_path / "docs"
docs.mkdir()
(docs / "audit").write_text("block", encoding="utf-8")
with pytest.raises(RuntimeError, match="无法创建报告输出目录"):
_ensure_report_dir(tmp_path)
class TestInjectHeader:
"""测试 _inject_header 兜底注入逻辑。"""
def test_skips_when_header_present(self):
from scripts.audit.run_audit import _inject_header
report = "# 标题\n\n- 生成时间: 2025-01-01T00:00:00Z\n- 仓库路径: `/repo`\n"
result = _inject_header(report, "2025-06-01T00:00:00Z", "/other")
# 不应修改已有头部
assert result == report
def test_injects_when_header_missing(self):
from scripts.audit.run_audit import _inject_header
report = "# 无头部报告\n\n内容..."
result = _inject_header(report, "2025-06-01T00:00:00Z", "/repo")
assert "生成时间: 2025-06-01T00:00:00Z" in result
assert "仓库路径: `/repo`" in result
class TestRunAudit:
"""测试 run_audit 完整流程(使用最小仓库结构)。"""
def _make_minimal_repo(self, tmp_path: Path) -> Path:
"""构造一个最小仓库结构,足以让 run_audit 跑通。"""
repo = tmp_path / "repo"
repo.mkdir()
# 核心代码目录
cli_dir = repo / "cli"
cli_dir.mkdir()
(cli_dir / "__init__.py").write_text("", encoding="utf-8")
(cli_dir / "main.py").write_text(
"# -*- coding: utf-8 -*-\ndef main(): pass\n",
encoding="utf-8",
)
# config 目录
config_dir = repo / "config"
config_dir.mkdir()
(config_dir / "__init__.py").write_text("", encoding="utf-8")
(config_dir / "defaults.py").write_text("DEFAULTS = {}\n", encoding="utf-8")
# docs 目录
docs_dir = repo / "docs"
docs_dir.mkdir()
(docs_dir / "README.md").write_text("# 文档\n", encoding="utf-8")
# 根目录文件
(repo / "README.md").write_text("# 项目\n", encoding="utf-8")
return repo
def test_creates_three_reports(self, tmp_path: Path):
from scripts.audit.run_audit import run_audit
repo = self._make_minimal_repo(tmp_path)
run_audit(repo)
audit_dir = repo / "docs" / "audit"
assert (audit_dir / "file_inventory.md").is_file()
assert (audit_dir / "flow_tree.md").is_file()
assert (audit_dir / "doc_alignment.md").is_file()
def test_reports_contain_timestamp(self, tmp_path: Path):
from scripts.audit.run_audit import run_audit
repo = self._make_minimal_repo(tmp_path)
run_audit(repo)
audit_dir = repo / "docs" / "audit"
# ISO 时间戳格式
ts_pattern = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z")
for name in ("file_inventory.md", "flow_tree.md", "doc_alignment.md"):
content = (audit_dir / name).read_text(encoding="utf-8")
assert ts_pattern.search(content), f"{name} 缺少时间戳"
def test_reports_contain_repo_path(self, tmp_path: Path):
from scripts.audit.run_audit import run_audit
repo = self._make_minimal_repo(tmp_path)
run_audit(repo)
audit_dir = repo / "docs" / "audit"
repo_str = str(repo.resolve())
for name in ("file_inventory.md", "flow_tree.md", "doc_alignment.md"):
content = (audit_dir / name).read_text(encoding="utf-8")
assert repo_str in content, f"{name} 缺少仓库路径"
def test_writes_only_to_docs_audit(self, tmp_path: Path):
"""验证所有写操作仅限 docs/audit/ 目录Property 15"""
from scripts.audit.run_audit import run_audit
repo = self._make_minimal_repo(tmp_path)
# 记录运行前的文件快照(排除 docs/audit/
before = set()
for p in repo.rglob("*"):
rel = p.relative_to(repo).as_posix()
if not rel.startswith("docs/audit"):
before.add((rel, p.stat().st_mtime if p.is_file() else None))
run_audit(repo)
# 运行后检查docs/audit/ 外的文件不应被修改
for p in repo.rglob("*"):
rel = p.relative_to(repo).as_posix()
if rel.startswith("docs/audit"):
continue
if p.is_file():
# 文件应在之前的快照中
found = any(r == rel for r, _ in before)
assert found, f"意外创建了 docs/audit/ 外的文件: {rel}"
def test_auto_creates_docs_audit_dir(self, tmp_path: Path):
from scripts.audit.run_audit import run_audit
repo = self._make_minimal_repo(tmp_path)
# 确保 docs/audit/ 不存在
audit_dir = repo / "docs" / "audit"
assert not audit_dir.exists()
run_audit(repo)
assert audit_dir.is_dir()