初始提交:飞球 ETL 系统全量代码

This commit is contained in:
Neo
2026-02-13 08:05:34 +08:00
commit 3c51f5485d
441 changed files with 117631 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
# -*- 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()