typespec1-10 Exercise 3: Checking Response Bodies Against the Contract ========================================================================= The contract: chapter 5's books.tsp compiled to OpenAPI 3.1 JSON with seal-object-schemas: true (extra properties not allowed). The script (body_check.py): """Check the BODIES a running Book Service returns against the schemas in the contract. py body_check.py PORT The contract is books_sealed.json: TypeSpec's OpenAPI 3.1 output with seal-object-schemas, so extra properties are not allowed. """ import json import pathlib import sys import urllib.error import urllib.request from jsonschema import Draft202012Validator from referencing import Registry, Resource contract = json.loads(pathlib.Path(__file__).with_name("books_sealed.json").read_text(encoding="utf8")) wrapper = {"$schema": "https://json-schema.org/draft/2020-12/schema", "components": contract["components"]} registry = Registry().with_resource("urn:contract", Resource.from_contents(wrapper)) base = f"http://127.0.0.1:{sys.argv[1]}" def check(label, method, path, schema_name, data=None): request = urllib.request.Request(base + path, method=method, data=None if data is None else json.dumps(data).encode(), headers={"Content-Type": "application/json"}) try: with urllib.request.urlopen(request) as r: status, body = r.status, r.read() except urllib.error.HTTPError as e: status, body = e.code, e.read() schema = {"$ref": f"urn:contract#/components/schemas/{schema_name}"} errors = list(Draft202012Validator(schema, registry=registry).iter_errors(json.loads(body))) print(f"{label:<28} {status} against {schema_name:<9} {'ok' if not errors else '; '.join(e.message[:70] for e in errors)}") check("GET /books/1", "GET", "/books/1", "Book") check("GET /books/99 (missing)", "GET", "/books/99", "NotFound") check("POST /books", "POST", "/books", "Book", {"isbn": "9780261103573", "title": "The Hobbit", "pages": 310}) Results: === port 8751 GET /books/1 200 against Book ok GET /books/99 (missing) 404 against NotFound Unevaluated properties are not valid under the given schema ('_' was u POST /books 201 against Book ok === port 8741 GET /books/1 200 against Book ok GET /books/99 (missing) 404 against NotFound ok POST /books 201 against Book ok THE DIFFERENCE Port 8741 is my faithful toy server: its 404 body is {"message": "..."} which matches NotFound. Port 8751 is the server generated by @typespec/http-server-js: its 404 body is {"_":404,"message":"..."}. The sealed schema rejects the '_' property ('Unevaluated properties are not valid'). The generated router wrote the whole object, including the property that carries the status code, as the body. Without seal-object-schemas the same check passes, because JSON Schema allows extra properties unless told otherwise: the sealing is what makes the check useful here. (I did not run it unsealed; that follows from the chapter 9 test, where an extra property was accepted by default.) WHAT THE CHECK DOES NOT DO Three calls only, response bodies only: it does not check status codes (chapter 4's checker did), headers such as location, or request handling. WHY THIS WORKS AS AN ANSWER --------------------------- It finds a real contract violation in generated code that compiled strictly, ran, returned the right statuses and passed a status-code check. That gap is the reason for the chapter's advice to test with a body check.