m2-market/schemas/tests/test_schemas.py

113 lines
3.8 KiB
Python

"""Schema validation tests for schemas/solution.schema.json and schemas/listing.schema.json.
Frozen v1 schemas (specs/001-market-first-wedge/data-model.md) — do not modify.
"""
import json
from pathlib import Path
import pytest
from jsonschema import Draft202012Validator
SCHEMAS_DIR = Path(__file__).resolve().parent.parent
FIXTURES_DIR = Path(__file__).resolve().parent / "fixtures"
def load(path):
with open(path) as f:
return json.load(f)
SOLUTION_SCHEMA = load(SCHEMAS_DIR / "solution.schema.json")
LISTING_SCHEMA = load(SCHEMAS_DIR / "listing.schema.json")
solution_validator = Draft202012Validator(SOLUTION_SCHEMA)
listing_validator = Draft202012Validator(LISTING_SCHEMA)
def fixture(name):
return load(FIXTURES_DIR / name)
def errors(validator, instance):
return list(validator.iter_errors(instance))
def flatten_errors(errs):
flat = []
stack = list(errs)
while stack:
err = stack.pop()
flat.append(err)
stack.extend(err.context)
return flat
# --- valid fixtures ---------------------------------------------------------
def test_valid_solution_passes():
errs = errors(solution_validator, fixture("valid_solution.json"))
assert errs == [], [e.message for e in errs]
def test_valid_listing_passes():
errs = errors(listing_validator, fixture("valid_listing.json"))
assert errs == [], [e.message for e in errs]
# --- invalid solution fixtures ----------------------------------------------
def test_solution_missing_evidence_fails_on_evidence_minitems():
errs = errors(solution_validator, fixture("solution_missing_evidence.json"))
assert errs, "expected validation errors"
assert any(
e.validator == "minItems" and list(e.path) == ["evidence"]
for e in errs
), [(e.validator, list(e.path), e.message) for e in errs]
def test_solution_tenant_violation_fails_on_else_branch():
errs = errors(solution_validator, fixture("solution_tenant_violation.json"))
assert errs, "expected validation errors"
# tenant_scope != "m2-core" routes through the top-level if/else; the
# else branch requires owner_initiated + scrub_status, both absent here.
# jsonschema surfaces these as top-level "required" errors (path == []).
missing_required_props = {
prop
for e in errs
if e.validator == "required" and list(e.path) == []
for prop in ("owner_initiated", "scrub_status")
if f"'{prop}'" in e.message
}
assert missing_required_props == {"owner_initiated", "scrub_status"}, [
(e.validator, list(e.path), e.message) for e in errs
]
def test_solution_bad_price_fails_on_price_amount_minimum():
errs = errors(solution_validator, fixture("solution_bad_price.json"))
assert errs, "expected validation errors"
flat = flatten_errors(errs)
assert any(
e.validator == "minimum" and list(e.path) in (["price", "amount"], ["amount"])
for e in flat
), [(e.validator, list(e.path), e.message) for e in flat]
# --- invalid listing fixtures ------------------------------------------------
def test_listing_bad_status_fails_on_status_enum():
errs = errors(listing_validator, fixture("listing_bad_status.json"))
assert errs, "expected validation errors"
assert any(
e.validator == "enum" and list(e.path) == ["status"]
for e in errs
), [(e.validator, list(e.path), e.message) for e in errs]
def test_listing_missing_tenant_visibility_fails_on_required():
errs = errors(listing_validator, fixture("listing_missing_tenant_visibility.json"))
assert errs, "expected validation errors"
assert any(
e.validator == "required" and "tenant_visibility" in e.message and list(e.path) == []
for e in errs
), [(e.validator, list(e.path), e.message) for e in errs]