Initial commit

This commit is contained in:
2026-10-27 15:22:41 +09:00
commit 98e530c186
269 changed files with 33654 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
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('클라이언트 로직이 참조하는 코드가 사전에 존재한다', () {
// dio_client 의 상태코드 역매핑과 세션 만료 승격이 이 코드들을 직접 사용한다.
//
// ⚠️ 전체 목록을 비교하지 않는 이유: 세 플랫폼의 사전은 backend 레지스트리에서
// 생성되므로 목록 일치는 구조적으로 보장되고, CI 가 드리프트를 검사한다.
// 전수 비교를 하면 도메인 코드를 추가할 때마다 무의미하게 깨진다.
const usedByClient = [
'SYS_001',
'REQ_001',
'AUTH_001',
'AUTH_002',
'AUTH_003',
'VAL_001',
'RES_001',
'BIZ_001',
];
for (final code in usedByClient) {
expect(ErrorLexicon.contains(code), isTrue, reason: '$code 누락');
}
});
test('모든 코드는 DOMAIN_NNN 네이밍 규약을 따른다', () {
final pattern = RegExp(r'^[A-Z]+_\d{3}$');
for (final code in ErrorLexicon.codes.keys) {
expect(pattern.hasMatch(code), isTrue, reason: '규약 위반: $code');
}
});
});
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);
});
});
}
+15
View File
@@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tma_app/main.dart';
void main() {
testWidgets('앱이 정상적으로 렌더링된다', (WidgetTester tester) async {
await tester.pumpWidget(
const ProviderScope(child: MyApp()),
);
expect(find.byType(MaterialApp), findsOneWidget);
expect(find.byType(Scaffold), findsOneWidget);
});
}