Files
langgraph/libs/sdk-js/src/tests/fetch.test.ts
T

202 lines
6.1 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
import { Client } from "../client.js";
import { overrideFetchImplementation } from "../singletons/fetch.js";
describe.each([["global"], ["mocked"]])(
"Client uses %s fetch",
(description: string) => {
let globalFetchMock: ReturnType<typeof vi.fn>;
let overriddenFetch: ReturnType<typeof vi.fn>;
let expectedFetchMock: ReturnType<typeof vi.fn>;
let unexpectedFetchMock: ReturnType<typeof vi.fn>;
beforeEach(() => {
globalFetchMock = vi.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
batch_ingest_config: {
use_multipart_endpoint: true,
},
}),
text: () => Promise.resolve(""),
headers: new Headers({}),
}),
);
overriddenFetch = vi.fn(() =>
Promise.resolve({
ok: true,
json: () =>
Promise.resolve({
batch_ingest_config: {
use_multipart_endpoint: true,
},
}),
text: () => Promise.resolve(""),
headers: new Headers({}),
}),
);
expectedFetchMock =
description === "mocked" ? overriddenFetch : globalFetchMock;
unexpectedFetchMock =
description === "mocked" ? globalFetchMock : overriddenFetch;
if (description === "mocked") {
overrideFetchImplementation(overriddenFetch);
} else {
overrideFetchImplementation(globalFetchMock);
}
// Mock global fetch
(globalThis as any).fetch = globalFetchMock;
});
afterEach(() => {
vi.restoreAllMocks();
});
describe("createRuns", () => {
it("should create an example with the given input and generation", async () => {
const client = new Client({ apiKey: "test-api-key" });
const thread = await client.threads.create();
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
expect(unexpectedFetchMock).not.toHaveBeenCalled();
vi.clearAllMocks(); // Clear all mocks before the next operation
// Then clear & run the function
await client.runs.create(thread.thread_id, "somegraph", {
input: { foo: "bar" },
});
expect(expectedFetchMock).toHaveBeenCalledTimes(1);
expect(unexpectedFetchMock).not.toHaveBeenCalled();
});
});
describe("header coalescing", () => {
it("should properly merge headers with conflicting name casing", async () => {
const client = new Client({ apiKey: "test-api-key" });
await (client.threads as any).fetch("/test", {
headers: { "X-Api-Key": "custom-value" },
});
expect(expectedFetchMock).toHaveBeenCalledWith(
expect.any(URL),
expect.objectContaining({
headers: expect.objectContaining({
"x-api-key": "custom-value",
}),
}),
);
});
it("should properly merge headers from multiple sources", async () => {
const client = new Client({
apiKey: "test-api-key",
defaultHeaders: {
"x-default": "default-value",
"x-override": "default-value",
},
});
await (client.threads as any).fetch("/test", {
headers: {
"x-custom": "custom-value",
"x-override": "custom-value",
},
});
expect(expectedFetchMock).toHaveBeenCalledWith(
expect.any(URL),
expect.objectContaining({
headers: expect.objectContaining({
"x-api-key": "test-api-key",
"x-default": "default-value",
"x-custom": "custom-value",
"x-override": "custom-value",
}),
}),
);
vi.clearAllMocks();
// Test with null/undefined values
await (client.threads as any).fetch("/test", {
headers: {
"x-null": null,
"x-undefined": undefined,
"x-empty": "",
},
});
expect(expectedFetchMock).toHaveBeenCalledWith(
expect.any(URL),
expect.objectContaining({
headers: expect.objectContaining({
"x-api-key": "test-api-key",
"x-default": "default-value",
}),
}),
);
expect(expectedFetchMock).not.toHaveBeenCalledWith(
expect.any(URL),
expect.objectContaining({
headers: expect.objectContaining({
"x-null": null,
"x-undefined": undefined,
}),
}),
);
});
it("should handle Headers object input", async () => {
const client = new Client({ apiKey: "test-api-key" });
const headers = new Headers();
headers.append("x-custom", "custom-value");
headers.append("x-multi", "value1");
headers.append("x-multi", "value2");
await (client.threads as any).fetch("/test", { headers });
expect(expectedFetchMock).toHaveBeenCalledWith(
expect.any(URL),
expect.objectContaining({
headers: expect.objectContaining({
"x-api-key": "test-api-key",
"x-custom": "custom-value",
"x-multi": "value1, value2",
}),
}),
);
});
it("should handle array of header tuples", async () => {
const client = new Client({
apiKey: "test-api-key",
defaultHeaders: {
"x-custom": "custom-value",
},
});
const headers = [
["x-multi", "value1"],
["x-multi", "value2"],
];
await (client.threads as any).fetch("/test", { headers });
expect(expectedFetchMock).toHaveBeenCalledWith(
expect.any(URL),
expect.objectContaining({
headers: expect.objectContaining({
"x-api-key": "test-api-key",
"x-custom": "custom-value",
"x-multi": "value1, value2",
}),
}),
);
});
});
},
);