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.
This commit is contained in:
@@ -2,8 +2,6 @@
|
||||
module.exports = {
|
||||
...require("./jest.config"),
|
||||
testMatch: ["**/integration/**/*.test.ts"],
|
||||
globalSetup: "<rootDir>/src/client/tests/integration/global-setup.ts",
|
||||
globalTeardown: "<rootDir>/src/client/tests/integration/global-teardown.ts",
|
||||
// Run integration tests serially to avoid rate limiting and race conditions
|
||||
maxWorkers: 1,
|
||||
};
|
||||
|
||||
@@ -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<void> {
|
||||
// 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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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],
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user