From 991f2bf2fd7e1986d5693967ce04b2ed6fbe9310 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Tue, 24 Mar 2026 03:56:07 +0900 Subject: [PATCH] test: align fallback expectations after merge --- src/message-agent-executor.test.ts | 98 +++++++++++++++++++++++++----- src/message-agent-executor.ts | 2 +- src/message-runtime.test.ts | 22 +++---- 3 files changed, 90 insertions(+), 32 deletions(-) diff --git a/src/message-agent-executor.test.ts b/src/message-agent-executor.test.ts index f66d5b8..0f39602 100644 --- a/src/message-agent-executor.test.ts +++ b/src/message-agent-executor.test.ts @@ -48,6 +48,7 @@ vi.mock('./provider-fallback.js', () => ({ getFallbackProviderName: vi.fn(() => 'kimi'), hasGroupProviderOverride: vi.fn(() => false), isFallbackEnabled: vi.fn(() => true), + isUsageExhausted: vi.fn(() => false), markPrimaryCooldown: vi.fn(), })); @@ -102,9 +103,85 @@ function makeDeps() { }; } +describe('runAgentForGroup room memory', () => { + beforeEach(() => { + vi.resetAllMocks(); + vi.mocked(providerFallback.getActiveProvider).mockResolvedValue('claude'); + vi.mocked(providerFallback.isFallbackEnabled).mockReturnValue(false); + vi.mocked(providerFallback.hasGroupProviderOverride).mockReturnValue(false); + vi.mocked(agentRunner.runAgentProcess).mockResolvedValue({ + status: 'success', + result: 'ok', + newSessionId: 'session-123', + }); + vi.mocked(buildRoomMemoryBriefing).mockResolvedValue( + '## Shared Room Memory\n- remembered context', + ); + }); + + it('injects a room memory briefing when starting a fresh session', async () => { + const group = { ...makeGroup(), folder: 'test-group' }; + const deps = makeDeps(); + + const result = await runAgentForGroup(deps, { + group, + prompt: 'hello', + chatJid: 'group@test', + runId: 'run-1', + }); + + expect(result).toBe('success'); + expect(buildRoomMemoryBriefing).toHaveBeenCalledWith({ + groupFolder: 'test-group', + groupName: 'Test Group', + }); + expect(agentRunner.runAgentProcess).toHaveBeenCalledWith( + group, + expect.objectContaining({ + prompt: 'hello', + sessionId: undefined, + memoryBriefing: '## Shared Room Memory\n- remembered context', + }), + expect.any(Function), + undefined, + undefined, + ); + }); + + it('skips the room memory briefing for existing sessions', async () => { + const group = { ...makeGroup(), folder: 'test-group' }; + const deps = { + ...makeDeps(), + getSessions: () => ({ 'test-group': 'session-existing' }), + }; + + const result = await runAgentForGroup(deps, { + group, + prompt: 'hello again', + chatJid: 'group@test', + runId: 'run-2', + }); + + expect(result).toBe('success'); + expect(buildRoomMemoryBriefing).not.toHaveBeenCalled(); + expect(agentRunner.runAgentProcess).toHaveBeenCalledWith( + group, + expect.objectContaining({ + prompt: 'hello again', + sessionId: 'session-existing', + memoryBriefing: undefined, + }), + expect.any(Function), + undefined, + undefined, + ); + }); +}); + describe('runAgentForGroup Claude rotation', () => { beforeEach(() => { - vi.clearAllMocks(); + vi.resetAllMocks(); + vi.mocked(buildRoomMemoryBriefing).mockResolvedValue(undefined); vi.mocked(providerFallback.getActiveProvider).mockResolvedValue('claude'); vi.mocked(providerFallback.isFallbackEnabled).mockReturnValue(true); vi.mocked(providerFallback.hasGroupProviderOverride).mockReturnValue(false); @@ -160,7 +237,7 @@ describe('runAgentForGroup Claude rotation', () => { expect(outputs).toEqual(['회전된 Claude 응답입니다.']); }); - it('falls back to Kimi only after all Claude accounts are exhausted', async () => { + it('stops after all Claude accounts are usage-exhausted without falling back to Kimi', async () => { const outputs: string[] = []; vi.mocked(tokenRotation.getTokenCount).mockReturnValue(2); @@ -213,25 +290,14 @@ describe('runAgentForGroup Claude rotation', () => { }, }); - expect(result).toBe('success'); - expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(3); + expect(result).toBe('error'); + expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(2); expect(tokenRotation.rotateToken).toHaveBeenCalledTimes(2); expect(providerFallback.markPrimaryCooldown).toHaveBeenCalledWith( 'usage-exhausted', undefined, ); - expect(agentRunner.runAgentProcess).toHaveBeenNthCalledWith( - 3, - expect.anything(), - expect.anything(), - expect.any(Function), - expect.any(Function), - expect.objectContaining({ - ANTHROPIC_BASE_URL: 'https://api.kimi.com/coding/', - ANTHROPIC_MODEL: 'kimi-k2.5', - }), - ); - expect(outputs).toEqual(['Kimi 폴백 응답입니다.']); + expect(outputs).toEqual([]); }); it('does not mistake a normal response quoting the banner text for a usage error', async () => { diff --git a/src/message-agent-executor.ts b/src/message-agent-executor.ts index b1eea0b..167e17f 100644 --- a/src/message-agent-executor.ts +++ b/src/message-agent-executor.ts @@ -48,7 +48,7 @@ function isClaudeUsageExhaustedMessage(text: string): boolean { const normalized = text .trim() .toLowerCase() - .replace(/[''`]/g, "'") + .replace(/[’‘`]/g, "'") .replace(/\s+/g, ' ') .replace(/^error:\s*/i, ''); const looksLikeBanner = diff --git a/src/message-runtime.test.ts b/src/message-runtime.test.ts index cadd29f..7bca002 100644 --- a/src/message-runtime.test.ts +++ b/src/message-runtime.test.ts @@ -180,7 +180,7 @@ function makeChannel(chatJid: string): Channel { describe('createMessageRuntime', () => { beforeEach(() => { - vi.clearAllMocks(); + vi.resetAllMocks(); vi.mocked(providerFallback.getActiveProvider).mockResolvedValue('claude'); vi.mocked(providerFallback.getFallbackProviderName).mockReturnValue('kimi'); vi.mocked(providerFallback.getFallbackEnvOverrides).mockReturnValue({ @@ -1957,7 +1957,7 @@ describe('createMessageRuntime', () => { ); }); - it('retries with the fallback provider when Claude returns only a usage exhaustion banner', async () => { + it('silently suppresses a usage exhaustion banner without falling back', async () => { const chatJid = 'group@test'; const group = makeGroup('claude-code'); const channel = makeChannel(chatJid); @@ -2025,19 +2025,15 @@ describe('createMessageRuntime', () => { }); expect(result).toBe(true); - expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(2); + expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(1); expect(providerFallback.markPrimaryCooldown).toHaveBeenCalledWith( 'usage-exhausted', undefined, ); - expect(channel.sendMessage).toHaveBeenCalledTimes(1); - expect(channel.sendMessage).toHaveBeenCalledWith( - chatJid, - 'usage fallback 응답입니다.', - ); + expect(channel.sendMessage).not.toHaveBeenCalled(); }); - it('suppresses duplicate streamed usage banners before retrying the fallback provider', async () => { + it('suppresses duplicate streamed usage banners without emitting a visible reply', async () => { const chatJid = 'group@test'; const group = makeGroup('claude-code'); const channel = makeChannel(chatJid); @@ -2110,16 +2106,12 @@ describe('createMessageRuntime', () => { }); expect(result).toBe(true); - expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(2); + expect(agentRunner.runAgentProcess).toHaveBeenCalledTimes(1); expect(providerFallback.markPrimaryCooldown).toHaveBeenCalledWith( 'usage-exhausted', undefined, ); - expect(channel.sendMessage).toHaveBeenCalledTimes(1); - expect(channel.sendMessage).toHaveBeenCalledWith( - chatJid, - 'duplicate banner fallback 응답입니다.', - ); + expect(channel.sendMessage).not.toHaveBeenCalled(); }); it('retries with the fallback provider when Claude ends with success-null-result before any output', async () => {