import 'package:dio/dio.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get_it/get_it.dart'; import 'package:superport/data/datasources/remote/api_client.dart'; import 'package:superport/data/datasources/remote/auth_remote_datasource.dart'; import 'package:superport/data/datasources/remote/company_remote_datasource.dart'; import 'package:superport/data/datasources/remote/user_remote_datasource.dart'; import 'package:superport/data/datasources/remote/equipment_remote_datasource.dart'; import 'package:superport/data/datasources/remote/license_remote_datasource.dart'; import 'package:superport/data/datasources/remote/warehouse_remote_datasource.dart'; import 'package:superport/data/datasources/remote/dashboard_remote_datasource.dart'; import 'package:superport/data/models/auth/login_request.dart'; import 'package:superport/services/auth_service.dart'; import 'package:superport/services/company_service.dart'; import 'package:superport/services/user_service.dart'; import 'package:superport/services/equipment_service.dart'; import 'package:superport/services/license_service.dart'; import 'package:superport/services/warehouse_service.dart'; import 'package:superport/services/dashboard_service.dart'; import 'package:superport/core/config/environment.dart'; /// 테스트용 메모리 기반 FlutterSecureStorage class TestSecureStorage extends FlutterSecureStorage { static final Map _storage = {}; const TestSecureStorage() : super(); @override Future containsKey({required String key, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions}) async { return _storage.containsKey(key); } @override Future delete({required String key, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions}) async { _storage.remove(key); } @override Future deleteAll({IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions}) async { _storage.clear(); } @override Future read({required String key, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions}) async { return _storage[key]; } @override Future> readAll({IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions}) async { return Map.from(_storage); } @override Future write({required String key, required String? value, IOSOptions? iOptions, AndroidOptions? aOptions, LinuxOptions? lOptions, WebOptions? webOptions, MacOsOptions? mOptions, WindowsOptions? wOptions}) async { if (value != null) { _storage[key] = value; } else { _storage.remove(key); } } // 테스트용 메서드 static void clearAll() { _storage.clear(); } } /// 실제 API 테스트를 위한 헬퍼 클래스 class RealApiTestHelper { static late GetIt getIt; static late ApiClient apiClient; static late FlutterSecureStorage secureStorage; static late AuthService authService; static String? _accessToken; /// 테스트 환경 초기화 static Future setupTestEnvironment() async { // Environment 초기화 await Environment.initialize('development'); // 테스트 환경에서는 TestWidgetsFlutterBinding을 사용하지 않음 // HTTP 요청이 차단되기 때문 getIt = GetIt.instance; // GetIt 초기화 if (getIt.isRegistered()) { await getIt.reset(); } // 실제 API 클라이언트 설정 apiClient = ApiClient(); secureStorage = const TestSecureStorage(); // 서비스 등록 getIt.registerSingleton(apiClient); getIt.registerSingleton(secureStorage); // Auth 서비스 등록 final authRemoteDataSource = AuthRemoteDataSourceImpl(apiClient); authService = AuthServiceImpl(authRemoteDataSource, secureStorage); getIt.registerSingleton(authService); // RemoteDataSource 등록 (일부 서비스가 GetIt을 통해 가져옴) final companyRemoteDataSource = CompanyRemoteDataSourceImpl(apiClient); final licenseRemoteDataSource = LicenseRemoteDataSourceImpl(apiClient: apiClient); final warehouseRemoteDataSource = WarehouseRemoteDataSourceImpl(apiClient: apiClient); final equipmentRemoteDataSource = EquipmentRemoteDataSourceImpl(); final userRemoteDataSource = UserRemoteDataSource(); final dashboardRemoteDataSource = DashboardRemoteDataSourceImpl(apiClient); getIt.registerSingleton(companyRemoteDataSource); getIt.registerSingleton(licenseRemoteDataSource); getIt.registerSingleton(warehouseRemoteDataSource); getIt.registerSingleton(equipmentRemoteDataSource); getIt.registerSingleton(userRemoteDataSource); getIt.registerSingleton(dashboardRemoteDataSource); // 기타 서비스 등록 getIt.registerSingleton(CompanyService(companyRemoteDataSource)); getIt.registerSingleton(UserService()); getIt.registerSingleton(EquipmentService()); getIt.registerSingleton(LicenseService(licenseRemoteDataSource)); getIt.registerSingleton(WarehouseService()); getIt.registerSingleton(DashboardServiceImpl(dashboardRemoteDataSource)); } /// 로그인 수행 및 토큰 저장 static Future loginAndGetToken() async { if (_accessToken != null) { return _accessToken!; } final loginRequest = LoginRequest( email: 'admin@superport.kr', password: 'admin123!', ); final result = await authService.login(loginRequest); return result.fold( (failure) => throw Exception('로그인 실패: ${failure.message}'), (loginResponse) { _accessToken = loginResponse.accessToken; apiClient.updateAuthToken(_accessToken!); return _accessToken!; }, ); } /// 테스트 환경 정리 static Future teardownTestEnvironment() async { _accessToken = null; apiClient.removeAuthToken(); // 테스트용 스토리지 정리 TestSecureStorage.clearAll(); await getIt.reset(); } /// API 응답 로깅 헬퍼 static void logResponse(String testName, Response response) { // === $testName === // Status Code: ${response.statusCode} // Headers: ${response.headers} // Data: ${response.data} // ================= } /// 에러 로깅 헬퍼 static void logError(String testName, dynamic error) { // === $testName - ERROR === if (error is DioException) { // Type: ${error.type} // Message: ${error.message} // Response: ${error.response?.data} // Status Code: ${error.response?.statusCode} } else { // Error: $error } // ======================== } } /// 테스트 데이터 생성 헬퍼 class TestDataHelper { static int _uniqueId = DateTime.now().millisecondsSinceEpoch; static String generateUniqueId() { return '${_uniqueId++}'; } static String generateUniqueEmail() { return 'test_${generateUniqueId()}@test.com'; } static String generateUniqueName(String prefix) { return '${prefix}_${generateUniqueId()}'; } /// 테스트용 회사 데이터 static Map createTestCompanyData() { return { 'name': generateUniqueName('Test Company'), 'business_number': '123-45-${generateUniqueId().substring(0, 5)}', 'phone': '010-${_uniqueId % 10000}-${(_uniqueId + 1) % 10000}', 'address': { 'zip_code': '12345', 'region': '서울시 강남구', 'detail_address': '테스트로 ${_uniqueId % 100}번길', }, }; } /// 테스트용 사용자 데이터 static Map createTestUserData({required int companyId}) { return { 'email': generateUniqueEmail(), 'password': 'Test1234!', 'name': generateUniqueName('Test User'), 'phone': '010-${_uniqueId % 10000}-${(_uniqueId + 1) % 10000}', 'company_id': companyId, 'role': 'M', // Member 'is_active': true, }; } /// 테스트용 장비 데이터 static Map createTestEquipmentData({ required int companyId, required int warehouseId, }) { return { 'name': generateUniqueName('Test Equipment'), 'model': 'Model-${generateUniqueId()}', 'serial_number': 'SN-${generateUniqueId()}', 'company_id': companyId, 'warehouse_id': warehouseId, 'status': 'I', // 입고 'quantity': 1, 'purchase_date': DateTime.now().toIso8601String(), }; } /// 테스트용 라이선스 데이터 static Map createTestLicenseData({required int companyId}) { return { 'name': generateUniqueName('Test License'), 'product_key': 'KEY-${generateUniqueId()}', 'company_id': companyId, 'license_type': 'subscription', 'quantity': 5, 'expiry_date': DateTime.now().add(const Duration(days: 365)).toIso8601String(), 'purchase_date': DateTime.now().toIso8601String(), }; } /// 테스트용 창고 데이터 static Map createTestWarehouseData({required int companyId}) { return { 'name': generateUniqueName('Test Warehouse'), 'company_id': companyId, 'location': '서울시 강남구 테스트로 ${_uniqueId % 100}', 'capacity': 1000, 'manager': generateUniqueName('Manager'), 'contact': '02-${_uniqueId % 10000}-${(_uniqueId + 1) % 10000}', }; } }