mirror of
https://github.com/jamesmurdza/awesome-ai-devtools.git
synced 2026-04-23 19:56:26 +02:00
f59798a9c3
Add a Python test file that validates the README.md structure: - Checks README exists and is not empty - Validates main title and required sections - Verifies markdown link syntax - Detects markdown formatting issues - Validates list item format - Checks for excessive duplicate entries Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
202 lines
6.1 KiB
Python
202 lines
6.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Test suite for validating the awesome-ai-devtools README.md file.
|
|
|
|
This script validates:
|
|
- Markdown structure and formatting
|
|
- Link syntax validity
|
|
- Required sections presence
|
|
- Alphabetical ordering of entries (optional check)
|
|
"""
|
|
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
def read_readme():
|
|
"""Read the README.md file content."""
|
|
readme_path = Path(__file__).parent / "README.md"
|
|
if not readme_path.exists():
|
|
raise FileNotFoundError("README.md not found in repository root")
|
|
return readme_path.read_text(encoding="utf-8")
|
|
|
|
|
|
def test_readme_exists():
|
|
"""Test that README.md file exists."""
|
|
readme_path = Path(__file__).parent / "README.md"
|
|
assert readme_path.exists(), "README.md should exist in the repository root"
|
|
print("✓ README.md exists")
|
|
|
|
|
|
def test_readme_not_empty():
|
|
"""Test that README.md is not empty."""
|
|
content = read_readme()
|
|
assert len(content) > 0, "README.md should not be empty"
|
|
print(f"✓ README.md is not empty ({len(content)} characters)")
|
|
|
|
|
|
def test_has_title():
|
|
"""Test that README has a main title (H1)."""
|
|
content = read_readme()
|
|
assert content.startswith("# "), "README.md should start with an H1 title"
|
|
print("✓ README.md has a main title")
|
|
|
|
|
|
def test_has_required_sections():
|
|
"""Test that README has key required sections."""
|
|
content = read_readme()
|
|
required_sections = [
|
|
"## IDEs",
|
|
"## Assistants",
|
|
"## Agents",
|
|
"## Testing",
|
|
"## Resources",
|
|
]
|
|
|
|
missing_sections = []
|
|
for section in required_sections:
|
|
if section not in content:
|
|
missing_sections.append(section)
|
|
|
|
assert not missing_sections, f"Missing required sections: {missing_sections}"
|
|
print(f"✓ All {len(required_sections)} required sections present")
|
|
|
|
|
|
def test_link_syntax():
|
|
"""Test that all markdown links have valid syntax."""
|
|
content = read_readme()
|
|
|
|
# Pattern for markdown links: [text](url)
|
|
link_pattern = r'\[([^\]]+)\]\(([^)]+)\)'
|
|
links = re.findall(link_pattern, content)
|
|
|
|
invalid_links = []
|
|
for text, url in links:
|
|
# Check for common issues
|
|
if not url.strip():
|
|
invalid_links.append(f"Empty URL for text: {text}")
|
|
elif url.startswith(" ") or url.endswith(" "):
|
|
invalid_links.append(f"URL has extra spaces: {url}")
|
|
elif not (url.startswith("http") or url.startswith("#") or url.startswith("/")):
|
|
# Allow http(s), anchors, and relative paths
|
|
if not url.startswith("mailto:"):
|
|
invalid_links.append(f"Potentially invalid URL: {url}")
|
|
|
|
assert not invalid_links, f"Invalid links found: {invalid_links}"
|
|
print(f"✓ All {len(links)} links have valid syntax")
|
|
|
|
|
|
def test_no_broken_markdown_formatting():
|
|
"""Test for common markdown formatting issues."""
|
|
content = read_readme()
|
|
issues = []
|
|
|
|
lines = content.split('\n')
|
|
for i, line in enumerate(lines, 1):
|
|
# Check for unclosed brackets
|
|
if line.count('[') != line.count(']'):
|
|
# Skip if it's a code block or intentional
|
|
if not line.strip().startswith('```'):
|
|
issues.append(f"Line {i}: Mismatched brackets")
|
|
|
|
# Check for unclosed parentheses in link context
|
|
if '](' in line:
|
|
if line.count('](') != line.count(')'):
|
|
issues.append(f"Line {i}: Possible unclosed link parenthesis")
|
|
|
|
# Only fail on clear issues, some edge cases are acceptable
|
|
critical_issues = [i for i in issues if "Mismatched brackets" in i]
|
|
assert len(critical_issues) < 5, f"Too many formatting issues: {critical_issues[:5]}"
|
|
print("✓ No critical markdown formatting issues")
|
|
|
|
|
|
def test_list_items_format():
|
|
"""Test that list items follow the expected format."""
|
|
content = read_readme()
|
|
|
|
# Pattern for list items: - [Name](url) — Description
|
|
list_item_pattern = r'^- \[.+\]\(.+\)'
|
|
|
|
lines = content.split('\n')
|
|
list_items = [line for line in lines if line.startswith('- [')]
|
|
|
|
assert len(list_items) > 10, "Should have more than 10 list items"
|
|
print(f"✓ Found {len(list_items)} properly formatted list items")
|
|
|
|
|
|
def test_no_duplicate_entries():
|
|
"""Test that there are no excessive duplicate tool entries.
|
|
|
|
Note: Some duplicates are acceptable if a tool appears in multiple categories.
|
|
This test warns about duplicates but only fails if there are many.
|
|
"""
|
|
content = read_readme()
|
|
|
|
# Extract tool names from links
|
|
link_pattern = r'^- \[([^\]]+)\]'
|
|
lines = content.split('\n')
|
|
|
|
tool_names = []
|
|
for line in lines:
|
|
match = re.match(link_pattern, line)
|
|
if match:
|
|
tool_names.append(match.group(1).lower())
|
|
|
|
duplicates = []
|
|
seen = set()
|
|
for name in tool_names:
|
|
if name in seen:
|
|
duplicates.append(name)
|
|
seen.add(name)
|
|
|
|
if duplicates:
|
|
print(f" Note: Found {len(duplicates)} duplicate(s): {duplicates[:5]}")
|
|
|
|
# Allow some duplicates (tools may appear in multiple categories)
|
|
assert len(duplicates) <= 5, f"Too many duplicate entries: {duplicates}"
|
|
print(f"✓ Acceptable duplicate count ({len(duplicates)}) among {len(tool_names)} tools")
|
|
|
|
|
|
def run_all_tests():
|
|
"""Run all tests and report results."""
|
|
tests = [
|
|
test_readme_exists,
|
|
test_readme_not_empty,
|
|
test_has_title,
|
|
test_has_required_sections,
|
|
test_link_syntax,
|
|
test_no_broken_markdown_formatting,
|
|
test_list_items_format,
|
|
test_no_duplicate_entries,
|
|
]
|
|
|
|
print("=" * 50)
|
|
print("Running README.md validation tests")
|
|
print("=" * 50)
|
|
|
|
passed = 0
|
|
failed = 0
|
|
|
|
for test in tests:
|
|
try:
|
|
test()
|
|
passed += 1
|
|
except AssertionError as e:
|
|
print(f"✗ {test.__name__}: {e}")
|
|
failed += 1
|
|
except Exception as e:
|
|
print(f"✗ {test.__name__}: Unexpected error - {e}")
|
|
failed += 1
|
|
|
|
print("=" * 50)
|
|
print(f"Results: {passed} passed, {failed} failed")
|
|
print("=" * 50)
|
|
|
|
return failed == 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
success = run_all_tests()
|
|
sys.exit(0 if success else 1)
|