From 535c5b1437e64602eba435e74ed2dc97a2c17281 Mon Sep 17 00:00:00 2001 From: admin Date: Thu, 15 Oct 2026 09:06:03 +0000 Subject: [PATCH] =?UTF-8?q?test:=20=E8=A3=9C=E6=B8=AC=E8=BC=B8=E5=87=BA?= =?UTF-8?q?=E6=94=B9=E5=AF=AB=E9=82=8A=E7=95=8C=E8=A1=8C=E7=82=BA=EF=BC=88?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B=E9=81=B8=E6=93=87=E3=80=81=E9=80=BE=E6=99=82?= =?UTF-8?q?=E8=88=87=E9=A1=8D=E5=BA=A6=E4=B8=8D=E8=B6=B3=E3=80=81=E5=8E=9F?= =?UTF-8?q?=E6=96=87=20log=20=E9=96=8B=E9=97=9C=E3=80=81=E5=89=8D=E7=B6=B4?= =?UTF-8?q?=E5=8F=AA=E5=8A=A0=E4=B8=80=E6=AC=A1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 實測行為皆符合規格,無需修正實作。 Co-Authored-By: Claude Sonnet 5.5 --- hooks/routing.test.ts | 98 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/hooks/routing.test.ts b/hooks/routing.test.ts index 79a1ae9..7a487da 100644 --- a/hooks/routing.test.ts +++ b/hooks/routing.test.ts @@ -2,9 +2,13 @@ import { expect, mock, test } from 'claude-code/testing' type Ctl = { complete: (e: any) => unknown; usage: unknown; seen?: any; submitted: string[] } -const setup = (on: any): Ctl => { +const setup = (on: any, kv?: Map): Ctl => { const ctl: Ctl = { complete: () => ({ isAnswered: false, reason: 'aborted' }), usage: { rateLimits: [], context: {} }, submitted: [] } - mock.store(on) + if (kv) { + // 自備 store 以便檢查寫入內容。 + on('store.get', (_$: any, e: any) => ({ value: kv.get(e.key) }) as never) + on('store.set', (_$: any, e: any) => (kv.set(e.key, e.value), { value: undefined }) as never) + } else mock.store(on) mock.clock(on, { now: Date.parse('2026-10-15T08:00:00Z') }) on('ui.log', () => ({ value: undefined }) as never) on('command.register', () => ({ value: { name: 'x' } }) as never) @@ -92,6 +96,96 @@ test('改寫失敗或遺失佔位符時顯示原文', async ($, on) => { expect(r2.message.content[0].text).toBe('原文 `y` 5') }) +test('改寫模型:長回覆用 Sonnet,一般回覆用 Haiku', async ($, on) => { + const ctl = setup(on) + const asked: string[] = [] + ctl.complete = e => (asked.push(e.model), { isAnswered: true, usage, text: String(e.prompt) }) + await start($) + await $.session.append(row('短回覆')) + await $.session.append(row('長'.repeat(1300))) + expect(asked).toEqual(['haiku', 'sonnet']) +}) + +test('longReply 可調整長回覆門檻', { options: { longReply: 10 } }, async ($, on) => { + const ctl = setup(on) + const asked: string[] = [] + ctl.complete = e => (asked.push(e.model), { isAnswered: true, usage, text: String(e.prompt) }) + await start($) + await $.session.append(row('十一個字的回覆內容喔喔')) + await $.session.append(row('短')) + expect(asked).toEqual(['sonnet', 'haiku']) +}) + +test('改寫逾時、額度不足或拋錯時直接顯示原文', async ($, on) => { + const ctl = setup(on) + await start($) + ctl.complete = () => ({ isAnswered: false, reason: 'timeout' }) + const r1: any = await $.session.append(row('逾時 1')) + expect(r1.message.content[0].text).toBe('亞絲娜 > 逾時 1') + ctl.complete = () => ({ isAnswered: false, reason: 'rate_limited' }) + const r2: any = await $.session.append(row('額度 2')) + expect(r2.message.content[0].text).toBe('額度 2') + ctl.complete = () => { throw new Error('quota') } + const r3: any = await $.session.append(row('拋錯 3')) + expect(r3.message.content[0].text).toBe('拋錯 3') +}) + +test('改寫逾時設定傳給模型呼叫', { options: { rewriteTimeoutSec: 5 } }, async ($, on) => { + const ctl = setup(on) + let timeout = 0 + ctl.complete = e => (timeout = e.timeoutMs, { isAnswered: false, reason: 'timeout' }) + await start($) + await $.session.append(row('x')) + expect(timeout).toBe(5000) +}) + +const rawLog = (kv: Map): string[] => ((kv.get('rawlog') as { raw: string }[] | undefined) ?? []).map(x => x.raw) + +test('原文 log 預設寫入,改寫失敗時不寫', async ($, on) => { + const kv = new Map() + const ctl = setup(on, kv) + await start($) + ctl.complete = e => ({ isAnswered: true, usage, text: String(e.prompt) }) + await $.session.append(row('原文甲')) + ctl.complete = () => ({ isAnswered: false, reason: 'timeout' }) + await $.session.append(row('原文乙')) + expect(rawLog(kv)).toEqual(['原文甲']) +}) + +test('在 /config 關閉原文 log 時不寫入', { options: { rawLog: false } }, async ($, on) => { + const kv = new Map() + const ctl = setup(on, kv) + ctl.complete = e => ({ isAnswered: true, usage, text: String(e.prompt) }) + await start($) + const r: any = await $.session.append(row('原文丙')) + expect(r.message.content[0].text).toBe('亞絲娜 > 原文丙') + expect(rawLog(kv)).toEqual([]) +}) + +test('前綴只加在整則回覆開頭一次,多段文字區塊與多次寫入都不重複', async ($, on) => { + const ctl = setup(on) + ctl.complete = e => ({ isAnswered: true, usage, text: String(e.prompt) }) + await start($) + const multi = { message: { type: 'assistant', role: 'assistant', content: [{ type: 'text', text: '第一段' }, { type: 'tool_use', id: 't', name: 'x', input: {} }, { type: 'text', text: '第二段' }] }, door: 'response', origin: { kind: 'model', model: 'm' }, uuid: 'u' } as never + const r: any = await $.session.append(multi) + expect(r.message.content[0].text).toBe('亞絲娜 > 第一段') + expect(r.message.content[2].text).toBe('第二段') + const r2: any = await $.session.append(row('同一回覆後續')) + expect(r2.message.content[0].text).toBe('同一回覆後續') +}) + +test('新的使用者輸入後,下一則回覆重新加前綴', async ($, on) => { + const ctl = setup(on) + ctl.complete = e => ({ isAnswered: true, usage, text: String(e.prompt) }) + await start($) + await $.session.append(row('第一則')) + ctl.complete = () => route('haiku') + await submit($, '再問一次') + ctl.complete = e => ({ isAnswered: true, usage, text: String(e.prompt) }) + const r: any = await $.session.append(row('第二則')) + expect(r.message.content[0].text).toBe('亞絲娜 > 第二則') +}) + test('好感度:由 0 開始,每日首次互動加分且有每日上限', async ($, on) => { const ctl = setup(on) ctl.complete = () => route('haiku', 'praise')