import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test"; // The profiles pinned below are managed presets judged for real by the // session handler's availability guard. Stand in for a signed-in platform // session so the pin path under test is the healthy one — same pattern as // inference-profile-session-handler.test.ts. mock.module("../providers/platform-proxy/context.js", () => ({ resolveManagedProxyContext: async () => ({ enabled: true, platformBaseUrl: "https://platform.example", assistantApiKey: "key", }), })); import { conversationMetadataSyncTag, SYNC_TAGS, } from "../daemon/message-types/sync.js"; import { createConversation, getConversation, } from "../persistence/conversation-crud.js"; import { getDb } from "../persistence/db-connection.js"; import { initializeDb } from "../persistence/db-init.js"; import { providerConnections } from "../persistence/schema/inference.js"; import { assistantEventHub } from "../runtime/assistant-event-hub.js"; import { ROUTES } from "../runtime/routes/conversation-management-routes.js"; import { BadRequestError, NotFoundError } from "../runtime/routes/errors.js"; import { resetDbForTesting } from "./db-test-helpers.js"; import { waitFor } from "./helpers/wait-for.js"; await initializeDb(); // The Vellum-managed connection row the managed presets resolve to. getDb() .insert(providerConnections) .values({ name: "vellum", provider: "vellum", auth: JSON.stringify({ type: "platform" }), createdAt: Date.now(), updatedAt: Date.now(), }) .onConflictDoNothing() .run(); const profileRoute = ROUTES.find( (r) => r.operationId === "setConversationInferenceProfile", )!; function clearTables(): void { const db = getDb(); db.run("DELETE FROM conversation_assistant_attention_state"); db.run("DELETE FROM external_conversation_bindings"); db.run("DELETE FROM conversation_keys"); db.run("DELETE FROM messages"); db.run("DELETE FROM conversations"); } describe("PUT /v1/conversations/:id/inference-profile", () => { beforeEach(() => { clearTables(); }); afterAll(() => { resetDbForTesting(); }); test("sets the override and emits a hub event for a known profile", async () => { const conversation = createConversation("inference-profile-route"); const received: Array<{ type: string; conversationId?: string; profile?: string | null; tags?: string[]; }> = []; const subscription = assistantEventHub.subscribe({ type: "process", callback: (event) => { received.push({ type: event.message.type, conversationId: event.conversationId, profile: event.message.type === "conversation_inference_profile_updated" ? event.message.profile : undefined, tags: event.message.type === "sync_changed" ? event.message.tags : undefined, }); }, }); const result = await profileRoute.handler({ pathParams: { id: conversation.id }, body: { profile: "quality-optimized" }, headers: {}, }); await waitFor(() => received.length === 2, { message: "Timed out waiting for inference profile route event", }); expect(result).toMatchObject({ conversationId: conversation.id, profile: "quality-optimized", }); expect(getConversation(conversation.id)?.inferenceProfile).toBe( "quality-optimized", ); expect(received).toEqual([ { type: "conversation_inference_profile_updated", conversationId: conversation.id, profile: "quality-optimized", tags: undefined, }, { type: "sync_changed", conversationId: undefined, profile: undefined, tags: [ SYNC_TAGS.conversationsList, conversationMetadataSyncTag(conversation.id), ], }, ]); subscription.dispose(); }); test("rejects unknown profile names with BadRequestError", async () => { const conversation = createConversation("inference-profile-unknown"); await expect( profileRoute.handler({ pathParams: { id: conversation.id }, body: { profile: "does-not-exist" }, headers: {}, }), ).rejects.toThrow(BadRequestError); expect(getConversation(conversation.id)?.inferenceProfile).toBeNull(); }); test("clears the override when profile is null", async () => { const conversation = createConversation("inference-profile-clear"); await profileRoute.handler({ pathParams: { id: conversation.id }, body: { profile: "balanced" }, headers: {}, }); await new Promise((resolve) => setTimeout(resolve, 0)); expect(getConversation(conversation.id)?.inferenceProfile).toBe("balanced"); const received: Array<{ profile?: string | null; tags?: string[] }> = []; const subscription = assistantEventHub.subscribe({ type: "process", callback: (event) => { if (event.message.type === "conversation_inference_profile_updated") { received.push({ profile: event.message.profile }); } else if (event.message.type === "sync_changed") { received.push({ tags: event.message.tags }); } }, }); const result = await profileRoute.handler({ pathParams: { id: conversation.id }, body: { profile: null }, headers: {}, }); await waitFor(() => received.length === 2, { message: "Timed out waiting for inference profile route event", }); expect(result).toMatchObject({ conversationId: conversation.id, profile: null, }); expect(getConversation(conversation.id)?.inferenceProfile).toBeNull(); expect(received).toEqual([ { profile: null }, { tags: [ SYNC_TAGS.conversationsList, conversationMetadataSyncTag(conversation.id), ], }, ]); subscription.dispose(); }); test("skips write and event when the profile is unchanged", async () => { const conversation = createConversation("inference-profile-noop"); await profileRoute.handler({ pathParams: { id: conversation.id }, body: { profile: "balanced" }, headers: {}, }); await new Promise((resolve) => setTimeout(resolve, 0)); const updatedAtAfterSet = getConversation(conversation.id)?.updatedAt; const received: Array<{ profile?: string | null }> = []; const subscription = assistantEventHub.subscribe({ type: "process", callback: (event) => { if (event.message.type === "conversation_inference_profile_updated") { received.push({ profile: event.message.profile }); } }, }); const result = await profileRoute.handler({ pathParams: { id: conversation.id }, body: { profile: "balanced" }, headers: {}, }); await Promise.resolve(); expect(result).toMatchObject({ conversationId: conversation.id, profile: "balanced", }); expect(getConversation(conversation.id)?.updatedAt).toBe(updatedAtAfterSet); expect(received).toEqual([]); subscription.dispose(); }); test("throws NotFoundError when the conversation does not exist", async () => { await expect( profileRoute.handler({ pathParams: { id: "missing" }, body: { profile: "balanced" }, headers: {}, }), ).rejects.toThrow(NotFoundError); }); });