From 9fd35fe7734950273db0c4df455a2ac7c98331a5 Mon Sep 17 00:00:00 2001 From: hmmhmmhm Date: Sun, 21 Jun 2026 15:41:27 +0900 Subject: [PATCH] degrade cli smoke for known emart24 upstream 403 --- scripts/ops/cli-smoke.ts | 15 ++++++++++++++- tests/scripts/cli-smoke.test.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/scripts/ops/cli-smoke.ts b/scripts/ops/cli-smoke.ts index 7d9edd2..4b08c8f 100644 --- a/scripts/ops/cli-smoke.ts +++ b/scripts/ops/cli-smoke.ts @@ -4,6 +4,7 @@ import { spawn } from 'node:child_process'; import path from 'node:path'; +import { EMART24_UPSTREAM_403_PATTERNS } from '../../src/api/healthCheckDefinitions.js'; interface CommandResult { exitCode: number; @@ -42,6 +43,7 @@ interface CliSmokeCommand { scenario: string; args: string[]; expectedExitCode?: number; + degradedFailurePatterns?: string[]; validate: Validator; } @@ -137,6 +139,7 @@ export const CLI_SMOKE_COMMANDS: CliSmokeCommand[] = [ service: 'emart24', scenario: '이마트24 상품명 검색', args: ['emart24-products', '커피', '--pageSize', '1', '--json'], + degradedFailurePatterns: EMART24_UPSTREAM_403_PATTERNS, validate: (stdout) => validateApiEnvelope(stdout, expectDataField('keyword', '커피')), }, { @@ -210,6 +213,11 @@ async function execCommand(command: string, args: string[]): Promise output.includes(pattern)) ?? false; +} + export async function runCliSmoke(deps: CliSmokeDeps = {}): Promise { const runCommand = deps.runCommand || execCommand; const writeOut = deps.writeOut || ((message: string) => process.stdout.write(`${message}\n`)); @@ -226,11 +234,16 @@ export async function runCliSmoke(deps: CliSmokeDeps = {}): Promise { return 1; } - for (const { args, expectedExitCode = 0, validate } of commands) { + for (const smokeCommand of commands) { + const { args, expectedExitCode = 0, validate } = smokeCommand; const fullArgs = [cliPath, ...args]; writeOut(`CLI smoke 실행: ${command} ${fullArgs.join(' ')}`); const result = await runCommand(command, fullArgs); if (result.exitCode !== expectedExitCode) { + if (matchesDegradedFailure(smokeCommand, result)) { + writeErr(`CLI smoke degraded: ${args.join(' ')} known upstream issue`); + continue; + } writeErr(`CLI smoke 실패: ${args.join(' ')} exited with ${result.exitCode}`); if (result.stdout) { writeErr(result.stdout.trim()); diff --git a/tests/scripts/cli-smoke.test.ts b/tests/scripts/cli-smoke.test.ts index b541084..07b2b2e 100644 --- a/tests/scripts/cli-smoke.test.ts +++ b/tests/scripts/cli-smoke.test.ts @@ -132,6 +132,32 @@ describe('runCliSmoke', () => { expect(runCommand.mock.calls.map(([, args]) => args[1])).toEqual(['gs25-products', 'gs25-stores']); }); + it('known upstream 403이면 degraded로 기록하고 통과한다', async () => { + const runCommand = vi.fn().mockResolvedValue({ + exitCode: 1, + stdout: JSON.stringify({ + success: false, + error: { + message: 'API 요청 실패: 403 Forbidden - 403 Forbidden', + }, + }), + stderr: '요청 실패: HTTP 500', + }); + const writeErr = vi.fn(); + + const exitCode = await runCliSmoke({ + runCommand, + writeOut: vi.fn(), + writeErr, + command: 'node', + cliPath: 'dist/bin.js', + service: 'emart24', + }); + + expect(exitCode).toBe(0); + expect(writeErr).toHaveBeenCalledWith(expect.stringContaining('CLI smoke degraded')); + }); + it('하나라도 실패하면 즉시 non-zero를 반환한다', async () => { const runCommand = vi .fn()