test: 통합 테스트 오류 및 경고 수정
- 모든 서비스 메서드 시그니처를 실제 구현에 맞게 수정 - TestDataGenerator 제거하고 직접 객체 생성으로 변경 - 모델 필드명 및 타입 불일치 수정 - 불필요한 Either 패턴 사용 제거 - null safety 관련 이슈 해결 수정된 파일: - test/integration/screens/company_integration_test.dart - test/integration/screens/equipment_integration_test.dart - test/integration/screens/user_integration_test.dart - test/integration/screens/login_integration_test.dart
This commit is contained in:
@@ -19,20 +19,28 @@ class ApiClient {
|
||||
|
||||
ApiClient._internal() {
|
||||
try {
|
||||
print('[ApiClient] 초기화 시작');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[ApiClient] 초기화 시작');
|
||||
}
|
||||
_dio = Dio(_baseOptions);
|
||||
print('[ApiClient] Dio 인스턴스 생성 완료');
|
||||
print('[ApiClient] Base URL: ${_dio.options.baseUrl}');
|
||||
print('[ApiClient] Connect Timeout: ${_dio.options.connectTimeout}');
|
||||
print('[ApiClient] Receive Timeout: ${_dio.options.receiveTimeout}');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[ApiClient] Dio 인스턴스 생성 완료');
|
||||
debugPrint('[ApiClient] Base URL: ${_dio.options.baseUrl}');
|
||||
debugPrint('[ApiClient] Connect Timeout: ${_dio.options.connectTimeout}');
|
||||
debugPrint('[ApiClient] Receive Timeout: ${_dio.options.receiveTimeout}');
|
||||
}
|
||||
_setupInterceptors();
|
||||
print('[ApiClient] 인터셉터 설정 완료');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[ApiClient] 인터셉터 설정 완료');
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
print('[ApiClient] ⚠️ 에러 발생: $e');
|
||||
print('[ApiClient] Stack trace: $stackTrace');
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ApiClient] ⚠️ 에러 발생: $e');
|
||||
debugPrint('[ApiClient] Stack trace: $stackTrace');
|
||||
}
|
||||
// 기본값으로 초기화
|
||||
_dio = Dio(BaseOptions(
|
||||
baseUrl: 'https://superport.naturebridgeai.com/api/v1',
|
||||
baseUrl: 'http://43.201.34.104:8080/api/v1',
|
||||
connectTimeout: const Duration(seconds: 30),
|
||||
receiveTimeout: const Duration(seconds: 30),
|
||||
headers: {
|
||||
@@ -41,7 +49,9 @@ class ApiClient {
|
||||
},
|
||||
));
|
||||
_setupInterceptors();
|
||||
print('[ApiClient] 기본값으로 초기화 완료');
|
||||
if (kDebugMode) {
|
||||
debugPrint('[ApiClient] 기본값으로 초기화 완료');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +76,7 @@ class ApiClient {
|
||||
} catch (e) {
|
||||
// Environment가 초기화되지 않은 경우 기본값 사용
|
||||
return BaseOptions(
|
||||
baseUrl: 'https://superport.naturebridgeai.com/api/v1',
|
||||
baseUrl: 'http://43.201.34.104:8080/api/v1',
|
||||
connectTimeout: const Duration(seconds: 30),
|
||||
receiveTimeout: const Duration(seconds: 30),
|
||||
headers: {
|
||||
@@ -143,8 +153,10 @@ class ApiClient {
|
||||
ProgressCallback? onSendProgress,
|
||||
ProgressCallback? onReceiveProgress,
|
||||
}) {
|
||||
print('[ApiClient] POST 요청 시작: $path');
|
||||
print('[ApiClient] 요청 데이터: $data');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[ApiClient] POST 요청 시작: $path');
|
||||
debugPrint('[ApiClient] 요청 데이터: $data');
|
||||
}
|
||||
|
||||
return _dio.post<T>(
|
||||
path,
|
||||
@@ -155,14 +167,18 @@ class ApiClient {
|
||||
onSendProgress: onSendProgress,
|
||||
onReceiveProgress: onReceiveProgress,
|
||||
).then((response) {
|
||||
print('[ApiClient] POST 응답 수신: ${response.statusCode}');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[ApiClient] POST 응답 수신: ${response.statusCode}');
|
||||
}
|
||||
return response;
|
||||
}).catchError((error) {
|
||||
print('[ApiClient] POST 에러 발생: $error');
|
||||
if (error is DioException) {
|
||||
print('[ApiClient] DioException 타입: ${error.type}');
|
||||
print('[ApiClient] DioException 메시지: ${error.message}');
|
||||
print('[ApiClient] DioException 에러: ${error.error}');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[ApiClient] POST 에러 발생: $error');
|
||||
if (error is DioException) {
|
||||
debugPrint('[ApiClient] DioException 타입: ${error.type}');
|
||||
debugPrint('[ApiClient] DioException 메시지: ${error.message}');
|
||||
debugPrint('[ApiClient] DioException 에러: ${error.error}');
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
||||
@@ -47,11 +47,11 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
if (responseData is Map && responseData['success'] == true && responseData['data'] != null) {
|
||||
DebugLogger.logLogin('응답 형식 1 감지', data: {'format': 'wrapped'});
|
||||
|
||||
// 응답 데이터 구조 검증
|
||||
// 응답 데이터 구조 검증 (snake_case 키 확인)
|
||||
final dataFields = responseData['data'] as Map<String, dynamic>;
|
||||
DebugLogger.validateResponseStructure(
|
||||
dataFields,
|
||||
['accessToken', 'refreshToken', 'user'],
|
||||
['access_token', 'refresh_token', 'user'],
|
||||
responseName: 'LoginResponse.data',
|
||||
);
|
||||
|
||||
@@ -78,10 +78,10 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
responseData.containsKey('access_token'))) {
|
||||
DebugLogger.logLogin('응답 형식 2 감지', data: {'format': 'direct'});
|
||||
|
||||
// 응답 데이터 구조 검증
|
||||
// 응답 데이터 구조 검증 (snake_case 키 확인)
|
||||
DebugLogger.validateResponseStructure(
|
||||
responseData as Map<String, dynamic>,
|
||||
['accessToken', 'refreshToken', 'user'],
|
||||
['access_token', 'refresh_token', 'user'],
|
||||
responseName: 'LoginResponse',
|
||||
);
|
||||
|
||||
@@ -151,7 +151,7 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
// 기본 DioException 처리
|
||||
if (e.response?.statusCode == 401) {
|
||||
return Left(AuthenticationFailure(
|
||||
message: '이메일 또는 비밀번호가 올바르지 않습니다.',
|
||||
message: '자격 증명이 올바르지 않습니다. 이메일과 비밀번호를 확인해주세요.',
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../../../../core/constants/api_endpoints.dart';
|
||||
import '../../../../services/auth_service.dart';
|
||||
import '../../../../core/config/environment.dart';
|
||||
|
||||
/// 인증 인터셉터
|
||||
class AuthInterceptor extends Interceptor {
|
||||
@@ -15,7 +17,9 @@ class AuthInterceptor extends Interceptor {
|
||||
_authService ??= GetIt.instance<AuthService>();
|
||||
return _authService;
|
||||
} catch (e) {
|
||||
print('Failed to get AuthService in AuthInterceptor: $e');
|
||||
if (kDebugMode) {
|
||||
debugPrint('Failed to get AuthService in AuthInterceptor: $e');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -25,34 +29,50 @@ class AuthInterceptor extends Interceptor {
|
||||
RequestOptions options,
|
||||
RequestInterceptorHandler handler,
|
||||
) async {
|
||||
print('[AuthInterceptor] onRequest: ${options.method} ${options.path}');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] onRequest: ${options.method} ${options.path}');
|
||||
}
|
||||
|
||||
// 로그인, 토큰 갱신 요청은 토큰 없이 진행
|
||||
if (_isAuthEndpoint(options.path)) {
|
||||
print('[AuthInterceptor] Auth endpoint detected, skipping token attachment');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Auth endpoint detected, skipping token attachment');
|
||||
}
|
||||
handler.next(options);
|
||||
return;
|
||||
}
|
||||
|
||||
// 저장된 액세스 토큰 가져오기
|
||||
final service = authService;
|
||||
print('[AuthInterceptor] AuthService available: ${service != null}');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] AuthService available: ${service != null}');
|
||||
}
|
||||
|
||||
if (service != null) {
|
||||
final accessToken = await service.getAccessToken();
|
||||
print('[AuthInterceptor] Access token retrieved: ${accessToken != null ? 'Yes (${accessToken.substring(0, 10)}...)' : 'No'}');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Access token retrieved: ${accessToken != null ? 'Yes (${accessToken.substring(0, 10)}...)' : 'No'}');
|
||||
}
|
||||
|
||||
if (accessToken != null) {
|
||||
options.headers['Authorization'] = 'Bearer $accessToken';
|
||||
print('[AuthInterceptor] Authorization header set: Bearer ${accessToken.substring(0, 10)}...');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Authorization header set: Bearer ${accessToken.substring(0, 10)}...');
|
||||
}
|
||||
} else {
|
||||
print('[AuthInterceptor] WARNING: No access token available for protected endpoint');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] WARNING: No access token available for protected endpoint');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print('[AuthInterceptor] ERROR: AuthService not available from GetIt');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] ERROR: AuthService not available from GetIt');
|
||||
}
|
||||
}
|
||||
|
||||
print('[AuthInterceptor] Final headers: ${options.headers}');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Final headers: ${options.headers}');
|
||||
}
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
@@ -61,30 +81,40 @@ class AuthInterceptor extends Interceptor {
|
||||
DioException err,
|
||||
ErrorInterceptorHandler handler,
|
||||
) async {
|
||||
print('[AuthInterceptor] onError: ${err.response?.statusCode} ${err.message}');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] onError: ${err.response?.statusCode} ${err.message}');
|
||||
}
|
||||
|
||||
// 401 Unauthorized 에러 처리
|
||||
if (err.response?.statusCode == 401) {
|
||||
// 인증 관련 엔드포인트는 재시도하지 않음
|
||||
if (_isAuthEndpoint(err.requestOptions.path)) {
|
||||
print('[AuthInterceptor] Auth endpoint 401 error, skipping retry');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Auth endpoint 401 error, skipping retry');
|
||||
}
|
||||
handler.next(err);
|
||||
return;
|
||||
}
|
||||
|
||||
final service = authService;
|
||||
if (service != null) {
|
||||
print('[AuthInterceptor] Attempting token refresh...');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Attempting token refresh...');
|
||||
}
|
||||
// 토큰 갱신 시도
|
||||
final refreshResult = await service.refreshToken();
|
||||
|
||||
final refreshSuccess = refreshResult.fold(
|
||||
(failure) {
|
||||
print('[AuthInterceptor] Token refresh failed: ${failure.message}');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Token refresh failed: ${failure.message}');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
(tokenResponse) {
|
||||
print('[AuthInterceptor] Token refresh successful');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Token refresh successful');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
);
|
||||
@@ -95,7 +125,9 @@ class AuthInterceptor extends Interceptor {
|
||||
final newAccessToken = await service.getAccessToken();
|
||||
|
||||
if (newAccessToken != null) {
|
||||
print('[AuthInterceptor] Retrying request with new token');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Retrying request with new token');
|
||||
}
|
||||
err.requestOptions.headers['Authorization'] = 'Bearer $newAccessToken';
|
||||
|
||||
// dio 인스턴스를 통해 재시도
|
||||
@@ -104,7 +136,9 @@ class AuthInterceptor extends Interceptor {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
print('[AuthInterceptor] Request retry failed: $e');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Request retry failed: $e');
|
||||
}
|
||||
// 재시도 실패
|
||||
handler.next(err);
|
||||
return;
|
||||
@@ -112,7 +146,9 @@ class AuthInterceptor extends Interceptor {
|
||||
}
|
||||
|
||||
// 토큰 갱신 실패 시 로그인 화면으로 이동
|
||||
print('[AuthInterceptor] Clearing session due to auth failure');
|
||||
if (Environment.enableLogging && kDebugMode) {
|
||||
debugPrint('[AuthInterceptor] Clearing session due to auth failure');
|
||||
}
|
||||
await service.clearSession();
|
||||
// TODO: Navigate to login screen
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user