-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Expand file tree
/
Copy pathtest_docs.py
More file actions
241 lines (190 loc) · 9.1 KB
/
Copy pathtest_docs.py
File metadata and controls
241 lines (190 loc) · 9.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
"""Tests for documentation consistency and integrity."""
from __future__ import annotations
import re
from pathlib import Path
import pytest
# Project root is 2 levels up from tests/
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
DOCS_ROOT = PROJECT_ROOT / "docs"
class TestDocStructure:
"""Test that documentation structure is consistent across languages."""
def test_docs_directory_exists(self):
"""docs directory should exist."""
assert DOCS_ROOT.exists(), "docs/ directory not found"
assert DOCS_ROOT.is_dir(), "docs/ should be a directory"
def test_zh_docs_exist(self):
"""Chinese docs directory should exist."""
zh_docs = DOCS_ROOT / "zh"
assert zh_docs.exists(), "docs/zh/ directory not found"
def test_en_docs_exist(self):
"""English docs directory should exist."""
en_docs = DOCS_ROOT / "en"
assert en_docs.exists(), "docs/en/ directory not found"
def test_zh_en_mirror_structure(self):
"""zh and en docs should have the same subdirectory structure."""
zh_dirs = set(_get_all_dirs(DOCS_ROOT / "zh"))
en_dirs = set(_get_all_dirs(DOCS_ROOT / "en"))
# Get relative paths from the language root
zh_rel = {d.relative_to(DOCS_ROOT / "zh") for d in zh_dirs}
en_rel = {d.relative_to(DOCS_ROOT / "en") for d in en_dirs}
missing_in_en = zh_rel - en_rel
missing_in_zh = en_rel - zh_rel
# Filter out known language-specific docs
known_diffs = {
Path("deploy/astrbot/desktop.md"),
Path("deploy/astrbot/rainyun.md"),
Path("dev/star/plugin.md"),
Path("dev/star/guides/other.md"),
Path("others/github-proxy.md"),
Path("others/ipv6.md"),
Path("use/knowledge-base-old.md"),
Path("config/model-config.md"),
Path("use/astrbot-sandbox.md"),
Path("config"), # en has config dir, zh doesn't
Path("community-events"),
Path("ospp"), # en has ospp dir, zh doesn't
}
unexpected_missing_en = missing_in_en - known_diffs
unexpected_missing_zh = missing_in_zh - known_diffs
assert not unexpected_missing_en, f"Missing in en docs: {unexpected_missing_en}"
assert not unexpected_missing_zh, f"Missing in zh docs: {unexpected_missing_zh}"
class TestCoreDocs:
"""Test that core documentation files exist and have content."""
def test_readme_exists_all_languages(self):
"""README files should exist for all languages."""
readme_files = [
PROJECT_ROOT / "README.md",
PROJECT_ROOT / "README_zh.md",
PROJECT_ROOT / "README_zh-TW.md",
PROJECT_ROOT / "README_ja.md",
PROJECT_ROOT / "README_fr.md",
PROJECT_ROOT / "README_ru.md",
]
for readme in readme_files:
assert readme.exists(), f"{readme.name} not found"
assert readme.stat().st_size > 0, f"{readme.name} is empty"
def test_readme_has_sponsors_section(self):
"""README files should contain Sponsors section."""
readme_files = [
PROJECT_ROOT / "README.md",
PROJECT_ROOT / "README_zh.md",
]
for readme in readme_files:
content = readme.read_text(encoding="utf-8")
assert "Sponsors" in content or "sponsors" in content.lower(), \
f"{readme.name} missing Sponsors section"
def test_agents_md_exists(self):
"""AGENTS.md should exist."""
agents_md = PROJECT_ROOT / "AGENTS.md"
assert agents_md.exists(), "AGENTS.md not found"
assert agents_md.stat().st_size > 0, "AGENTS.md is empty"
def test_claude_md_exists(self):
"""AGENTS.md should exist and contain AI coding guidelines (merged from CLAUDE.md)."""
agents_md = PROJECT_ROOT / "AGENTS.md"
assert agents_md.exists(), "AGENTS.md not found (CLAUDE.md content was merged into AGENTS.md)"
assert agents_md.stat().st_size > 0, "AGENTS.md is empty"
content = agents_md.read_text(encoding="utf-8")
assert "AstrBot" in content, "AGENTS.md should contain project guidelines"
def test_startup_guide_exists(self):
"""Startup guide should exist in both languages."""
startup_zh = DOCS_ROOT / "zh" / "startup.md"
startup_en = DOCS_ROOT / "en" / "startup.md"
assert startup_zh.exists(), "docs/zh/startup.md not found"
assert startup_en.exists(), "docs/en/startup.md not found"
def test_core_docs_have_content(self):
"""Core documentation files should have minimum content."""
min_lines = 10
core_docs = [
DOCS_ROOT / "zh" / "startup.md",
DOCS_ROOT / "en" / "startup.md",
PROJECT_ROOT / "AGENTS.md",
]
for doc in core_docs:
if doc.exists():
lines = [l for l in doc.read_text(encoding="utf-8").splitlines() if l.strip()]
assert len(lines) >= min_lines, f"{doc.name} has less than {min_lines} lines"
class TestDocLinks:
"""Test for broken links in documentation."""
def test_no_broken_internal_links_readme(self):
"""README.md should not have obviously broken internal links."""
readme = PROJECT_ROOT / "README.md"
if not readme.exists():
pytest.skip("README.md not found")
content = readme.read_text(encoding="utf-8")
# Check for common broken patterns
broken_patterns = [
r"\[.*\]\(\s*\)", # Empty links: [text]()
r"\[.*\]\(\s*#\s*\)", # Empty anchor: [text](#)
]
for pattern in broken_patterns:
matches = re.findall(pattern, content)
assert not matches, f"Found broken link pattern: {pattern}"
def test_readme_has_valid_structure(self):
"""README.md should have key sections."""
readme = PROJECT_ROOT / "README.md"
if not readme.exists():
pytest.skip("README.md not found")
content = readme.read_text(encoding="utf-8")
required_sections = [
"Key Features",
"Supported",
"Deployment",
]
for section in required_sections:
assert section in content, f"README.md missing '{section}' section"
class TestDocFormatting:
"""Test documentation formatting standards."""
def test_markdown_files_have_no_trailing_whitespace(self):
"""Markdown files should not have trailing whitespace on lines."""
md_files = list(DOCS_ROOT.glob("**/*.md"))
if not md_files:
pytest.skip("No markdown files found in docs/")
files_with_issues = []
for md_file in md_files[:20]: # Check first 20 files
content = md_file.read_text(encoding="utf-8")
lines = content.splitlines()
for i, line in enumerate(lines, 1):
if line.rstrip() != line:
files_with_issues.append(f"{md_file.relative_to(PROJECT_ROOT)}:{i}")
break
assert not files_with_issues, \
f"Files with trailing whitespace: {files_with_issues}"
def test_readme_files_end_with_newline(self):
"""README files should end with a newline."""
readme_files = list(PROJECT_ROOT.glob("README*.md"))
files_without_newline = []
for readme in readme_files:
content = readme.read_text(encoding="utf-8")
if content and not content.endswith("\n"):
files_without_newline.append(readme.name)
assert not files_without_newline, \
f"Files without trailing newline: {files_without_newline}"
class TestSkillDocs:
"""Test that skill documentation exists."""
def test_dev_guide_skill_exists(self):
"""Developer guide skill should exist in docs/skills."""
skill_doc = DOCS_ROOT / "skills" / "astrbot-dev-guide.md"
assert skill_doc.exists(), "docs/skills/astrbot-dev-guide.md not found"
content = skill_doc.read_text(encoding="utf-8")
assert len(content) > 100, "Skill doc is too short"
def test_skill_doc_has_frontmatter(self):
"""Skill doc should have frontmatter with name and description."""
skill_doc = DOCS_ROOT / "skills" / "astrbot-dev-guide.md"
if not skill_doc.exists():
pytest.skip("Skill doc not found")
content = skill_doc.read_text(encoding="utf-8")
assert "---" in content, "Skill doc missing frontmatter delimiter"
assert "name:" in content, "Skill doc missing 'name' in frontmatter"
assert "description:" in content, "Skill doc missing 'description' in frontmatter"
# Helper functions
def _get_all_dirs(root: Path) -> list[Path]:
"""Get all directories under root, including root itself."""
dirs = [root]
for item in root.rglob("*"):
if item.is_dir() and not _is_ignored_dir(item):
dirs.append(item)
return dirs
def _is_ignored_dir(path: Path) -> bool:
"""Check if path is an ignored directory (like node_modules)."""
ignored_names = {"node_modules", ".ignored", "__pycache__", ".git"}
return any(part in ignored_names for part in path.parts)