diff --git a/src/acp/translator.session-rate-limit.test.ts b/src/acp/translator.session-rate-limit.test.ts index 21273e241..3e3977da1 100644 --- a/src/acp/translator.session-rate-limit.test.ts +++ b/src/acp/translator.session-rate-limit.test.ts @@ -52,6 +52,25 @@ function createPromptRequest( } as unknown as PromptRequest; } +async function expectOversizedPromptRejected(params: { sessionId: string; text: string }) { + const request = vi.fn(async () => ({ ok: true })) as GatewayClient["request"]; + const sessionStore = createInMemorySessionStore(); + const agent = new AcpGatewayAgent(createConnection(), createGateway(request), { + sessionStore, + }); + await agent.loadSession(createLoadSessionRequest(params.sessionId)); + + await expect(agent.prompt(createPromptRequest(params.sessionId, params.text))).rejects.toThrow( + /maximum allowed size/i, + ); + expect(request).not.toHaveBeenCalledWith("chat.send", expect.anything(), expect.anything()); + const session = sessionStore.getSession(params.sessionId); + expect(session?.activeRunId).toBeNull(); + expect(session?.abortController).toBeNull(); + + sessionStore.clearAllSessionsForTest(); +} + describe("acp session creation rate limit", () => { it("rate limits excessive newSession bursts", async () => { const sessionStore = createInMemorySessionStore(); @@ -94,42 +113,16 @@ describe("acp session creation rate limit", () => { describe("acp prompt size hardening", () => { it("rejects oversized prompt blocks without leaking active runs", async () => { - const request = vi.fn(async () => ({ ok: true })) as GatewayClient["request"]; - const sessionStore = createInMemorySessionStore(); - const agent = new AcpGatewayAgent(createConnection(), createGateway(request), { - sessionStore, + await expectOversizedPromptRejected({ + sessionId: "prompt-limit-oversize", + text: "a".repeat(2 * 1024 * 1024 + 1), }); - const sessionId = "prompt-limit-oversize"; - await agent.loadSession(createLoadSessionRequest(sessionId)); - - await expect( - agent.prompt(createPromptRequest(sessionId, "a".repeat(2 * 1024 * 1024 + 1))), - ).rejects.toThrow(/maximum allowed size/i); - expect(request).not.toHaveBeenCalledWith("chat.send", expect.anything(), expect.anything()); - const session = sessionStore.getSession(sessionId); - expect(session?.activeRunId).toBeNull(); - expect(session?.abortController).toBeNull(); - - sessionStore.clearAllSessionsForTest(); }); it("rejects oversize final messages from cwd prefix without leaking active runs", async () => { - const request = vi.fn(async () => ({ ok: true })) as GatewayClient["request"]; - const sessionStore = createInMemorySessionStore(); - const agent = new AcpGatewayAgent(createConnection(), createGateway(request), { - sessionStore, + await expectOversizedPromptRejected({ + sessionId: "prompt-limit-prefix", + text: "a".repeat(2 * 1024 * 1024), }); - const sessionId = "prompt-limit-prefix"; - await agent.loadSession(createLoadSessionRequest(sessionId)); - - await expect( - agent.prompt(createPromptRequest(sessionId, "a".repeat(2 * 1024 * 1024))), - ).rejects.toThrow(/maximum allowed size/i); - expect(request).not.toHaveBeenCalledWith("chat.send", expect.anything(), expect.anything()); - const session = sessionStore.getSession(sessionId); - expect(session?.activeRunId).toBeNull(); - expect(session?.abortController).toBeNull(); - - sessionStore.clearAllSessionsForTest(); }); }); diff --git a/src/agents/chutes-oauth.e2e.test.ts b/src/agents/chutes-oauth.e2e.test.ts index 079dbe361..72da322a0 100644 --- a/src/agents/chutes-oauth.e2e.test.ts +++ b/src/agents/chutes-oauth.e2e.test.ts @@ -14,6 +14,27 @@ const urlToString = (url: Request | URL | string): string => { return "url" in url ? url.url : String(url); }; +function createStoredCredential( + now: number, +): Parameters[0]["credential"] { + return { + access: "at_old", + refresh: "rt_old", + expires: now - 10_000, + email: "fred", + clientId: "cid_test", + } as unknown as Parameters[0]["credential"]; +} + +function expectRefreshedCredential( + refreshed: Awaited>, + now: number, +) { + expect(refreshed.access).toBe("at_new"); + expect(refreshed.refresh).toBe("rt_old"); + expect(refreshed.expires).toBe(now + 1800 * 1000 - 5 * 60 * 1000); +} + describe("chutes-oauth", () => { it("exchanges code for tokens and stores username as email", async () => { const fetchFn = withFetchPreconnect(async (input: RequestInfo | URL, init?: RequestInit) => { @@ -87,20 +108,12 @@ describe("chutes-oauth", () => { const now = 2_000_000; const refreshed = await refreshChutesTokens({ - credential: { - access: "at_old", - refresh: "rt_old", - expires: now - 10_000, - email: "fred", - clientId: "cid_test", - } as unknown as Parameters[0]["credential"], + credential: createStoredCredential(now), fetchFn, now, }); - expect(refreshed.access).toBe("at_new"); - expect(refreshed.refresh).toBe("rt_old"); - expect(refreshed.expires).toBe(now + 1800 * 1000 - 5 * 60 * 1000); + expectRefreshedCredential(refreshed, now); }); it("refreshes tokens and ignores empty refresh_token values", async () => { @@ -122,19 +135,11 @@ describe("chutes-oauth", () => { const now = 3_000_000; const refreshed = await refreshChutesTokens({ - credential: { - access: "at_old", - refresh: "rt_old", - expires: now - 10_000, - email: "fred", - clientId: "cid_test", - } as unknown as Parameters[0]["credential"], + credential: createStoredCredential(now), fetchFn, now, }); - expect(refreshed.access).toBe("at_new"); - expect(refreshed.refresh).toBe("rt_old"); - expect(refreshed.expires).toBe(now + 1800 * 1000 - 5 * 60 * 1000); + expectRefreshedCredential(refreshed, now); }); }); diff --git a/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.e2e.test.ts b/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.e2e.test.ts index d929ff16f..cf275cff0 100644 --- a/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.e2e.test.ts +++ b/src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.e2e.test.ts @@ -32,6 +32,20 @@ async function getSessionsSpawnTool(opts: CreateOpenClawToolsOpts) { type GatewayRequest = { method?: string; params?: unknown }; type AgentWaitCall = { runId?: string; timeoutMs?: number }; +function buildDiscordCleanupHooks(onDelete: (key: string | undefined) => void) { + return { + onAgentSubagentSpawn: (params: unknown) => { + const rec = params as { channel?: string; timeout?: number } | undefined; + expect(rec?.channel).toBe("discord"); + expect(rec?.timeout).toBe(1); + }, + onSessionsDelete: (params: unknown) => { + const rec = params as { key?: string } | undefined; + onDelete(rec?.key); + }, + }; +} + function setupSessionsSpawnGatewayMock(opts: { includeSessionsList?: boolean; includeChatHistory?: boolean; @@ -216,15 +230,9 @@ describe("openclaw-tools: subagents (sessions_spawn lifecycle)", () => { callGatewayMock.mockReset(); let deletedKey: string | undefined; const ctx = setupSessionsSpawnGatewayMock({ - onAgentSubagentSpawn: (params) => { - const rec = params as { channel?: string; timeout?: number } | undefined; - expect(rec?.channel).toBe("discord"); - expect(rec?.timeout).toBe(1); - }, - onSessionsDelete: (params) => { - const rec = params as { key?: string } | undefined; - deletedKey = rec?.key; - }, + ...buildDiscordCleanupHooks((key) => { + deletedKey = key; + }), }); const tool = await getSessionsSpawnTool({ @@ -309,15 +317,9 @@ describe("openclaw-tools: subagents (sessions_spawn lifecycle)", () => { let deletedKey: string | undefined; const ctx = setupSessionsSpawnGatewayMock({ includeChatHistory: true, - onAgentSubagentSpawn: (params) => { - const rec = params as { channel?: string; timeout?: number } | undefined; - expect(rec?.channel).toBe("discord"); - expect(rec?.timeout).toBe(1); - }, - onSessionsDelete: (params) => { - const rec = params as { key?: string } | undefined; - deletedKey = rec?.key; - }, + ...buildDiscordCleanupHooks((key) => { + deletedKey = key; + }), agentWaitResult: { status: "ok", startedAt: 3000, endedAt: 4000 }, });