"""Runtime terminal error codes map to the contract's HTTP statuses (no Docker).""" from __future__ import annotations from pathlib import Path import httpx import pytest from httpx import ASGITransport from backend.app import create_app from backend.artifacts import ArtifactStore from backend.containers import FakeContainerManager from backend.dev.fake_peer import ScriptedContainerPeer from backend.model import FakeModelAdapter from backend.settings import Settings from tests.backend.conftest import make_test_confluence_client_factory pytestmark = pytest.mark.integration ORIGIN = "http://testserver" def error_peer(code: str): async def steps(reader, writer, peer): start = await peer._read_line(reader) assert start["type"] == "start" await peer._write_msg(writer, {"v": 1, "type": "error", "id": "a_err", "payload": {"code": code, "message": "RUNTIME-DETAIL-MUST-NOT-LEAK"}}) return lambda: ScriptedContainerPeer(custom_steps=steps) @pytest.mark.parametrize("code,status,expected", [ ("model_output_limit", 502, "model_output_limit"), ("model_context_exceeded", 400, "model_context_exceeded"), ("query_timeout", 504, "query_timeout"), ("connectivity_failed", 502, "connectivity_failed"), ("execution_failed", 500, "execution_failed"), ("invalid_input", 500, "execution_failed"), ("something_unknown", 500, "execution_failed"), ]) @pytest.mark.asyncio async def test_runtime_error_code_mapping(tmp_path: Path, code, status, expected): settings = Settings(query_timeout_seconds=20.0, artifact_storage_dir=tmp_path / "a") app = create_app( settings=settings, container_manager=FakeContainerManager(error_peer(code)), artifact_store=ArtifactStore(settings.artifact_storage_dir), model_adapter=FakeModelAdapter(), confluence_client_factory=make_test_confluence_client_factory(), ) async with httpx.AsyncClient(transport=ASGITransport(app=app), base_url=ORIGIN) as c: r = await c.post("/api/v1/query", json={"prompt": "x", "credentials": {"url": "https://approved.example.com", "pat": "valid-pat"}}, headers={"Origin": ORIGIN}) assert r.status_code == status body = r.json() assert body["error"]["code"] == expected assert "RUNTIME-DETAIL" not in r.text assert r.headers["cache-control"] == "no-store"