chore: bootstrap YoVision DevHarness

This commit is contained in:
QiuSW
2026-08-11 18:21:31 +08:00
commit 4e6f75f1a3
18 changed files with 1706 additions and 0 deletions
+184
View File
@@ -0,0 +1,184 @@
from __future__ import annotations
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "dev_scripts"))
from check_harness import ( # noqa: E402
CORE_DOCUMENT_REQUIREMENTS,
CORE_PAGE_PATHS,
REQUIRED_FILES,
check_claude_code_entry,
check_core_documents,
check_agent_efficiency_rules,
check_task_template,
core_mapping_errors,
missing_sections,
)
from wiki_docs import load_config # noqa: E402
class CoreDocumentTests(unittest.TestCase):
def test_current_core_documents_have_required_sections(self) -> None:
errors: list[str] = []
check_core_documents(errors)
self.assertEqual(errors, [])
def test_missing_sections_reports_each_heading(self) -> None:
missing = missing_sections("# 页面\n## 已有\n", ("## 已有", "## 缺少"))
self.assertEqual(missing, ["## 缺少"])
def test_every_core_document_is_required(self) -> None:
for path in CORE_DOCUMENT_REQUIREMENTS:
self.assertIn(path, REQUIRED_FILES)
def test_every_core_page_has_exact_mapping(self) -> None:
config = load_config()
mappings = {mapping.page: mapping.path for mapping in config.mappings}
for page, path in CORE_PAGE_PATHS.items():
self.assertEqual(mappings.get(page), path)
def test_existing_project_adoption_guide_is_core_document(self) -> None:
path = "docs/08-existing-project-adoption.md"
self.assertEqual(
CORE_PAGE_PATHS.get("Existing-Project-Adoption-Guide"),
path,
)
self.assertIn(path, CORE_DOCUMENT_REQUIREMENTS)
self.assertIn("## 可复制 Agent 指令", CORE_DOCUMENT_REQUIREMENTS[path])
def test_missing_or_wrong_core_mapping_is_reported(self) -> None:
errors = core_mapping_errors({"Home": "docs/wrong.md"})
self.assertTrue(any("Home -> docs/README.md" in error for error in errors))
self.assertTrue(
any("Architecture-and-Code-Map" in error for error in errors)
)
class TaskTemplateTests(unittest.TestCase):
def test_task_template_requires_document_impact(self) -> None:
errors: list[str] = []
check_task_template(errors)
self.assertEqual(errors, [])
def test_task_template_requires_delivery_document_impact(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
template = root / ".gitea" / "issue_template" / "task.md"
template.parent.mkdir(parents=True)
template.write_text(
"## 依赖与并行\n"
"- 前置工单:无 / #编号\n"
"- 是否允许与前置工单并行:是 / 否\n"
"- 原因:\n"
"## 原始需求\n"
"- 来源:用户对话 / Gitea / 其他\n"
"- 提出时间:\n"
"- 关键原话或脱敏摘要:\n"
"## 需求变化记录\n"
"| 日期 | 变化内容 | 原因 | 用户确认 |\n"
"## 文档影响\n"
"- [ ] 不影响长期文档,原因:\n"
"- [ ] 更新架构与代码地图\n"
"- [ ] 更新业务规则与术语\n"
"- [ ] 更新常见修改或故障排查\n",
encoding="utf-8",
)
errors: list[str] = []
check_task_template(errors, root)
self.assertIn("单元任务模板缺少:## 交付文档影响", errors)
self.assertIn(
"单元任务模板缺少:- [ ] 无交付文档影响,原因:",
errors,
)
def test_task_template_requires_subproject_impact(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
template = root / ".gitea" / "issue_template" / "task.md"
template.parent.mkdir(parents=True)
template.write_text("## 基本信息\n", encoding="utf-8")
errors: list[str] = []
check_task_template(errors, root)
self.assertIn("单元任务模板缺少:## 子项目影响", errors)
self.assertIn(
"单元任务模板缺少:- 是否跨子项目:是 / 否",
errors,
)
self.assertIn(
"单元任务模板缺少:- 是否修改共享接口或契约:是 / 否;唯一事实来源:",
errors,
)
def test_task_template_requires_dependency_fields(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
template = root / ".gitea" / "issue_template" / "task.md"
template.parent.mkdir(parents=True)
template.write_text(
"## 文档影响\n"
"- [ ] 不影响长期文档,原因:\n"
"- [ ] 更新架构与代码地图\n"
"- [ ] 更新业务规则与术语\n"
"- [ ] 更新常见修改或故障排查\n",
encoding="utf-8",
)
errors: list[str] = []
check_task_template(errors, root)
self.assertIn("单元任务模板缺少:## 依赖与并行", errors)
self.assertIn("单元任务模板缺少:- 前置工单:无 / #编号", errors)
def test_task_template_requires_requirement_traceability(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
template = root / ".gitea" / "issue_template" / "task.md"
template.parent.mkdir(parents=True)
template.write_text(
"## 依赖与并行\n"
"- 前置工单:无 / #编号\n"
"- 是否允许与前置工单并行:是 / 否\n"
"- 原因:\n"
"## 文档影响\n"
"- [ ] 不影响长期文档,原因:\n"
"- [ ] 更新架构与代码地图\n"
"- [ ] 更新业务规则与术语\n"
"- [ ] 更新常见修改或故障排查\n",
encoding="utf-8",
)
errors: list[str] = []
check_task_template(errors, root)
self.assertIn("单元任务模板缺少:## 原始需求", errors)
self.assertIn("单元任务模板缺少:## 需求变化记录", errors)
class AgentRuleTests(unittest.TestCase):
def test_required_agent_rules_are_present(self) -> None:
errors: list[str] = []
check_agent_efficiency_rules(errors)
self.assertEqual(errors, [])
def test_claude_code_entry_imports_shared_rules(self) -> None:
errors: list[str] = []
check_claude_code_entry(errors)
self.assertEqual(errors, [])
def test_claude_code_entry_requires_exact_import_line(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
(root / "CLAUDE.md").write_text(
"共同规则事实来源\n只记录 Claude Code 特有\n"
"共同规则只修改 `AGENTS.md`\n",
encoding="utf-8",
)
errors: list[str] = []
check_claude_code_entry(errors, root)
self.assertIn("CLAUDE.md 缺少独立的 @AGENTS.md 导入", errors)
if __name__ == "__main__":
unittest.main()
+163
View File
@@ -0,0 +1,163 @@
from __future__ import annotations
import json
import os
import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import Mock, patch
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "dev_scripts"))
from new_task_archive import build_archive, safe_title # noqa: E402
from wiki_docs import ( # noqa: E402
Config,
Mapping,
WikiClient,
WikiDocsError,
WikiPage,
dirty_mirror_paths,
load_config,
parse_mirror,
render_mirror,
sync_all,
validate_mappings,
)
class MappingTests(unittest.TestCase):
def test_rejects_path_outside_docs(self) -> None:
with self.assertRaisesRegex(WikiDocsError, "docs/"):
validate_mappings([{"page": "Home", "path": "README.md"}])
def test_rejects_duplicate_page(self) -> None:
with self.assertRaisesRegex(WikiDocsError, "重复映射"):
validate_mappings(
[
{"page": "Home", "path": "docs/README.md"},
{"page": "Home", "path": "docs/other.md"},
]
)
def test_normalizes_api_suffix_from_environment(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "wiki-docs.json"
path.write_text(
json.dumps(
{
"schema_version": 1,
"gitea_url": "http://configured.example",
"owner": "owner",
"repository": "repo",
"mappings": [
{"page": "Home", "path": "docs/README.md"}
],
}
),
encoding="utf-8",
)
with patch.dict(
os.environ, {"GITEA_URL": "http://gitea.example/api/v1"}, clear=False
):
config = load_config(path)
self.assertEqual(config.gitea_url, "http://gitea.example")
class MirrorTests(unittest.TestCase):
def setUp(self) -> None:
self.page = WikiPage(
title="Home",
sub_url="Home",
text="# 首页\n",
revision="a" * 40,
html_url="http://gitea.example/o/r/wiki/Home",
)
def test_render_includes_traceable_metadata(self) -> None:
rendered = render_mirror(self.page)
metadata, body = parse_mirror(rendered)
self.assertEqual(metadata["wiki_page"], "Home")
self.assertEqual(metadata["wiki_revision"], "a" * 40)
self.assertTrue(metadata["synchronized_at"].endswith("Z"))
self.assertEqual(body, "# 首页\n")
def test_unchanged_revision_preserves_sync_time(self) -> None:
first = render_mirror(self.page)
second = render_mirror(self.page, first)
self.assertEqual(first, second)
@patch("wiki_docs.subprocess.run")
def test_dirty_mirror_paths_are_reported(self, run) -> None:
run.return_value.stdout = " M docs/README.md\n"
config = Config(
path=Path("wiki-docs.json"),
gitea_url="http://gitea.example",
owner="o",
repository="r",
mappings=(Mapping("Home", "docs/README.md"),),
)
self.assertEqual(dirty_mirror_paths(config), [" M docs/README.md"])
@patch("wiki_docs.dirty_mirror_paths", return_value=[" M docs/README.md"])
def test_sync_stops_before_reading_wiki_when_mirror_is_dirty(self, _dirty) -> None:
config = Config(
path=Path("wiki-docs.json"),
gitea_url="http://gitea.example",
owner="o",
repository="r",
mappings=(Mapping("Home", "docs/README.md"),),
)
client = Mock()
with self.assertRaisesRegex(WikiDocsError, "未提交改动"):
sync_all(config, client)
client.get_page.assert_not_called()
class WikiClientTests(unittest.TestCase):
def test_encoded_unicode_sub_url_is_not_double_encoded(self) -> None:
config = Config(
path=Path("wiki-docs.json"),
gitea_url="http://gitea.example",
owner="o",
repository="r",
mappings=(Mapping("中文", "docs/chinese.md"),),
)
client = WikiClient(config, token="")
client.list_pages = Mock(
return_value=[{"title": "中文", "sub_url": "%E4%B8%AD%E6%96%87.-"}]
)
encoded = __import__("base64").b64encode("# 中文\n".encode()).decode()
with patch.object(
client,
"_request",
return_value={
"title": "中文",
"content_base64": encoded,
"last_commit": {"sha": "b" * 40},
},
) as request:
page = client.get_page("中文")
api_path = request.call_args.args[1]
self.assertIn("%E4%B8%AD%E6%96%87.-", api_path)
self.assertNotIn("%25E4", api_path)
self.assertTrue(page.html_url.endswith("/%E4%B8%AD%E6%96%87.-"))
class ArchiveTests(unittest.TestCase):
def test_safe_title_handles_windows_characters(self) -> None:
self.assertEqual(safe_title(' 修复:"登录" / 超时 '), "修复-登录-超时")
def test_build_archive_replaces_known_fields(self) -> None:
template = "# <工单号> <标题>\nYYYY-MM-DD\n<链接>\n<页面名>\n"
result = build_archive(template, "12", "修复登录", "Task-12-login", "http://i/12")
self.assertIn("# 12 修复登录", result)
self.assertIn("http://i/12", result)
self.assertIn("Task-12-login", result)
self.assertNotIn("YYYY-MM-DD", result)
if __name__ == "__main__":
unittest.main()