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+ "\n on:\n " ,
88+ "\n push:\n " ,
89+ "\n pull_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