"""Golden wire behavior captured before the tooling/structure migration. Fixtures include permissive payloads and existing failures deliberately. Changing them requires a separate, explicitly approved API behavior change. """ from __future__ import annotations import asyncio import json import os import unittest from pathlib import Path from unittest.mock import patch import httpx from fastapi.testclient import TestClient with patch.dict(os.environ, {}, clear=True), patch("dotenv.load_dotenv", return_value=False): from vocero_api import main as server from vocero_api.core import config as settings from vocero_api.providers import apipilot from vocero_api.realtime import proxy from vocero_api.services import context ASYNC_CLIENT = httpx.AsyncClient FIXTURE = Path(__file__).parent / "fixtures" / "legacy-contracts.json" BASE_CONFIG = { "LOAD_BALANCER_URL": "", "SPEECH_TO_SPEECH_URL": "", "S2S_PROXY_TARGET": "", "S2S_PRIMARY_PROMPT": "", "SERPER_KEY": "", "S2S_REASONING_TYPE": "PROXY", "S2S_INJECT_PROFILE": False, "S2S_INJECT_REPORTS": False, "S2S_DEBUG_PROMPT": False, "S2S_START_SESSION_MODE": "", "S2S_THEME": "", "VOCERO_USER_PHONE_AUTH_URL": "", "S2S_REPORT_LIST": "", "S2S_LOGIN_BY_PHONE": "", "S2S_LATEST_SUMMARY": "", "S2S_LATEST_SUMMARY_TEXT": "", "S2S_APIPILOT_TOOLS_URL": "", "S2S_APIPILOT_TOOLS_EXECUTE_URL": "", "S2S_APIPILOT_SERVICE_TOKEN": "", "S2S_APIPILOT_API_KEY": "", "OPENAI_API_KEY": "", "S2S_REPORTS_QUICK_HINT_MAX": 5, "S2S_REPORTS_INJECT_MODE": "minimal", } TOOL = { "name": "fixture_tool", "description": "A fixture tool.", "parameters": {"type": "object", "properties": {"count": {"type": "integer"}}, "required": ["count"]}, "requires_auth": False, "future_definition_field": {"nested": True}, } def cases(): """Each case controls all upstream IO; no deployment services are contacted.""" yield "config-default", "GET", "/api/config", {}, {}, [] yield ( "config-proxy", "GET", "/api/config", { "headers": {"x-forwarded-proto": "https", "x-forwarded-host": "voice.test, internal.test"}, }, {"S2S_PROXY_TARGET": "ws://private.test/voice"}, [], ) yield ( "config-direct", "GET", "/api/config", {}, {"SPEECH_TO_SPEECH_URL": "wss://voice.test/realtime?token=fixture"}, [], ) yield "config-lb", "GET", "/api/config", {}, {"LOAD_BALANCER_URL": "http://lb.test"}, [] for path in ("/api/me", "/api/session/heartbeat", "/api/session/end", "/", "/server.py"): yield f"removed-{path}", "GET", path, {}, {}, [] for path in ("/project", "/project/", "/project/index.html", "/public/docs.html"): yield f"redirect-{path}", "GET", path, {}, {}, [] for path in ("/api/session", "/api/queue/end"): yield f"disabled-{path}", "POST", path, {}, {}, [] for method in ("GET", "DELETE"): yield f"disabled-queue-{method}", method, "/api/queue/ticket", {}, {}, [] for name, payload in ( ("missing", {}), ("blank", {"query": " "}), ("wrong-type", {"query": 17}), ("null", {"query": None}), ("unconfigured", {"query": "hello"}), ): yield f"search-{name}", "POST", "/api/search", {"json": payload}, {}, [] search_config = {"SERPER_KEY": "server-fixture-key"} yield ( "search-success-extra-fields", "POST", "/api/search", { "json": { "query": " hello ", "key": "caller-fixture-key", "unknown_request_field": [1, 2], } }, search_config, [ ( 200, { "answerBox": {"answer": "Answer"}, "organic": [ {"title": "Title", "snippet": "Text", "link": "https://example.test", "unknown": 1}, ], "future_upstream": {"nested": True}, }, ) ], ) yield ( "search-loose-values", "POST", "/api/search", {"json": {"query": "hello"}}, search_config, [ ( 200, { "answerBox": {"answer": {"future": [1, None]}}, "organic": [{"title": None, "snippet": 3, "link": {"href": "unusual"}}, {}], }, ) ], ) yield ( "search-upstream-error", "POST", "/api/search", {"json": {"query": "hello"}}, search_config, [(429, {"message": "Fixture credits exhausted"})], ) yield ( "search-invalid-upstream-json", "POST", "/api/search", {"json": {"query": "hello"}}, search_config, [(200, b"not-json")], ) yield "profile-missing-phone", "GET", "/api/user/phone", {}, {}, [] yield "profile-unconfigured", "GET", "/api/user/phone?phoneNumber=123", {}, {}, [] profile_config = {"VOCERO_USER_PHONE_AUTH_URL": "http://profile.test/user"} yield "profile-blank-phone", "GET", "/api/user/phone?phoneNumber=%20", {}, profile_config, [] for name, status, payload in ( ("object", 200, {"result": {"name": "Caller", "unknown": {"nested": [1, None]}}}), ("array", 200, [1, {"future": "field"}]), ("null", 200, None), ("string", 200, "opaque profile"), ("error", 403, {"future_error": True}), ("invalid-json", 200, b"not-json"), ): yield ( f"profile-{name}", "GET", "/api/user/phone?phoneNumber=%2B56000000000", {}, profile_config, [(status, payload)], ) yield ( "profile-login-token", "GET", "/api/user/phone?phoneNumber=123", {}, { **profile_config, "S2S_LOGIN_BY_PHONE": "http://profile.test/login", }, [(200, {"result": {"data": {"accessToken": "fixture-token", "future": True}}}), (200, {"name": "Caller"})], ) yield "reports-disabled", "GET", "/api/user/reports", {}, {}, [] yield "reports-unconfigured", "GET", "/api/user/reports", {}, {"S2S_INJECT_REPORTS": True}, [] reports_config = {"S2S_INJECT_REPORTS": True, "S2S_REPORT_LIST": "http://reports.test/list"} yield "reports-empty", "GET", "/api/user/reports", {}, reports_config, [(200, [])] yield "reports-upstream-failure", "GET", "/api/user/reports", {}, reports_config, [(503, {"error": "fixture"})] yield ( "reports-normalization", "GET", "/api/user/reports?phoneNumber=123", {}, reports_config, [ ( 200, { "result": [ { "sourceScheduleLabel": "Daily", "date": "2026-01-02T12:00:00Z", "podcastScript": "private script", "future": True, }, { "sourceScheduleLabel": "Daily", "date": "2026-01-01T12:00:00Z", "podcastScript": "older script", }, {"sourceSchedule": {"name": "Weekly", "date": "2026-01-03T12:00:00Z"}, "text": "weekly script"}, ], "future": True, }, ) ], ) lb_config = {"LOAD_BALANCER_URL": "http://lb.test"} for name, status, payload in ( ( "grant", 200, {"session_id": "session", "connect_url": "ws://voice.test?token=fixture", "future": {"nested": True}}, ), ("camelcase", 200, {"sessionId": "session", "connectUrl": "ws://voice.test", "unknown": None}), ("queued", 200, {"state": "queued", "queue_id": "ticket", "position": 3, "future": [1]}), ("empty", 200, {}), ("capacity", 503, {"state": "at_capacity", "future": True}), ("other-error", 429, {"error": "fixture"}), ("invalid-json", 200, b"not-json"), ): yield ( f"session-{name}", "POST", "/api/session", {"content": b"arbitrary ignored request body"}, lb_config, [(status, payload)], ) for name, status, payload in ( ("waiting", 200, {"state": "queued", "queue_id": "ticket", "position": None, "future": True}), ("grant", 200, {"session_id": "session", "connect_url": "ws://voice.test"}), ("expired", 404, {"unknown": True}), ("error", 500, {}), ("invalid-json", 200, b"bad"), ): yield f"queue-{name}", "GET", "/api/queue/ticket", {}, lb_config, [(status, payload)] yield "queue-delete", "DELETE", "/api/queue/ticket", {}, lb_config, [(500, {})] for name, kwargs, upstream in ( ("valid", {"json": {"queueId": "ticket", "future": True}}, [(204, b"")]), ("empty", {"content": b""}, []), ("invalid-json", {"content": b"bad"}, []), ("array", {"json": ["ticket"]}, []), ("null-id", {"json": {"queueId": None}}, []), ("numeric-id", {"json": {"queueId": 42}}, []), ): yield f"queue-end-{name}", "POST", "/api/queue/end", kwargs, lb_config, upstream for name, payload in (("missing", {}), ("proxy", {"transcript": "hello"}), ("proxy-blank", {"transcript": " "})): yield f"reasoning-{name}", "POST", "/api/reasoning", {"json": payload}, {}, [] yield ( "reasoning-OPENAI-legacy-unbound-token", "POST", "/api/reasoning", {"json": {"transcript": "hello"}}, {"S2S_REASONING_TYPE": "OPENAI"}, [], ) yield ( "reasoning-legacy-unbound-profile", "POST", "/api/reasoning", {"json": {"transcript": "hello"}}, { "S2S_REASONING_TYPE": "OPENAI", "S2S_INJECT_PROFILE": True, "S2S_LATEST_SUMMARY_TEXT": "fixture summary", }, [], ) reasoning_config = { **profile_config, "S2S_INJECT_PROFILE": True, "S2S_REASONING_TYPE": "OPENAI", } reasoning_request = { "json": { "transcript": " hello ", "phoneNumber": "123", "instructions": "Fixture instructions", "language": "es", "future": True, } } yield ( "reasoning-openai-answer", "POST", "/api/reasoning", reasoning_request, { **reasoning_config, "OPENAI_API_KEY": "fixture-key", }, [ (200, {"name": "Caller"}), ( 200, { "choices": [{"message": {"content": " answer ", "future": True}}], "usage": {"total_tokens": 12}, "future": True, }, ), ], ) for method, path, kwargs in ( ("GET", "/api/apipilot/tools", {}), ("POST", "/api/apipilot/execute", {"json": {"name": "fixture_tool"}}), ): yield f"apipilot-disabled-{method}", method, path, kwargs, {}, [] tool_config = {"S2S_APIPILOT_TOOLS_URL": "http://tools.test/apipilot"} yield "tools-query-validation", "GET", "/api/apipilot/tools?refresh=invalid", {}, tool_config, [] for name, payload in ( ("list", [TOOL]), ("result", {"result": [TOOL]}), ("envelope", {"data": {"tools": [TOOL]}, "future": True}), ("opaque", "anything"), ): yield ( f"tools-{name}", "GET", "/api/apipilot/tools?include_unauthorized=false&refresh=true", {"headers": {"Authorization": "Bearer fixture-token", "x-demo-session": "fixture-session"}}, tool_config, [(200, payload)], ) yield "tools-upstream-error", "GET", "/api/apipilot/tools", {}, tool_config, [(403, {"error": "fixture"})] yield ( "execute-discovery-error-envelope", "POST", "/api/apipilot/execute", {"json": {"name": "fixture_tool"}}, tool_config, [(503, {})], ) yield ( "execute-unknown-tool", "POST", "/api/apipilot/execute", {"json": {"name": "unknown"}}, tool_config, [(200, [TOOL]), (200, [TOOL])], ) yield ( "execute-missing-argument", "POST", "/api/apipilot/execute", {"json": {"name": "fixture_tool"}}, tool_config, [(200, [TOOL])], ) yield ( "execute-api-key-precheck", "POST", "/api/apipilot/execute", {"json": {"name": "fixture_tool", "arguments": {"count": 1}}}, { **tool_config, "S2S_APIPILOT_API_KEY": "fixture-key", }, [(200, [{**TOOL, "requires_auth": True}])], ) for name, payload in ( ("envelope", {"success": True, "data": {"future": [1, None]}, "error": None, "future_envelope": True}), ("raw-list", [{"future": True}]), ("minimal-envelope", {"success": True}), ( "error", { "success": False, "data": None, "error": {"status_code": 409, "code": "FIXTURE", "message": "Fixture", "future": True}, }, ), ): yield ( f"execute-{name}", "POST", "/api/apipilot/execute", { "json": { "name": "fixture_tool", "arguments": {"count": "wrong-type-is-accepted", "unknown": 1}, "future": True, } }, tool_config, [(200, [TOOL]), (200, payload)], ) def capture_http(): captures = {} for name, method, path, kwargs, config, responses in cases(): remaining = iter(responses) requests = [] def upstream(request, *, requests=requests, remaining=remaining): requests.append( { "method": request.method, "url": str(request.url), "body": request.content.decode(), "headers": { key: request.headers[key] for key in ( "authorization", "x-demo-session", "x-api-key", "content-type", ) if key in request.headers }, } ) status, payload = next(remaining) if isinstance(payload, bytes): return httpx.Response(status, content=payload) return httpx.Response( status, content=json.dumps(payload).encode(), headers={"content-type": "application/json"} ) with ( patch.multiple(settings, **{**BASE_CONFIG, **config}), patch.object(apipilot, "_APIPILOT_CACHE", {}), patch.object(context, "_build_today_fixed_context_block", return_value="Fixture current date: 2026-01-04"), patch.object( httpx, "AsyncClient", side_effect=lambda **opts: ASYNC_CLIENT(transport=httpx.MockTransport(upstream), **opts), ), TestClient(server.app, raise_server_exceptions=False) as client, ): response = client.request(method, path, follow_redirects=False, **kwargs) captures[name] = { "status": response.status_code, "body": response.text, "headers": { key: response.headers[key] for key in ("content-type", "location", "set-cookie") if key in response.headers }, "upstream": requests, } if list(remaining): raise AssertionError(f"Unused upstream fixtures in {name}") return captures def capture_openapi_requests(): schema = server.app.openapi() return { "operations": { f"{method.upper()} {path}": { key: operation[key] for key in ("operationId", "parameters", "requestBody") if key in operation } for path, methods in schema["paths"].items() for method, operation in methods.items() }, "models": { name: schema["components"]["schemas"][name] for name in ( "SearchRequest", "ReasoningRequest", "ApipilotExecuteRequest", "HTTPValidationError", "ValidationError", ) }, } async def capture_realtime(): frames = [] class Socket: async def send_text(self, message): frames.append(message) async def send_bytes(self, message): frames.append({"binary": message.hex()}) async def upstream(): yield b"\x00\x01\x02\x03" yield json.dumps({"type": "future.event", "future": {"nested": [None, 1]}}) yield json.dumps({"type": "response.text.done", "text": "Hello [rightspeak]world[endspeak]", "future": True}) yield json.dumps({"type": "error", "error": {"message": "Unknown or invalid event: session.update"}}) yield json.dumps({"type": "future.after.suppressed.error"}) await proxy._upstream_to_client(Socket(), upstream(), "fixture", {}) return frames def capture(): return { "http": capture_http(), "openapi_requests": capture_openapi_requests(), "realtime": asyncio.run(capture_realtime()), } class LegacyCompatibilityTests(unittest.TestCase): def test_native_transcripts_are_relayed_without_external_http_calls(self): events = [ {"type": "conversation.item.input_audio_transcription.completed", "transcript": "Hola", "item_id": "u1"}, {"type": "response.audio_transcript.done", "transcript": "Respuesta", "response_id": "r1"}, {"type": "response.output_audio_transcript.done", "transcript": "Respuesta", "response_id": "r1"}, {"type": "response.content_part.done", "part": {"type": "text", "text": "Respuesta"}}, *[ { "type": event_type, "item": {"id": "a1", "role": "assistant", "content": [{"type": "text", "text": "Respuesta"}]}, } for event_type in ("response.output_item.done", "conversation.item.created") ], {"type": "response.text.done", "text": "Respuesta", "response_id": "r1"}, ] messages = [json.dumps(event) for event in events] forwarded = [] class Socket: async def send_text(self, message): forwarded.append(message) async def upstream(): for message in messages: yield message async def relay(): await proxy._upstream_to_client(Socket(), upstream(), "fixture", {}) # Allow any accidentally scheduled background HTTP work to run. await asyncio.sleep(0) for mode in ("PROXY", "OPENAI"): forwarded.clear() with ( self.subTest(mode=mode), patch.multiple(settings, **{**BASE_CONFIG, "S2S_REASONING_TYPE": mode}), patch.object(httpx, "AsyncClient", side_effect=AssertionError("Unexpected HTTP call")) as http, ): asyncio.run(relay()) self.assertEqual(forwarded, messages) http.assert_not_called() def test_wire_contracts_match_pre_migration_capture(self): expected = json.loads(FIXTURE.read_text()) actual = capture() for name, response in actual["http"].items(): with self.subTest(case=name): self.assertEqual(response, expected["http"][name]) # Additive conversation/MCP routes have their own contract tests. Every # pre-existing operation and request schema still matches the capture. operations = actual["openapi_requests"]["operations"] added = set(operations) - set(expected["openapi_requests"]["operations"]) self.assertEqual( added, { "POST /api/conversations", "DELETE /api/conversations/current", "POST /api/conversations/current/interrupt", "GET /api/mcp/tools", }, ) for key in added: del operations[key] self.assertEqual(actual["openapi_requests"], expected["openapi_requests"]) self.assertEqual(actual["realtime"], expected["realtime"]) self.assertEqual(set(actual["http"]), set(expected["http"]))