#!/usr/bin/env python3 """Emissar Resolve v0.1 checks. python validate.py 1. Runs resolve-example.mjs --json and validates every record against the JSON Schema root, every 200 body against $defs/LookupResponse and every problem body against $defs/Problem (Draft 2020-12, with format checks). 2. Negative tests: records the schema MUST reject (an observed tel record, an observed record claiming verified status, a brand record without an organization) are rejected. 3. Parses the OpenAPI YAML, validates it with openapi-spec-validator when installed, resolves every $ref, and checks that each status code the example returned is declared for the matching path. Needs: pip install --user jsonschema pyyaml rfc3339-validator (and optionally openapi-spec-validator). """ import copy import json import pathlib import re import subprocess import sys import yaml from jsonschema import Draft202012Validator, FormatChecker HERE = pathlib.Path(__file__).resolve().parent SPECS = HERE.parent.parent SCHEMA_PATH = SPECS / "schemas" / "resolve-v0.1.schema.json" OPENAPI_PATH = SPECS / "openapi" / "resolve-v0.1.yaml" failures = [] def check(ok, label): print(f" {'PASS' if ok else 'FAIL'} {label}") if not ok: failures.append(label) schema = json.loads(SCHEMA_PATH.read_text(encoding="utf-8")) Draft202012Validator.check_schema(schema) fc = FormatChecker() root = Draft202012Validator(schema, format_checker=fc) def sub(name): return Draft202012Validator({**schema, "$ref": f"#/$defs/{name}"}, format_checker=fc) lookup_v, problem_v = sub("LookupResponse"), sub("Problem") out = subprocess.run( ["node", str(HERE / "resolve-example.mjs"), "--json"], capture_output=True, text=True, encoding="utf-8", check=True ).stdout data = json.loads(out) print(f"JSON Schema ({SCHEMA_PATH.name}, Draft 2020-12, format checks on)") for r in data["records"]: errs = list(root.iter_errors(r)) check(not errs, f"record {r['id']} {r['identifier']['type']}:{r['identifier']['value']} ({r['verification']['level']})" + (f": {errs[0].message}" if errs else "")) for resp in data["responses"]: body, status = resp["body"], resp["status"] if body is None: check(status in (304, 308), f"{status} {resp['request']['path']} has no body") continue v = lookup_v if status == 200 else problem_v errs = list(v.iter_errors(body)) check(not errs, f"{status} {resp['request']['path']} body is a valid {'LookupResponse' if status == 200 else 'Problem'}" + (f": {errs[0].message}" if errs else "")) print("Schema negative tests (each MUST be rejected)") observed = next(r for r in data["records"] if r["source"] == "crawl") claim_tel = next(r for r in data["records"] if r["identifier"]["type"] == "tel") claim_brand = next(r for r in data["records"] if r["identifier"]["type"] == "brand") bad = [] x = copy.deepcopy(observed); x["identifier"] = {"type": "tel", "value": "+15195550142"}; bad.append(("observed tel record", x)) x = copy.deepcopy(observed); x["verification"]["level"] = "verified-control"; bad.append(("crawl record marked verified-control", x)) x = copy.deepcopy(observed); x["identifier"] = {"type": "brand", "value": "opensea", "country": "US"}; bad.append(("brand record from a crawl", x)) x = copy.deepcopy(claim_tel); x["source"] = "claim"; x["verification"]["level"] = "observed"; bad.append(("claimed record at level observed", x)) x = copy.deepcopy(claim_brand); del x["organization"]; bad.append(("brand record without organization", x)) x = copy.deepcopy(claim_brand); x["verification"]["level"] = "verified-control"; x.pop("organization"); bad.append(("brand record below verified-organization", x)) x = copy.deepcopy(claim_tel); x["verification"]["method"] = "dns-txt"; bad.append(("tel record verified by dns-txt", x)) x = copy.deepcopy(claim_tel); x["verification"]["level"] = "verified-control"; x.pop("organization"); bad.append(("tel record below verified-organization", x)) x = copy.deepcopy(claim_tel); x["identifier"]["value"] = "+1 519 555 0142"; bad.append(("non-canonical tel value", x)) x = copy.deepcopy(observed); x["identifier"]["value"] = "OpenSea.io"; bad.append(("non-canonical domain value", x)) x = copy.deepcopy(observed); x["cardUrl"] = "http://opensea.io/.well-known/agent-card.json"; bad.append(("http card URL", x)) for label, rec in bad: check(not root.is_valid(rec), label) print(f"OpenAPI ({OPENAPI_PATH.name})") text = OPENAPI_PATH.read_text(encoding="utf-8") api = yaml.safe_load(text) check(str(api.get("openapi", "")).startswith("3.1"), f"YAML parses; openapi {api.get('openapi')}") try: from openapi_spec_validator import validate validate(api, base_uri=OPENAPI_PATH.as_uri()) check(True, "openapi-spec-validator: valid OpenAPI 3.1 (external $refs resolved)") except ImportError: print(" SKIP openapi-spec-validator not installed") except Exception as e: # noqa: BLE001 check(False, f"openapi-spec-validator: {str(e).splitlines()[0]}") def resolve_ref(ref): target, _, pointer = ref.partition("#") doc = api if not target else json.loads((OPENAPI_PATH.parent / target).read_text(encoding="utf-8")) node = doc for part in [p for p in pointer.split("/") if p]: node = node[part.replace("~1", "/").replace("~0", "~")] return node def walk(node): if isinstance(node, dict): if "$ref" in node: yield node["$ref"] for v in node.values(): yield from walk(v) elif isinstance(node, list): for v in node: yield from walk(v) refs = sorted(set(walk(api))) missing = [] for ref in refs: try: resolve_ref(ref) except (KeyError, FileNotFoundError): missing.append(ref) check(not missing, f"all {len(refs)} distinct $refs resolve" + (f"; missing: {missing}" if missing else "")) templates = {p: re.compile("^" + re.sub(r"\{[^/]+\}", "[^/]+", p) + "$") for p in api["paths"]} for resp in data["responses"]: p = resp["request"]["path"] match = next((t for t, rx in templates.items() if rx.match(p)), None) declared = match and str(resp["status"]) in api["paths"][match]["get"]["responses"] check(bool(declared), f"{resp['status']} for {p} is declared in the OpenAPI document" + (f" ({match})" if match else "")) print(f"\n{'ALL CHECKS PASSED' if not failures else f'{len(failures)} FAILED'}") sys.exit(1 if failures else 0)