feat(test): integration test for ts-sdk
This commit is contained in:
@@ -59,6 +59,8 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
working-directory: mem0-ts
|
||||
env:
|
||||
MEM0_API_KEY: ${{ secrets.MEM0_API_KEY }}
|
||||
run: pnpm run test:ci
|
||||
|
||||
- name: Verify package exports
|
||||
|
||||
@@ -674,12 +674,17 @@ export default class MemoryClient {
|
||||
async createWebhook(webhook: WebhookPayload): Promise<Webhook> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._captureEvent("create_webhook", []);
|
||||
const { eventTypes, projectId, webhookId, ...rest } = webhook;
|
||||
const payload = {
|
||||
...rest,
|
||||
event_types: eventTypes,
|
||||
};
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/api/v1/webhooks/projects/${this.projectId}/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(webhook),
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
@@ -688,16 +693,18 @@ export default class MemoryClient {
|
||||
async updateWebhook(webhook: WebhookPayload): Promise<{ message: string }> {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
this._captureEvent("update_webhook", []);
|
||||
const project_id = webhook.projectId || this.projectId;
|
||||
const { eventTypes, projectId, webhookId, ...rest } = webhook;
|
||||
const payload = {
|
||||
...rest,
|
||||
event_types: eventTypes,
|
||||
project: projectId || this.projectId,
|
||||
};
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/api/v1/webhooks/${webhook.webhookId}/`,
|
||||
`${this.host}/api/v1/webhooks/${webhookId}/`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify({
|
||||
...webhook,
|
||||
projectId: project_id,
|
||||
}),
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
@@ -723,12 +730,15 @@ export default class MemoryClient {
|
||||
if (this.telemetryId === "") await this.ping();
|
||||
const payloadKeys = Object.keys(data || {});
|
||||
this._captureEvent("feedback", [payloadKeys]);
|
||||
const payload: Record<string, any> = { ...data };
|
||||
if (this.organizationId) payload.org_id = String(this.organizationId);
|
||||
if (this.projectId) payload.project_id = String(this.projectId);
|
||||
const response = await this._fetchWithErrorHandling(
|
||||
`${this.host}/v1/feedback/`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: this.headers,
|
||||
body: JSON.stringify(data),
|
||||
body: JSON.stringify(payload),
|
||||
},
|
||||
);
|
||||
return response;
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Integration tests: Batch operations.
|
||||
*
|
||||
* Tests batch update and batch delete against the real API.
|
||||
*
|
||||
* Run: MEM0_API_KEY=your-key npx jest batch.test.ts --forceExit
|
||||
*/
|
||||
import { MemoryClient } from "../../mem0";
|
||||
import { randomUUID } from "crypto";
|
||||
import {
|
||||
describeIntegration,
|
||||
createTestClient,
|
||||
suppressTelemetryNoise,
|
||||
seedTestMemories,
|
||||
waitForMemories,
|
||||
cleanupTestUser,
|
||||
} from "./helpers";
|
||||
|
||||
jest.setTimeout(120_000);
|
||||
|
||||
const TEST_USER_ID = `integration-batch-${randomUUID()}`;
|
||||
|
||||
describeIntegration("MemoryClient Integration — Batch Operations", () => {
|
||||
let client: MemoryClient;
|
||||
let cleanup: () => void;
|
||||
let memoryIds: string[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
cleanup = suppressTelemetryNoise();
|
||||
client = createTestClient();
|
||||
memoryIds = await seedTestMemories(client, TEST_USER_ID);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupTestUser(client, TEST_USER_ID);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("batch updates memories", async () => {
|
||||
expect(memoryIds.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const batchPayload = memoryIds
|
||||
.slice(0, Math.min(2, memoryIds.length))
|
||||
.map((id) => ({
|
||||
memoryId: id,
|
||||
text: `Batch updated content for ${id}`,
|
||||
}));
|
||||
|
||||
const result = await client.batchUpdate(batchPayload);
|
||||
expect(result).toBeDefined();
|
||||
|
||||
// Verify the update took effect on at least one memory
|
||||
const updated = await client.get(memoryIds[0]);
|
||||
expect(typeof updated.memory).toBe("string");
|
||||
});
|
||||
|
||||
test("batch deletes throwaway memories", async () => {
|
||||
// Add a throwaway memory, wait for it, then batch-delete
|
||||
await client.add(
|
||||
[
|
||||
{
|
||||
role: "user" as const,
|
||||
content: "Temporary memory for batch delete test.",
|
||||
},
|
||||
{ role: "assistant" as const, content: "Noted!" },
|
||||
],
|
||||
{ user_id: TEST_USER_ID },
|
||||
);
|
||||
|
||||
// Wait for new memory to appear
|
||||
const before = await waitForMemories(
|
||||
client,
|
||||
TEST_USER_ID,
|
||||
memoryIds.length + 1,
|
||||
);
|
||||
const throwawayIds = before
|
||||
.filter((m) => !memoryIds.includes(m.id))
|
||||
.map((m) => m.id);
|
||||
|
||||
// Assert we actually have something to delete
|
||||
expect(throwawayIds.length).toBeGreaterThan(0);
|
||||
|
||||
const result = await client.batchDelete(throwawayIds);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── 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);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Delete single ────────────────────────────────────────
|
||||
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.");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Integration tests: Feedback and memory export.
|
||||
*
|
||||
* Tests feedback submission and the export lifecycle
|
||||
* (create → retrieve) against the real API.
|
||||
*
|
||||
* Run: MEM0_API_KEY=your-key npx jest feedback-and-export.test.ts --forceExit
|
||||
*/
|
||||
import { MemoryClient } from "../../mem0";
|
||||
import { Feedback } from "../../mem0.types";
|
||||
import { randomUUID } from "crypto";
|
||||
import {
|
||||
describeIntegration,
|
||||
createTestClient,
|
||||
suppressTelemetryNoise,
|
||||
seedTestMemories,
|
||||
cleanupTestUser,
|
||||
} from "./helpers";
|
||||
|
||||
jest.setTimeout(120_000);
|
||||
|
||||
const TEST_USER_ID = `integration-fb-export-${randomUUID()}`;
|
||||
|
||||
describeIntegration("MemoryClient Integration — Feedback & Export", () => {
|
||||
let client: MemoryClient;
|
||||
let cleanup: () => void;
|
||||
let memoryIds: string[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
cleanup = suppressTelemetryNoise();
|
||||
client = createTestClient();
|
||||
memoryIds = await seedTestMemories(client, TEST_USER_ID);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupTestUser(client, TEST_USER_ID);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
// ─── Feedback ─────────────────────────────────────────────
|
||||
describe("feedback", () => {
|
||||
test("submits positive feedback on a memory", async () => {
|
||||
const memoryId = memoryIds[0];
|
||||
expect(memoryId).toBeDefined();
|
||||
|
||||
const result = await client.feedback({
|
||||
memory_id: memoryId,
|
||||
feedback: Feedback.POSITIVE,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Export ───────────────────────────────────────────────
|
||||
describe("memory export", () => {
|
||||
let exportId: string | undefined;
|
||||
|
||||
test("creates a memory export", async () => {
|
||||
const result = await client.createMemoryExport({
|
||||
filters: { AND: [{ user_id: TEST_USER_ID }] },
|
||||
schema: { memory: "string", user_id: "string" },
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(
|
||||
typeof result.id === "string" || typeof result.message === "string",
|
||||
).toBe(true);
|
||||
|
||||
exportId = result.id;
|
||||
});
|
||||
|
||||
test("retrieves a memory export", async () => {
|
||||
if (!exportId) return;
|
||||
|
||||
const result = await client.getMemoryExport({
|
||||
memory_export_id: exportId,
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Shared helpers for MemoryClient real integration tests.
|
||||
*
|
||||
* Provides environment gating, client factory, polling helpers,
|
||||
* and console suppression for telemetry noise.
|
||||
*/
|
||||
import { MemoryClient } from "../../mem0";
|
||||
import type { Memory } from "../../mem0.types";
|
||||
|
||||
// ─── Environment gate ────────────────────────────────────
|
||||
export const API_KEY = process.env.MEM0_API_KEY;
|
||||
export const describeIntegration = API_KEY ? describe : describe.skip;
|
||||
|
||||
/**
|
||||
* Create a MemoryClient with the real API key.
|
||||
* Call this inside beforeAll — not at module scope — so it only
|
||||
* runs when the suite is not skipped.
|
||||
*/
|
||||
export function createTestClient(): MemoryClient {
|
||||
return new MemoryClient({ apiKey: API_KEY! });
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll getAll until memories appear for a user.
|
||||
* The Mem0 API processes memories asynchronously — after add()
|
||||
* we need to wait for them to be available.
|
||||
*/
|
||||
export async function waitForMemories(
|
||||
client: MemoryClient,
|
||||
userId: string,
|
||||
minCount: number,
|
||||
maxWaitMs = 30_000,
|
||||
): Promise<Memory[]> {
|
||||
const start = Date.now();
|
||||
while (Date.now() - start < maxWaitMs) {
|
||||
const memories = await client.getAll({ user_id: userId });
|
||||
if (Array.isArray(memories) && memories.length >= minCount) {
|
||||
return memories;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 2_000));
|
||||
}
|
||||
return await client.getAll({ user_id: userId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppress telemetry console noise during tests.
|
||||
* Returns a cleanup function to call in afterAll.
|
||||
*/
|
||||
export function suppressTelemetryNoise(): () => void {
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
|
||||
jest.spyOn(console, "error").mockImplementation((...args: unknown[]) => {
|
||||
if (
|
||||
String(args[0] ?? "").match(
|
||||
/Telemetry|Failed to initialize|Failed to capture/,
|
||||
)
|
||||
)
|
||||
return;
|
||||
originalError(...args);
|
||||
});
|
||||
jest.spyOn(console, "warn").mockImplementation((...args: unknown[]) => {
|
||||
if (String(args[0] ?? "").match(/telemetry|Telemetry/)) return;
|
||||
originalWarn(...args);
|
||||
});
|
||||
|
||||
return () => jest.restoreAllMocks();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add test memories and wait for them to be processed.
|
||||
* Returns the memory IDs once available.
|
||||
*/
|
||||
export async function seedTestMemories(
|
||||
client: MemoryClient,
|
||||
userId: string,
|
||||
): Promise<string[]> {
|
||||
await client.add(
|
||||
[
|
||||
{
|
||||
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.",
|
||||
},
|
||||
],
|
||||
{ user_id: userId },
|
||||
);
|
||||
|
||||
await client.add(
|
||||
[
|
||||
{
|
||||
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!",
|
||||
},
|
||||
],
|
||||
{ user_id: userId },
|
||||
);
|
||||
|
||||
// Wait for async processing
|
||||
const memories = await waitForMemories(client, userId, 1);
|
||||
return memories.map((m) => m.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all test data for a user. Best-effort — ignores errors.
|
||||
*/
|
||||
export async function cleanupTestUser(
|
||||
client: MemoryClient,
|
||||
userId: string,
|
||||
): Promise<void> {
|
||||
try {
|
||||
await client.deleteAll({ user_id: userId });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
await client.deleteUsers({ user_id: userId });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Integration tests: Client initialization and error handling.
|
||||
*
|
||||
* Tests ping, org/project resolution, and invalid credentials.
|
||||
* These tests do NOT need pre-seeded memories.
|
||||
*
|
||||
* Run: MEM0_API_KEY=your-key npx jest initialization.test.ts --forceExit
|
||||
*/
|
||||
import { MemoryClient } from "../../mem0";
|
||||
import {
|
||||
MemoryError,
|
||||
MemoryNotFoundError,
|
||||
ValidationError,
|
||||
} from "../../../common/exceptions";
|
||||
import {
|
||||
describeIntegration,
|
||||
createTestClient,
|
||||
suppressTelemetryNoise,
|
||||
} from "./helpers";
|
||||
|
||||
jest.setTimeout(60_000);
|
||||
|
||||
describeIntegration("MemoryClient Integration — Initialization", () => {
|
||||
let client: MemoryClient;
|
||||
let cleanup: () => void;
|
||||
|
||||
beforeAll(() => {
|
||||
cleanup = suppressTelemetryNoise();
|
||||
client = createTestClient();
|
||||
});
|
||||
|
||||
afterAll(() => cleanup());
|
||||
|
||||
test("client pings successfully and resolves org/project", async () => {
|
||||
await client.ping();
|
||||
expect(client.organizationId).toBeTruthy();
|
||||
expect(client.projectId).toBeTruthy();
|
||||
});
|
||||
|
||||
test("get with invalid ID throws ValidationError", async () => {
|
||||
// Non-UUID string triggers a 400 ValidationError, not a 404
|
||||
await expect(client.get("nonexistent-memory-id-12345")).rejects.toThrow(
|
||||
ValidationError,
|
||||
);
|
||||
});
|
||||
|
||||
test("get with non-existent UUID throws MemoryNotFoundError", async () => {
|
||||
await expect(
|
||||
client.get("00000000-0000-0000-0000-000000000000"),
|
||||
).rejects.toThrow(MemoryNotFoundError);
|
||||
});
|
||||
|
||||
test("all SDK exceptions are MemoryError subclasses", async () => {
|
||||
await expect(client.get("nonexistent-memory-id-12345")).rejects.toThrow(
|
||||
MemoryError,
|
||||
);
|
||||
});
|
||||
|
||||
test("invalid API key throws on ping", async () => {
|
||||
const badClient = new MemoryClient({ apiKey: "invalid-key-12345" });
|
||||
await expect(badClient.ping()).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Integration tests: User management, project, and webhooks.
|
||||
*
|
||||
* Tests users(), getProject(), and the full webhook lifecycle
|
||||
* (create → list → update → delete) against the real API.
|
||||
*
|
||||
* Run: MEM0_API_KEY=your-key npx jest management.test.ts --forceExit
|
||||
*/
|
||||
import { MemoryClient } from "../../mem0";
|
||||
import type { Webhook } from "../../mem0.types";
|
||||
import { WebhookEvent } from "../../mem0.types";
|
||||
import { randomUUID } from "crypto";
|
||||
import {
|
||||
describeIntegration,
|
||||
createTestClient,
|
||||
suppressTelemetryNoise,
|
||||
seedTestMemories,
|
||||
cleanupTestUser,
|
||||
} from "./helpers";
|
||||
|
||||
jest.setTimeout(120_000);
|
||||
|
||||
const TEST_USER_ID = `integration-mgmt-${randomUUID()}`;
|
||||
|
||||
describeIntegration(
|
||||
"MemoryClient Integration — Users, Project & Webhooks",
|
||||
() => {
|
||||
let client: MemoryClient;
|
||||
let cleanup: () => void;
|
||||
let webhookId: string | undefined;
|
||||
|
||||
beforeAll(async () => {
|
||||
cleanup = suppressTelemetryNoise();
|
||||
client = createTestClient();
|
||||
await seedTestMemories(client, TEST_USER_ID);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (webhookId) {
|
||||
try {
|
||||
await client.deleteWebhook({ webhookId });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
await cleanupTestUser(client, TEST_USER_ID);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
// ─── Users ────────────────────────────────────────────────
|
||||
describe("user management", () => {
|
||||
test("lists users and finds test user", async () => {
|
||||
const allUsers = await client.users();
|
||||
|
||||
expect(typeof allUsers.count).toBe("number");
|
||||
expect(Array.isArray(allUsers.results)).toBe(true);
|
||||
|
||||
if (allUsers.results.length > 0) {
|
||||
const user = allUsers.results[0];
|
||||
expect(typeof user.id).toBe("string");
|
||||
expect(typeof user.name).toBe("string");
|
||||
expect(typeof user.type).toBe("string");
|
||||
}
|
||||
|
||||
const testUser = allUsers.results.find((u) => u.name === TEST_USER_ID);
|
||||
expect(testUser).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Project ──────────────────────────────────────────────
|
||||
describe("project management", () => {
|
||||
test("gets project with custom_instructions field", async () => {
|
||||
const project = await client.getProject({
|
||||
fields: ["custom_instructions"],
|
||||
});
|
||||
|
||||
expect(project).toBeDefined();
|
||||
expect(typeof project).toBe("object");
|
||||
expect("custom_instructions" in project).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Webhooks ─────────────────────────────────────────────
|
||||
describe("webhook lifecycle", () => {
|
||||
const webhookUrl = `https://example.com/webhook-test-${randomUUID()}`;
|
||||
|
||||
test("creates a webhook", async () => {
|
||||
const created = await client.createWebhook({
|
||||
name: `integration-test-webhook-${randomUUID().slice(0, 8)}`,
|
||||
url: webhookUrl,
|
||||
eventTypes: [WebhookEvent.MEMORY_ADDED, WebhookEvent.MEMORY_UPDATED],
|
||||
projectId: String(client.projectId),
|
||||
webhookId: "",
|
||||
});
|
||||
|
||||
expect(created).toBeDefined();
|
||||
expect(typeof created.webhook_id).toBe("string");
|
||||
|
||||
webhookId = created.webhook_id;
|
||||
});
|
||||
|
||||
test("lists webhooks and finds the created one", async () => {
|
||||
const webhooks = await client.getWebhooks();
|
||||
|
||||
expect(Array.isArray(webhooks)).toBe(true);
|
||||
|
||||
if (webhookId) {
|
||||
const found = webhooks.find(
|
||||
(w: Webhook) => w.webhook_id === webhookId,
|
||||
);
|
||||
expect(found).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("updates a webhook", async () => {
|
||||
if (!webhookId) return;
|
||||
|
||||
const result = await client.updateWebhook({
|
||||
webhookId,
|
||||
name: `updated-webhook-${randomUUID().slice(0, 8)}`,
|
||||
url: webhookUrl,
|
||||
eventTypes: [
|
||||
WebhookEvent.MEMORY_ADDED,
|
||||
WebhookEvent.MEMORY_UPDATED,
|
||||
WebhookEvent.MEMORY_DELETED,
|
||||
],
|
||||
projectId: String(client.projectId),
|
||||
});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
|
||||
test("deletes the webhook", async () => {
|
||||
if (!webhookId) return;
|
||||
|
||||
const result = await client.deleteWebhook({ webhookId });
|
||||
expect(result).toBeDefined();
|
||||
|
||||
webhookId = undefined;
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Integration tests: Search and history operations.
|
||||
*
|
||||
* Tests search v1, search v2, and memory history against the real API.
|
||||
*
|
||||
* Run: MEM0_API_KEY=your-key npx jest search.test.ts --forceExit
|
||||
*/
|
||||
import { MemoryClient } from "../../mem0";
|
||||
import { randomUUID } from "crypto";
|
||||
import {
|
||||
describeIntegration,
|
||||
createTestClient,
|
||||
suppressTelemetryNoise,
|
||||
seedTestMemories,
|
||||
cleanupTestUser,
|
||||
} from "./helpers";
|
||||
|
||||
jest.setTimeout(120_000);
|
||||
|
||||
const TEST_USER_ID = `integration-search-${randomUUID()}`;
|
||||
|
||||
describeIntegration("MemoryClient Integration — Search & History", () => {
|
||||
let client: MemoryClient;
|
||||
let cleanup: () => void;
|
||||
let memoryIds: string[] = [];
|
||||
|
||||
beforeAll(async () => {
|
||||
cleanup = suppressTelemetryNoise();
|
||||
client = createTestClient();
|
||||
memoryIds = await seedTestMemories(client, TEST_USER_ID);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanupTestUser(client, TEST_USER_ID);
|
||||
cleanup();
|
||||
});
|
||||
|
||||
// ─── Search v1 ────────────────────────────────────────────
|
||||
describe("search v1", () => {
|
||||
test("searches memories by user_id and returns results with scores", async () => {
|
||||
const results = await client.search("What is my favorite color?", {
|
||||
user_id: TEST_USER_ID,
|
||||
});
|
||||
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
|
||||
const first = results[0];
|
||||
expect(typeof first.id).toBe("string");
|
||||
expect(typeof first.memory).toBe("string");
|
||||
expect(typeof first.score).toBe("number");
|
||||
expect(first.score).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Search v2 ────────────────────────────────────────────
|
||||
describe("search v2", () => {
|
||||
test("searches with OR filters and returns results", async () => {
|
||||
const results = await client.search("What do you know about me?", {
|
||||
filters: {
|
||||
OR: [{ user_id: TEST_USER_ID }],
|
||||
},
|
||||
api_version: "v2",
|
||||
});
|
||||
|
||||
expect(Array.isArray(results)).toBe(true);
|
||||
expect(results.length).toBeGreaterThan(0);
|
||||
|
||||
const first = results[0];
|
||||
expect(typeof first.id).toBe("string");
|
||||
expect(typeof first.memory).toBe("string");
|
||||
expect(typeof first.score).toBe("number");
|
||||
});
|
||||
});
|
||||
|
||||
// ─── History ──────────────────────────────────────────────
|
||||
describe("memory history", () => {
|
||||
test("returns history with at least an ADD event", async () => {
|
||||
const memoryId = memoryIds[0];
|
||||
const history = await client.history(memoryId);
|
||||
|
||||
expect(Array.isArray(history)).toBe(true);
|
||||
expect(history.length).toBeGreaterThanOrEqual(1);
|
||||
|
||||
const entry = history[0];
|
||||
expect(typeof entry.id).toBe("string");
|
||||
expect(typeof entry.memory_id).toBe("string");
|
||||
expect(["ADD", "UPDATE", "DELETE", "NOOP"]).toContain(entry.event);
|
||||
expect(new Date(entry.created_at).toString()).not.toBe("Invalid Date");
|
||||
expect(new Date(entry.updated_at).toString()).not.toBe("Invalid Date");
|
||||
expect(
|
||||
entry.new_memory === null || typeof entry.new_memory === "string",
|
||||
).toBe(true);
|
||||
expect(
|
||||
entry.old_memory === null || typeof entry.old_memory === "string",
|
||||
).toBe(true);
|
||||
|
||||
const events = history.map((h) => h.event);
|
||||
expect(events).toContain("ADD");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user