test: 補測輸出改寫邊界行為 #19
+96
-2
@@ -2,9 +2,13 @@ import { expect, mock, test } from 'claude-code/testing'
|
|||||||
|
|
||||||
type Ctl = { complete: (e: any) => unknown; usage: unknown; seen?: any; submitted: string[]; clock?: any }
|
type Ctl = { complete: (e: any) => unknown; usage: unknown; seen?: any; submitted: string[]; clock?: any }
|
||||||
|
|
||||||
const setup = (on: any): Ctl => {
|
const setup = (on: any, kv?: Map<string, unknown>): Ctl => {
|
||||||
const ctl: Ctl = { complete: () => ({ isAnswered: false, reason: 'aborted' }), usage: { rateLimits: [], context: {} }, submitted: [] }
|
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)
|
||||||
ctl.clock = mock.clock(on, { now: Date.parse('2026-10-15T08:00:00Z') })
|
ctl.clock = mock.clock(on, { now: Date.parse('2026-10-15T08:00:00Z') })
|
||||||
on('ui.log', () => ({ value: undefined }) as never)
|
on('ui.log', () => ({ value: undefined }) as never)
|
||||||
on('command.register', () => ({ value: { name: 'x' } }) 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')
|
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, unknown>): string[] => ((kv.get('rawlog') as { raw: string }[] | undefined) ?? []).map(x => x.raw)
|
||||||
|
|
||||||
|
test('原文 log 預設寫入,改寫失敗時不寫', async ($, on) => {
|
||||||
|
const kv = new Map<string, unknown>()
|
||||||
|
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<string, unknown>()
|
||||||
|
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) => {
|
test('好感度:由 0 開始,每日首次互動加分且有每日上限', async ($, on) => {
|
||||||
const ctl = setup(on)
|
const ctl = setup(on)
|
||||||
ctl.complete = () => route('haiku', 'praise')
|
ctl.complete = () => route('haiku', 'praise')
|
||||||
|
|||||||
Reference in New Issue
Block a user