Files
mem0/mem0-ts/src/client/tests/integration/crud.test.ts
T

254 lines
8.5 KiB
TypeScript

/**
* Integration tests: Memory CRUD operations.
*
* Tests the full lifecycle: add → get → getAll → update → delete.
* Validates response shapes against the real API.
*
* Run: MEM0_API_KEY=your-key npx jest crud.test.ts --forceExit
*/
import { MemoryClient } from "../../mem0";
import { MemoryError } from "../../../common/exceptions";
import { randomUUID } from "crypto";
import {
describeIntegration,
createTestClient,
suppressTelemetryNoise,
waitForMemories,
cleanupTestUser,
} from "./helpers";
jest.setTimeout(120_000);
const TEST_USER_ID = `integration-crud-${randomUUID()}`;
describeIntegration("MemoryClient Integration — CRUD", () => {
let client: MemoryClient;
let cleanup: () => void;
let memoryIds: string[] = [];
beforeAll(() => {
cleanup = suppressTelemetryNoise();
client = createTestClient();
});
afterAll(async () => {
await cleanupTestUser(client, TEST_USER_ID);
cleanup();
});
// ─── Add ──────────────────────────────────────────────────
describe("add memories", () => {
test("add returns a pending response with event_id", async () => {
const messages = [
{
role: "user" as const,
content: "Hi, I'm integration-test-user. My favorite color is blue.",
},
{
role: "assistant" as const,
content:
"Nice to meet you! I'll remember that your favorite color is blue.",
},
];
const result = await client.add(messages, { user_id: TEST_USER_ID });
// API processes memories asynchronously — returns PENDING
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
// Validate response shape
for (const item of result) {
expect(item).toHaveProperty("status");
expect(item).toHaveProperty("event_id");
}
});
test("adds a second batch of messages", async () => {
const messages = [
{
role: "user" as const,
content: "I work as a software engineer at Acme Corp.",
},
{
role: "assistant" as const,
content: "Got it, you're a software engineer at Acme Corp!",
},
];
const result = await client.add(messages, { user_id: TEST_USER_ID });
expect(Array.isArray(result)).toBe(true);
});
test("memories become available after async processing", async () => {
const memories = await waitForMemories(client, TEST_USER_ID, 1);
expect(memories.length).toBeGreaterThan(0);
// Store IDs for later tests
memoryIds = memories.map((m) => m.id);
expect(memoryIds.length).toBeGreaterThan(0);
expect(typeof memoryIds[0]).toBe("string");
});
});
// ─── Get by ID ────────────────────────────────────────────
describe("get memory by ID", () => {
test("retrieves a specific memory with correct shape", async () => {
const memoryId = memoryIds[0];
expect(memoryId).toBeDefined();
const memory = await client.get(memoryId);
expect(memory.id).toBe(memoryId);
expect(typeof memory.memory).toBe("string");
expect(memory.memory!.length).toBeGreaterThan(0);
expect(typeof memory.user_id).toBe("string");
expect(
memory.metadata === null || typeof memory.metadata === "object",
).toBe(true);
expect(
Array.isArray(memory.categories) || memory.categories === null,
).toBe(true);
expect(new Date(memory.created_at || "").toString()).not.toBe(
"Invalid Date",
);
expect(new Date(memory.updated_at || "").toString()).not.toBe(
"Invalid Date",
);
});
});
// ─── Get all ──────────────────────────────────────────────
describe("get all memories", () => {
test("returns all memories for test user", async () => {
const memories = await client.getAll({ user_id: TEST_USER_ID });
expect(Array.isArray(memories)).toBe(true);
expect(memories.length).toBeGreaterThanOrEqual(memoryIds.length);
for (const mem of memories) {
expect(typeof mem.id).toBe("string");
expect(typeof mem.memory).toBe("string");
}
});
test("returns paginated results with page and page_size", async () => {
const page1 = await client.getAll({
user_id: TEST_USER_ID,
page: 1,
page_size: 1,
});
// Paginated response is an object with results array
expect(page1).toBeDefined();
});
});
// ─── Update ───────────────────────────────────────────────
describe("update memory", () => {
test("updates memory text and verifies the content changed", async () => {
const memoryId = memoryIds[0];
// Read original text before update
const original = await client.get(memoryId);
const originalText = original.memory;
await client.update(memoryId, {
text: "My favorite color is green (updated)",
});
const updated = await client.get(memoryId);
expect(typeof updated.memory).toBe("string");
expect(updated.memory).not.toBe(originalText);
});
test("updates memory metadata", async () => {
const memoryId = memoryIds[0];
await client.update(memoryId, {
metadata: { source: "integration-test", priority: "high" },
});
const updated = await client.get(memoryId);
expect(updated.metadata).toBeDefined();
expect(updated.metadata.source).toBe("integration-test");
expect(updated.metadata.priority).toBe("high");
});
});
// ─── Edge cases ──────────────────────────────────────────
describe("edge cases", () => {
test("add with metadata attaches metadata to the memory", async () => {
const result = await client.add(
[
{ role: "user" as const, content: "I prefer dark mode in all apps." },
{
role: "assistant" as const,
content: "Noted, dark mode preference saved!",
},
],
{
user_id: TEST_USER_ID,
metadata: { source: "integration-test", category: "preferences" },
},
);
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBeGreaterThan(0);
});
test("getAll for non-existent user returns empty array", async () => {
const memories = await client.getAll({
user_id: `nonexistent-user-${randomUUID()}`,
});
expect(Array.isArray(memories)).toBe(true);
expect(memories.length).toBe(0);
});
test("deleteAll for non-existent user does not throw", async () => {
const result = await client.deleteAll({
user_id: `nonexistent-user-${randomUUID()}`,
});
expect(result).toBeDefined();
expect(typeof result.message).toBe("string");
});
});
// ─── Delete single ────────────────────────────────────────
// NOTE: Delete tests run last to avoid race conditions with
// other tests that depend on the seeded memories.
describe("delete memory", () => {
test("deletes a single memory by ID", async () => {
const memoryId = memoryIds[0];
expect(memoryId).toBeDefined();
const result = await client.delete(memoryId);
expect(result).toBeDefined();
expect(typeof result.message).toBe("string");
});
test("getting deleted memory throws MemoryError", async () => {
const memoryId = memoryIds[0];
await expect(client.get(memoryId)).rejects.toThrow(MemoryError);
});
});
// ─── Delete all + delete user ─────────────────────────────
describe("cleanup operations", () => {
test("deletes all memories for test user", async () => {
const result = await client.deleteAll({ user_id: TEST_USER_ID });
expect(result).toBeDefined();
expect(typeof result.message).toBe("string");
});
test("deletes the test user entity", async () => {
const result = await client.deleteUsers({ user_id: TEST_USER_ID });
expect(result).toBeDefined();
expect(result.message).toBe("Entity deleted successfully.");
});
});
});