diff --git a/wizard-work/spark/json_validate_standalone.py b/wizard-work/spark/json_validate_standalone.py new file mode 100644 index 0000000..0f60175 --- /dev/null +++ b/wizard-work/spark/json_validate_standalone.py @@ -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 [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() \ No newline at end of file diff --git a/wizard-work/spark/test_json_validator.py b/wizard-work/spark/test_json_validator.py new file mode 100644 index 0000000..7dfaea3 --- /dev/null +++ b/wizard-work/spark/test_json_validator.py @@ -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()) \ No newline at end of file