Skip to main content

#!/usr/bin/env python3 """ CODITECT Script Validation Test Suite

Comprehensive tests to verify all 233 scripts have correct syntax, proper documentation headers, and valid structure.

Run: python3 scripts/tests/test_scripts.py python3 scripts/tests/test_scripts.py -v # Verbose python3 scripts/tests/test_scripts.py --script invoke-agent # Single script

Author: CODITECT Team Version: 1.0.0 Created: 2025-12-22 """

import os import re import sys import ast import json import argparse import subprocess from pathlib import Path from typing import Dict, List, Tuple, Optional from dataclasses import dataclass

Shared Colors module (consolidates 36 duplicate definitions)

_script_dir = Path(file).parent.parent # tests/ -> scripts/ sys.path.insert(0, str(_script_dir / "core")) from colors import Colors

class TestResult: passed: bool message: str script: str test_name: str

@dataclass class ScriptValidation: name: str path: str script_type: str # 'python' or 'shell' passed: bool tests: List[TestResult] errors: List[str] warnings: List[str]

class ScriptTestSuite: """Comprehensive test suite for CODITECT scripts"""

def __init__(self, framework_root: Path, verbose: bool = False):
self.framework_root = framework_root
self.scripts_dir = framework_root / "scripts"
self.verbose = verbose
self.results: List[ScriptValidation] = []

def get_all_scripts(self) -> List[Tuple[Path, str]]:
"""Get all script files with their types"""
scripts = []

# Python scripts in scripts/ and scripts/core/
for pattern in ['*.py', 'core/*.py', 'tests/*.py']:
for f in sorted(self.scripts_dir.glob(pattern)):
if f.name != '__init__.py' and not f.name.startswith('.'):
scripts.append((f, 'python'))

# Shell scripts
for pattern in ['*.sh', 'core/*.sh']:
for f in sorted(self.scripts_dir.glob(pattern)):
if not f.name.startswith('.'):
scripts.append((f, 'shell'))

return scripts

def test_file_exists(self, script_path: Path, script_name: str) -> TestResult:
"""Test that script file exists and is readable"""
if not script_path.exists():
return TestResult(False, "File does not exist", script_name, "file_exists")
if not script_path.is_file():
return TestResult(False, "Path is not a file", script_name, "file_exists")
return TestResult(True, "File exists", script_name, "file_exists")

def test_python_syntax(self, script_path: Path, script_name: str) -> TestResult:
"""Test Python script syntax"""
try:
content = script_path.read_text(encoding='utf-8')
ast.parse(content)
return TestResult(True, "Valid Python syntax", script_name, "python_syntax")
except SyntaxError as e:
return TestResult(
passed=False,
message=f"Syntax error at line {e.lineno}: {e.msg}",
script=script_name,
test_name="python_syntax"
)
except Exception as e:
return TestResult(False, f"Parse error: {str(e)}", script_name, "python_syntax")

def test_shell_syntax(self, script_path: Path, script_name: str) -> TestResult:
"""Test shell script syntax using bash -n"""
try:
result = subprocess.run(
['bash', '-n', str(script_path)],
capture_output=True,
text=True,
timeout=5
)
if result.returncode == 0:
return TestResult(True, "Valid shell syntax", script_name, "shell_syntax")
else:
return TestResult(
passed=False,
message=f"Syntax error: {result.stderr.strip()[:100]}",
script=script_name,
test_name="shell_syntax"
)
except subprocess.TimeoutExpired:
return TestResult(True, "Syntax check timed out (likely OK)", script_name, "shell_syntax")
except Exception as e:
return TestResult(False, f"Check failed: {str(e)}", script_name, "shell_syntax")

def test_has_shebang(self, script_path: Path, script_name: str, script_type: str) -> TestResult:
"""Test that script has proper shebang line"""
content = script_path.read_text(encoding='utf-8')
first_line = content.split('\n')[0] if content else ''

if script_type == 'python':
valid_shebangs = ['#!/usr/bin/env python3', '#!/usr/bin/env python', '#!/usr/bin/python3']
if any(first_line.startswith(s) for s in valid_shebangs):
return TestResult(True, "Has valid Python shebang", script_name, "has_shebang")
return TestResult(False, f"Missing or invalid shebang: {first_line[:50]}", script_name, "has_shebang")

elif script_type == 'shell':
valid_shebangs = ['#!/bin/bash', '#!/usr/bin/env bash', '#!/bin/sh']
if any(first_line.startswith(s) for s in valid_shebangs):
return TestResult(True, "Has valid shell shebang", script_name, "has_shebang")
return TestResult(False, f"Missing or invalid shebang: {first_line[:50]}", script_name, "has_shebang")

return TestResult(True, "Shebang not required", script_name, "has_shebang")

def test_has_docstring(self, script_path: Path, script_name: str, script_type: str) -> TestResult:
"""Test that script has documentation header"""
content = script_path.read_text(encoding='utf-8')

if script_type == 'python':
# Check for module docstring
if '"""' in content[:500] or "'''" in content[:500]:
return TestResult(True, "Has Python docstring", script_name, "has_docstring")
return TestResult(False, "Missing module docstring", script_name, "has_docstring")

elif script_type == 'shell':
# Check for comment header
lines = content.split('\n')
has_header = any(line.startswith('#') and len(line) > 10 for line in lines[1:10])
if has_header:
return TestResult(True, "Has shell comment header", script_name, "has_docstring")
return TestResult(False, "Missing comment documentation", script_name, "has_docstring")

return TestResult(True, "Documentation not required", script_name, "has_docstring")

def test_no_hardcoded_paths(self, script_path: Path, script_name: str) -> TestResult:
"""Test that script doesn't have hardcoded absolute paths"""
content = script_path.read_text(encoding='utf-8')

# Common hardcoded path patterns to avoid
bad_patterns = [
r'/Users/\w+/',
r'/home/\w+/',
r'C:\\Users\\\w+',
r'/var/www/',
]

found = []
for pattern in bad_patterns:
matches = re.findall(pattern, content)
if matches:
found.extend(matches[:2])

if found:
return TestResult(
passed=False,
message=f"Hardcoded paths found: {', '.join(set(found))}",
script=script_name,
test_name="no_hardcoded_paths"
)
return TestResult(True, "No hardcoded paths", script_name, "no_hardcoded_paths")

def test_no_secrets(self, script_path: Path, script_name: str) -> TestResult:
"""Test that script doesn't contain potential secrets"""
content = script_path.read_text(encoding='utf-8')

# Patterns that might indicate secrets
secret_patterns = [
(r'api[_-]?key\s*=\s*["\'][^"\']{20,}["\']', 'API key'),
(r'password\s*=\s*["\'][^"\']+["\']', 'password'),
(r'secret\s*=\s*["\'][^"\']{10,}["\']', 'secret'),
(r'token\s*=\s*["\'][^"\']{20,}["\']', 'token'),
(r'-----BEGIN.*PRIVATE KEY-----', 'private key'),
]

found = []
for pattern, name in secret_patterns:
if re.search(pattern, content, re.IGNORECASE):
found.append(name)

if found:
return TestResult(
passed=False,
message=f"Potential secrets found: {', '.join(found)}",
script=script_name,
test_name="no_secrets"
)
return TestResult(True, "No secrets detected", script_name, "no_secrets")

def test_is_executable(self, script_path: Path, script_name: str) -> TestResult:
"""Test that script has executable permissions (warning only)"""
is_exec = os.access(script_path, os.X_OK)
if is_exec:
return TestResult(True, "File is executable", script_name, "is_executable")
return TestResult(True, "File not executable (consider chmod +x)", script_name, "is_executable")

def test_minimum_content(self, script_path: Path, script_name: str) -> TestResult:
"""Test that script has minimum content"""
content = script_path.read_text(encoding='utf-8')
if len(content) < 50:
return TestResult(
passed=False,
message=f"Script too short ({len(content)} chars)",
script=script_name,
test_name="minimum_content"
)
return TestResult(True, f"Adequate content ({len(content)} chars)", script_name, "minimum_content")

def validate_script(self, script_path: Path, script_type: str) -> ScriptValidation:
"""Run all validation tests on a script"""
script_name = script_path.stem
tests = []
errors = []
warnings = []

# Basic tests
tests.append(self.test_file_exists(script_path, script_name))

# Syntax tests
if script_type == 'python':
tests.append(self.test_python_syntax(script_path, script_name))
elif script_type == 'shell':
tests.append(self.test_shell_syntax(script_path, script_name))

# Documentation tests
tests.append(self.test_has_shebang(script_path, script_name, script_type))
tests.append(self.test_has_docstring(script_path, script_name, script_type))

# Security tests
tests.append(self.test_no_hardcoded_paths(script_path, script_name))
tests.append(self.test_no_secrets(script_path, script_name))

# Quality tests
tests.append(self.test_minimum_content(script_path, script_name))

# Collect errors
for test in tests:
if not test.passed:
errors.append(f"{test.test_name}: {test.message}")

passed = all(t.passed for t in tests)

return ScriptValidation(
name=script_name,
path=str(script_path),
script_type=script_type,
passed=passed,
tests=tests,
errors=errors,
warnings=warnings
)

def run_all(self, specific_script: Optional[str] = None) -> bool:
"""Run validation on all scripts"""
scripts = self.get_all_scripts()

if specific_script:
scripts = [(s, t) for s, t in scripts if s.stem == specific_script]
if not scripts:
print(f"{Colors.RED}Script not found: {specific_script}{Colors.RESET}")
return False

print(f"{Colors.BOLD}CODITECT Script Validation Test Suite{Colors.RESET}")
print("=" * 50)

python_count = sum(1 for _, t in scripts if t == 'python')
shell_count = sum(1 for _, t in scripts if t == 'shell')
print(f"\nValidating {len(scripts)} scripts ({python_count} Python, {shell_count} Shell)...\n")

passed_count = 0
failed_count = 0
total_tests = 0

for script_path, script_type in scripts:
validation = self.validate_script(script_path, script_type)
self.results.append(validation)

total_tests += len(validation.tests)
passed_tests = sum(1 for t in validation.tests if t.passed)

if validation.passed:
passed_count += 1
if self.verbose:
print(f"{validation.name} ({script_type}): {Colors.GREEN}PASS{Colors.RESET} ({passed_tests}/{len(validation.tests)} tests)")
else:
print(f"{validation.name}: {Colors.GREEN}PASS{Colors.RESET}")
else:
failed_count += 1
print(f"{validation.name}: {Colors.RED}FAIL{Colors.RESET}")
for error in validation.errors:
print(f" {Colors.RED}✗{Colors.RESET} {error}")

# Summary
print("\n" + "=" * 50)
print(f"{Colors.BOLD}Test Summary{Colors.RESET}")
print("=" * 50)
print(f"\nScripts: {passed_count}/{len(scripts)} passed")
print(f"Tests: {sum(1 for r in self.results for t in r.tests if t.passed)}/{total_tests} passed")

if failed_count == 0:
print(f"\n{Colors.GREEN}{Colors.BOLD}All scripts passed validation!{Colors.RESET}")
return True
else:
print(f"\n{Colors.RED}{Colors.BOLD}{failed_count} script(s) failed validation{Colors.RESET}")
return False

def get_framework_root() -> Path: """Get framework root directory""" script_path = Path(file).resolve() return script_path.parent.parent.parent

def main(): parser = argparse.ArgumentParser(description='CODITECT Script Validation Test Suite') parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output') parser.add_argument('--script', type=str, help='Test specific script') parser.add_argument('--json', action='store_true', help='Output results as JSON') args = parser.parse_args()

framework_root = get_framework_root()
suite = ScriptTestSuite(framework_root, verbose=args.verbose)

success = suite.run_all(specific_script=args.script)

if args.json:
results = {
'total_scripts': len(suite.results),
'passed': sum(1 for r in suite.results if r.passed),
'failed': sum(1 for r in suite.results if not r.passed),
'scripts': [
{
'name': r.name,
'type': r.script_type,
'passed': r.passed,
'errors': r.errors
}
for r in suite.results
]
}
print(json.dumps(results, indent=2))

sys.exit(0 if success else 1)

if name == 'main': main()