From d4f5b9f32f538b9e48f4f2ad332c3681a1e3bc3a Mon Sep 17 00:00:00 2001 From: hmmhmmhm Date: Tue, 13 Oct 2026 18:32:07 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9D=B4=EB=A7=88=ED=8A=B824=20?= =?UTF-8?q?=EC=9E=AC=EA=B3=A0=20=EC=A1=B0=ED=9A=8C=EC=9D=98=20=EC=A4=91?= =?UTF-8?q?=EA=B3=84=20=EC=98=A4=EB=A5=98=20=EC=83=81=ED=83=9C=20=EB=B3=B4?= =?UTF-8?q?=EC=A1=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/emart24Handlers.ts | 4 +- tests/api/emart24-inventory-errors.test.ts | 97 ++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 tests/api/emart24-inventory-errors.test.ts diff --git a/src/api/emart24Handlers.ts b/src/api/emart24Handlers.ts index 6aad671e..0604774c 100644 --- a/src/api/emart24Handlers.ts +++ b/src/api/emart24Handlers.ts @@ -1,10 +1,11 @@ +import { ServiceError } from '../core/errors.js'; import { convenienceTransportFromBindings } from '../utils/convenienceTransport.js'; import { isFreshOperationalRequest } from './operationalFreshness.js'; /** * 이마트24 GET API 핸들러 */ -import { type ApiContext, errorResponse, successResponse } from './response.js'; +import { type ApiContext, errorResponse, serviceErrorResponse, successResponse } from './response.js'; import { calculateDistanceM, fetchEmart24Stores, @@ -236,6 +237,7 @@ export async function handleEmart24CheckInventory(c: ApiContext) { stores: result.inventory.stores, }); } catch (error) { + if (error instanceof ServiceError) return serviceErrorResponse(c, error, 'emart24_inventory'); const message = error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.'; return errorResponse(c, 'EMART24_INVENTORY_CHECK_FAILED', message, 500); } diff --git a/tests/api/emart24-inventory-errors.test.ts b/tests/api/emart24-inventory-errors.test.ts new file mode 100644 index 00000000..d03e1ee5 --- /dev/null +++ b/tests/api/emart24-inventory-errors.test.ts @@ -0,0 +1,97 @@ +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { handleEmart24CheckInventory } from '../../src/api/emart24Handlers.js'; +import { ServiceError } from '../../src/core/errors.js'; +import * as inventoryLookup from '../../src/services/emart24/inventoryLookup.js'; + +function context(query: Record = { pluCd: '123', bizNoArr: '00940' }) { + return { + env: {}, + req: { query: (name: string) => query[name] }, + header: vi.fn(), + json: vi.fn((data, status = 200) => ({ data, status })), + } as unknown as Parameters[0]; +} + +afterEach(() => vi.restoreAllMocks()); + +describe('이마트24 재고 전송 오류 보존', () => { + it.each([ + new ServiceError( + 'CONVENIENCE_RELAY_FAILED', + '편의점 릴레이 요청에 실패했습니다.', + 502, + true, + 403, + ), + new ServiceError( + 'CONVENIENCE_RELAY_FAILED', + '편의점 릴레이 요청에 실패했습니다.', + 429, + true, + 429, + { quotaReason: 'daily', retryAfter: 120 }, + ), + new ServiceError( + 'CONVENIENCE_RELAY_CONFIG_ERROR', + '편의점 릴레이 설정을 확인하세요.', + 503, + false, + ), + new ServiceError( + 'CONVENIENCE_RELAY_TIMEOUT', + '편의점 릴레이 요청 시간이 초과되었습니다.', + 504, + true, + ), + ])('알려진 $status 오류의 코드·진단·재시도 정보를 반환한다', async (error) => { + const lookup = vi.spyOn(inventoryLookup, 'lookupEmart24Inventory').mockRejectedValue(error); + const c = context(); + await handleEmart24CheckInventory(c); + expect(lookup).toHaveBeenCalledTimes(1); + expect(c.json).toHaveBeenCalledWith( + expect.objectContaining({ + success: false, + error: { code: error.code, message: error.message }, + diagnostics: expect.objectContaining({ + code: error.code, + status: error.status, + retryable: error.retryable, + upstreamStatus: error.upstreamStatus, + operation: 'emart24_inventory', + quotaReason: error.quotaReason, + retryAfter: error.retryAfter, + }), + }), + error.status, + ); + if (error.retryAfter !== undefined) { + expect(c.header).toHaveBeenCalledWith('Retry-After', '120'); + } else { + expect(c.header).not.toHaveBeenCalled(); + } + }); + + it.each([new Error('예상하지 못한 오류'), undefined])( + '일반 오류는 기존 500 응답을 유지한다', + async (error) => { + vi.spyOn(inventoryLookup, 'lookupEmart24Inventory').mockRejectedValue(error); + const c = context(); + await handleEmart24CheckInventory(c); + expect(c.json).toHaveBeenCalledWith( + expect.objectContaining({ + success: false, + error: { + code: 'EMART24_INVENTORY_CHECK_FAILED', + message: error?.message ?? '알 수 없는 오류가 발생했습니다.', + }, + diagnostics: expect.objectContaining({ + code: 'EMART24_INVENTORY_CHECK_FAILED', + status: 500, + }), + }), + 500, + ); + expect(c.header).not.toHaveBeenCalled(); + }, + ); +});