"""Schema-invalid synthetic manifests must not crash semantic validation.""" from __future__ import annotations import io import json import sys import tempfile import unittest from contextlib import redirect_stdout from pathlib import Path from unittest import mock REPO_ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(REPO_ROOT / "scripts")) try: import validate_schemas except ImportError as exc: validate_schemas = None IMPORT_ERROR = str(exc) else: IMPORT_ERROR = "" @unittest.skipIf(validate_schemas is None, f"validate_schemas unavailable: {IMPORT_ERROR}") class SemanticManifestValidation(unittest.TestCase): def test_invalid_synthetic_manifest_reports_schema_errors_without_crashing(self): with tempfile.TemporaryDirectory(dir=REPO_ROOT) as directory: root = Path(directory) (root / "emulators").mkdir() platforms = root / "platforms" platforms.mkdir() (platforms / "_data_dirs.yml").write_text( "data_directories: {}\n", encoding="utf-8" ) (root / "database.json").write_text( json.dumps({"files": None}), encoding="utf-8" ) install = root / "install" install.mkdir() (install / "synthetic.json").write_text( json.dumps({"files": None}), encoding="utf-8" ) api = root / "website" / "public" / "api" / "v1" api.mkdir(parents=True) (api / "catalog.json").write_text( json.dumps({"count": 1, "items": None}), encoding="utf-8" ) output = io.StringIO() with mock.patch.object(validate_schemas, "ROOT", root), mock.patch.object( sys, "argv", ["validate_schemas.py"] ), redirect_stdout(output): result = validate_schemas.main() self.assertEqual(result, 1) text = output.getvalue().replace("\\", "/") self.assertIn("database.json", text) self.assertIn("install/synthetic.json", text) self.assertIn("website/public/api/v1/catalog.json", text) self.assertIn("files", text) self.assertIn("items", text) self.assertNotIn("Traceback", text) def test_valid_synthetic_manifest_still_runs_semantic_checks(self): manifest = { "manifest_version": 2, "source": "full", "regions": [], "platform": "synthetic", "display_name": "Synthetic", "version": "test", "generated": "2026-10-21T00:00:00Z", "base_destination": "", "detect": [], "standalone_copies": [], "total_files": 1, "total_size": 0, "total_omitted": 0, "omitted_files": [], "files": [], } with tempfile.TemporaryDirectory(dir=REPO_ROOT) as directory: path = Path(directory) / "synthetic.json" path.write_text(json.dumps(manifest), encoding="utf-8") errors = validate_schemas._validate_json_files( [path], "install-manifest.schema.json", validate_schemas._semantic_install_checks, ) self.assertEqual(len(errors), 1) self.assertIn("total_files does not equal len(files)", errors[0]) if __name__ == "__main__": unittest.main() try: import jsonschema except ImportError: jsonschema = None @unittest.skipIf(jsonschema is None, "jsonschema unavailable") class DataRootIsKeyedBySystem(unittest.TestCase): """AetherSX2 gave Android/data/xyz.aethersx2.android only in its notes: the site could not print the folder, nor which OS tabs apply.""" def _errors(self, value): schema = json.loads((REPO_ROOT / "schemas/emulator.schema.json").read_text(encoding="utf-8")) validator = jsonschema.Draft202012Validator(schema["properties"]["data_root"]) return list(validator.iter_errors(value)) def test_a_system_with_its_path_and_reference(self): self.assertEqual(self._errors({"android": { "path": "Android/data/xyz.aethersx2.android/files", "source_ref": "a.kt:1"}}), []) def test_an_unknown_system_or_a_path_without_reference(self): self.assertTrue(self._errors({"amiga": {"path": "x", "source_ref": "y"}})) self.assertTrue(self._errors({"windows": {"path": "%APPDATA%/X"}})) self.assertTrue(self._errors({}))