chore(workflow): adopt lightweight governance (#221)

This commit is contained in:
QiuSW
2026-09-05 12:14:14 +08:00
parent 003ab38a36
commit e91fc0cc28
18 changed files with 620 additions and 146 deletions
+240 -2
View File
@@ -5,8 +5,11 @@ import os
import sys
import tempfile
import unittest
from argparse import Namespace
from io import BytesIO
from pathlib import Path
from unittest.mock import Mock, patch
from unittest.mock import MagicMock, Mock, patch
from urllib.error import HTTPError
ROOT = Path(__file__).resolve().parents[1]
@@ -21,6 +24,7 @@ from harness import ( # noqa: E402
existing_task_mirrors,
export_task_archives,
main as harness_main,
run_sync,
safe_title,
task_target,
)
@@ -126,8 +130,215 @@ class MirrorTests(unittest.TestCase):
sync_all(config, client)
client.get_page.assert_not_called()
@patch("wiki_docs.dirty_mirror_paths", return_value=[])
def test_incremental_sync_lists_once_and_skips_unchanged_body(self, _dirty) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
target = root / "docs" / "README.md"
target.parent.mkdir(parents=True)
target.write_text(render_mirror(self.page), encoding="utf-8")
config = Config(
path=root / "wiki-docs.json",
gitea_url="http://gitea.example",
owner="o",
repository="r",
mappings=(Mapping("Home", "docs/README.md"),),
)
client = Mock()
client.list_pages.return_value = [
{
"title": "Home",
"sub_url": "Home",
"last_commit": {"sha": self.page.revision},
}
]
with patch("wiki_docs.ROOT", root):
messages = sync_all(config, client)
self.assertTrue(messages[0].startswith("无变化:"))
client.list_pages.assert_called_once_with()
client.get_page_from_metadata.assert_not_called()
@patch("wiki_docs.dirty_mirror_paths", return_value=[])
def test_incremental_sync_fetches_changed_page(self, _dirty) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
target = root / "docs" / "README.md"
target.parent.mkdir(parents=True)
target.write_text(render_mirror(self.page), encoding="utf-8")
changed = WikiPage(
title="Home",
sub_url="Home",
text="# 新首页\n",
revision="b" * 40,
html_url=self.page.html_url,
)
metadata = {
"title": "Home",
"sub_url": "Home",
"last_commit": {"sha": changed.revision},
}
config = Config(
path=root / "wiki-docs.json",
gitea_url="http://gitea.example",
owner="o",
repository="r",
mappings=(Mapping("Home", "docs/README.md"),),
)
client = Mock()
client.list_pages.return_value = [metadata]
client.get_page_from_metadata.return_value = changed
with patch("wiki_docs.ROOT", root):
messages = sync_all(config, client)
_metadata, body = parse_mirror(target.read_text(encoding="utf-8"))
self.assertTrue(messages[0].startswith("已更新:"))
self.assertEqual(body, changed.text)
client.get_page_from_metadata.assert_called_once_with(metadata, "Home")
@patch("wiki_docs.dirty_mirror_paths", return_value=[])
def test_sync_does_not_write_partial_mirrors_when_later_read_fails(
self, _dirty
) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
first_target = root / "docs" / "first.md"
second_target = root / "docs" / "second.md"
first_target.parent.mkdir(parents=True)
first_original = render_mirror(self.page)
first_target.write_text(first_original, encoding="utf-8")
second_target.write_text(first_original, encoding="utf-8")
config = Config(
path=root / "wiki-docs.json",
gitea_url="http://gitea.example",
owner="o",
repository="r",
mappings=(
Mapping("First", "docs/first.md"),
Mapping("Second", "docs/second.md"),
),
)
pages = [
{
"title": name,
"sub_url": name,
"last_commit": {"sha": revision},
}
for name, revision in (("First", "b" * 40), ("Second", "c" * 40))
]
changed = WikiPage(
title="First",
sub_url="First",
text="# Changed\n",
revision="b" * 40,
html_url="http://gitea.example/o/r/wiki/First",
)
client = Mock()
client.list_pages.return_value = pages
client.get_page_from_metadata.side_effect = [
changed,
WikiDocsError("rate limited"),
]
with patch("wiki_docs.ROOT", root):
with self.assertRaises(WikiDocsError):
sync_all(config, client)
self.assertEqual(first_target.read_text(encoding="utf-8"), first_original)
@patch("wiki_docs.dirty_mirror_paths", return_value=[])
def test_sync_fetches_when_remote_revision_is_missing(self, _dirty) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
target = root / "docs" / "README.md"
target.parent.mkdir(parents=True)
target.write_text(render_mirror(self.page), encoding="utf-8")
metadata = {"title": "Home", "sub_url": "Home"}
config = Config(
path=root / "wiki-docs.json",
gitea_url="http://gitea.example",
owner="o",
repository="r",
mappings=(Mapping("Home", "docs/README.md"),),
)
client = Mock()
client.list_pages.return_value = [metadata]
client.get_page_from_metadata.return_value = self.page
with patch("wiki_docs.ROOT", root):
sync_all(config, client)
client.get_page_from_metadata.assert_called_once_with(metadata, "Home")
def test_deep_check_fetches_unchanged_page_body(self) -> None:
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
target = root / "docs" / "README.md"
target.parent.mkdir(parents=True)
target.write_text(render_mirror(self.page), encoding="utf-8")
metadata = {
"title": "Home",
"sub_url": "Home",
"last_commit": {"sha": self.page.revision},
}
config = Config(
path=root / "wiki-docs.json",
gitea_url="http://gitea.example",
owner="o",
repository="r",
mappings=(Mapping("Home", "docs/README.md"),),
)
client = Mock()
client.list_pages.return_value = [metadata]
client.get_page_from_metadata.return_value = self.page
with patch("wiki_docs.ROOT", root):
sync_all(config, client, check=True, deep_check=True)
client.list_pages.assert_called_once_with()
client.get_page_from_metadata.assert_called_once_with(metadata, "Home")
@patch("harness.run_check", return_value=0)
@patch("harness.sync_all", return_value=["已更新:docs/README.md"])
@patch("harness.WikiClient")
@patch("harness.load_config")
def test_verify_reuses_one_deep_sync(
self, load, client_class, sync, check
) -> None:
config = Mock()
load.return_value = config
result = run_sync(
Namespace(verify=True, check=False, deep_check=False, config="config.json")
)
self.assertEqual(result, 0)
sync.assert_called_once_with(
config, client_class.return_value, check=False, deep_check=True
)
check.assert_called_once()
class WikiClientTests(unittest.TestCase):
@patch("wiki_docs.time.sleep")
@patch("wiki_docs.urlopen")
def test_get_retries_a_rate_limited_request(self, urlopen, sleep) -> None:
config = Config(
path=Path("wiki-docs.json"),
gitea_url="http://gitea.example",
owner="o",
repository="r",
mappings=(),
)
limited = HTTPError(
"http://gitea.example/api/v1/repos/o/r/wiki/pages",
429,
"Too Many Requests",
None,
BytesIO(b"limited"),
)
success = MagicMock()
success.__enter__.return_value.read.return_value = b"[]"
urlopen.side_effect = [limited, success]
result = WikiClient(config, token="token")._request(
"GET", "/repos/o/r/wiki/pages"
)
self.assertEqual(result, [])
self.assertEqual(urlopen.call_count, 2)
sleep.assert_called_once_with(1.0)
def test_encoded_unicode_sub_url_is_not_double_encoded(self) -> None:
config = Config(
path=Path("wiki-docs.json"),
@@ -323,7 +534,7 @@ class GovernancePolicyTests(unittest.TestCase):
self.assertEqual(errors, [])
agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
self.assertIn("Gitea 工单是单次任务", agents)
self.assertIn("需要工单的任务以 Gitea 工单作为单次", agents)
self.assertIn("标准流程不创建 Wiki 任务归档", agents)
self.assertIn("Wiki 同步由长期事实变化触发,不由任务完成触发", agents)
@@ -340,6 +551,33 @@ class GovernancePolicyTests(unittest.TestCase):
self.assertIn("### 线上原型审核与按需导出", workflow)
self.assertIn("## Windows PowerShell 与 UTF-8", local_development)
def test_goauto_uses_lightweight_governance_and_explicit_authorization(self) -> None:
agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
profile = (ROOT / "docs" / "00-project-profile.md").read_text(
encoding="utf-8"
)
workflow = (ROOT / "docs" / "01-workflow.md").read_text(encoding="utf-8")
for text in (agents, profile, workflow):
self.assertIn("轻量治理", text)
for text in (agents, workflow):
self.assertIn("明确授权后的执行", text)
self.assertIn("不能单独构成人工授权", text)
self.assertIn("不得仅因操作不可逆而重复询问或拒绝", text)
self.assertIn("平台自身强制的审批", text)
def test_language_and_windows_command_policy_is_documented(self) -> None:
agents = (ROOT / "AGENTS.md").read_text(encoding="utf-8")
workflow = (ROOT / "docs" / "01-workflow.md").read_text(encoding="utf-8")
local_development = (
ROOT / "docs" / "04-local-development-and-verification.md"
).read_text(encoding="utf-8")
for text in (agents, workflow):
self.assertIn("默认使用中文", text)
self.assertIn("日志和错误原文保持原样", text)
self.assertIn("不得默认套用 Bash", agents)
self.assertIn("### PowerShell 语法与外部命令", local_development)
self.assertIn("rg -n -g '*.md'", local_development)
def test_deployment_template_is_required_and_mapped(self) -> None:
path = "docs/templates/deployment.md"
self.assertIn(path, harness.REQUIRED_FILES)