import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { __resetActiveStoreForTests, setActiveSelection, setRegisteredBackends, } from "#/api/backend-registry/active-store"; import type { Backend } from "#/api/backend-registry/types"; import AgentServerConversationService from "#/api/conversation-service/agent-server-conversation-service.api"; import { getFetchCall, getJsonBody, mockJsonResponse, } from "./fetch-test-utils"; const cloudBackend: Backend = { id: "prod", name: "Production", host: "https://app.all-hands.dev", apiKey: "bearer-token", kind: "cloud", }; const localBackend: Backend = { id: "self-hosted", name: "Self-hosted", host: "http://192.168.1.99:9999", apiKey: "local-key", kind: "local", }; const runtimeResponse = { id: "conv-abc", title: "Test conversation", created_at: "2026-04-16T00:00:00Z", updated_at: "2026-04-16T00:00:00Z", execution_status: "idle", metrics: null, stats: { usage_to_metrics: { agent: { model_name: "test-model", accumulated_cost: 1.23, max_budget_per_task: null, accumulated_token_usage: null, costs: [], response_latencies: [], token_usages: [], }, }, }, }; const fetchMock = vi.fn(); afterEach(() => { window.localStorage.clear(); __resetActiveStoreForTests(); fetchMock.mockReset(); vi.unstubAllGlobals(); vi.restoreAllMocks(); }); describe("AgentServerConversationService.getRuntimeConversation", () => { describe("cloud mode", () => { beforeEach(() => { __resetActiveStoreForTests(); setRegisteredBackends([cloudBackend]); setActiveSelection({ backendId: cloudBackend.id }); fetchMock.mockReset(); vi.stubGlobal("fetch", fetchMock); }); it("targets the conversation runtime host directly and forwards X-Session-API-Key", async () => { // Arrange fetchMock.mockResolvedValue(mockJsonResponse(runtimeResponse)); const conversationUrl = "http://abc123.runtime.all-hands.dev/api/conversations/conv-abc"; // Act const result = await AgentServerConversationService.getRuntimeConversation( "conv-abc", conversationUrl, "session-xyz", ); // Assert — fetch goes directly to the runtime host, not through // /api/cloud-proxy (which was removed from the agent-server). expect(fetchMock).toHaveBeenCalledOnce(); const [url, init] = getFetchCall(fetchMock); expect(url).toContain("abc123.runtime.all-hands.dev"); expect(url).toContain("/api/conversations/conv-abc"); expect(url).not.toMatch(/\/api\/cloud-proxy$/); expect(init.headers).toMatchObject({ "X-Session-API-Key": "session-xyz", }); expect(result.stats.usage_to_metrics.agent?.accumulated_cost).toBe(1.23); }); }); describe("local mode", () => { beforeEach(() => { __resetActiveStoreForTests(); setRegisteredBackends([localBackend]); setActiveSelection({ backendId: localBackend.id }); fetchMock.mockReset(); vi.stubGlobal("fetch", fetchMock); }); it("targets the conversation_url host (not the active backend host) and forwards X-Session-API-Key", async () => { // Arrange fetchMock.mockResolvedValue(mockJsonResponse(runtimeResponse)); const conversationUrl = "http://192.168.1.42:8888/api/conversations/conv-abc"; // Act const result = await AgentServerConversationService.getRuntimeConversation( "conv-abc", conversationUrl, "session-xyz", ); // Assert expect(fetchMock).toHaveBeenCalledOnce(); const [url, init] = getFetchCall(fetchMock); expect(url).toContain("192.168.1.42:8888"); expect(url).not.toContain(localBackend.host); expect(init.headers).toMatchObject({ "X-Session-API-Key": "session-xyz", }); expect(result.id).toBe("conv-abc"); }); }); });