"""Smoke tests for the custom routes mounted via langgraph.json's ``http`` field. We test the Starlette app directly — no need to spin up langgraph dev. ``GET /api/models`` and ``POST /api/runtime-snapshots`` belong to the unified model registry API (``EvoScientist.model_registry.http_api``); their contract tests live in ``tests/test_model_registry_http.py`` and ``tests/test_delegation_auth.py``. The legacy provider routes (``/api/provider-profiles``, ``/api/provider-actions``, ``/api/config``, ``/api/default-model``) and the ``x-evoscientist-admin-token`` check were removed with the unified model configuration refactor; the first test pins down that they are gone. """ from __future__ import annotations from unittest.mock import patch import pytest from langchain_core.messages import AIMessage, HumanMessage from starlette.testclient import TestClient from EvoScientist.langgraph_dev.http import app client = TestClient(app) @pytest.mark.parametrize( ("method", "path"), [ ("GET", "/api/provider-profiles"), ("PUT", "/api/provider-profiles"), ("GET", "/api/config"), ("PATCH", "/api/config"), ("POST", "/api/config"), ("POST", "/api/provider-actions"), ("PUT", "/api/default-model"), ], ) def test_legacy_provider_routes_are_gone(method, path): """The removed routes must not exist — even with the old admin token.""" headers = {"X-EvoScientist-Admin-Token": "admin-secret"} response = client.request(method, path, headers=headers, json={}) assert response.status_code == 404 def test_final_answer_extracts_latest_ai_text_blocks(): async def fake_metadata(_thread_id): return {"updated_at": "2026-07-06T14:14:53+00:00"} async def fake_messages(_thread_id): return [ HumanMessage(content="question"), AIMessage(content="old answer"), AIMessage( content=[ {"type": "reasoning", "text": "internal"}, {"type": "text", "text": "Part A"}, {"type": "tool_use", "name": "search"}, {"type": "output_text", "text": "Part B"}, ] ), ] async def fake_runtime(_request, _thread_id): return { "found": True, "complete": True, "completed_at": "2026-07-06T14:15:00+00:00", } with ( patch( "EvoScientist.langgraph_dev.http._get_thread_metadata_for_http", new=fake_metadata, ), patch( "EvoScientist.langgraph_dev.http._get_thread_messages_for_http", new=fake_messages, ), patch( "EvoScientist.langgraph_dev.http._read_thread_runtime_state", new=fake_runtime, ), ): resp = client.get("/api/threads/thread-1/final-answer") assert resp.status_code == 200 assert resp.json() == { "content": "Part A\n\nPart B", "completed_at": "2026-07-06T14:15:00+00:00", "complete": True, } def test_final_answer_skips_tool_selection_json_text(): async def fake_metadata(_thread_id): return {"updated_at": "2026-07-06T14:14:53+00:00"} async def fake_messages(_thread_id): return [ HumanMessage(content="question"), AIMessage(content="stable answer"), AIMessage( content=( '{"tools":["search_papers","get_abstract"]}' '{"tools":["web_search_exa"]}' ) ), ] async def fake_runtime(_request, _thread_id): return { "found": True, "complete": True, "completed_at": "2026-07-06T14:15:00+00:00", } with ( patch( "EvoScientist.langgraph_dev.http._get_thread_metadata_for_http", new=fake_metadata, ), patch( "EvoScientist.langgraph_dev.http._get_thread_messages_for_http", new=fake_messages, ), patch( "EvoScientist.langgraph_dev.http._read_thread_runtime_state", new=fake_runtime, ), ): resp = client.get("/api/threads/thread-1/final-answer") assert resp.status_code == 200 assert resp.json()["content"] == "stable answer" def test_final_answer_returns_404_for_unknown_thread(): async def fake_metadata(_thread_id): return None with patch( "EvoScientist.langgraph_dev.http._get_thread_metadata_for_http", new=fake_metadata, ): resp = client.get("/api/threads/missing/final-answer") assert resp.status_code == 404 assert resp.json() == {"error": "thread not found"} def test_final_answer_does_not_mark_complete_when_runtime_state_fails(): async def fake_metadata(_thread_id): return {"updated_at": "2026-07-06T14:14:53+00:00"} async def fake_messages(_thread_id): return [AIMessage(content="checkpoint answer")] async def fake_runtime(_request, _thread_id): raise RuntimeError("langgraph runtime unavailable") with ( patch( "EvoScientist.langgraph_dev.http._get_thread_metadata_for_http", new=fake_metadata, ), patch( "EvoScientist.langgraph_dev.http._get_thread_messages_for_http", new=fake_messages, ), patch( "EvoScientist.langgraph_dev.http._read_thread_runtime_state", new=fake_runtime, ), ): resp = client.get("/api/threads/thread-1/final-answer") assert resp.status_code == 200 assert resp.json() == { "content": "checkpoint answer", "completed_at": None, "complete": False, }