trainlog/tests/test_timestamp_validation.py

57 lines
3.2 KiB
Python

#!/usr/bin/env python3
"""Focused frozen timestamp grammar and exact chronology regressions."""
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "tools"))
from validate_json import TrainlogSemanticError, parse_timestamp # noqa: E402
import validate_json as validation # noqa: E402
class TimestampValidationTest(unittest.TestCase):
def test_document_admission_does_not_depend_on_optional_format_checker(self):
schema = validation.load_json(validation.SCHEMA_PATH)
# Simulate missing, stricter RFC-only, and permissive optional checkers.
# Exercise the actual document path, including schema checks before semantics.
for mode in ("missing", "strict", "permissive"):
checker = validation.jsonschema.FormatChecker()
checker.checkers.pop("date-time", None)
if mode != "missing":
checker.checks("date-time")(
lambda value, strict=mode == "strict": not strict
or not isinstance(value, str)
or len(value) > 19 and value[16] == ":"
)
original_checks = checker.checkers.copy()
validator = validation.jsonschema.Draft202012Validator(schema, format_checker=checker)
with self.subTest(mode=mode):
self.assertEqual([], validation.validate_document(
validator, validation.VALID_FIXTURE_DIR / "temporal-extended-forms.json"))
self.assertTrue(validation.validate_document(
validator, validation.INVALID_FIXTURE_DIR / "timestamp-space-separator.json"))
self.assertEqual(original_checks, checker.checkers)
def test_selected_grammar(self):
for value in ("0001-01-01T00:00Z", "2000-02-29t23:59:59.00000000000000000001z",
"2026-09-05T18:34+23:59", "2026-09-05T18:34:12-00:00",
"9999-12-31T23:59:59.9-23:59"):
with self.subTest(value=value): self.assertIsNotNone(parse_timestamp(value, "probe"))
def test_platform_only_forms_rejected(self):
for value in ("0000-01-01T00:00:00Z", "2026-02-29T00:00:00Z",
"2026-09-05 18:34:12+02:00", "20260905T183412+0200",
"2026-W36-5T18:34:12+02:00", "2026-09-05T18:34:12,5+02:00",
"2026-09-05T18:34:12+0200", "2026-09-05T18:34:12+02",
"2026-09-05T18:34:12.5", "2026-09-05T18:34.5Z",
"2026-09-05T18:34:60Z", "2026-09-05T18:34:12+24:00"):
with self.subTest(value=value), self.assertRaises(TrainlogSemanticError):
parse_timestamp(value, "probe")
def test_exact_fraction_and_rollover(self):
earlier = parse_timestamp("2026-01-01T00:00:00.12345678901234567890Z", "probe")
later = parse_timestamp("2026-01-01T00:00:00.12345678901234567891Z", "probe")
equal = parse_timestamp("2026-01-01T00:00:00.1234567890123456789000Z", "probe")
self.assertLess(earlier, later); self.assertEqual(earlier, equal)
self.assertLess(parse_timestamp("2025-12-31T09:14:59Z", "probe"),
parse_timestamp("2026-01-01T00:15:00+15:00", "probe"))
if __name__ == "__main__": unittest.main()