refactor: fix webhook create/update serialization, add payload types, and MEMORY_CATEGORIZED event (#4429)

Co-authored-by: Saket Aryan <saketaryan2002@gmail.com>
This commit is contained in:
Kartik
2026-03-19 22:26:17 +05:30
committed by GitHub
co-authored by Saket Aryan
parent 88abb29de9
commit 6663b738d5
11 changed files with 362 additions and 128 deletions
@@ -1,6 +1,7 @@
/**
* MemoryClient unit tests — getWebhooks, createWebhook, updateWebhook, deleteWebhook.
* Tests verify request URL and HTTP method, not mock response values.
* Tests verify request URL, HTTP method, and payload serialization.
* One expect per test case.
*/
import { MemoryClient } from "../mem0";
import { WebhookEvent } from "../mem0.types";
@@ -14,17 +15,26 @@ import {
installConsoleSuppression();
describe("MemoryClient - Webhooks", () => {
test("getWebhooks sends GET to /api/v1/webhooks/projects/:id/", async () => {
// ─── Helpers ──────────────────────────────────────────────
function webhookMock(extra?: Map<string, { status: number; body: unknown }>) {
return setupMockFetch(extra);
}
function createClient() {
return new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
}
// ─── getWebhooks ──────────────────────────────────────────
describe("MemoryClient - getWebhooks", () => {
test("sends GET to /api/v1/webhooks/projects/:id/", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/api/v1/webhooks/projects/", { status: 200, body: [] });
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const mock = webhookMock(extra);
const client = createClient();
await client.getWebhooks();
const call = mock.mock.calls.find(
@@ -33,117 +43,170 @@ describe("MemoryClient - Webhooks", () => {
);
expect(call).toBeDefined();
});
});
test("createWebhook sends POST to /api/v1/webhooks/projects/:id/", async () => {
// ─── createWebhook ────────────────────────────────────────
describe("MemoryClient - createWebhook", () => {
async function callCreate() {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/api/v1/webhooks/projects/", {
status: 200,
body: { webhook_id: "wh_new" },
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const mock = webhookMock(extra);
const client = createClient();
await client.createWebhook({
name: "new-hook",
url: "https://example.com",
eventTypes: [WebhookEvent.MEMORY_ADDED],
projectId: TEST_PROJECT_ID,
webhookId: "",
});
return mock;
}
test("sends POST to /api/v1/webhooks/projects/:id/", async () => {
const mock = await callCreate();
expect(findFetchCall(mock, "/api/v1/webhooks/", "POST")).toBeDefined();
});
test("createWebhook includes webhook payload in body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/api/v1/webhooks/projects/", {
status: 200,
body: { webhook_id: "wh_new" },
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.createWebhook({
name: "new-hook",
url: "https://example.com",
eventTypes: [WebhookEvent.MEMORY_ADDED],
projectId: TEST_PROJECT_ID,
webhookId: "",
});
const call = findFetchCall(mock, "/api/v1/webhooks/", "POST");
const body = getFetchBody(call!);
test("body contains name", async () => {
const mock = await callCreate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
);
expect(body.name).toBe("new-hook");
});
test("body contains url", async () => {
const mock = await callCreate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
);
expect(body.url).toBe("https://example.com");
});
test("updateWebhook sends PUT to /api/v1/webhooks/:id/", async () => {
test("body contains event_types in snake_case", async () => {
const mock = await callCreate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
);
expect(body.event_types).toStrictEqual([WebhookEvent.MEMORY_ADDED]);
});
test("body does not contain camelCase eventTypes", async () => {
const mock = await callCreate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
);
expect(body.eventTypes).toBeUndefined();
});
test("body does not contain projectId", async () => {
const mock = await callCreate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
);
expect(body.projectId).toBeUndefined();
});
test("body does not contain webhookId", async () => {
const mock = await callCreate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/", "POST")!,
);
expect(body.webhookId).toBeUndefined();
});
});
// ─── updateWebhook ────────────────────────────────────────
describe("MemoryClient - updateWebhook", () => {
async function callUpdate() {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/api/v1/webhooks/wh_1/", {
status: 200,
body: { message: "Webhook updated" },
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
const mock = webhookMock(extra);
const client = createClient();
await client.updateWebhook({
webhookId: "wh_1",
name: "updated-hook",
url: "https://new-url.com",
eventTypes: [WebhookEvent.MEMORY_ADDED],
projectId: TEST_PROJECT_ID,
});
return mock;
}
test("sends PUT to /api/v1/webhooks/:id/", async () => {
const mock = await callUpdate();
expect(findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")).toBeDefined();
});
test("updateWebhook includes updated fields in body", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/api/v1/webhooks/wh_1/", {
status: 200,
body: { message: "Webhook updated" },
});
const mock = setupMockFetch(extra);
const client = new MemoryClient({
apiKey: TEST_API_KEY,
organizationId: TEST_ORG_ID,
projectId: TEST_PROJECT_ID,
});
await client.updateWebhook({
webhookId: "wh_1",
name: "updated-hook",
url: "https://new-url.com",
eventTypes: [WebhookEvent.MEMORY_ADDED],
projectId: TEST_PROJECT_ID,
});
const call = findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT");
const body = getFetchBody(call!);
test("body contains name", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.name).toBe("updated-hook");
});
test("body contains url", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.url).toBe("https://new-url.com");
});
test("deleteWebhook sends DELETE to /api/v1/webhooks/:id/", async () => {
test("body contains event_types in snake_case", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.event_types).toStrictEqual([WebhookEvent.MEMORY_ADDED]);
});
test("body does not contain camelCase eventTypes", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.eventTypes).toBeUndefined();
});
test("body does not contain project_id", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.project_id).toBeUndefined();
});
test("body does not contain projectId", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.projectId).toBeUndefined();
});
test("body does not contain webhookId", async () => {
const mock = await callUpdate();
const body = getFetchBody(
findFetchCall(mock, "/api/v1/webhooks/wh_1/", "PUT")!,
);
expect(body.webhookId).toBeUndefined();
});
});
// ─── deleteWebhook ────────────────────────────────────────
describe("MemoryClient - deleteWebhook", () => {
test("sends DELETE to /api/v1/webhooks/:id/", async () => {
const extra = new Map<string, { status: number; body: unknown }>();
extra.set("/api/v1/webhooks/wh_1/", {
status: 200,
body: { message: "Webhook deleted" },
});
const mock = setupMockFetch(extra);
const mock = webhookMock(extra);
const client = new MemoryClient({ apiKey: TEST_API_KEY });
await client.deleteWebhook({ webhookId: "wh_1" });