from __future__ import annotations import json import unittest from jsonschema import Draft202012Validator from jsonschema.exceptions import ValidationError as JSONSchemaValidationError from scripts.export_contracts import CONTRACT_DIRECTORY, contract_documents from services.openai_realtime.bridge import OpenAIRealtimeBridge from services.openai_realtime.events import ( RealtimeEventError, error_event, require_event, ) from services.openai_realtime.schemas import ( EXTENSION_SERVER_EVENT_TYPES, SERVER_EVENT_TYPES, RealtimeServerEvent, ) class PublicContractSnapshotTests(unittest.TestCase): def test_generated_contracts_match_committed_snapshots(self): for name, generated in contract_documents().items(): path = CONTRACT_DIRECTORY / name self.assertTrue(path.exists(), f"Missing contract snapshot: {path}") committed = json.loads(path.read_text(encoding="utf-8")) self.assertEqual(committed, generated, f"Regenerate {name}") def test_openapi_declares_every_public_http_media_type(self): openapi = contract_documents()["openapi.json"] paths = openapi["paths"] self.assertIn("application/json", paths["/v1/responses"]["post"]["requestBody"]["content"]) response_content = paths["/v1/responses"]["post"]["responses"]["200"]["content"] self.assertIn("application/json", response_content) self.assertIn("text/event-stream", response_content) call_content = paths["/v1/realtime/calls"]["post"]["requestBody"]["content"] self.assertEqual( set(call_content), {"multipart/form-data", "application/sdp"}, ) self.assertIn( "application/sdp", paths["/v1/realtime/calls"]["post"]["responses"]["200"]["content"], ) public_operations = [ operation for path, methods in paths.items() if path.startswith("/v1/") for method, operation in methods.items() if method in {"get", "post", "delete"} ] for operation in public_operations: self.assertIn("401", operation["responses"]) self.assertIn("application/json", operation["responses"]["401"]["content"]) self.assertIn("security", operation) for response in operation["responses"].values(): self.assertIn("X-Request-ID", response.get("headers", {})) class RealtimeEventContractTests(unittest.TestCase): def test_client_schema_validates_supported_event_and_links_errors(self): schema = contract_documents()[ "openai-realtime-client-events.schema.json" ] Draft202012Validator.check_schema(schema) validator = Draft202012Validator(schema) event = { "type": "conversation.item.create", "event_id": "evt_123", "item": { "type": "message", "role": "user", "content": [{"type": "input_text", "text": "你好"}], }, } validator.validate(event) event = require_event(event) self.assertEqual(event["event_id"], "evt_123") invalid_event = { "type": "input_audio_buffer.append", "event_id": "evt_bad", } with self.assertRaises(JSONSchemaValidationError): validator.validate(invalid_event) with self.assertRaises(RealtimeEventError) as raised: require_event(invalid_event) wire_error = error_event(raised.exception) self.assertEqual(wire_error["error"]["event_id"], "evt_bad") extension_event = { "type": "x.interactive_media.capabilities.update", "event_id": "evt_extension", "version": "1", "capabilities": ["workflow_events"], } validator.validate(extension_event) require_event(extension_event) extension_without_version = { key: value for key, value in extension_event.items() if key != "version" } with self.assertRaises(JSONSchemaValidationError): validator.validate(extension_without_version) with self.assertRaises(RealtimeEventError): require_event(extension_without_version) def test_server_schema_enumerates_every_emitted_event_name(self): schema = contract_documents()[ "openai-realtime-server-events.schema.json" ] Draft202012Validator.check_schema(schema) validator = Draft202012Validator(schema) for event_type in SERVER_EVENT_TYPES: event = {"type": event_type, "event_id": "event_contract"} if event_type in EXTENSION_SERVER_EVENT_TYPES: event["version"] = "1" validator.validate(event) parsed = RealtimeServerEvent.model_validate( event ) self.assertEqual(parsed.type, event_type) with self.assertRaises(JSONSchemaValidationError): validator.validate( { "type": "x.interactive_media.workflow.event", "event_id": "event_without_version", } ) def test_text_response_events_follow_realtime_order(self): class FakeSession: output_is_audio = False active_response_id: str | None = None active_output_item_id: str | None = None response_text = "" def begin_response(self): self.active_response_id = self.active_response_id or "resp_1" self.active_output_item_id = self.active_output_item_id or "item_1" return self.active_response_id, self.active_output_item_id def finish_response(self): result = ( self.active_response_id, self.active_output_item_id, self.response_text, ) self.active_response_id = None self.active_output_item_id = None self.response_text = "" return result bridge = OpenAIRealtimeBridge(FakeSession(), channel="websocket") events = [ *bridge._start_assistant_output(), *bridge._assistant_delta("你好"), *bridge._end_assistant_output(False), ] event_types = [event["type"] for event in events] self.assertEqual(event_types[0:3], [ "response.created", "response.output_item.added", "response.content_part.added", ]) self.assertIn("response.output_text.delta", event_types) self.assertEqual(event_types[-1], "response.done") validator = Draft202012Validator( contract_documents()[ "openai-realtime-server-events.schema.json" ] ) for event in events: validator.validate(event) RealtimeServerEvent.model_validate(event) if __name__ == "__main__": unittest.main()