From a06cc2875e60870b2bef11a47f8ccb1427fe7494 Mon Sep 17 00:00:00 2001 From: sujata-m Date: Tue, 30 Sep 2025 16:26:04 +0530 Subject: [PATCH] [0.2.15] Fixed Bug 2404 ## Dev Board Ticket https://dev.azure.com/TDEI-UW/TDEI/_workitems/edit/2404 ## Changes - Updated geometry schemas to require the canonical `$schema` field (`https://sidewalks.washington.edu/opensidewalks/0.2/schema.json`). - Added validation tests ensuring missing or incorrect `$schema` metadata is surfaced as an error. ## Testing - Added new unit test cases to support the changes --- CHANGELOG.md | 5 ++ .../schema/Linestring_schema.json | 1 + .../schema/Point_schema.json | 1 + .../schema/Polygon_schema.json | 1 + src/python_osw_validation/version.py | 2 +- tests/unit_tests/test_schema_metadata.py | 85 +++++++++++++++++++ 6 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 tests/unit_tests/test_schema_metadata.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 6040b38..ab9585b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change log +### 0.2.15 +- Update the base schema to make the $schema key is required +- Added unit test cases for that + + ### 0.2.14 - Improved GeoJSON parse error reporting with detailed file, line, and column context. - Added unit tests covering JSON parsing and file read failure scenarios. diff --git a/src/python_osw_validation/schema/Linestring_schema.json b/src/python_osw_validation/schema/Linestring_schema.json index 5694383..991cfc5 100644 --- a/src/python_osw_validation/schema/Linestring_schema.json +++ b/src/python_osw_validation/schema/Linestring_schema.json @@ -2,6 +2,7 @@ "title": "root", "type": "object", "required": [ + "$schema", "type", "features" ], diff --git a/src/python_osw_validation/schema/Point_schema.json b/src/python_osw_validation/schema/Point_schema.json index 9990cd3..462317e 100644 --- a/src/python_osw_validation/schema/Point_schema.json +++ b/src/python_osw_validation/schema/Point_schema.json @@ -2,6 +2,7 @@ "title": "root", "type": "object", "required": [ + "$schema", "type", "features" ], diff --git a/src/python_osw_validation/schema/Polygon_schema.json b/src/python_osw_validation/schema/Polygon_schema.json index 0540b2f..17f37df 100644 --- a/src/python_osw_validation/schema/Polygon_schema.json +++ b/src/python_osw_validation/schema/Polygon_schema.json @@ -2,6 +2,7 @@ "title": "root", "type": "object", "required": [ + "$schema", "type", "features" ], diff --git a/src/python_osw_validation/version.py b/src/python_osw_validation/version.py index 4a8a4ed..aace94f 100644 --- a/src/python_osw_validation/version.py +++ b/src/python_osw_validation/version.py @@ -1 +1 @@ -__version__ = '0.2.14' \ No newline at end of file +__version__ = '0.2.15' \ No newline at end of file diff --git a/tests/unit_tests/test_schema_metadata.py b/tests/unit_tests/test_schema_metadata.py new file mode 100644 index 0000000..080c000 --- /dev/null +++ b/tests/unit_tests/test_schema_metadata.py @@ -0,0 +1,85 @@ +import json +import os +import tempfile +import unittest +import zipfile + +from src.python_osw_validation import OSWValidation + +ASSETS_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "assets") +MINIMAL_ZIP = os.path.join(ASSETS_DIR, "minimal.zip") +UW_ZONES_VALID_ZIP = os.path.join(ASSETS_DIR, "UW.zones.valid.zip") + + +class TestSchemaRequirementDuringValidation(unittest.TestCase): + """Ensure that GeoJSON files without $schema fail validation.""" + + def _validate_missing_schema(self, source_zip: str, relative_paths): + with tempfile.TemporaryDirectory() as tmpdir: + extracted_dir = os.path.join(tmpdir, "extracted") + os.makedirs(extracted_dir, exist_ok=True) + + with zipfile.ZipFile(source_zip) as src_zip: + src_zip.extractall(extracted_dir) + + for rel_path in relative_paths: + geojson_path = os.path.join(extracted_dir, rel_path) + with open(geojson_path, encoding="utf-8") as geojson_file: + data = json.load(geojson_file) + data.pop("$schema", None) + with open(geojson_path, "w", encoding="utf-8") as geojson_file: + json.dump(data, geojson_file) + + modified_zip = os.path.join(tmpdir, "modified.zip") + with zipfile.ZipFile(modified_zip, "w", zipfile.ZIP_DEFLATED) as dst_zip: + for root, _, files in os.walk(extracted_dir): + for filename in files: + file_path = os.path.join(root, filename) + arcname = os.path.relpath(file_path, extracted_dir) + dst_zip.write(file_path, arcname) + + validation = OSWValidation(zipfile_path=modified_zip) + return validation.validate(max_errors=5) + + def _assert_missing_schema_failure(self, result): + self.assertFalse(result.is_valid) + self.assertIsNotNone(result.errors) + self.assertTrue( + any("\"$schema\" is a required property" in error for error in result.errors), + f"Expected missing $schema error, got: {result.errors}", + ) + self.assertIsNotNone(result.issues) + self.assertTrue( + any( + "\"$schema\" is a required property" in message + for issue in result.issues + for message in ( + issue.get("error_message") + if isinstance(issue.get("error_message"), list) + else [issue.get("error_message")] + ) + if message + ), + f"Expected missing $schema issue, got: {result.issues}", + ) + + def test_point_geojson_requires_schema(self): + result = self._validate_missing_schema( + MINIMAL_ZIP, + ["minimal/wa.microsoft.graph.nodes.OSW.geojson"], + ) + self._assert_missing_schema_failure(result) + + def test_linestring_geojson_requires_schema(self): + result = self._validate_missing_schema( + MINIMAL_ZIP, + ["minimal/wa.microsoft.graph.edges.OSW.geojson"], + ) + self._assert_missing_schema_failure(result) + + def test_polygon_geojson_requires_schema(self): + result = self._validate_missing_schema( + UW_ZONES_VALID_ZIP, + ["UW.zones.valid/UW.zones.geojson"], + ) + self._assert_missing_schema_failure(result) \ No newline at end of file