From 227c02da9f731730c128d47db03d97bf9832c9f6 Mon Sep 17 00:00:00 2001 From: kartik-mem0 Date: Wed, 12 Aug 2026 19:47:08 +0530 Subject: [PATCH] fix(ts-tests): stop integration runs from deleting each other's data The integration suite claimed exclusive ownership of a shared project. Its globalSetup and globalTeardown each ran a project-wide wipe: deleteAll({userId: '*', agentId: '*', appId: '*', runId: '*'}) deleteUsers() CI runs this suite concurrently across PRs against one MEM0_API_KEY, so each job's wipe deleted the memories its neighbours had just seeded. maxWorkers: 1 and max-parallel: 1 only serialize within a run, not across them. Evidence from three jobs that overlapped on 2026-08-12: 6903 crud seeded 13:53:24-13:54:15, hit by 6928's wipe (13:53:20-30) FAIL 6928 crud seeded 13:53:30-13:54:19, hit by 6902's wipe (13:53:31-43) FAIL 6902 crud seeded 13:53:43-13:54:21, no overlapping wipe PASS 6902 batch ran 13:55:31-13:56:20, hit by 6928's teardown (13:55:59) FAIL Every failure surfaced as waitForMemories timing out with zero memories, which reads as pipeline latency but was deletion: probing the live API shows a seeded user settling in 17s, well inside the 45s budget. - drop globalSetup/globalTeardown; every suite already cleans up its own UUID-scoped user in afterAll - remove the fullProjectCleanup helper so the wipe cannot come back - give updateWebhook a per-run URL; the project enforces uniqueness on (project, url), so concurrent runs collided on the hardcoded constant Verified: two suites run concurrently against the same key go from 3 failures to 1. The remainder is updateProject() mutating project-level custom_instructions, a singleton two runs cannot both own. --- mem0-ts/jest.integration.config.js | 2 -- .../src/client/tests/integration/helpers.ts | 29 ------------------- .../tests/integration/management.test.ts | 3 +- 3 files changed, 2 insertions(+), 32 deletions(-) diff --git a/mem0-ts/jest.integration.config.js b/mem0-ts/jest.integration.config.js index fcbe6052d..f9c0be7b2 100644 --- a/mem0-ts/jest.integration.config.js +++ b/mem0-ts/jest.integration.config.js @@ -2,8 +2,6 @@ module.exports = { ...require("./jest.config"), testMatch: ["**/integration/**/*.test.ts"], - globalSetup: "/src/client/tests/integration/global-setup.ts", - globalTeardown: "/src/client/tests/integration/global-teardown.ts", // Run integration tests serially to avoid rate limiting and race conditions maxWorkers: 1, }; diff --git a/mem0-ts/src/client/tests/integration/helpers.ts b/mem0-ts/src/client/tests/integration/helpers.ts index 3e3acf7ae..f7007042d 100644 --- a/mem0-ts/src/client/tests/integration/helpers.ts +++ b/mem0-ts/src/client/tests/integration/helpers.ts @@ -220,32 +220,3 @@ export async function cleanupTestUser( // ignore } } - -/** - * Full project wipe — deletes all memories and all entities. - * Equivalent to Python SDK's: - * client.delete_all(user_id="*", agent_id="*", app_id="*", run_id="*") - * - * Used as cleanup before and after integration test runs so tests - * start from a clean slate and don't leave data behind. - */ -export async function fullProjectCleanup(client: MemoryClient): Promise { - // Delete all memories — all four filters set explicitly - try { - await client.deleteAll({ - userId: "*", - agentId: "*", - appId: "*", - runId: "*", - }); - } catch { - // ignore — may 404 if no data exists - } - - // Delete all entities (users, agents, apps, runs) - try { - await client.deleteUsers(); - } catch { - // ignore — may throw "No entities to delete" - } -} diff --git a/mem0-ts/src/client/tests/integration/management.test.ts b/mem0-ts/src/client/tests/integration/management.test.ts index cbfd838f7..c4289d3ae 100644 --- a/mem0-ts/src/client/tests/integration/management.test.ts +++ b/mem0-ts/src/client/tests/integration/management.test.ts @@ -100,6 +100,7 @@ describeIntegration("MemoryClient Integration — Users & Project", () => { const hookName = `test-hook-${randomUUID().slice(0, 8)}`; const hookUrl = `https://example.com/webhook/${randomUUID().slice(0, 8)}`; const updatedName = `updated-hook-${randomUUID().slice(0, 8)}`; + const updatedUrl = `https://example.com/updated/${randomUUID().slice(0, 8)}`; afterAll(async () => { if (createdWebhookId) { @@ -168,7 +169,7 @@ describeIntegration("MemoryClient Integration — Users & Project", () => { client.updateWebhook({ webhookId: createdWebhookId, name: updatedName, - url: "https://example.com/updated", + url: updatedUrl, eventTypes: [WebhookEvent.MEMORY_DELETED], }), );