feat: 에러 코드 SSOT 체계 도입

사용자에게 노출되는 실패가 에러 코드 없이 만들어질 수 없도록 구조를 바꾼다.

기존에는 예외 필터가 HTTP 상태로 코드를 추론해 도메인별 에러를 표현할 수 없었고,
세 플랫폼의 에러 사전을 손으로 맞춰야 해서 어긋나기 쉬웠다.

- 코드↔메시지↔HTTP 상태를 error-codes.ts 레지스트리 한 곳으로 통합
- BusinessException 은 에러 코드를 생성자 필수 인자로 받아 코드 누락을 타입으로 차단
- sync:error-codes 스크립트가 프론트·플러터 사전을 자동 생성 (수동 복사 제거)
- 429 전용 코드 REQ_001 추가 (기존에는 "시스템 오류"로 잘못 안내)
- AUTH_002(세션 만료)를 클라이언트에서 실제로 발행하도록 구현
- 응답에 traceId 를 실어 로그와 대조 가능하게 함

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 14:29:15 +09:00
parent 3b397b7788
commit f7ea60b8e7
16 changed files with 1022 additions and 165 deletions
+47
View File
@@ -0,0 +1,47 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:tma_app/core/api/api_exception.dart';
import 'package:tma_app/core/api/error_lexicon.dart';
void main() {
group('ErrorLexicon', () {
test('등록된 코드는 해당 한국어 메시지를 반환한다', () {
expect(
ErrorLexicon.getMessage('AUTH_001'),
'로그인이 필요한 서비스입니다. 로그인 후 이용해 주세요.',
);
});
test('미등록 코드는 SYS_001 폴백 메시지를 반환한다', () {
expect(
ErrorLexicon.getMessage('UNKNOWN_999'),
ErrorLexicon.getMessage('SYS_001'),
);
});
test('세 플랫폼 공유 에러 코드가 모두 등록되어 있다', () {
// backend error-codes.ts 레지스트리와 동일한 목록이어야 한다.
const expected = [
'SYS_001',
'REQ_001',
'AUTH_001',
'AUTH_002',
'AUTH_003',
'VAL_001',
'RES_001',
'BIZ_001',
];
expect(ErrorLexicon.codes.keys.toSet(), expected.toSet());
});
});
group('ApiException', () {
test('코드만으로 생성하면 사전의 메시지가 채워진다', () {
final exception = ApiException.fromCode('RES_001', statusCode: 404);
expect(exception.code, 'RES_001');
expect(exception.message, ErrorLexicon.getMessage('RES_001'));
expect(exception.statusCode, 404);
});
});
}