import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:superport/data/datasources/remote/api_client.dart'; import 'package:superport/models/warehouse_location_model.dart'; import 'package:superport/domain/usecases/warehouse_location/get_warehouse_locations_usecase.dart'; import 'package:superport/domain/usecases/warehouse_location/create_warehouse_location_usecase.dart'; import 'package:superport/domain/usecases/warehouse_location/update_warehouse_location_usecase.dart'; import 'package:superport/domain/usecases/warehouse_location/delete_warehouse_location_usecase.dart'; import 'package:superport/services/warehouse_service.dart'; import '../base/base_screen_test.dart'; import '../../framework/models/test_models.dart'; /// 창고 위치 관리 화면 자동화 테스트 /// /// 테스트 범위: /// - 창고 위치 목록 조회 /// - 창고 위치 생성 /// - 창고 위치 수정 /// - 창고 위치 삭제 /// - 장비 입고 연동 테스트 class WarehouseScreenTest extends BaseScreenTest { late WarehouseService warehouseService; late GetWarehouseLocationsUseCase getWarehouseLocationsUseCase; late CreateWarehouseLocationUseCase createWarehouseLocationUseCase; late UpdateWarehouseLocationUseCase updateWarehouseLocationUseCase; late DeleteWarehouseLocationUseCase deleteWarehouseLocationUseCase; // 테스트 데이터 final List createdWarehouseIds = []; WarehouseScreenTest({ required ApiClient apiClient, required GetIt getIt, required super.testContext, required super.errorDiagnostics, required super.autoFixer, required super.dataGenerator, required super.reportCollector, }) : super( apiClient: apiClient, getIt: getIt, ); @override ScreenMetadata getScreenMetadata() { return ScreenMetadata( screenName: 'WarehouseLocation', screenPath: '/warehouse-location', screenType: ScreenType.list, features: [ 'list_view', 'search', 'pagination', 'create', 'update', 'delete', 'equipment_integration', ], ); } @override Future initializeServices() async { try { // UseCase 인스턴스 가져오기 getWarehouseLocationsUseCase = getIt(); createWarehouseLocationUseCase = getIt(); updateWarehouseLocationUseCase = getIt(); deleteWarehouseLocationUseCase = getIt(); // Legacy 서비스 (점진적 마이그레이션을 위해 유지) warehouseService = getIt(); _log('✅ Warehouse 서비스 초기화 완료'); } catch (e) { _log('❌ Warehouse 서비스 초기화 실패: $e'); throw TestSetupError( message: 'Warehouse 서비스 초기화 실패', details: {'error': e.toString()}, ); } } @override Future performAdditionalSetup() async { // 테스트용 창고 위치 데이터 생성 await _createTestWarehouses(); } @override Future performAdditionalCleanup() async { // 생성된 테스트 데이터 정리 await _cleanupTestWarehouses(); } @override Future> detectFeatures(ScreenMetadata metadata) async { final features = []; // 기본 CRUD 기능 features.add(TestFeature( featureName: '창고 위치 목록 조회', testSteps: [ TestStep( name: '전체 창고 위치 목록 조회', action: () => _testGetWarehouseList(), expectedResult: '창고 위치 목록이 정상적으로 조회됨', ), TestStep( name: '페이징 처리', action: () => _testPagination(), expectedResult: '페이지별로 데이터가 정확히 나뉘어짐', ), TestStep( name: '검색 기능', action: () => _testSearch(), expectedResult: '검색어에 매칭되는 창고 위치만 조회됨', ), ], )); features.add(TestFeature( featureName: '창고 위치 생성', testSteps: [ TestStep( name: '정상 창고 위치 생성', action: () => _testCreateWarehouse(), expectedResult: '창고 위치가 성공적으로 생성됨', ), TestStep( name: '중복 위치 체크', action: () => _testDuplicateCheck(), expectedResult: '동일한 위치명으로 생성 시 경고', ), TestStep( name: '필수 필드 검증', action: () => _testRequiredFieldValidation(), expectedResult: '필수 필드 누락 시 에러 발생', ), ], )); features.add(TestFeature( featureName: '창고 위치 수정', testSteps: [ TestStep( name: '기본 정보 수정', action: () => _testUpdateWarehouse(), expectedResult: '창고 위치 정보가 정상적으로 수정됨', ), TestStep( name: '주소 정보 수정', action: () => _testUpdateAddress(), expectedResult: '주소 정보가 정상적으로 수정됨', ), TestStep( name: '담당자 정보 수정', action: () => _testUpdateContact(), expectedResult: '담당자 정보가 정상적으로 수정됨', ), ], )); features.add(TestFeature( featureName: '창고 위치 삭제', testSteps: [ TestStep( name: '일반 삭제', action: () => _testDeleteWarehouse(), expectedResult: '창고 위치가 성공적으로 삭제됨', ), TestStep( name: '사용중인 창고 삭제 방지', action: () => _testDeleteUsedWarehouse(), expectedResult: '장비가 입고된 창고는 삭제 불가', ), ], )); features.add(TestFeature( featureName: '장비 연동', testSteps: [ TestStep( name: '입고 가능 여부 확인', action: () => _testEquipmentCapacity(), expectedResult: '창고 용량 확인 가능', ), TestStep( name: '장비 입고 이력 조회', action: () => _testEquipmentHistory(), expectedResult: '해당 창고의 입고 이력 조회 가능', ), ], )); return features; } // ===== 테스트 구현 메서드들 ===== /// 테스트용 창고 위치 데이터 생성 Future _createTestWarehouses() async { try { for (int i = 0; i < 3; i++) { final warehouseData = dataGenerator.generateWarehouseLocation( name: '테스트창고_${testSessionId}_$i', address: '서울시 강남구 테스트로 $i', ); final result = await createWarehouseLocationUseCase.execute(warehouseData); result.fold( (failure) => _log('창고 위치 생성 실패: ${failure.message}'), (warehouse) { createdWarehouseIds.add(warehouse.id!); _log('테스트 창고 위치 생성: ${warehouse.name} (ID: ${warehouse.id})'); }, ); } } catch (e) { _log('테스트 창고 위치 생성 중 에러: $e'); } } /// 테스트 데이터 정리 Future _cleanupTestWarehouses() async { for (final id in createdWarehouseIds) { try { await deleteWarehouseLocationUseCase.execute(id); _log('테스트 창고 위치 삭제: ID $id'); } catch (e) { _log('창고 위치 삭제 실패 (ID: $id): $e'); } } createdWarehouseIds.clear(); } /// 창고 위치 목록 조회 테스트 Future _testGetWarehouseList() async { final result = await getWarehouseLocationsUseCase.execute( page: 1, size: 10, ); result.fold( (failure) => throw TestException('창고 위치 목록 조회 실패: ${failure.message}'), (response) { assert(response.warehouseLocations.isNotEmpty, '창고 위치 목록이 비어있음'); assert(response.totalCount > 0, '전체 개수가 0'); _log('창고 위치 목록 조회 성공: ${response.warehouseLocations.length}개'); }, ); } /// 페이징 테스트 Future _testPagination() async { // 첫 페이지 final page1Result = await getWarehouseLocationsUseCase.execute( page: 1, size: 5, ); page1Result.fold( (failure) => throw TestException('페이지 1 조회 실패: ${failure.message}'), (page1) async { // 두 번째 페이지 final page2Result = await getWarehouseLocationsUseCase.execute( page: 2, size: 5, ); page2Result.fold( (failure) => _log('페이지 2 조회 실패 (데이터 부족일 수 있음): ${failure.message}'), (page2) { // 페이지별 데이터가 다른지 확인 if (page2.warehouseLocations.isNotEmpty) { final page1Ids = page1.warehouseLocations.map((w) => w.id).toSet(); final page2Ids = page2.warehouseLocations.map((w) => w.id).toSet(); assert(page1Ids.intersection(page2Ids).isEmpty, '페이지 간 데이터 중복'); } _log('페이징 테스트 성공'); }, ); }, ); } /// 검색 테스트 Future _testSearch() async { final searchTerm = '테스트창고_$testSessionId'; final result = await getWarehouseLocationsUseCase.execute( page: 1, size: 10, search: searchTerm, ); result.fold( (failure) => throw TestException('검색 실패: ${failure.message}'), (response) { for (final warehouse in response.warehouseLocations) { assert( warehouse.name.contains(searchTerm) || warehouse.address.contains(searchTerm), '검색 결과가 검색어와 매치되지 않음' ); } _log('검색 테스트 성공: ${response.warehouseLocations.length}개 검색됨'); }, ); } /// 창고 위치 생성 테스트 Future _testCreateWarehouse() async { final warehouseData = dataGenerator.generateWarehouseLocation( name: '신규창고_${DateTime.now().millisecondsSinceEpoch}', address: '서울시 서초구 신규로 123', ); final result = await createWarehouseLocationUseCase.execute(warehouseData); result.fold( (failure) => throw TestException('창고 위치 생성 실패: ${failure.message}'), (warehouse) { assert(warehouse.id != null, '생성된 창고 위치 ID가 null'); assert(warehouse.name == warehouseData.name, '창고명 불일치'); createdWarehouseIds.add(warehouse.id!); _log('창고 위치 생성 성공: ${warehouse.name} (ID: ${warehouse.id})'); }, ); } /// 중복 체크 테스트 Future _testDuplicateCheck() async { final warehouseName = '중복창고_${DateTime.now().millisecondsSinceEpoch}'; // 첫 번째 생성 (성공해야 함) final warehouse1 = dataGenerator.generateWarehouseLocation( name: warehouseName, address: '주소1', ); final result1 = await createWarehouseLocationUseCase.execute(warehouse1); result1.fold( (failure) => throw TestException('첫 번째 창고 생성 실패: ${failure.message}'), (warehouse) => createdWarehouseIds.add(warehouse.id!), ); // 두 번째 생성 (경고 또는 성공) final warehouse2 = dataGenerator.generateWarehouseLocation( name: warehouseName, // 동일한 이름 address: '주소2', ); final result2 = await createWarehouseLocationUseCase.execute(warehouse2); result2.fold( (failure) => _log('중복 체크 - 실패 처리됨: ${failure.message}'), (warehouse) { createdWarehouseIds.add(warehouse.id!); _log('중복 이름 허용됨 - 주의 필요'); }, ); } /// 필수 필드 검증 테스트 Future _testRequiredFieldValidation() async { // 필수 필드가 누락된 창고 데이터 final invalidWarehouse = WarehouseLocation( name: '', // 빈 이름 address: '', phone: '', manager: '', ); final result = await createWarehouseLocationUseCase.execute(invalidWarehouse); result.fold( (failure) => _log('필수 필드 검증 성공: ${failure.message}'), (warehouse) { createdWarehouseIds.add(warehouse.id!); throw TestException('필수 필드 검증 실패 - 빈 값이 허용됨'); }, ); } /// 창고 위치 수정 테스트 Future _testUpdateWarehouse() async { if (createdWarehouseIds.isEmpty) { await _createTestWarehouses(); } final warehouseId = createdWarehouseIds.first; final updatedData = dataGenerator.generateWarehouseLocation( name: '수정된창고_$testSessionId', address: '수정된 주소', ); final result = await updateWarehouseLocationUseCase.execute( id: warehouseId, warehouseLocation: updatedData, ); result.fold( (failure) => throw TestException('창고 위치 수정 실패: ${failure.message}'), (warehouse) { assert(warehouse.name == updatedData.name, '창고명 수정 실패'); _log('창고 위치 수정 성공: ${warehouse.name}'); }, ); } /// 주소 정보 수정 테스트 Future _testUpdateAddress() async { if (createdWarehouseIds.isEmpty) { await _createTestWarehouses(); } final warehouseId = createdWarehouseIds.first; final newAddress = '경기도 성남시 분당구 새주소로 456'; // 기존 데이터를 가져와서 주소만 변경 final warehouse = dataGenerator.generateWarehouseLocation( name: '주소수정테스트', address: newAddress, ); final result = await updateWarehouseLocationUseCase.execute( id: warehouseId, warehouseLocation: warehouse, ); result.fold( (failure) => throw TestException('주소 수정 실패: ${failure.message}'), (updated) { assert(updated.address == newAddress, '주소 수정 실패'); _log('주소 수정 성공: ${updated.address}'); }, ); } /// 담당자 정보 수정 테스트 Future _testUpdateContact() async { if (createdWarehouseIds.isEmpty) { await _createTestWarehouses(); } final warehouseId = createdWarehouseIds.first; final newManager = '새담당자'; final newPhone = '010-9999-8888'; final warehouse = dataGenerator.generateWarehouseLocation( name: '담당자수정테스트', address: '테스트주소', )..manager = newManager ..phone = newPhone; final result = await updateWarehouseLocationUseCase.execute( id: warehouseId, warehouseLocation: warehouse, ); result.fold( (failure) => throw TestException('담당자 정보 수정 실패: ${failure.message}'), (updated) { assert(updated.manager == newManager, '담당자 수정 실패'); assert(updated.phone == newPhone, '연락처 수정 실패'); _log('담당자 정보 수정 성공: ${updated.manager} / ${updated.phone}'); }, ); } /// 창고 위치 삭제 테스트 Future _testDeleteWarehouse() async { // 삭제용 창고 생성 final warehouseData = dataGenerator.generateWarehouseLocation( name: '삭제테스트_${DateTime.now().millisecondsSinceEpoch}', address: '삭제될 주소', ); final createResult = await createWarehouseLocationUseCase.execute(warehouseData); createResult.fold( (failure) => throw TestException('삭제 테스트용 창고 생성 실패: ${failure.message}'), (warehouse) async { // 삭제 final deleteResult = await deleteWarehouseLocationUseCase.execute(warehouse.id!); deleteResult.fold( (failure) => throw TestException('창고 위치 삭제 실패: ${failure.message}'), (_) => _log('창고 위치 삭제 성공: ID ${warehouse.id}'), ); }, ); } /// 사용중인 창고 삭제 방지 테스트 Future _testDeleteUsedWarehouse() async { // 장비가 입고된 창고는 삭제할 수 없어야 함 // 이 테스트는 백엔드에서 처리되어야 함 _log('사용중인 창고 삭제 방지 테스트 - 백엔드 구현 필요'); } /// 장비 입고 용량 확인 테스트 Future _testEquipmentCapacity() async { // 창고의 용량 관리는 별도 기능이 필요할 수 있음 _log('장비 입고 용량 확인 테스트 - 추가 기능 구현 필요'); } /// 장비 입고 이력 조회 테스트 Future _testEquipmentHistory() async { // 창고별 장비 입고 이력은 Equipment API와 연동 필요 _log('장비 입고 이력 조회 테스트 - Equipment API 연동 필요'); } void _log(String message) { print('[WarehouseScreenTest] $message'); } }