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/company_model.dart'; import 'package:superport/domain/usecases/company/get_companies_usecase.dart'; import 'package:superport/domain/usecases/company/create_company_usecase.dart'; import 'package:superport/domain/usecases/company/update_company_usecase.dart'; import 'package:superport/domain/usecases/company/delete_company_usecase.dart'; import 'package:superport/domain/usecases/company/toggle_company_status_usecase.dart'; import 'package:superport/services/company_service.dart'; import '../base/base_screen_test.dart'; import '../../framework/models/test_models.dart'; /// 회사 관리 화면 자동화 테스트 /// /// 테스트 범위: /// - 회사 목록 조회 (페이징, 검색, 필터) /// - 회사 생성 (중복 체크, 유효성 검증) /// - 회사 수정 (지점 관리 포함) /// - 회사 삭제 (연관 데이터 체크) /// - 상태 토글 (활성/비활성) class CompanyScreenTest extends BaseScreenTest { late CompanyService companyService; late GetCompaniesUseCase getCompaniesUseCase; late CreateCompanyUseCase createCompanyUseCase; late UpdateCompanyUseCase updateCompanyUseCase; late DeleteCompanyUseCase deleteCompanyUseCase; late ToggleCompanyStatusUseCase toggleCompanyStatusUseCase; // 테스트 데이터 final List createdCompanyIds = []; CompanyScreenTest({ 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: 'Company', screenPath: '/company', screenType: ScreenType.list, features: [ 'list_view', 'search', 'pagination', 'create', 'update', 'delete', 'status_toggle', 'branch_management', ], ); } @override Future initializeServices() async { try { // UseCase 인스턴스 가져오기 getCompaniesUseCase = getIt(); createCompanyUseCase = getIt(); updateCompanyUseCase = getIt(); deleteCompanyUseCase = getIt(); toggleCompanyStatusUseCase = getIt(); // Legacy 서비스 (점진적 마이그레이션을 위해 유지) companyService = getIt(); _log('✅ Company 서비스 초기화 완료'); } catch (e) { _log('❌ Company 서비스 초기화 실패: $e'); throw TestSetupError( message: 'Company 서비스 초기화 실패', details: {'error': e.toString()}, ); } } @override Future performAdditionalSetup() async { // 테스트용 회사 데이터 생성 await _createTestCompanies(); } @override Future performAdditionalCleanup() async { // 생성된 테스트 데이터 정리 await _cleanupTestCompanies(); } @override Future> detectFeatures(ScreenMetadata metadata) async { final features = []; // 기본 CRUD 기능 features.add(TestFeature( featureName: '회사 목록 조회', testSteps: [ TestStep( name: '전체 회사 목록 조회', action: () => _testGetCompanyList(), expectedResult: '회사 목록이 정상적으로 조회됨', ), TestStep( name: '페이징 처리', action: () => _testPagination(), expectedResult: '페이지별로 데이터가 정확히 나뉘어짐', ), TestStep( name: '검색 기능', action: () => _testSearch(), expectedResult: '검색어에 매칭되는 회사만 조회됨', ), ], )); features.add(TestFeature( featureName: '회사 생성', testSteps: [ TestStep( name: '정상 회사 생성', action: () => _testCreateCompany(), expectedResult: '회사가 성공적으로 생성됨', ), TestStep( name: '중복 체크', action: () => _testDuplicateCheck(), expectedResult: '동일한 사업자번호로 생성 시 에러 발생', ), TestStep( name: '필수 필드 검증', action: () => _testRequiredFieldValidation(), expectedResult: '필수 필드 누락 시 에러 발생', ), ], )); features.add(TestFeature( featureName: '회사 수정', testSteps: [ TestStep( name: '기본 정보 수정', action: () => _testUpdateCompany(), expectedResult: '회사 정보가 정상적으로 수정됨', ), TestStep( name: '지점 추가', action: () => _testAddBranch(), expectedResult: '지점이 성공적으로 추가됨', ), TestStep( name: '지점 삭제', action: () => _testRemoveBranch(), expectedResult: '지점이 성공적으로 삭제됨', ), ], )); features.add(TestFeature( featureName: '회사 삭제', testSteps: [ TestStep( name: '일반 삭제', action: () => _testDeleteCompany(), expectedResult: '회사가 성공적으로 삭제됨', ), TestStep( name: '연관 데이터 체크', action: () => _testDeleteWithRelatedData(), expectedResult: '연관 데이터가 있을 경우 경고 메시지 표시', ), ], )); features.add(TestFeature( featureName: '상태 관리', testSteps: [ TestStep( name: '활성/비활성 토글', action: () => _testToggleStatus(), expectedResult: '회사 상태가 정상적으로 변경됨', ), TestStep( name: '비활성 회사 필터링', action: () => _testInactiveFilter(), expectedResult: '비활성 회사가 필터링되어 표시됨', ), ], )); return features; } // ===== 테스트 구현 메서드들 ===== /// 테스트용 회사 데이터 생성 Future _createTestCompanies() async { try { for (int i = 0; i < 5; i++) { final companyData = dataGenerator.generateCompany( name: '테스트회사_${testSessionId}_$i', businessNumber: '${1234567890 + i}', ); final result = await createCompanyUseCase.call(companyData); result.fold( (failure) => _log('회사 생성 실패: ${failure.message}'), (company) { createdCompanyIds.add(company.id!); _log('테스트 회사 생성: ${company.name} (ID: ${company.id})'); }, ); } } catch (e) { _log('테스트 회사 생성 중 에러: $e'); } } /// 테스트 데이터 정리 Future _cleanupTestCompanies() async { for (final id in createdCompanyIds) { try { await deleteCompanyUseCase.call(id); _log('테스트 회사 삭제: ID $id'); } catch (e) { _log('회사 삭제 실패 (ID: $id): $e'); } } createdCompanyIds.clear(); } /// 회사 목록 조회 테스트 Future _testGetCompanyList() async { final result = await getCompaniesUseCase.call( page: 1, size: 10, ); result.fold( (failure) => throw TestException('회사 목록 조회 실패: ${failure.message}'), (companies) { assert(companies.isNotEmpty, '회사 목록이 비어있음'); _log('회사 목록 조회 성공: ${companies.length}개'); }, ); } /// 페이징 테스트 Future _testPagination() async { // 첫 페이지 final page1Result = await getCompaniesUseCase.call( page: 1, size: 5, ); page1Result.fold( (failure) => throw TestException('페이지 1 조회 실패: ${failure.message}'), (page1) async { // 두 번째 페이지 final page2Result = await getCompaniesUseCase.call( page: 2, size: 5, ); page2Result.fold( (failure) => _log('페이지 2 조회 실패 (데이터 부족일 수 있음): ${failure.message}'), (page2) { // 페이지별 데이터가 다른지 확인 if (page2.isNotEmpty) { final page1Ids = page1.map((c) => c.id).toSet(); final page2Ids = page2.map((c) => c.id).toSet(); assert(page1Ids.intersection(page2Ids).isEmpty, '페이지 간 데이터 중복'); } _log('페이징 테스트 성공'); }, ); }, ); } /// 검색 테스트 Future _testSearch() async { final searchTerm = '테스트회사_$testSessionId'; final result = await getCompaniesUseCase.call( page: 1, size: 10, search: searchTerm, ); result.fold( (failure) => throw TestException('검색 실패: ${failure.message}'), (companies) { for (final company in companies) { assert( company.name.contains(searchTerm) || company.businessNumber.contains(searchTerm), '검색 결과가 검색어와 매치되지 않음' ); } _log('검색 테스트 성공: ${companies.length}개 검색됨'); }, ); } /// 회사 생성 테스트 Future _testCreateCompany() async { final companyData = dataGenerator.generateCompany( name: '신규테스트회사_$testSessionId', businessNumber: '${DateTime.now().millisecondsSinceEpoch}', ); final result = await createCompanyUseCase.call(companyData); result.fold( (failure) => throw TestException('회사 생성 실패: ${failure.message}'), (company) { assert(company.id != null, '생성된 회사 ID가 null'); assert(company.name == companyData.name, '회사명 불일치'); createdCompanyIds.add(company.id!); _log('회사 생성 성공: ${company.name} (ID: ${company.id})'); }, ); } /// 중복 체크 테스트 Future _testDuplicateCheck() async { final businessNumber = '9999999999'; final company1 = dataGenerator.generateCompany( name: '중복테스트1_$testSessionId', businessNumber: businessNumber, ); // 첫 번째 생성 (성공해야 함) final result1 = await createCompanyUseCase.call(company1); int? firstId; result1.fold( (failure) => throw TestException('첫 번째 회사 생성 실패: ${failure.message}'), (company) { firstId = company.id; createdCompanyIds.add(company.id!); }, ); // 두 번째 생성 (실패해야 함) final company2 = dataGenerator.generateCompany( name: '중복테스트2_$testSessionId', businessNumber: businessNumber, ); final result2 = await createCompanyUseCase.call(company2); result2.fold( (failure) => _log('중복 체크 성공: ${failure.message}'), (company) { createdCompanyIds.add(company.id!); throw TestException('중복 사업자번호로 생성이 허용됨'); }, ); } /// 필수 필드 검증 테스트 Future _testRequiredFieldValidation() async { // 필수 필드가 누락된 회사 데이터 final invalidCompany = Company( name: '', // 빈 이름 businessNumber: '', companyType: CompanyType.customer, address: '', phone: '', isActive: true, ); final result = await createCompanyUseCase.call(invalidCompany); result.fold( (failure) => _log('필수 필드 검증 성공: ${failure.message}'), (company) { createdCompanyIds.add(company.id!); throw TestException('필수 필드 검증 실패 - 빈 값이 허용됨'); }, ); } /// 회사 수정 테스트 Future _testUpdateCompany() async { if (createdCompanyIds.isEmpty) { await _createTestCompanies(); } final companyId = createdCompanyIds.first; final updatedData = dataGenerator.generateCompany( name: '수정된회사_$testSessionId', businessNumber: '1111111111', ); final result = await updateCompanyUseCase.call( id: companyId, company: updatedData, ); result.fold( (failure) => throw TestException('회사 수정 실패: ${failure.message}'), (company) { assert(company.name == updatedData.name, '회사명 수정 실패'); _log('회사 수정 성공: ${company.name}'); }, ); } /// 지점 추가 테스트 Future _testAddBranch() async { // 지점 관리는 별도 API가 필요할 수 있음 // 현재는 스킵하거나 mock 처리 _log('지점 추가 테스트 - 구현 예정'); } /// 지점 삭제 테스트 Future _testRemoveBranch() async { // 지점 관리는 별도 API가 필요할 수 있음 // 현재는 스킵하거나 mock 처리 _log('지점 삭제 테스트 - 구현 예정'); } /// 회사 삭제 테스트 Future _testDeleteCompany() async { // 삭제용 회사 생성 final companyData = dataGenerator.generateCompany( name: '삭제테스트_$testSessionId', businessNumber: '${DateTime.now().millisecondsSinceEpoch}', ); final createResult = await createCompanyUseCase.call(companyData); createResult.fold( (failure) => throw TestException('삭제 테스트용 회사 생성 실패: ${failure.message}'), (company) async { // 삭제 final deleteResult = await deleteCompanyUseCase.call(company.id!); deleteResult.fold( (failure) => throw TestException('회사 삭제 실패: ${failure.message}'), (_) => _log('회사 삭제 성공: ID ${company.id}'), ); }, ); } /// 연관 데이터가 있는 회사 삭제 테스트 Future _testDeleteWithRelatedData() async { // 연관 데이터 체크는 백엔드에서 처리되어야 함 // 현재는 기본 삭제 테스트로 대체 _log('연관 데이터 체크 테스트 - 백엔드 구현 필요'); } /// 상태 토글 테스트 Future _testToggleStatus() async { if (createdCompanyIds.isEmpty) { await _createTestCompanies(); } final companyId = createdCompanyIds.first; // 현재 상태를 비활성으로 변경 final result = await toggleCompanyStatusUseCase.call( id: companyId, isActive: false, ); result.fold( (failure) => throw TestException('상태 변경 실패: ${failure.message}'), (company) { assert(!company.isActive, '비활성화 실패'); _log('회사 비활성화 성공: ${company.name}'); }, ); // 다시 활성으로 변경 final result2 = await toggleCompanyStatusUseCase.call( id: companyId, isActive: true, ); result2.fold( (failure) => throw TestException('상태 변경 실패: ${failure.message}'), (company) { assert(company.isActive, '활성화 실패'); _log('회사 활성화 성공: ${company.name}'); }, ); } /// 비활성 회사 필터링 테스트 Future _testInactiveFilter() async { // 필터링은 프론트엔드에서 처리하거나 API 파라미터로 처리 // 현재 API가 지원하는지 확인 필요 _log('비활성 회사 필터링 테스트 - API 확인 필요'); } void _log(String message) { print('[CompanyScreenTest] $message'); } }