assistant-cloud
Version:
Cloud integration for assistant-ui
142 lines (116 loc) • 4.92 kB
text/typescript
import { describe, expect, it, vi } from "vitest";
import type { AssistantCloudAPI } from "./AssistantCloudAPI";
import { AssistantCloudThreads } from "./AssistantCloudThreads";
const createCloudThreads = () => {
const makeRequest = vi.fn();
const api = { makeRequest } as unknown as AssistantCloudAPI;
return { threads: new AssistantCloudThreads(api), makeRequest };
};
const threadResponse = {
title: null,
last_message_at: "2026-07-16T12:30:00.000Z",
metadata: { created_at: "leave-this-string-untouched" },
external_id: null,
id: "thread-1",
project_id: "project-1",
created_at: "2026-07-16T12:00:00.000Z",
updated_at: "2026-07-16T12:15:00.123Z",
workspace_id: "workspace-1",
is_archived: false,
};
describe("AssistantCloudThreads responses", () => {
it("validates created thread IDs", async () => {
const { threads, makeRequest } = createCloudThreads();
makeRequest.mockResolvedValueOnce({ thread_id: "thread-1" });
await expect(
threads.create({ last_message_at: new Date() }),
).resolves.toEqual({ thread_id: "thread-1" });
makeRequest.mockResolvedValueOnce({});
await expect(
threads.create({ last_message_at: new Date() }),
).rejects.toThrow(
'Invalid Assistant Cloud response for "thread_id": expected a string',
);
});
it("sends upsert with the external id when creating a thread", async () => {
const { threads, makeRequest } = createCloudThreads();
makeRequest.mockResolvedValueOnce({ thread_id: "thread-1" });
const last_message_at = new Date("2026-09-25T09:30:00.000Z");
await expect(
threads.create({ last_message_at, external_id: "crm:42", upsert: true }),
).resolves.toEqual({ thread_id: "thread-1" });
expect(makeRequest).toHaveBeenCalledWith("/threads", {
method: "POST",
body: { last_message_at, external_id: "crm:42", upsert: true },
});
});
it("claims anonymous threads and validates the moved count", async () => {
const { threads, makeRequest } = createCloudThreads();
makeRequest.mockResolvedValueOnce({ moved: 2 });
await expect(
threads.claim({ refresh_token: "anonymous-refresh" }),
).resolves.toEqual({ moved: 2 });
expect(makeRequest).toHaveBeenLastCalledWith("/threads/claim", {
method: "POST",
body: { refresh_token: "anonymous-refresh" },
});
makeRequest.mockResolvedValueOnce({ moved: 1.5 });
await expect(
threads.claim({ refresh_token: "anonymous-refresh" }),
).rejects.toThrow(
'Invalid Assistant Cloud response for "moved": expected an integer',
);
});
it("forwards both archive filter values", async () => {
const { threads, makeRequest } = createCloudThreads();
makeRequest.mockResolvedValue({ threads: [] });
await threads.list({ is_archived: false });
expect(makeRequest).toHaveBeenLastCalledWith("/threads", {
query: { is_archived: "false" },
});
await threads.list({ is_archived: true });
expect(makeRequest).toHaveBeenLastCalledWith("/threads", {
query: { is_archived: "true" },
});
});
it("decodes canonical thread list responses", async () => {
const { threads, makeRequest } = createCloudThreads();
makeRequest.mockResolvedValue({ threads: [threadResponse] });
const result = await threads.list();
const thread = result.threads[0]!;
expect(thread.created_at).toBeInstanceOf(Date);
expect(thread.updated_at).toBeInstanceOf(Date);
expect(thread.last_message_at).toBeInstanceOf(Date);
expect(thread.updated_at.toISOString()).toBe("2026-07-16T12:15:00.123Z");
expect(thread.title).toBe("");
expect(thread.metadata).toEqual({
created_at: "leave-this-string-untouched",
});
});
it("unwraps and decodes a single thread response", async () => {
const { threads, makeRequest } = createCloudThreads();
makeRequest.mockResolvedValue({ thread: threadResponse });
const result = await threads.get("thread-1");
expect(result.created_at).toBeInstanceOf(Date);
expect(result.created_at.toISOString()).toBe("2026-07-16T12:00:00.000Z");
});
it("rejects a bare thread that is missing the response envelope", async () => {
const { threads, makeRequest } = createCloudThreads();
makeRequest.mockResolvedValue(threadResponse);
await expect(threads.get("thread-1")).rejects.toThrow(
'Invalid Assistant Cloud response for "thread": expected an object',
);
});
it("rejects genuinely invalid response timestamps with field context", async () => {
const { threads, makeRequest } = createCloudThreads();
makeRequest.mockResolvedValue({
thread: {
...threadResponse,
updated_at: "not-a-timestamp",
},
});
await expect(threads.get("thread-1")).rejects.toThrow(
'Invalid Assistant Cloud response for "thread.updated_at": expected a canonical ISO timestamp',
);
});
});