Skip to content

Commit 16cafda

Browse files
CodeRabbit Generated Unit Tests: Add pytest suites for README validation and GitHub-like slugify
1 parent d5fffc4 commit 16cafda

File tree

1 file changed

+219
-0
lines changed

1 file changed

+219
-0
lines changed

tests/test_readme_validation.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
# Test framework: pytest (assumed/preferred based on common Python projects)
2+
# These tests validate README content added/modified in the PR, focusing on
3+
# workflow badges, Usage snippet, Optional Inputs, admonitions, and key sections.
4+
5+
from pathlib import Path
6+
import re
7+
import pytest
8+
from typing import List, Optional
9+
10+
11+
README_CANDIDATES = [
12+
Path("README.md"),
13+
Path("Readme.md"),
14+
Path("README.MD"),
15+
Path("README.rst"),
16+
]
17+
18+
19+
def _normalize(text: str) -> str:
20+
return text.replace("\r\n", "\n").replace("\r", "\n")
21+
22+
23+
@pytest.fixture(scope="module")
24+
def readme_text() -> str:
25+
for p in README_CANDIDATES:
26+
if p.exists():
27+
return _normalize(p.read_text(encoding="utf-8"))
28+
pytest.skip("README not found at repository root")
29+
30+
31+
def _extract_code_blocks(md: str, lang: Optional[str] = None) -> List[str]:
32+
# Matches fenced code blocks, optionally filtered by language tag.
33+
tag = re.escape(lang) if lang else r"[a-zA-Z0-9_\-]*"
34+
pattern = rf"```{tag}\s*\n(.*?)\n```"
35+
return re.findall(pattern, md, flags=re.DOTALL)
36+
37+
38+
def _extract_subsection(md: str, heading: str) -> Optional[str]:
39+
# Extracts the body under a '### <heading>' until next ###/## or EOF.
40+
pat = rf"(?ms)^###\s+`?{re.escape(heading)}`?\s*$\n(.*?)(?=^\s*###\s+`?.*?`?\s*$|^\s*##\s+.*$|\Z)"
41+
m = re.search(pat, md)
42+
return m.group(1) if m else None
43+
44+
45+
def test_readme_exists(readme_text: str) -> None:
46+
assert isinstance(readme_text, str) and len(readme_text) > 50, "README should exist and be non-trivial"
47+
48+
49+
def test_top_badges_present(readme_text: str) -> None:
50+
t = readme_text
51+
assert re.search(
52+
r"\[\!\[Commit Check\]\(https://img\.shields\.io/github/actions/workflow/status/commit-check/commit-check-action/commit-check\.yml\?branch=main&label=Commit%20Check&color=blue&logo=github\)\]\(https://github\.com/commit-check/commit-check-action/actions/workflows/commit-check\.yml\)",
53+
t,
54+
), "Workflow status badge with branch=main should be present"
55+
assert re.search(
56+
r"https://img\.shields\.io/github/v/release/commit-check/commit-check-action\?color=blue",
57+
t,
58+
), "GitHub release (latest SemVer) badge should be present"
59+
assert re.search(
60+
r"https://img\.shields\.io/static/v1\?label=Used%20by&message=\d+&color=informational",
61+
t,
62+
), "'Used by' shields.io badge should be present (do not assert specific count)"
63+
assert "https://img.shields.io/badge/Marketplace-commit--check--action-blue" in t, "Marketplace badge should be present"
64+
assert re.search(r"slsa\.dev/images/gh-badge-level3\.svg\?color=blue", t), "SLSA level 3 badge should be present"
65+
66+
67+
def test_table_of_contents_has_expected_links(readme_text: str) -> None:
68+
anchors = [
69+
"Usage",
70+
"Optional Inputs",
71+
"GitHub Action Job Summary",
72+
"GitHub Pull Request Comments",
73+
"Badging Your Repository",
74+
"Versioning",
75+
]
76+
for anchor in anchors:
77+
slug = anchor.lower().replace(" ", "-")
78+
assert re.search(rf"^\* \[{re.escape(anchor)}\]\(#{re.escape(slug)}\)\s*$", readme_text, flags=re.MULTILINE), f"TOC entry for {anchor} should exist"
79+
80+
81+
def test_usage_yaml_block_contains_required_items(readme_text: str) -> None:
82+
blocks = _extract_code_blocks(readme_text, "yaml")
83+
assert blocks, "A YAML Usage code block is expected"
84+
y = blocks[0]
85+
required_snippets = [
86+
"name: Commit Check",
87+
"\non:\n",
88+
"\npush:\n",
89+
"\npull_request:\n",
90+
"branches: 'main'",
91+
"jobs:",
92+
"commit-check:",
93+
"runs-on: ubuntu-latest",
94+
"uses: actions/checkout@v5",
95+
"with:",
96+
"ref: ${{ github.event.pull_request.head.sha }}",
97+
"fetch-depth: 0",
98+
"uses: commit-check/commit-check-action@v1",
99+
"env:",
100+
"GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}",
101+
]
102+
for snippet in required_snippets:
103+
assert snippet in y, f"Usage YAML should include: {snippet!r}"
104+
105+
# Validate GitHub expressions patterns
106+
assert re.search(r"\$\{\{\s*github\.event\.pull_request\.head\.sha\s*\}\}", y), "PR head SHA expression must be present"
107+
assert re.search(r"\$\{\{\s*github\.event_name\s*==\s*['\"]pull_request['\"]\s*\}\}", y), "Conditional pr-comments expression must be present"
108+
109+
110+
def test_commit_check_action_inputs_in_usage_block(readme_text: str) -> None:
111+
y = _extract_code_blocks(readme_text, "yaml")[0]
112+
# Inputs expected in Usage 'with:' configuration
113+
inputs = [
114+
"message: true",
115+
"branch: true",
116+
"author-name: true",
117+
"author-email: true",
118+
"commit-signoff: true",
119+
"merge-base: false",
120+
"imperative: false",
121+
"job-summary: true",
122+
"pr-comments: ${{ github.event_name == 'pull_request' }}",
123+
]
124+
for i in inputs:
125+
assert i in y, f"Usage YAML should configure input: {i}"
126+
127+
128+
def test_optional_inputs_sections_and_defaults(readme_text: str) -> None:
129+
t = readme_text
130+
expected_defaults = {
131+
"message": "true",
132+
"branch": "true",
133+
"author-name": "true",
134+
"author-email": "true",
135+
"commit-signoff": "true",
136+
"merge-base": "false",
137+
"imperative": "false",
138+
"dry-run": "false",
139+
"job-summary": "true",
140+
"pr-comments": "false",
141+
}
142+
for name, default in expected_defaults.items():
143+
sec = _extract_subsection(t, name)
144+
assert sec is not None, f"Missing Optional Input subsection: {name}"
145+
assert re.search(rf"Default:\s*`{re.escape(default)}`", sec), f"Default for `{name}` should be `{default}`"
146+
# Each section should have a brief description
147+
assert re.search(r"- \*\*Description\*\*:", sec), f"`{name}` subsection should include a Description bullet"
148+
149+
150+
def test_important_admonitions_present(readme_text: str) -> None:
151+
t = readme_text
152+
# Expect two IMPORTANT blocks: merge-base and pr-comments
153+
important_blocks = re.findall(r"^> \[\!IMPORTANT\]\n>(?:.*\n?)+?(?=\n^[^>]|$)", t, flags=re.MULTILINE)
154+
assert len(important_blocks) >= 2, "At least two IMPORTANT admonitions should be present"
155+
156+
assert re.search(r"`merge-base` is an experimental feature", t), "merge-base IMPORTANT note should be present"
157+
assert re.search(r"`pr-comments` is an experimental feature", t), "pr-comments IMPORTANT note should be present"
158+
assert re.search(r"\(#77\)", t) and "commit-check-action/issues/77" in t, "pr-comments note should reference issue #77"
159+
160+
161+
def test_github_action_job_summary_section_images(readme_text: str) -> None:
162+
t = readme_text
163+
assert "## GitHub Action Job Summary" in t, "Job Summary section heading missing"
164+
assert re.search(r"\!\[Success job summary\]\(", t), "Success job summary image should be present"
165+
assert re.search(r"\!\[Failure job summary\]\(", t), "Failure job summary image should be present"
166+
167+
168+
def test_pull_request_comments_section_images(readme_text: str) -> None:
169+
t = readme_text
170+
assert "## GitHub Pull Request Comments" in t, "PR Comments section heading missing"
171+
assert re.search(r"\!\[Success pull request comment\]\(", t), "Success PR comment image should be present"
172+
assert re.search(r"\!\[Failure pull request comment\]\(", t), "Failure PR comment image should be present"
173+
174+
175+
def test_used_by_section_and_links(readme_text: str) -> None:
176+
t = readme_text
177+
assert "## Used By" in t, "Used By section heading missing"
178+
# Ensure at least one org avatar and the dependents link exist
179+
assert "avatars.githubusercontent.com" in t, "Expected org avatars in Used By section"
180+
assert "/commit-check/commit-check-action/network/dependents" in t, "Dependents 'many more' link should be present"
181+
182+
183+
def test_badging_section_contains_md_and_rst_snippets(readme_text: str) -> None:
184+
t = readme_text
185+
assert "## Badging Your Repository" in t, "Badging section missing"
186+
assert "Markdown" in t and "reStructuredText" in t, "Both Markdown and reStructuredText subsections should be present"
187+
188+
md_blocks = _extract_code_blocks(t, None)
189+
joined = "\n\n".join(md_blocks)
190+
# Badge URL should appear in the code examples
191+
assert "actions/workflows/commit-check.yml/badge.svg" in joined, "Badge SVG URL should be in code snippets"
192+
assert "actions/workflows/commit-check.yml" in joined, "Workflow link should be in code snippets"
193+
194+
195+
def test_versioning_and_feedback_links(readme_text: str) -> None:
196+
t = readme_text
197+
assert "## Versioning" in t, "Versioning section missing"
198+
assert re.search(r"\(https?://semver\.org/?\)", t), "Semantic Versioning link should be present"
199+
assert "## Have questions or feedback?" in t, "Feedback section missing"
200+
assert "https://github.com/commit-check/commit-check/issues" in t, "Issues link should be present"
201+
202+
203+
def test_all_images_have_alt_attributes(readme_text: str) -> None:
204+
# Basic check for HTML <img ... alt="..."> tags in README
205+
t = readme_text
206+
imgs = re.findall(r"<img\s+[^>]*>", t)
207+
for tag in imgs:
208+
assert re.search(r'alt="[^"]+"', tag), f"Image tag missing alt attribute: {tag}"
209+
210+
211+
def test_workflow_badge_consistency(readme_text: str) -> None:
212+
# Top badge and badging section should reference the same workflow path
213+
t = readme_text
214+
# Extract all commit-check workflow badge URLs
215+
urls = re.findall(r"https://github\.com/commit-check/commit-check-action/actions/workflows/commit-check\.yml(?:/badge\.svg)?", t)
216+
assert urls, "Expected workflow badge/link URLs"
217+
# Ensure both base workflow URL and badge.svg appear
218+
assert any(u.endswith("badge.svg") for u in urls) or "actions/workflows/commit-check.yml/badge.svg" in t, "badge.svg URL should be present"
219+
assert any(u.endswith("commit-check.yml") for u in urls), "Base workflow URL should be present"

0 commit comments

Comments
 (0)