From cb172cd6a5622190c5c90b676191c42beb3cdcbd Mon Sep 17 00:00:00 2001 From: deflis Date: Mon, 2 Nov 2026 01:21:07 +0900 Subject: [PATCH] =?UTF-8?q?NarouNovelFetch=E3=81=ABHTTP=E3=82=B9=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=82=BF=E3=82=B9=E3=81=AE=E6=A4=9C=E8=A8=BC=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/narou-fetch.ts | 4 ++++ test/narou-fetch.test.ts | 30 +++++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/narou-fetch.ts b/src/narou-fetch.ts index 425cdeb..71ebb47 100644 --- a/src/narou-fetch.ts +++ b/src/narou-fetch.ts @@ -39,6 +39,10 @@ export default class NarouNovelFetch extends NarouNovel { const res = await (this.fetch ?? fetch)(url, options?.fetchOptions); + if (!res.ok) { + throw new Error(`HTTP ${res.status}: ${res.statusText}`); + } + if (!query.gzip) { return (await res.json()) as T; } diff --git a/test/narou-fetch.test.ts b/test/narou-fetch.test.ts index 427b939..01af106 100644 --- a/test/narou-fetch.test.ts +++ b/test/narou-fetch.test.ts @@ -82,6 +82,7 @@ describe('NarouNovelFetch', () => { it('should use custom fetch function when provided', async () => { // カスタムフェッチ関数をモック const customFetchMock = vi.fn().mockResolvedValue({ + ok: true, json: vi.fn().mockResolvedValue(mockData) }); @@ -95,6 +96,33 @@ describe('NarouNovelFetch', () => { expect(result).toEqual(mockData); }); + it.each([403, 404, 429, 500])('should reject HTTP %i before reading the body', async (status) => { + for (const gzip of [0, 5] as const) { + const response = Response.json(mockData, { status }); + const jsonSpy = vi.spyOn(response, 'json'); + const bufferSpy = vi.spyOn(response, 'arrayBuffer'); + const narouFetch = new NarouNovelFetch(vi.fn().mockResolvedValue(response)); + + await expect(narouFetch.executeRanking({ rtype: '20260101-d', gzip })) + .rejects.toThrow(`HTTP ${status}:`); + expect(jsonSpy).not.toHaveBeenCalled(); + expect(bufferSpy).not.toHaveBeenCalled(); + expect(response.bodyUsed).toBe(false); + } + }); + + it.each([0, 5] as const)('should still reject plain API errors returned with HTTP 200 (gzip=%i)', async (gzip) => { + const response = new Response('Error: Invalid date.', { status: 200 }); + const narouFetch = new NarouNovelFetch(vi.fn().mockResolvedValue(response)); + const result = narouFetch.executeRanking({ rtype: '20260101-d', gzip }); + + if (gzip === 0) { + await expect(result).rejects.toBeInstanceOf(SyntaxError); + } else { + await expect(result).rejects.toBe('Error: Invalid date.'); + } + }); + it('should set gzip to 5 when undefined', async () => { // URLパラメータをキャプチャするモック const requestSpy = vi.fn(); @@ -185,4 +213,4 @@ describe('NarouNovelFetch', () => { { fetchOptions: { headers: { 'user-agent': 'node-narou' } } } ); }); -}); \ No newline at end of file +});