Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion scripts/ops/cli-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -42,6 +43,7 @@ interface CliSmokeCommand {
scenario: string;
args: string[];
expectedExitCode?: number;
degradedFailurePatterns?: string[];
validate: Validator;
}

Expand Down Expand Up @@ -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', '커피')),
},
{
Expand Down Expand Up @@ -210,6 +213,11 @@ async function execCommand(command: string, args: string[]): Promise<CommandResu
});
}

function matchesDegradedFailure(command: CliSmokeCommand, result: CommandResult): boolean {
const output = `${result.stdout}\n${result.stderr}`;
return command.degradedFailurePatterns?.some((pattern) => output.includes(pattern)) ?? false;
}

export async function runCliSmoke(deps: CliSmokeDeps = {}): Promise<number> {
const runCommand = deps.runCommand || execCommand;
const writeOut = deps.writeOut || ((message: string) => process.stdout.write(`${message}\n`));
Expand All @@ -226,11 +234,16 @@ export async function runCliSmoke(deps: CliSmokeDeps = {}): Promise<number> {
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());
Expand Down
26 changes: 26 additions & 0 deletions tests/scripts/cli-smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 - <!DOCTYPE html><title>403 Forbidden</title>',
},
}),
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()
Expand Down