From 5f9c04c0aa1be989fdaa77e2c2f99a26e848c817 Mon Sep 17 00:00:00 2001 From: mmaxjr Date: Thu, 6 Aug 2026 12:27:06 -0300 Subject: [PATCH] Enforce record format validation --- pyproject.toml | 4 +++- scripts/validate_records.py | 19 +++++++++++++------ tests/test_validate_data.py | 23 +++++++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 28f1704..ca6b4e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,8 @@ dev = [ # GPL, and over the [format-nongpl] extra, which pulls in thirteen # distributions to reach the same two checkers. "rfc3986-validator>=0.1.1", + # Registers the "date-time" format checker used by AVE record metadata. + "rfc3339-validator>=0.1.4", ] # Not a Python library -- nothing here imports "bawbel_ave" as a package. @@ -30,4 +32,4 @@ dev = [ packages = [] [tool.pytest.ini_options] -testpaths = ["tests"] \ No newline at end of file +testpaths = ["tests"] diff --git a/scripts/validate_records.py b/scripts/validate_records.py index a760cbf..b990e30 100644 --- a/scripts/validate_records.py +++ b/scripts/validate_records.py @@ -12,10 +12,11 @@ # consumer of the corpus. A stray vendor product name is a neutrality # violation this project enforces everywhere else; records shouldn't be # the one place it's unchecked. -# How: jsonschema.Draft202012Validator against schema/ave-record-1.1.0.schema.json -# (handles the draft-vs-active conditional required set natively), plus a -# handful of checks the schema's additionalProperties:false already implies -# but which deserve a readable, named failure message of their own +# How: jsonschema.Draft202012Validator with format checking enabled against +# schema/ave-record-1.1.0.schema.json (handles the draft-vs-active +# conditional required set natively and enforces date-time / uri metadata), +# plus a handful of checks the schema's additionalProperties:false already +# implies but which deserve a readable, named failure message of their own import json import re import sys @@ -55,6 +56,12 @@ def check_schema(record: dict, validator: jsonschema.Draft202012Validator) -> li for e in validator.iter_errors(record)] +def build_validator(schema: dict) -> jsonschema.Draft202012Validator: + return jsonschema.Draft202012Validator( + schema, format_checker=jsonschema.Draft202012Validator.FORMAT_CHECKER + ) + + def check_no_old_field_names(record: dict) -> list[str]: return [f"stale field name '{f}' still present (renamed in v1.1.0)" for f in OLD_FIELD_NAMES if f in record] @@ -136,7 +143,7 @@ def check_no_vendor_boilerplate(raw_text: str) -> list[str]: def main() -> int: schema = json.loads(SCHEMA_PATH.read_text()) jsonschema.Draft202012Validator.check_schema(schema) - validator = jsonschema.Draft202012Validator(schema) + validator = build_validator(schema) paths = sorted(RECORDS_DIR.glob("AVE-*.json")) if not paths: @@ -169,4 +176,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) diff --git a/tests/test_validate_data.py b/tests/test_validate_data.py index e69de29..80884b1 100644 --- a/tests/test_validate_data.py +++ b/tests/test_validate_data.py @@ -0,0 +1,23 @@ +import json + +from scripts import validate_records + + +def test_record_validator_rejects_invalid_date_time_format(): + schema = json.loads(validate_records.SCHEMA_PATH.read_text(encoding="utf-8")) + validator = validate_records.build_validator(schema) + record = { + "ave_id": "AVE-2026-99999", + "schema_version": "1.1.0", + "status": "draft", + "title": "Invalid date-time fixture", + "description": "A minimal draft record with malformed published metadata.", + "attack_class": "test", + "behavioral_fingerprint": "test", + "references": [{"title": "Example", "url": "https://example.com"}], + "published": "not-a-date-time", + } + + errors = validate_records.check_schema(record, validator) + + assert any("not-a-date-time" in error for error in errors)