'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const { fetchReleases } = require('../releases'); // 以可控的假回應替換全域 fetch,並於結束後還原 function withFetch(handler, run) { const original = globalThis.fetch; globalThis.fetch = handler; return Promise.resolve() .then(run) .finally(() => { globalThis.fetch = original; }); } // 建立模擬 Response 物件 const jsonResponse = (body, ok = true) => ({ ok, text: async () => (typeof body === 'string' ? body : JSON.stringify(body)), }); test('fetchReleases 單頁(不足一頁即停止)', async () => { await withFetch( async () => jsonResponse([{ tag_name: 'v1.0.0' }]), async () => { const releases = await fetchReleases('https://gitea.example.com/api'); assert.deepEqual(releases, [{ tag_name: 'v1.0.0' }]); }, ); }); test('fetchReleases 跨頁合併直到不足一頁', async () => { const pages = { 1: Array.from({ length: 10 }, (_, i) => ({ tag_name: `v1.0.${i}` })), 2: [{ tag_name: 'v1.1.0' }], }; const seen = []; await withFetch( async (url) => { const page = Number(new URL(url).searchParams.get('page')); seen.push(page); return jsonResponse(pages[page]); }, async () => { const releases = await fetchReleases('https://gitea.example.com/api'); assert.equal(releases.length, 11); assert.deepEqual(seen, [1, 2]); }, ); }); test('fetchReleases 對非 2xx 回應拋錯', async () => { await withFetch( async () => jsonResponse('Not Found', false), async () => { await assert.rejects( () => fetchReleases('https://gitea.example.com/api'), /release API 請求失敗/, ); }, ); }); test('fetchReleases 對無法解析的 JSON 回應拋錯', async () => { await withFetch( async () => jsonResponse('{ this is not valid json', true), async () => { await assert.rejects( () => fetchReleases('https://gitea.example.com/api'), /回傳資料無法解析/, ); }, ); }); test('fetchReleases 對非陣列回應拋錯', async () => { await withFetch( async () => jsonResponse({ message: 'oops' }), async () => { await assert.rejects( () => fetchReleases('https://gitea.example.com/api'), /回傳非陣列資料/, ); }, ); }); test('fetchReleases 有 token 時帶入授權標頭', async () => { let captured; await withFetch( async (url, options) => { captured = options; return jsonResponse([]); }, async () => { await fetchReleases('https://gitea.example.com/api', { token: 'secret' }); assert.equal(captured.headers.Authorization, 'token secret'); }, ); });