/** * PATCH /kbdb/records/:recordId proxy 測試(2026-08-11,三元組 library 補標需求核實) * * 背景:基本盤 kbdb/src/routes/records.ts 早有 PATCH /records/:recordId(mira-dissolve T2.1, * updateRecord 已支援「補一個 record 原本沒有的 slot 值」的 idempotent grow)。但這條 cypher * proxy(kbdb-proxy.ts)之前只轉發 GET/POST /kbdb/records,沒開 PATCH——外部(工作流/CLI/ * 任何走 X-Arcrun-API-Key 的呼叫者)打不到,等於基本盤能力在,通道沒開。 * * 驗證 IO 接線(聚合真身在 KBDB 基本盤,這裡只測轉發,比照 kbdb-map-proxy.test.ts 慣例): * 1. 租戶閘:無 X-Arcrun-API-Key → 401 不碰 KBDB * 2. body 沒有 values → 400,不轉發 * 3. 轉發:PATCH /kbdb/records/:id → base PATCH /records/:id,body 只帶 { values } * 4. base 404(record 不存在)→ 原樣透傳,不假裝成功 * * KBDB 打 fetchMock 假 host(wrangler.test.toml KBDB_BASE_URL=https://kbdb.test)+ * disableNetConnect——測試絕不外連。 */ import { SELF, fetchMock } from 'cloudflare:test'; import { beforeAll, afterEach, describe, it, expect } from 'vitest'; const KEY = { 'X-Arcrun-API-Key': 'leo', 'Content-Type': 'application/json' }; beforeAll(() => { fetchMock.activate(); fetchMock.disableNetConnect(); }); afterEach(() => fetchMock.assertNoPendingInterceptors()); describe('PATCH /kbdb/records/:recordId — 租戶閘', () => { it('無 X-Arcrun-API-Key → 401,不碰 KBDB', async () => { const res = await SELF.fetch('http://localhost/kbdb/records/rec_1', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ values: { library: 'kb' } }), }); expect(res.status).toBe(401); }); }); describe('PATCH /kbdb/records/:recordId — 參數驗證', () => { it('body 沒有 values → 400,不轉發', async () => { const res = await SELF.fetch('http://localhost/kbdb/records/rec_1', { method: 'PATCH', headers: KEY, body: JSON.stringify({}), }); expect(res.status).toBe(400); }); }); describe('PATCH /kbdb/records/:recordId — 轉發', () => { it('轉發 base PATCH /records/:id,body 只帶 values(不夾帶其他欄位)', async () => { fetchMock .get('https://kbdb.test') .intercept({ path: '/records/rec_1', method: 'PATCH', body: JSON.stringify({ values: { library: 'gitea:Leo/kb' } }), }) .reply(200, { success: true, record: { record_id: 'rec_1', template_id: 'tpl-triplet', values: { library: 'gitea:Leo/kb' } }, }); const res = await SELF.fetch('http://localhost/kbdb/records/rec_1', { method: 'PATCH', headers: KEY, body: JSON.stringify({ values: { library: 'gitea:Leo/kb' } }), }); expect(res.status).toBe(200); const data = (await res.json()) as { success: boolean; record: { values: Record } }; expect(data.success).toBe(true); expect(data.record.values.library).toBe('gitea:Leo/kb'); }); it('base 404(record 不存在)→ 原樣透傳,不假裝成功', async () => { fetchMock .get('https://kbdb.test') .intercept({ path: '/records/nope', method: 'PATCH' }) .reply(404, { success: false, error: 'not found' }); const res = await SELF.fetch('http://localhost/kbdb/records/nope', { method: 'PATCH', headers: KEY, body: JSON.stringify({ values: { library: 'kb' } }), }); expect(res.status).toBe(404); const data = (await res.json()) as { success: boolean }; expect(data.success).toBe(false); }); });