chore: fixing the ci integration test and token management for integration
This commit is contained in:
@@ -3,9 +3,13 @@
|
|||||||
*
|
*
|
||||||
* Provides environment gating, client factory, polling helpers,
|
* Provides environment gating, client factory, polling helpers,
|
||||||
* and console suppression for telemetry noise.
|
* and console suppression for telemetry noise.
|
||||||
|
*
|
||||||
|
* API credit budget: these helpers are designed to minimize API calls.
|
||||||
|
* Each CI run should use ~40 calls total across all test files.
|
||||||
*/
|
*/
|
||||||
import { MemoryClient } from "../../mem0";
|
import { MemoryClient } from "../../mem0";
|
||||||
import type { Memory } from "../../mem0.types";
|
import type { Memory } from "../../mem0.types";
|
||||||
|
import { NetworkError, RateLimitError } from "../../../common/exceptions";
|
||||||
|
|
||||||
// ─── Environment gate ────────────────────────────────────
|
// ─── Environment gate ────────────────────────────────────
|
||||||
export const API_KEY = process.env.MEM0_API_KEY;
|
export const API_KEY = process.env.MEM0_API_KEY;
|
||||||
@@ -15,9 +19,35 @@ export const describeIntegration = API_KEY ? describe : describe.skip;
|
|||||||
* Create a MemoryClient with the real API key.
|
* Create a MemoryClient with the real API key.
|
||||||
* Call this inside beforeAll — not at module scope — so it only
|
* Call this inside beforeAll — not at module scope — so it only
|
||||||
* runs when the suite is not skipped.
|
* runs when the suite is not skipped.
|
||||||
|
*
|
||||||
|
* The returned client retries transient errors (502, 503, 504, 429)
|
||||||
|
* with backoff so CI runs are not flaky.
|
||||||
*/
|
*/
|
||||||
export function createTestClient(): MemoryClient {
|
export function createTestClient(): MemoryClient {
|
||||||
return new MemoryClient({ apiKey: API_KEY! });
|
const client = new MemoryClient({ apiKey: API_KEY! });
|
||||||
|
|
||||||
|
const originalFetch = (client as any)._fetchWithErrorHandling.bind(client);
|
||||||
|
(client as any)._fetchWithErrorHandling = async (
|
||||||
|
url: string,
|
||||||
|
options: any,
|
||||||
|
) => {
|
||||||
|
const MAX_RETRIES = 2;
|
||||||
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
||||||
|
try {
|
||||||
|
return await originalFetch(url, options);
|
||||||
|
} catch (error: any) {
|
||||||
|
const isTransient =
|
||||||
|
error instanceof NetworkError || error instanceof RateLimitError;
|
||||||
|
if (isTransient && attempt < MAX_RETRIES) {
|
||||||
|
await new Promise((r) => setTimeout(r, 3_000 * attempt));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,6 +72,27 @@ export async function waitForMemories(
|
|||||||
return await client.getAll({ user_id: userId });
|
return await client.getAll({ user_id: userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poll search until results appear. Only used by search tests —
|
||||||
|
* other test files should NOT call this to avoid wasting API credits.
|
||||||
|
*/
|
||||||
|
export async function waitForSearchResults(
|
||||||
|
client: MemoryClient,
|
||||||
|
query: string,
|
||||||
|
options: Record<string, any>,
|
||||||
|
maxWaitMs = 30_000,
|
||||||
|
): Promise<Memory[]> {
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < maxWaitMs) {
|
||||||
|
const results = await client.search(query, options);
|
||||||
|
if (Array.isArray(results) && results.length > 0) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
await new Promise((r) => setTimeout(r, 3_000));
|
||||||
|
}
|
||||||
|
return await client.search(query, options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Suppress telemetry console noise during tests.
|
* Suppress telemetry console noise during tests.
|
||||||
* Returns a cleanup function to call in afterAll.
|
* Returns a cleanup function to call in afterAll.
|
||||||
@@ -69,7 +120,10 @@ export function suppressTelemetryNoise(): () => void {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add test memories and wait for them to be processed.
|
* Add test memories and wait for them to be processed.
|
||||||
* Returns the memory IDs once available.
|
* Returns the memory IDs once available via getAll.
|
||||||
|
*
|
||||||
|
* NOTE: This only waits for the listing index. If your test needs
|
||||||
|
* search results, call waitForSearchResults() separately.
|
||||||
*/
|
*/
|
||||||
export async function seedTestMemories(
|
export async function seedTestMemories(
|
||||||
client: MemoryClient,
|
client: MemoryClient,
|
||||||
@@ -104,17 +158,7 @@ export async function seedTestMemories(
|
|||||||
{ user_id: userId },
|
{ user_id: userId },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Wait for async processing (listing index)
|
|
||||||
const memories = await waitForMemories(client, userId, 1);
|
const memories = await waitForMemories(client, userId, 1);
|
||||||
|
|
||||||
// Also wait for search index — it can lag behind getAll under load (e.g. CI)
|
|
||||||
const start = Date.now();
|
|
||||||
while (Date.now() - start < 60_000) {
|
|
||||||
const results = await client.search("favorite color", { user_id: userId });
|
|
||||||
if (Array.isArray(results) && results.length > 0) break;
|
|
||||||
await new Promise((r) => setTimeout(r, 3_000));
|
|
||||||
}
|
|
||||||
|
|
||||||
return memories.map((m) => m.id);
|
return memories.map((m) => m.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
suppressTelemetryNoise,
|
suppressTelemetryNoise,
|
||||||
seedTestMemories,
|
seedTestMemories,
|
||||||
cleanupTestUser,
|
cleanupTestUser,
|
||||||
|
waitForSearchResults,
|
||||||
} from "./helpers";
|
} from "./helpers";
|
||||||
|
|
||||||
jest.setTimeout(120_000);
|
jest.setTimeout(120_000);
|
||||||
@@ -38,9 +39,12 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
|||||||
// ─── Search v1 ────────────────────────────────────────────
|
// ─── Search v1 ────────────────────────────────────────────
|
||||||
describe("search v1", () => {
|
describe("search v1", () => {
|
||||||
test("searches memories by user_id and returns results with scores", async () => {
|
test("searches memories by user_id and returns results with scores", async () => {
|
||||||
const results = await client.search("What is my favorite color?", {
|
// Search index may lag behind listing index — poll until ready
|
||||||
user_id: TEST_USER_ID,
|
const results = await waitForSearchResults(
|
||||||
});
|
client,
|
||||||
|
"What is my favorite color?",
|
||||||
|
{ user_id: TEST_USER_ID },
|
||||||
|
);
|
||||||
|
|
||||||
expect(Array.isArray(results)).toBe(true);
|
expect(Array.isArray(results)).toBe(true);
|
||||||
expect(results.length).toBeGreaterThan(0);
|
expect(results.length).toBeGreaterThan(0);
|
||||||
@@ -56,12 +60,14 @@ describeIntegration("MemoryClient Integration — Search & History", () => {
|
|||||||
// ─── Search v2 ────────────────────────────────────────────
|
// ─── Search v2 ────────────────────────────────────────────
|
||||||
describe("search v2", () => {
|
describe("search v2", () => {
|
||||||
test("searches with OR filters and returns results", async () => {
|
test("searches with OR filters and returns results", async () => {
|
||||||
const results = await client.search("What do you know about me?", {
|
const results = await waitForSearchResults(
|
||||||
filters: {
|
client,
|
||||||
OR: [{ user_id: TEST_USER_ID }],
|
"What do you know about me?",
|
||||||
|
{
|
||||||
|
filters: { OR: [{ user_id: TEST_USER_ID }] },
|
||||||
|
api_version: "v2",
|
||||||
},
|
},
|
||||||
api_version: "v2",
|
);
|
||||||
});
|
|
||||||
|
|
||||||
expect(Array.isArray(results)).toBe(true);
|
expect(Array.isArray(results)).toBe(true);
|
||||||
expect(results.length).toBeGreaterThan(0);
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user