/** * Arcrun#92 — 「回應太大」不准再被說成「請求失敗」 * * 背景:零件(main.go)給 host function 的接收緩衝區是固定大小(http_request 64KB、 * claude_api 1MB)。回應超過這個大小時,舊 host 會照寫不誤 → 覆寫零件記憶體 → 零件 * 切片 panic,或 writeOut 撞 memory 邊界丟例外 → 回 1 → 零件印一句 * "HTTP request failed"。使用者拿到那句會去查連線/URL/防火牆,全部查錯方向。 * * 這一支測的是**訊息有沒有講真話**,不是「有沒有回錯誤」: * 1. 判定規則本身(沒宣告容量的舊零件不可被誤判成太大) * 2. 訊息內容:真實數字 + 撇清錯誤方向 + 具體該怎麼辦 + 機器可讀欄位 */ import { describe, it, expect } from 'vitest'; import { outFitsCapacity, formatBytes, oversizeResponseEnvelope, HOST_TOO_LARGE, } from '../src/lib/wasi-shim'; describe('容量握手的判定規則', () => { it('宣告 64KB、資料 200KB → 塞不下', () => { expect(outFitsCapacity(65536, 200_000)).toBe(false); }); it('剛好等於容量 → 塞得下(不可 off-by-one 誤殺)', () => { expect(outFitsCapacity(65536, 65536)).toBe(true); }); it('舊零件沒宣告容量(0)→ 一律視為塞得下,維持舊行為', () => { // 這條是防「換一種說謊」:不能因為新規則就把 claude_api 那種 1MB 緩衝區的 // 大回應統統誤判成「太大」。沒宣告 = host 不知道上限 = 不准亂猜。 expect(outFitsCapacity(0, 900_000)).toBe(true); }); it('HOST_TOO_LARGE 與零件端的 hostTooLarge 常數同值(registry/components/*/main.go)', () => { expect(HOST_TOO_LARGE).toBe(3); }); }); describe('formatBytes', () => { it('分別用 bytes / KB / MB', () => { expect(formatBytes(512)).toBe('512 bytes'); expect(formatBytes(65536)).toBe('64 KB'); expect(formatBytes(3_355_443)).toBe('3.2 MB'); }); }); describe('「回應太大」的訊息本身', () => { const env = oversizeResponseEnvelope(3_355_443, 65536); it('講出實際大小與上限(不是只說「太大」)', () => { expect(env.error).toContain('3.2 MB'); expect(env.error).toContain('64 KB'); expect(env.actual_bytes).toBe(3_355_443); expect(env.limit_bytes).toBe(65536); }); it('明講「不是連線失敗」,把使用者從錯誤方向拉回來', () => { expect(env.error).toContain('不是連線失敗'); }); it('給得出下一步(分頁/篩選),不是叫人「稍後再試」', () => { expect(env.error).toMatch(/分頁|篩選/); expect(env.error).not.toMatch(/稍後再試|請重新操作/); }); it('不准退回萬用句', () => { expect(env.error).not.toMatch(/請求失敗|HTTP request failed|未知錯誤/); }); it('帶機器可讀欄位,上層不必比對字串', () => { expect(env.code).toBe('response_too_large'); }); it('status 不偽造上游狀態碼(對方沒回 413)', () => { expect(env.status).toBe(0); }); });