Files
mem0/mem0-ts/src/client/tests/memoryClient.init.test.ts
T

192 lines
6.7 KiB
TypeScript

/**
* MemoryClient unit tests — constructor, validation, ping.
*/
import { MemoryClient } from "../mem0";
import {
MemoryNotFoundError,
ValidationError,
MemoryError,
} from "../../common/exceptions";
import { createMockFetch, TEST_API_KEY, TEST_HOST } from "./helpers";
import {
setupMockFetch,
installConsoleSuppression,
MOCK_PING_RESPONSE,
} from "./setup";
installConsoleSuppression();
// ─── Initialization ──────────────────────────────────────
describe("MemoryClient - Initialization", () => {
beforeEach(() => setupMockFetch());
test("throws when API key is empty string", () => {
expect(() => new MemoryClient({ apiKey: "" })).toThrow(
"Mem0 API key is required",
);
});
test("throws when API key is whitespace only", () => {
expect(() => new MemoryClient({ apiKey: " " })).toThrow(
"Mem0 API key cannot be empty",
);
});
test("throws when API key is not a string", () => {
expect(
() => new MemoryClient({ apiKey: 123 as unknown as string }),
).toThrow("Mem0 API key must be a string");
});
test("sets default host to https://api.mem0.ai", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
expect(client.host).toBe("https://api.mem0.ai");
});
test("uses custom host when provided", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY, host: TEST_HOST });
expect(client.host).toBe(TEST_HOST);
});
test("sets Authorization header with Token prefix", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
expect(client.headers["Authorization"]).toBe(`Token ${TEST_API_KEY}`);
});
test("creates axios client with 60s timeout", () => {
const client = new MemoryClient({ apiKey: TEST_API_KEY });
expect(client.client.defaults.timeout).toBe(60000);
});
});
// ─── Ping ────────────────────────────────────────────────
describe("MemoryClient - ping()", () => {
test("sets telemetryId from user_email in response", async () => {
setupMockFetch();
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.ping();
expect(client.telemetryId).toBe("test@example.com");
});
test("throws AuthenticationError on 401 response", async () => {
const { AuthenticationError } = await import("../../common/exceptions");
const responses = new Map<string, { status: number; body: unknown }>();
responses.set("/v1/ping/", {
status: 401,
body: "Invalid API key",
});
global.fetch = createMockFetch(responses);
const client = new MemoryClient({ apiKey: "bad-key" });
await expect(client.ping()).rejects.toThrow(AuthenticationError);
});
test("throws on invalid (non-object) response format", async () => {
const responses = new Map<string, { status: number; body: unknown }>();
responses.set("/v1/ping/", { status: 200, body: "not an object" });
global.fetch = createMockFetch(responses);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.ping()).rejects.toThrow("Invalid response format");
});
test("throws on status !== ok in response", async () => {
const responses = new Map<string, { status: number; body: unknown }>();
responses.set("/v1/ping/", {
status: 200,
body: { status: "error", message: "API Key is invalid" },
});
global.fetch = createMockFetch(responses);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.ping()).rejects.toThrow("API Key is invalid");
});
});
// ─── Error Handling ──────────────────────────────────────
describe("MemoryClient - Error Handling", () => {
test("404 throws MemoryNotFoundError with server response text", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/gone/", { status: 404, body: "Memory not found" });
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.get("gone")).rejects.toThrow(MemoryNotFoundError);
await expect(client.get("gone")).rejects.toThrow("Memory not found");
});
test("500 throws MemoryError with server response text", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/err/", {
status: 500,
body: "Internal server error",
});
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.get("err")).rejects.toThrow(MemoryError);
await expect(client.get("err")).rejects.toThrow("Internal server error");
});
test("400 throws ValidationError with details from server", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/bad/", {
status: 400,
body: "Invalid request: user_id is required",
});
setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.get("bad")).rejects.toThrow(ValidationError);
await expect(client.get("bad")).rejects.toThrow(
"Invalid request: user_id is required",
);
});
test("Authorization header is included in fetch calls", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/v1/memories/mem_1/", {
status: 200,
body: { id: "mem_1" },
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.get("mem_1");
const call = mock.mock.calls.find((c: [string, RequestInit]) =>
c[0].includes("/v1/memories/mem_1/"),
);
const headers = call![1].headers as Record<string, string>;
expect(headers["Authorization"]).toContain(TEST_API_KEY);
});
test("network failure (fetch throws) is propagated", async () => {
global.fetch = jest.fn(async (url: string | URL | Request) => {
const urlStr = typeof url === "string" ? url : url.toString();
if (urlStr.includes("/v1/memories/net_err/")) {
throw new TypeError("Failed to fetch");
}
if (urlStr.includes("/v1/ping/")) {
return {
ok: true,
status: 200,
json: async () => MOCK_PING_RESPONSE,
text: async () => JSON.stringify(MOCK_PING_RESPONSE),
} as Response;
}
return {
ok: false,
status: 404,
text: async () => "Not found",
} as Response;
});
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await expect(client.get("net_err")).rejects.toThrow();
});
});