spark: add JSON validator CLI with test suite (4/4 tests pass)
This commit is contained in:
1 parent
838588136c
commit
c72e1de9ff
2 files changed
+157
No files matched your search
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
"""JSON Validator CLI - Validates JSON files and reports syntax errors."""
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def validate_json_file(filepath: str) -> dict:
|
||||
"""Validate a JSON file and return result dict."""
|
||||
result = {
|
||||
"file": filepath,
|
||||
"valid": False,
|
||||
"error": None,
|
||||
"line": None,
|
||||
"column": None,
|
||||
"decoded": None
|
||||
}
|
||||
|
||||
try:
|
||||
content = Path(filepath).read_text()
|
||||
except FileNotFoundError:
|
||||
result["error"] = f"File not found: {filepath}"
|
||||
return result
|
||||
except Exception as e:
|
||||
result["error"] = f"Read error: {e}"
|
||||
return result
|
||||
|
||||
try:
|
||||
data = json.loads(content)
|
||||
result["valid"] = True
|
||||
result["decoded"] = data
|
||||
return result
|
||||
except json.JSONDecodeError as e:
|
||||
result["error"] = str(e.msg)
|
||||
result["line"] = e.lineno
|
||||
result["column"] = e.colno
|
||||
# Show context around error
|
||||
lines = content.split('\n')
|
||||
if e.lineno and e.lineno <= len(lines):
|
||||
result["context"] = lines[e.lineno - 1]
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: json_validate_standalone.py <file.json> [file2.json...]")
|
||||
sys.exit(1)
|
||||
|
||||
exit_code = 0
|
||||
results = []
|
||||
|
||||
for filepath in sys.argv[1:]:
|
||||
result = validate_json_file(filepath)
|
||||
results.append(result)
|
||||
|
||||
if result["valid"]:
|
||||
print(f"VALID: {filepath}")
|
||||
else:
|
||||
print(f"INVALID: {filepath}")
|
||||
print(f" Error: {result['error']}")
|
||||
if result.get('line'):
|
||||
print(f" Line: {result['line']}, Column: {result['column']}")
|
||||
if result.get('context'):
|
||||
print(f" Context: {result['context'][:60]}")
|
||||
exit_code = 1
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test suite for json_validate_standalone.py"""
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
def run_validator(files):
|
||||
"""Run validator and return (exit_code, output)."""
|
||||
cmd = ['python3', '/root/.hermes/wizard-outputs/spark/json_validate_standalone.py'] + files
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
return result.returncode, result.stdout
|
||||
|
||||
def test_valid_json():
|
||||
"""Test that valid JSON returns exit code 0."""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
||||
json.dump({"test": "data"}, f)
|
||||
f.flush()
|
||||
exit_code, output = run_validator([f.name])
|
||||
Path(f.name).unlink()
|
||||
assert exit_code == 0, f"Expected 0, got {exit_code}"
|
||||
assert "VALID:" in output, f"Expected 'VALID:' in output"
|
||||
return True
|
||||
|
||||
def test_invalid_json():
|
||||
"""Test that invalid JSON returns exit code 1."""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
||||
f.write('{invalid json')
|
||||
f.flush()
|
||||
exit_code, output = run_validator([f.name])
|
||||
Path(f.name).unlink()
|
||||
assert exit_code == 1, f"Expected 1, got {exit_code}"
|
||||
assert "INVALID:" in output, f"Expected 'INVALID:' in output"
|
||||
return True
|
||||
|
||||
def test_nonexistent_file():
|
||||
"""Test handling of nonexistent file."""
|
||||
exit_code, output = run_validator(['/nonexistent/file.json'])
|
||||
assert exit_code == 1, f"Expected 1 for nonexistent file"
|
||||
assert "File not found" in output, f"Expected 'File not found' in output"
|
||||
return True
|
||||
|
||||
def test_multiple_files_mixed():
|
||||
"""Test with multiple files, some valid some invalid."""
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f1:
|
||||
json.dump({"a": 1}, f1)
|
||||
f1.flush()
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f2:
|
||||
f2.write('{bad}')
|
||||
f2.flush()
|
||||
exit_code, output = run_validator([f1.name, f2.name])
|
||||
Path(f1.name).unlink()
|
||||
Path(f2.name).unlink()
|
||||
assert exit_code == 1, "Should fail due to invalid file"
|
||||
assert "VALID:" in output and "INVALID:" in output
|
||||
return True
|
||||
|
||||
def main():
|
||||
tests = [
|
||||
("Valid JSON", test_valid_json),
|
||||
("Invalid JSON", test_invalid_json),
|
||||
("Nonexistent file", test_nonexistent_file),
|
||||
("Multiple files mixed", test_multiple_files_mixed),
|
||||
]
|
||||
|
||||
passed = 0
|
||||
failed = 0
|
||||
|
||||
for name, test_fn in tests:
|
||||
try:
|
||||
test_fn()
|
||||
print(f"PASS: {name}")
|
||||
passed += 1
|
||||
except AssertionError as e:
|
||||
print(f"FAIL: {name} - {e}")
|
||||
failed += 1
|
||||
except Exception as e:
|
||||
print(f"ERROR: {name} - {e}")
|
||||
failed += 1
|
||||
|
||||
print(f"\nResults: {passed} passed, {failed} failed")
|
||||
return 0 if failed == 0 else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user