120 lines
5.2 KiB
Python
120 lines
5.2 KiB
Python
"""Tests use isolated temporary fixtures and require only Python's standard library."""
|
|
from pathlib import Path
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
from check_docs import check_repository, heading_slug
|
|
|
|
|
|
class DocumentationChecks(unittest.TestCase):
|
|
def setUp(self):
|
|
self.directory = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.directory.cleanup)
|
|
self.root = Path(self.directory.name)
|
|
self.write("README.md", "# Entry\n")
|
|
|
|
def write(self, name, content):
|
|
path = self.root / name
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(content, encoding="utf-8")
|
|
|
|
def errors(self):
|
|
return check_repository(self.root).errors
|
|
|
|
def test_valid_local_link_and_unicode_anchor(self):
|
|
self.write("README.md", "# Entry\n[Topic](topic.md#用户身份)\n")
|
|
self.write("topic.md", "# 用户身份\n")
|
|
self.assertEqual(self.errors(), [])
|
|
|
|
def test_missing_file_is_reported(self):
|
|
self.write("README.md", "# Entry\n[Missing](absent.md)\n")
|
|
self.assertTrue(any("missing link target" in item for item in self.errors()))
|
|
|
|
def test_missing_anchor_is_reported(self):
|
|
self.write("README.md", "# Entry\n[Missing](topic.md#absent)\n")
|
|
self.write("topic.md", "# Topic\n")
|
|
self.assertTrue(any("missing heading anchor" in item for item in self.errors()))
|
|
|
|
def test_template_links_inside_fences_are_ignored(self):
|
|
self.write("README.md", "# Entry\n````markdown\n[Placeholder](none.md)\n```text\nexample\n```\n````\n")
|
|
self.assertEqual(self.errors(), [])
|
|
|
|
def test_unclosed_fence_is_reported(self):
|
|
self.write("README.md", "# Entry\n```text\nexample\n")
|
|
self.assertTrue(any("unclosed code fence" in item for item in self.errors()))
|
|
|
|
def test_duplicate_heading_anchor_suffix(self):
|
|
self.write("README.md", "# Entry\n[Second](topic.md#topic-1)\n")
|
|
self.write("topic.md", "# Topic\n## Topic\n")
|
|
self.assertEqual(self.errors(), [])
|
|
|
|
def test_external_link_is_not_fetched(self):
|
|
self.write("README.md", "# Entry\n[External](https://example.invalid/path#missing)\n")
|
|
self.assertEqual(self.errors(), [])
|
|
self.assertEqual(check_repository(self.root).links, 0)
|
|
|
|
def test_machine_specific_link_is_rejected(self):
|
|
self.write("README.md", "# Entry\n[Local](/absolute/local.md)\n")
|
|
self.assertTrue(any("absolute link" in item for item in self.errors()))
|
|
|
|
def test_escape_from_repository_is_rejected(self):
|
|
self.write("README.md", "# Entry\n[Outside](../outside.md)\n")
|
|
self.assertTrue(any("escapes repository" in item for item in self.errors()))
|
|
|
|
def test_windows_absolute_link_is_rejected(self):
|
|
self.write("README.md", "# Entry\n[Local](C:/workspace/local.md)\n")
|
|
self.assertTrue(any("absolute link" in item for item in self.errors()))
|
|
|
|
def test_protocol_relative_web_link_is_not_fetched(self):
|
|
self.write("README.md", "# Entry\n[External](//example.invalid/path)\n")
|
|
self.assertEqual(self.errors(), [])
|
|
|
|
def test_file_uri_is_rejected(self):
|
|
self.write("README.md", "# Entry\n[Local](file:///workspace/local.md)\n")
|
|
self.assertTrue(any("absolute link" in item for item in self.errors()))
|
|
|
|
def test_invalid_utf8_is_reported_without_crashing(self):
|
|
(self.root / "README.md").write_bytes(b"\xff")
|
|
self.assertTrue(any("cannot read UTF-8" in item for item in self.errors()))
|
|
|
|
def test_rule_id_mismatch_is_reported(self):
|
|
self.write("authoring/rules/DOC-001.md", "# DOC-002 Wrong identity\n")
|
|
self.assertTrue(any("heading ID differ" in item for item in self.errors()))
|
|
|
|
def test_source_id_mismatch_is_reported(self):
|
|
self.write("authoring/research/SRC-01.md", "# SRC-02 Wrong identity\n")
|
|
self.assertTrue(any("heading ID differ" in item for item in self.errors()))
|
|
|
|
def guide_fixture(self, code):
|
|
self.write("README.md", "# Entry\n[Guide](guide/01-example.md)\n")
|
|
self.write("guide/01-example.md", "# Guide\n```endorphin\n" + code + "\n```\n")
|
|
|
|
def test_valid_use(self):
|
|
self.guide_fixture("use alice:shop.pricing")
|
|
self.assertEqual(self.errors(), [])
|
|
|
|
def test_use_without_user_separator_is_rejected(self):
|
|
self.guide_fixture("use alice.shop.pricing")
|
|
self.assertTrue(any("complete namespace key" in item for item in self.errors()))
|
|
|
|
def test_selective_import_is_rejected(self):
|
|
self.guide_fixture("use alice:shop.ui::{Column}")
|
|
self.assertTrue(any("complete namespace key" in item for item in self.errors()))
|
|
|
|
def test_source_namespace_declaration_is_rejected(self):
|
|
self.guide_fixture("namespace alice:shop")
|
|
self.assertTrue(any("own namespace" in item for item in self.errors()))
|
|
|
|
def test_gapped_chapter_sequence_is_rejected(self):
|
|
self.write("guide/02-example.md", "# Guide\n")
|
|
self.assertTrue(any("chapter numbering" in item for item in self.errors()))
|
|
|
|
def test_heading_punctuation_is_removed(self):
|
|
self.assertEqual(heading_slug("4.4 名称引用"), "44-名称引用")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|