Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions packages/tools/src/shared/forget-memory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { afterEach, describe, expect, it, vi } from "vitest"
import { forgetMemoryRequest } from "./forget-memory"

const originalFetch = globalThis.fetch

afterEach(() => {
globalThis.fetch = originalFetch
vi.restoreAllMocks()
})

describe("forgetMemoryRequest signal composition", () => {
it("aborts via the caller's signal even though the 30s timeout is also armed", async () => {
// Simulate a server that never answers, honoring the signal like real
// fetch: rejection on abort proves the composed signal is wired
// through to the request.
const fetchMock = vi.fn().mockImplementation(
(_url: unknown, init?: RequestInit) =>
new Promise<Response>((_resolve, reject) => {
const signal = init?.signal as AbortSignal | undefined
signal?.addEventListener("abort", () => {
reject(new Error("aborted"))
})
}),
)
globalThis.fetch = fetchMock as unknown as typeof fetch

const controller = new AbortController()
const promise = forgetMemoryRequest(
"sm_key",
{ containerTag: "c" },
"https://api.example.com",
{
signal: controller.signal,
},
)

controller.abort()
await expect(promise).rejects.toThrow()
expect(fetchMock.mock.calls.length).toBe(1)
})

it("applies the 30s deadline when no caller signal is provided", async () => {
const fetchMock = vi.fn().mockImplementation((_url, init) => {
const signal = (init as RequestInit).signal as AbortSignal
// The timeout must be armed even without a caller signal.
expect(signal).toBeTruthy()
return Promise.resolve(new Response(null, { status: 200 }))
})
globalThis.fetch = fetchMock as unknown as typeof fetch

await expect(
forgetMemoryRequest(
"sm_key",
{ containerTag: "c" },
"https://api.example.com",
),
).resolves.toBeUndefined()
})
})
8 changes: 7 additions & 1 deletion packages/tools/src/shared/forget-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ export async function forgetMemoryRequest(
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(params),
signal: options?.signal ?? AbortSignal.timeout(FETCH_TIMEOUT_MS),
// Compose, don't choose: `??` let a caller-supplied signal silently
// drop the 30s safety timeout (and vice versa). Any signal firing β€”
// caller abort or the deadline β€” must abort the request.
signal: AbortSignal.any([
...(options?.signal ? [options.signal] : []),
AbortSignal.timeout(FETCH_TIMEOUT_MS),
]),
})

if (!response.ok) {
Expand Down