import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:superport/core/utils/debug_logger.dart'; import 'package:superport/core/config/environment.dart' as env; /// 로그인 문제 진단을 위한 유틸리티 클래스 class LoginDiagnostics { /// 로그인 프로세스 전체 진단 static Future> runFullDiagnostics() async { final results = {}; try { // 1. 환경 설정 확인 results['environment'] = _checkEnvironment(); // 2. 네트워크 연결 확인 results['network'] = await _checkNetworkConnectivity(); // 3. API 엔드포인트 확인 results['apiEndpoint'] = await _checkApiEndpoint(); // 4. 모델 직렬화 테스트 results['serialization'] = _testSerialization(); // 5. 저장소 접근 테스트 results['storage'] = await _testStorageAccess(); DebugLogger.log( '로그인 진단 완료', tag: 'DIAGNOSTICS', data: results, ); return results; } catch (e, stackTrace) { DebugLogger.logError( '진단 중 오류 발생', error: e, stackTrace: stackTrace, ); return { 'error': e.toString(), 'stackTrace': stackTrace.toString(), }; } } /// 환경 설정 확인 static Map _checkEnvironment() { return { 'apiBaseUrl': env.Environment.apiBaseUrl, 'isDebugMode': kDebugMode, 'platform': defaultTargetPlatform.toString(), }; } /// 네트워크 연결 확인 static Future> _checkNetworkConnectivity() async { final dio = Dio(); final results = {}; try { // Google DNS로 연결 테스트 final response = await dio.get('https://dns.google/resolve?name=google.com'); results['internetConnection'] = response.statusCode == 200; } catch (e) { results['internetConnection'] = false; results['error'] = e.toString(); } // API 서버 연결 테스트 try { final response = await dio.get( '${env.Environment.apiBaseUrl}/health', options: Options( validateStatus: (status) => status != null && status < 500, ), ); results['apiServerReachable'] = true; results['apiServerStatus'] = response.statusCode; } catch (e) { results['apiServerReachable'] = false; results['apiServerError'] = e.toString(); } return results; } /// API 엔드포인트 확인 static Future> _checkApiEndpoint() async { final dio = Dio(); final results = {}; try { // OPTIONS 요청으로 CORS 확인 final response = await dio.request( '${env.Environment.apiBaseUrl}/auth/login', options: Options( method: 'OPTIONS', validateStatus: (status) => true, ), ); results['corsEnabled'] = response.statusCode == 200 || response.statusCode == 204; results['allowedMethods'] = response.headers['access-control-allow-methods']; results['allowedHeaders'] = response.headers['access-control-allow-headers']; // 실제 로그인 엔드포인트 테스트 (잘못된 자격 증명으로) final loginResponse = await dio.post( '${env.Environment.apiBaseUrl}/auth/login', data: { 'email': 'test@test.com', 'password': 'test', }, options: Options( validateStatus: (status) => true, ), ); results['loginEndpointStatus'] = loginResponse.statusCode; results['loginResponseType'] = loginResponse.data?.runtimeType.toString(); // 응답 구조 분석 if (loginResponse.data is Map) { final data = loginResponse.data as Map; results['responseKeys'] = data.keys.toList(); results['hasSuccessField'] = data.containsKey('success'); results['hasDataField'] = data.containsKey('data'); results['hasAccessToken'] = data.containsKey('accessToken') || data.containsKey('access_token'); } } catch (e) { results['error'] = e.toString(); if (e is DioException) { results['dioErrorType'] = e.type.toString(); results['dioMessage'] = e.message; } } return results; } /// 모델 직렬화 테스트 static Map _testSerialization() { final results = {}; try { // LoginRequest 테스트 results['loginRequestValid'] = true; // LoginResponse 테스트 (형식 1) final loginResponse1 = { 'success': true, 'data': { 'accessToken': 'test_token', 'refreshToken': 'refresh_token', 'tokenType': 'Bearer', 'expiresIn': 3600, 'user': { 'id': 1, 'username': 'testuser', 'email': 'test@example.com', 'name': '테스트', 'role': 'USER', }, }, }; results['format1Valid'] = _validateResponseFormat1(loginResponse1); // LoginResponse 테스트 (형식 2) final loginResponse2 = { 'accessToken': 'test_token', 'refreshToken': 'refresh_token', 'tokenType': 'Bearer', 'expiresIn': 3600, 'user': { 'id': 1, 'username': 'testuser', 'email': 'test@example.com', 'name': '테스트', 'role': 'USER', }, }; results['format2Valid'] = _validateResponseFormat2(loginResponse2); } catch (e) { results['error'] = e.toString(); } return results; } /// 저장소 접근 테스트 static Future> _testStorageAccess() async { final results = {}; try { // 실제 FlutterSecureStorage 테스트는 의존성 주입이 필요하므로 // 여기서는 기본적인 체크만 수행 results['platformSupported'] = true; // 플랫폼별 특이사항 체크 if (defaultTargetPlatform == TargetPlatform.iOS) { results['note'] = 'iOS Keychain 사용'; } else if (defaultTargetPlatform == TargetPlatform.android) { results['note'] = 'Android KeyStore 사용'; } else { results['note'] = '웹 또는 데스크톱 플랫폼'; } } catch (e) { results['error'] = e.toString(); } return results; } /// 응답 형식 1 검증 static bool _validateResponseFormat1(Map response) { try { if (!response.containsKey('success') || response['success'] != true) { return false; } if (!response.containsKey('data') || response['data'] is! Map) { return false; } final data = response['data'] as Map; final requiredFields = ['accessToken', 'refreshToken', 'user']; for (final field in requiredFields) { if (!data.containsKey(field)) { return false; } } return true; } catch (e) { return false; } } /// 응답 형식 2 검증 static bool _validateResponseFormat2(Map response) { try { final requiredFields = ['accessToken', 'refreshToken', 'user']; for (final field in requiredFields) { if (!response.containsKey(field)) { return false; } } if (response['user'] is! Map) { return false; } return true; } catch (e) { return false; } } /// 진단 결과를 읽기 쉬운 형식으로 포맷 static String formatDiagnosticsReport(Map diagnostics) { final buffer = StringBuffer(); buffer.writeln('=== 로그인 진단 보고서 ===\n'); // 환경 설정 if (diagnostics.containsKey('environment')) { buffer.writeln('## 환경 설정'); final env = diagnostics['environment'] as Map; env.forEach((key, value) { buffer.writeln('- $key: $value'); }); buffer.writeln(); } // 네트워크 상태 if (diagnostics.containsKey('network')) { buffer.writeln('## 네트워크 상태'); final network = diagnostics['network'] as Map; buffer.writeln('- 인터넷 연결: ${network['internetConnection'] == true ? '✅' : '❌'}'); if (network.containsKey('apiServerReachable')) { buffer.writeln('- API 서버 접근: ${network['apiServerReachable'] == true ? '✅' : '❌'}'); } buffer.writeln(); } // API 엔드포인트 if (diagnostics.containsKey('apiEndpoint')) { buffer.writeln('## API 엔드포인트'); final api = diagnostics['apiEndpoint'] as Map; if (api['skip'] == true) { buffer.writeln('- Mock 모드로 건너뜀'); } else { buffer.writeln('- CORS 활성화: ${api['corsEnabled'] == true ? '✅' : '❌'}'); buffer.writeln('- 로그인 엔드포인트 상태: ${api['loginEndpointStatus']}'); if (api.containsKey('responseKeys')) { buffer.writeln('- 응답 키: ${api['responseKeys']}'); } } buffer.writeln(); } // 직렬화 테스트 if (diagnostics.containsKey('serialization')) { buffer.writeln('## 모델 직렬화'); final serial = diagnostics['serialization'] as Map; buffer.writeln('- LoginRequest: ${serial['loginRequestValid'] == true ? '✅' : '❌'}'); buffer.writeln('- 응답 형식 1: ${serial['format1Valid'] == true ? '✅' : '❌'}'); buffer.writeln('- 응답 형식 2: ${serial['format2Valid'] == true ? '✅' : '❌'}'); buffer.writeln(); } // 오류 정보 if (diagnostics.containsKey('error')) { buffer.writeln('## ⚠️ 오류 발생'); buffer.writeln(diagnostics['error']); } return buffer.toString(); } }