chore(vocero): remove the legacy ApiPilot integration (#3)

Vocero now retrieves AquaPortal content through authenticated MCP. This removes the obsolete ApiPilot integration from the API and browser while preserving the separately deployed speech pipeline.

Release notes:

- Removed ApiPilot endpoints, configuration, credential overrides, and HTTP/realtime tool execution.
- Removed browser ApiPilot catalog loading and prompt generation while retaining authenticated MCP discovery.
- Updated regression coverage and operating documentation for the retired routes and settings.

Reviewed-on: AquaPortal/s2s#3
This commit is contained in:
2026-10-08 11:23:41 -04:00
parent 0311d3f1ff
commit dfed03ac7c
39 changed files with 198 additions and 3314 deletions
+5 -336
View File
@@ -2,7 +2,7 @@
"http": {
"config-default": {
"status": 200,
"body": "{\"search\":false,\"lb\":false,\"allowDirect\":true,\"s2sUrl\":\"\",\"primaryPrompt\":\"\",\"reasoningType\":\"PROXY\",\"injectProfile\":false,\"debugPrompt\":false,\"startSessionMode\":\"\",\"theme\":\"\",\"apipilot\":false}",
"body": "{\"search\":false,\"lb\":false,\"allowDirect\":true,\"s2sUrl\":\"\",\"primaryPrompt\":\"\",\"reasoningType\":\"PROXY\",\"injectProfile\":false,\"debugPrompt\":false,\"startSessionMode\":\"\",\"theme\":\"\"}",
"headers": {
"content-type": "application/json"
},
@@ -10,7 +10,7 @@
},
"config-proxy": {
"status": 200,
"body": "{\"search\":false,\"lb\":false,\"allowDirect\":true,\"s2sUrl\":\"wss://voice.test/v1/realtime\",\"primaryPrompt\":\"\",\"reasoningType\":\"PROXY\",\"injectProfile\":false,\"debugPrompt\":false,\"startSessionMode\":\"\",\"theme\":\"\",\"apipilot\":false}",
"body": "{\"search\":false,\"lb\":false,\"allowDirect\":true,\"s2sUrl\":\"wss://voice.test/v1/realtime\",\"primaryPrompt\":\"\",\"reasoningType\":\"PROXY\",\"injectProfile\":false,\"debugPrompt\":false,\"startSessionMode\":\"\",\"theme\":\"\"}",
"headers": {
"content-type": "application/json"
},
@@ -18,7 +18,7 @@
},
"config-direct": {
"status": 200,
"body": "{\"search\":false,\"lb\":false,\"allowDirect\":true,\"s2sUrl\":\"wss://voice.test/realtime?token=fixture\",\"primaryPrompt\":\"\",\"reasoningType\":\"PROXY\",\"injectProfile\":false,\"debugPrompt\":false,\"startSessionMode\":\"\",\"theme\":\"\",\"apipilot\":false}",
"body": "{\"search\":false,\"lb\":false,\"allowDirect\":true,\"s2sUrl\":\"wss://voice.test/realtime?token=fixture\",\"primaryPrompt\":\"\",\"reasoningType\":\"PROXY\",\"injectProfile\":false,\"debugPrompt\":false,\"startSessionMode\":\"\",\"theme\":\"\"}",
"headers": {
"content-type": "application/json"
},
@@ -26,7 +26,7 @@
},
"config-lb": {
"status": 200,
"body": "{\"search\":false,\"lb\":true,\"allowDirect\":false,\"s2sUrl\":\"\",\"primaryPrompt\":\"\",\"reasoningType\":\"PROXY\",\"injectProfile\":false,\"debugPrompt\":false,\"startSessionMode\":\"\",\"theme\":\"\",\"apipilot\":false}",
"body": "{\"search\":false,\"lb\":true,\"allowDirect\":false,\"s2sUrl\":\"\",\"primaryPrompt\":\"\",\"reasoningType\":\"PROXY\",\"injectProfile\":false,\"debugPrompt\":false,\"startSessionMode\":\"\",\"theme\":\"\"}",
"headers": {
"content-type": "application/json"
},
@@ -736,7 +736,7 @@
},
"upstream": []
},
"reasoning-OPENAI-legacy-unbound-token": {
"reasoning-openai-profile-disabled": {
"status": 500,
"body": "Internal Server Error",
"headers": {
@@ -775,279 +775,6 @@
}
}
]
},
"apipilot-disabled-GET": {
"status": 501,
"body": "{\"success\":false,\"data\":null,\"error\":{\"code\":\"APIPILOT_NOT_CONFIGURED\",\"message\":\"ApiPilot no está disponible en este despliegue (S2S_APIPILOT_TOOLS_URL no establecido).\",\"status_code\":501,\"raw\":null}}",
"headers": {
"content-type": "application/json"
},
"upstream": []
},
"apipilot-disabled-POST": {
"status": 501,
"body": "{\"success\":false,\"data\":null,\"error\":{\"code\":\"APIPILOT_NOT_CONFIGURED\",\"message\":\"ApiPilot no está disponible en este despliegue (S2S_APIPILOT_TOOLS_URL no establecido).\",\"status_code\":501,\"raw\":null}}",
"headers": {
"content-type": "application/json"
},
"upstream": []
},
"tools-query-validation": {
"status": 422,
"body": "{\"detail\":[{\"type\":\"bool_parsing\",\"loc\":[\"query\",\"refresh\"],\"msg\":\"Input should be a valid boolean, unable to interpret input\",\"input\":\"invalid\"}]}",
"headers": {
"content-type": "application/json"
},
"upstream": []
},
"tools-list": {
"status": 200,
"body": "{\"success\":true,\"data\":{\"count\":1,\"include_unauthorized\":false,\"tools\":[{\"name\":\"fixture_tool\",\"description\":\"A fixture tool.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"count\":{\"type\":\"integer\"}},\"required\":[\"count\"]},\"requires_auth\":false}]},\"error\":null}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=false",
"body": "",
"headers": {
"authorization": "Bearer fixture-token",
"x-demo-session": "fixture-session"
}
}
]
},
"tools-result": {
"status": 200,
"body": "{\"success\":true,\"data\":{\"count\":1,\"include_unauthorized\":false,\"tools\":[{\"name\":\"fixture_tool\",\"description\":\"A fixture tool.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"count\":{\"type\":\"integer\"}},\"required\":[\"count\"]},\"requires_auth\":false}]},\"error\":null}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=false",
"body": "",
"headers": {
"authorization": "Bearer fixture-token",
"x-demo-session": "fixture-session"
}
}
]
},
"tools-envelope": {
"status": 200,
"body": "{\"success\":true,\"data\":{\"count\":1,\"include_unauthorized\":false,\"tools\":[{\"name\":\"fixture_tool\",\"description\":\"A fixture tool.\",\"parameters\":{\"type\":\"object\",\"properties\":{\"count\":{\"type\":\"integer\"}},\"required\":[\"count\"]},\"requires_auth\":false}]},\"error\":null}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=false",
"body": "",
"headers": {
"authorization": "Bearer fixture-token",
"x-demo-session": "fixture-session"
}
}
]
},
"tools-opaque": {
"status": 200,
"body": "{\"success\":true,\"data\":{\"count\":0,\"include_unauthorized\":false,\"tools\":[]},\"error\":null}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=false",
"body": "",
"headers": {
"authorization": "Bearer fixture-token",
"x-demo-session": "fixture-session"
}
}
]
},
"tools-upstream-error": {
"status": 502,
"body": "{\"success\":false,\"data\":null,\"error\":{\"code\":\"APIPILOT_BAD_UPSTREAM\",\"message\":\"Backend ApiPilot tools devolvió HTTP 403\",\"status_code\":403}}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {}
}
]
},
"execute-discovery-error-envelope": {
"status": 502,
"body": "{\"detail\":{\"code\":\"APIPILOT_BAD_UPSTREAM\",\"message\":\"Backend ApiPilot tools devolvió HTTP 503\",\"status_code\":503}}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {}
}
]
},
"execute-unknown-tool": {
"status": 404,
"body": "{\"success\":false,\"data\":null,\"error\":{\"code\":\"TOOL_NOT_FOUND\",\"message\":\"Herramienta «unknown» no encontrada en el catálogo ApiPilot. Actualice la lista de herramientas y vuelva a intentarlo.\",\"status_code\":404,\"raw\":null}}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {}
},
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {}
}
]
},
"execute-missing-argument": {
"status": 400,
"body": "{\"success\":false,\"data\":null,\"error\":{\"code\":\"INVALID_ARGUMENTS\",\"message\":\"Falta el parámetro requerido: count\",\"status_code\":400,\"raw\":{\"removed\":[]}}}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {}
}
]
},
"execute-api-key-precheck": {
"status": 401,
"body": "{\"success\":false,\"data\":null,\"error\":{\"code\":\"AUTH_REQUIRED\",\"message\":\"La herramienta «fixture_tool» requiere autenticación. Inicie sesión o proporcione un token para continuar.\",\"status_code\":401,\"raw\":null}}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {
"authorization": "Bearer fixture-key",
"x-demo-session": "fixture-key",
"x-api-key": "fixture-key"
}
}
]
},
"execute-envelope": {
"status": 200,
"body": "{\"success\":true,\"data\":{\"future\":[1,null]},\"error\":null,\"future_envelope\":true}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {}
},
{
"method": "POST",
"url": "http://tools.test/apipilot/execute",
"body": "{\"name\":\"fixture_tool\",\"arguments\":{\"count\":\"wrong-type-is-accepted\"}}",
"headers": {
"content-type": "application/json"
}
}
]
},
"execute-raw-list": {
"status": 200,
"body": "{\"success\":true,\"data\":[{\"future\":true}],\"error\":null}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {}
},
{
"method": "POST",
"url": "http://tools.test/apipilot/execute",
"body": "{\"name\":\"fixture_tool\",\"arguments\":{\"count\":\"wrong-type-is-accepted\"}}",
"headers": {
"content-type": "application/json"
}
}
]
},
"execute-minimal-envelope": {
"status": 200,
"body": "{\"success\":true}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {}
},
{
"method": "POST",
"url": "http://tools.test/apipilot/execute",
"body": "{\"name\":\"fixture_tool\",\"arguments\":{\"count\":\"wrong-type-is-accepted\"}}",
"headers": {
"content-type": "application/json"
}
}
]
},
"execute-error": {
"status": 409,
"body": "{\"success\":false,\"data\":null,\"error\":{\"status_code\":409,\"code\":\"FIXTURE\",\"message\":\"Fixture\",\"future\":true}}",
"headers": {
"content-type": "application/json"
},
"upstream": [
{
"method": "GET",
"url": "http://tools.test/apipilot/tools?include_unauthorized=true",
"body": "",
"headers": {}
},
{
"method": "POST",
"url": "http://tools.test/apipilot/execute",
"body": "{\"name\":\"fixture_tool\",\"arguments\":{\"count\":\"wrong-type-is-accepted\"}}",
"headers": {
"content-type": "application/json"
}
}
]
}
},
"openapi_requests": {
@@ -1055,44 +782,6 @@
"GET /api/config": {
"operationId": "config_api_config_get"
},
"GET /api/apipilot/tools": {
"operationId": "apipilot_list_tools_api_apipilot_tools_get",
"parameters": [
{
"name": "include_unauthorized",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"default": true,
"title": "Include Unauthorized"
}
},
{
"name": "refresh",
"in": "query",
"required": false,
"schema": {
"type": "boolean",
"default": false,
"title": "Refresh"
}
}
]
},
"POST /api/apipilot/execute": {
"operationId": "apipilot_execute_api_apipilot_execute_post",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApipilotExecuteRequest"
}
}
},
"required": true
}
},
"POST /api/reasoning": {
"operationId": "reasoning_api_reasoning_post",
"requestBody": {
@@ -1264,26 +953,6 @@
],
"title": "ReasoningRequest"
},
"ApipilotExecuteRequest": {
"properties": {
"name": {
"type": "string",
"title": "Name"
},
"arguments": {
"additionalProperties": true,
"type": "object",
"title": "Arguments",
"default": {}
}
},
"type": "object",
"required": [
"name"
],
"title": "ApipilotExecuteRequest",
"description": "Body for the ApiPilot execute proxy endpoint. Mirrors §2.2 request shape."
},
"HTTPValidationError": {
"properties": {
"detail": {
+1 -100
View File
@@ -19,7 +19,6 @@ 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
@@ -42,21 +41,10 @@ BASE_CONFIG = {
"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():
@@ -268,7 +256,7 @@ def cases():
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",
"reasoning-openai-profile-disabled",
"POST",
"/api/reasoning",
{"json": {"transcript": "hello"}},
@@ -323,91 +311,6 @@ def cases():
],
)
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 = {}
@@ -442,7 +345,6 @@ def capture_http():
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,
@@ -482,7 +384,6 @@ def capture_openapi_requests():
for name in (
"SearchRequest",
"ReasoningRequest",
"ApipilotExecuteRequest",
"HTTPValidationError",
"ValidationError",
)
+2 -2
View File
@@ -143,8 +143,8 @@ def test_http_chains_tools_retains_history_and_ignores_browser_identity(enabled,
assert client.post("/api/reasoning", json=body).status_code == 401
assert client.get("/api/user/phone?phoneNumber=123").status_code == 409
assert client.get("/api/user/reports").status_code == 409
assert client.get("/api/apipilot/tools").status_code == 409
assert client.post("/api/apipilot/execute", json={"name": "get_report"}).status_code == 409
assert client.get("/api/apipilot/tools").status_code == 404
assert client.post("/api/apipilot/execute", json={"name": "get_report"}).status_code == 404
enabled.assert_awaited_once_with("+56000000001")
+3 -8
View File
@@ -5,14 +5,13 @@ from fastapi.responses import JSONResponse
from pydantic import ValidationError
from vocero_api.api.responses import validated_json_response
from vocero_api.models.requests import ApipilotExecuteRequest, QueueEndPayload, SearchRequest
from vocero_api.models.requests import QueueEndPayload, SearchRequest
from vocero_api.models.responses import (
AllocationResponse,
QueueCancellationResponse,
ReasoningResponse,
ReportsResponse,
SearchResponse,
ToolExecutionResponse,
UserProfileResponse,
)
@@ -22,9 +21,7 @@ from vocero_api.models.responses import (
[
(AllocationResponse, {}),
(AllocationResponse, {"state": [1, None], "connectUrl": "wss://host", "future": {"x": 1.0}}),
(ToolExecutionResponse, {"success": True, "unknown": {"nested": [None, 1.0, False]}}),
(ToolExecutionResponse, {"success": False, "data": 42, "error": "loose error", "future": []}),
(ReasoningResponse, {"answer": "á", "usage_pass2": [1, None], "future": True}),
(ReasoningResponse, {"answer": "á", "future": [1, None, True]}),
(SearchResponse, {"query": "q", "answer": {"x": 2}, "results": [{"title": [], "snippet": 7, "url": None}]}),
(
ReportsResponse,
@@ -47,10 +44,8 @@ def test_owned_response_fields_are_actually_validated(payload):
validated_json_response(payload, QueueCancellationResponse)
def test_request_models_keep_legacy_extra_field_and_argument_behavior():
def test_request_models_keep_legacy_extra_field_and_beacon_behavior():
assert SearchRequest.model_validate({"query": "q", "future": True}).model_dump() == {"query": "q", "key": None}
arguments = {"arbitrary": {"nested": [1, None]}, "unknown": True}
assert ApipilotExecuteRequest.model_validate({"name": "tool", "arguments": arguments}).arguments == arguments
for value in [None, "id", 42, False, [], {}, {"future": True}]:
assert QueueEndPayload.model_validate({"queueId": value, "future": 1}).queueId == value
assert QueueEndPayload.model_validate({}).queueId == ""
+24 -36
View File
@@ -7,10 +7,9 @@ import json
import os
import threading
import unittest
from unittest.mock import ANY, AsyncMock, Mock, patch
from unittest.mock import AsyncMock, Mock, patch
import httpx
from fastapi import Request
from fastapi.testclient import TestClient
from websockets.sync.server import serve
@@ -18,8 +17,7 @@ from websockets.sync.server import serve
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, caller
from vocero_api.services import tool_context
from vocero_api.providers import caller
GRANT = {
@@ -182,34 +180,27 @@ class BackendContractTests(unittest.TestCase):
self.assertEqual(lookup.headers["authorization"], "Bearer fixture-token")
self.assertEqual(lookup.url.params["phoneNumber"], "+56000000000")
def test_caller_auth_headers_remain_independent(self):
request = Request(
{
"type": "http",
"headers": [
(b"authorization", b"Bearer fixture-token"),
(b"x-demo-session", b"fixture-demo"),
],
}
)
self.assertEqual(apipilot._apipilot_parse_authorization(request), ("Bearer fixture-token", "fixture-demo"))
def test_apipilot_routes_and_contracts_are_retired(self):
self.assertNotIn("apipilot", self.client.get("/api/config").json())
spec = self.client.get("/openapi.json").json()
for method, path in (("GET", "/api/apipilot/tools"), ("POST", "/api/apipilot/execute")):
self.assertNotIn(path, spec["paths"])
response = self.client.request(method, path, json={"name": "get_report"})
self.assertEqual(response.status_code, 404)
schemas = spec["components"]["schemas"]
for name in ("ApipilotExecuteRequest", "ToolCatalogResponse", "ToolExecutionResponse", "ToolInvocation"):
self.assertNotIn(name, schemas)
self.assertNotIn("apipilot", schemas["ConfigResponse"]["properties"])
self.assertEqual(set(schemas["ReasoningResponse"]["properties"]), {"answer"})
self.upstream.assert_not_called()
class VoiceProxyTests(unittest.TestCase):
def test_apipilot_catalog_and_caller_defaults_reach_the_voice_session(self):
def test_caller_and_episode_context_reach_the_voice_session(self):
closed = threading.Event()
catalog = AsyncMock(
return_value=[
{
"name": "get_caller_briefing",
"description": "Retrieve the caller's local test briefing.",
"requires_auth": True,
"parameters": {"required": ["phone", "episode_id", "email"]},
}
]
)
login = AsyncMock(return_value=("fixture-token", {}))
profile = AsyncMock(return_value={"user": {"email": "caller@example.test"}})
profile = AsyncMock(return_value={"user": {"name": "Local Caller", "email": "caller@example.test"}})
episode = AsyncMock(return_value={"title": "Local episode", "text": "Local episode text"})
def voice(socket):
try:
@@ -226,13 +217,12 @@ class VoiceProxyTests(unittest.TestCase):
target = f"ws://127.0.0.1:{upstream.socket.getsockname()[1]}/v1/realtime"
with (
patch.object(settings, "S2S_PROXY_TARGET", target),
patch.object(settings, "S2S_INJECT_PROFILE", False),
patch.object(settings, "S2S_APIPILOT_TOOLS_URL", "http://catalog.test/tools"),
patch.object(settings, "S2S_INJECT_PROFILE", True),
patch.object(settings, "S2S_LOGIN_BY_PHONE", "http://profile.test/login"),
patch.object(settings, "VOCERO_USER_PHONE_AUTH_URL", "http://profile.test/user"),
patch.object(apipilot, "_apipilot_fetch_tools", catalog),
patch.object(caller, "_login_by_phone", login),
patch.object(caller, "_fetch_user_profile_for_phone", profile),
patch.object(caller, "_fetch_podcast_episode", episode),
patch.object(httpx, "AsyncClient", side_effect=AssertionError("Unexpected HTTP request")),
TestClient(server.app) as client,
):
@@ -246,11 +236,9 @@ class VoiceProxyTests(unittest.TestCase):
instructions = update["session"]["instructions"]
for expected in (
"Local fixture",
tool_context.APIPILOT_PASS1_LIST_MARKER,
"name: get_caller_briefing",
'phone = "+56000000000"',
'episode_id = "fixture-episode"',
'email = "caller@example.test"',
"Local Caller",
"Local episode text",
"caller@example.test",
):
self.assertIn(expected, instructions)
# Configuration injection must preserve ordinary audio forwarding.
@@ -260,7 +248,7 @@ class VoiceProxyTests(unittest.TestCase):
self.assertTrue(closed.wait(3), "Client disconnect must close the upstream WebSocket")
login.assert_awaited_once_with("+56000000000")
profile.assert_awaited_once_with("+56000000000", auth_token="fixture-token")
catalog.assert_awaited_once_with(auth_header="Bearer fixture-token", demo_session=ANY, refresh=False)
episode.assert_awaited_once_with("fixture-episode")
def test_proxy_voice_frames_keepalive_and_client_disconnect(self):
closed = threading.Event()