UNPKG

assistant-cloud

Version:

Cloud integration for assistant-ui

252 lines (211 loc) 7.31 kB
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { AssistantCloudAPI, CloudAPIError } from "../AssistantCloudAPI"; import { CloudResponseError } from "../cloudResponse"; describe("AssistantCloudAPI", () => { beforeEach(() => { vi.restoreAllMocks(); }); afterEach(() => { vi.unstubAllGlobals(); }); it("serializes query params, merges auth headers, and sends JSON body", async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, headers: new Headers(), json: vi.fn().mockResolvedValue({ data: "ok" }), }); vi.stubGlobal("fetch", fetchMock); const api = new AssistantCloudAPI({ apiKey: "test-key", userId: "u-1", workspaceId: "w-1", }); await api.makeRawRequest("/threads", { method: "POST", query: { archived: false, detailed: true, page: 2 }, headers: { "X-Test": "1" }, body: { hello: "world" }, }); expect(fetchMock).toHaveBeenCalledTimes(1); const [url, init] = fetchMock.mock.calls[0]!; // false values are excluded from query params; others are serialized expect(url.toString()).toBe( "https://backend.assistant-api.com/v1/threads?detailed=true&page=2", ); // Real API-key auth headers are merged with custom headers expect(init.headers).toMatchObject({ Authorization: "Bearer test-key", "Aui-User-Id": "u-1", "Aui-Workspace-Id": "w-1", "Content-Type": "application/json", "X-Test": "1", }); expect(init.method).toBe("POST"); expect(init.body).toBe(JSON.stringify({ hello: "world" })); }); it("uses custom baseUrl when provided with apiKey config", async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, headers: new Headers(), json: vi.fn().mockResolvedValue({}), }); vi.stubGlobal("fetch", fetchMock); const api = new AssistantCloudAPI({ baseUrl: "https://custom.example.com", apiKey: "test-key", userId: "u-1", workspaceId: "w-1", }); await api.makeRawRequest("/threads"); const [url] = fetchMock.mock.calls[0]!; expect(url.toString()).toBe("https://custom.example.com/v1/threads"); }); it("strips a trailing slash from a custom baseUrl", async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, headers: new Headers(), json: vi.fn().mockResolvedValue({}), }); vi.stubGlobal("fetch", fetchMock); const api = new AssistantCloudAPI({ baseUrl: "https://custom.example.com/", apiKey: "test-key", userId: "u-1", workspaceId: "w-1", }); await api.makeRawRequest("/threads"); const [url] = fetchMock.mock.calls[0]!; expect(url.toString()).toBe("https://custom.example.com/v1/threads"); }); it("rejects before fetch when auth token callback returns null", async () => { const fetchMock = vi.fn(); vi.stubGlobal("fetch", fetchMock); const api = new AssistantCloudAPI({ baseUrl: "https://test.example.com", authToken: async () => null, }); await expect(api.makeRawRequest("/threads")).rejects.toThrow( "Authorization failed", ); expect(fetchMock).not.toHaveBeenCalled(); }); it("returns false from initializeAuth when auth token callback returns null", async () => { const api = new AssistantCloudAPI({ baseUrl: "https://test.example.com", authToken: async () => null, }); await expect(api.initializeAuth()).resolves.toBe(false); }); it("rejects initializeAuth with context for malformed anonymous responses", async () => { vi.stubGlobal( "fetch", vi.fn().mockResolvedValue({ ok: true, json: vi.fn().mockResolvedValue({ access_token: 123, refresh_token: null, }), }), ); const api = new AssistantCloudAPI({ baseUrl: "https://test.example.com", anonymous: true, }); await expect(api.initializeAuth()).rejects.toThrow( new CloudResponseError( 'Invalid Assistant Cloud response for "anonymous auth token response.access_token": expected a string', ), ); }); it("throws APIError with parsed message for JSON error responses", async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: false, status: 400, headers: new Headers(), text: vi .fn() .mockResolvedValue( JSON.stringify({ message: "invalid request payload" }), ), }); vi.stubGlobal("fetch", fetchMock); const api = new AssistantCloudAPI({ apiKey: "test-key", userId: "u-1", workspaceId: "w-1", }); const error = await api.makeRawRequest("/threads").catch((e) => e); expect(error).toBeInstanceOf(CloudAPIError); expect(error.name).toBe("CloudAPIError"); expect(error.message).toBe("invalid request payload"); expect(error.status).toBe(400); }); it("throws generic error with status for non-JSON error responses", async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: false, status: 502, headers: new Headers(), text: vi.fn().mockResolvedValue("Bad Gateway"), }); vi.stubGlobal("fetch", fetchMock); const api = new AssistantCloudAPI({ apiKey: "test-key", userId: "u-1", workspaceId: "w-1", }); const error = await api.makeRawRequest("/threads").catch((e) => e); expect(error).toBeInstanceOf(CloudAPIError); expect(error.message).toBe("Request failed with status 502, Bad Gateway"); expect(error.status).toBe(502); }); it("makeRequest returns parsed JSON from a successful response", async () => { const responseData = { threads: [{ id: "t-1" }] }; const fetchMock = vi.fn().mockResolvedValue({ ok: true, headers: new Headers(), text: vi.fn().mockResolvedValue(JSON.stringify(responseData)), }); vi.stubGlobal("fetch", fetchMock); const api = new AssistantCloudAPI({ apiKey: "test-key", userId: "u-1", workspaceId: "w-1", }); const result = await api.makeRequest("/threads"); expect(result).toEqual(responseData); }); it("makeRequest returns undefined from a successful empty response", async () => { const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 204, headers: new Headers(), text: vi.fn().mockResolvedValue(""), }); vi.stubGlobal("fetch", fetchMock); const api = new AssistantCloudAPI({ apiKey: "test-key", userId: "u-1", workspaceId: "w-1", }); await expect( api.makeRequest("/threads/t-1", { method: "DELETE" }), ).resolves.toBeUndefined(); }); it("makeRequest returns undefined when content-length is zero", async () => { const text = vi.fn().mockResolvedValue(""); const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200, headers: new Headers({ "content-length": "0" }), text, }); vi.stubGlobal("fetch", fetchMock); const api = new AssistantCloudAPI({ apiKey: "test-key", userId: "u-1", workspaceId: "w-1", }); await expect(api.makeRequest("/threads/t-1")).resolves.toBeUndefined(); expect(text).not.toHaveBeenCalled(); }); });