diff --git a/TEST_GUIDE.md b/TEST_GUIDE.md new file mode 100644 index 0000000..6da0bdf --- /dev/null +++ b/TEST_GUIDE.md @@ -0,0 +1,215 @@ +# Flutter 테스트 자동화 가이드 + +## 📋 개요 +이 문서는 Flutter 앱의 테스트 자동화를 위한 가이드입니다. 각 화면의 버튼 클릭, 서버 통신, 데이터 입력/수정/저장 등 모든 액션에 대한 테스트를 포함합니다. + +## 🏗️ 테스트 구조 + +``` +test/ +├── helpers/ # 테스트 헬퍼 클래스 +│ ├── test_helpers.dart # 기본 테스트 헬퍼 +│ ├── mock_data_helpers.dart # Mock 데이터 생성 헬퍼 +│ └── simple_mock_services.dart # Mock 서비스 설정 +├── unit/ # 단위 테스트 +│ └── controllers/ # 컨트롤러 테스트 +├── widget/ # Widget 테스트 +│ └── screens/ # 화면별 Widget 테스트 +└── integration/ # 통합 테스트 +``` + +## 🔧 테스트 환경 설정 + +### 1. 필요한 패키지 (pubspec.yaml) +```yaml +dev_dependencies: + flutter_test: + sdk: flutter + integration_test: + sdk: flutter + mockito: ^5.4.5 + build_runner: ^2.4.9 + golden_toolkit: ^0.15.0 + mocktail: ^1.0.3 + fake_async: ^1.3.1 + test: ^1.25.2 + coverage: ^1.7.2 + patrol: ^3.6.0 +``` + +### 2. Mock 클래스 생성 +```bash +flutter pub run build_runner build --delete-conflicting-outputs +``` + +## 📝 테스트 작성 예제 + +### 1. 컨트롤러 단위 테스트 +```dart +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/mockito.dart'; + +void main() { + late CompanyListController controller; + late MockMockDataService mockDataService; + late MockCompanyService mockCompanyService; + late GetIt getIt; + + setUp(() { + getIt = setupTestGetIt(); + mockDataService = MockMockDataService(); + mockCompanyService = MockCompanyService(); + + // GetIt에 서비스 등록 + getIt.registerSingleton(mockCompanyService); + + // Mock 설정 + SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService); + SimpleMockServiceHelpers.setupCompanyServiceMock(mockCompanyService); + + controller = CompanyListController(dataService: mockDataService); + }); + + tearDown(() { + controller.dispose(); + getIt.reset(); + }); + + group('CompanyListController 테스트', () { + test('검색 기능 테스트', () async { + await controller.updateSearchKeyword('테스트'); + expect(controller.searchKeyword, '테스트'); + }); + }); +} +``` + +### 2. Widget 테스트 +```dart +testWidgets('화면 렌더링 테스트', (WidgetTester tester) async { + await pumpTestWidget( + tester, + const CompanyListRedesign(), + ); + + await pumpAndSettleWithTimeout(tester); + + expect(find.text('회사 관리'), findsOneWidget); + expect(find.byType(TextField), findsOneWidget); +}); +``` + +### 3. Mock 데이터 생성 +```dart +// 회사 목록 생성 +final companies = MockDataHelpers.createMockCompanyList(count: 5); + +// 특정 회사 생성 +final company = MockDataHelpers.createMockCompany( + id: 1, + name: '테스트 회사', +); +``` + +## 🎯 테스트 전략 + +### 1. 단위 테스트 (Unit Tests) +- **대상**: 컨트롤러, 서비스, 유틸리티 함수 +- **목적**: 개별 컴포넌트의 로직 검증 +- **실행**: `flutter test test/unit/` + +### 2. Widget 테스트 +- **대상**: 개별 화면 및 위젯 +- **목적**: UI 렌더링 및 상호작용 검증 +- **실행**: `flutter test test/widget/` + +### 3. 통합 테스트 (Integration Tests) +- **대상**: 전체 사용자 플로우 +- **목적**: 실제 앱 동작 검증 +- **실행**: `flutter test integration_test/` + +## 🔍 주요 테스트 케이스 + +### 화면별 필수 테스트 +1. **초기 렌더링**: 화면이 올바르게 표시되는지 확인 +2. **데이터 로딩**: API 호출 및 데이터 표시 확인 +3. **사용자 입력**: 텍스트 입력, 버튼 클릭 등 +4. **네비게이션**: 화면 전환 동작 확인 +5. **에러 처리**: 네트워크 오류, 유효성 검사 실패 등 +6. **상태 관리**: 로딩, 성공, 실패 상태 전환 + +## 🚨 주의사항 + +### 1. 모델 불일치 문제 +- 실제 모델과 Mock 모델의 구조가 일치하는지 확인 +- 특히 `Address`, `Company`, `User` 모델 주의 + +### 2. 서비스 시그니처 +- Mock 서비스의 메서드 시그니처가 실제 서비스와 일치해야 함 +- 반환 타입 특히 주의 (예: `User` vs `AuthUser`) + +### 3. GetIt 설정 +- 테스트 전 반드시 GetIt 초기화 +- 테스트 후 반드시 GetIt reset + +## 📊 테스트 커버리지 + +### 커버리지 확인 +```bash +flutter test --coverage +genhtml coverage/lcov.info -o coverage/html +open coverage/html/index.html +``` + +### 목표 커버리지 +- 단위 테스트: 80% 이상 +- Widget 테스트: 70% 이상 +- 통합 테스트: 주요 사용자 시나리오 100% + +## 🔄 CI/CD 통합 + +### GitHub Actions 예제 +```yaml +name: Test +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: subosito/flutter-action@v2 + - run: flutter pub get + - run: flutter test + - run: flutter test --coverage +``` + +## 📚 추가 리소스 +- [Flutter Testing Documentation](https://flutter.dev/docs/testing) +- [Mockito Documentation](https://pub.dev/packages/mockito) +- [GetIt Documentation](https://pub.dev/packages/get_it) + +## 🔧 문제 해결 + +### Mock 클래스를 찾을 수 없을 때 +```bash +flutter pub run build_runner build --delete-conflicting-outputs +``` + +### 테스트가 타임아웃될 때 +`pumpAndSettleWithTimeout` 헬퍼 사용: +```dart +await pumpAndSettleWithTimeout(tester, timeout: Duration(seconds: 10)); +``` + +### GetIt 관련 오류 +```dart +setUp(() { + getIt = setupTestGetIt(); // 반드시 첫 번째로 실행 +}); + +tearDown(() { + getIt.reset(); // 반드시 실행 +}); +``` \ No newline at end of file diff --git a/TEST_PROGRESS.md b/TEST_PROGRESS.md new file mode 100644 index 0000000..aa5e86f --- /dev/null +++ b/TEST_PROGRESS.md @@ -0,0 +1,200 @@ +# Flutter 테스트 자동화 진행 상황 + +## 📅 작업 요약 +- **목표**: 각 화면의 버튼 클릭, 서버 통신, 데이터 입력/수정/저장 등 모든 액션에 대한 테스트 자동화 +- **진행 상황**: Phase 2 진행 중 (Widget 테스트 구현) + +## ✅ 완료된 작업 + +### 1. Phase 1: 프로젝트 분석 및 설정 (완료) +- ✅ 코드베이스 분석 및 프로젝트 구조 파악 +- ✅ 모든 화면(Screen/Page) 파일 식별 및 목록화 +- ✅ API 서비스 및 네트워크 통신 구조 분석 +- ✅ 테스트 패키지 설치 및 환경 설정 + +### 2. 테스트 인프라 구축 (완료) +- ✅ 테스트 디렉토리 구조 설계 +- ✅ 테스트 헬퍼 클래스 생성 + - `test_helpers.dart`: 기본 테스트 유틸리티 + - `mock_data_helpers.dart`: Mock 데이터 생성 + - `simple_mock_services.dart`: Mock 서비스 설정 +- ✅ Mock 클래스 생성 (build_runner 사용) + +### 3. 단위 테스트 구현 (진행 중) +#### CompanyListController 테스트 ✅ +- 검색 키워드 업데이트 +- 회사 선택/해제 +- 전체 선택/해제 +- 필터 적용 +- 회사 삭제 +- 에러 처리 + +#### EquipmentListController 테스트 ✅ +- 장비 선택/해제 +- 전체 선택 +- 상태 필터 변경 +- 장비 삭제 +- 선택된 장비 수 계산 +- 에러 처리 + +#### UserListController 테스트 ✅ +- 초기 상태 확인 +- 사용자 목록 로드 +- 검색 쿼리 설정 및 검색 +- 필터 설정 (회사, 권한, 활성 상태) +- 필터 초기화 +- 사용자 삭제 +- 사용자 상태 변경 +- 페이지네이션 (더 불러오기) +- Mock 모드 필터링 +- 지점명 조회 +- 에러 처리 + +### 4. 문서화 (완료) +- ✅ `TEST_GUIDE.md`: 테스트 작성 가이드 +- ✅ `TEST_PROGRESS.md`: 진행 상황 문서 (현재 문서) + +## 🔧 해결된 주요 이슈 + +### 1. 모델 불일치 문제 +- **문제**: Mock 데이터 모델과 실제 프로젝트 모델 구조 차이 +- **해결**: + - Address 모델: streetAddress → zipCode/region/detailAddress + - User 모델: companyId 필수 파라미터 추가 + - AuthUser vs User 타입 정리 + +### 2. 서비스 시그니처 불일치 +- **문제**: Mock 서비스 메서드와 실제 서비스 메서드 시그니처 차이 +- **해결**: + - CompanyService.getCompanies 파라미터 수정 + - EquipmentService 반환 타입 정리 + - Clean Architecture 패턴 제거 (Either → 직접 반환) + +### 3. Controller 메서드명 차이 +- **문제**: 테스트에서 사용한 메서드가 실제 컨트롤러에 없음 +- **해결**: + - toggleAllSelection → toggleSelectAll + - toggleEquipmentSelection → selectEquipment + +## 📋 남은 작업 + +### Phase 2: Widget 테스트 구현 (진행 중) +- [ ] 사용자 관리 화면 Widget 테스트 +- [ ] 라이선스 관리 화면 Widget 테스트 +- [ ] 창고 관리 화면 Widget 테스트 +- [ ] 대시보드 화면 Widget 테스트 + +### Phase 2: Integration 테스트 +- [ ] 로그인 플로우 테스트 +- [ ] 회사 등록/수정/삭제 플로우 +- [ ] 장비 입고/출고 플로우 +- [ ] 사용자 관리 플로우 + +### Phase 3: CI/CD 및 고급 기능 +- [ ] GitHub Actions 설정 +- [ ] 테스트 커버리지 리포트 +- [ ] E2E 테스트 (Patrol 사용) +- [ ] 성능 테스트 + +## 🛠️ 사용된 기술 스택 + +### 테스트 프레임워크 +```yaml +dev_dependencies: + flutter_test: + sdk: flutter + mockito: ^5.4.5 + build_runner: ^2.4.9 + get_it: ^7.7.0 +``` + +### 테스트 구조 +``` +test/ +├── helpers/ +│ ├── test_helpers.dart +│ ├── mock_data_helpers.dart +│ ├── simple_mock_services.dart +│ └── simple_mock_services.mocks.dart +├── unit/ +│ └── controllers/ +│ ├── company_list_controller_test.dart +│ ├── equipment_list_controller_test.dart +│ └── user_list_controller_test.dart +├── widget/ +│ └── screens/ +└── integration/ +``` + +## 💡 다음 단계 추천 + +1. **Widget 테스트 구현** + - UserListScreen Widget 테스트 + - CompanyListScreen Widget 테스트 + - EquipmentListScreen Widget 테스트 + +2. **Integration 테스트 시작** + - 주요 사용자 시나리오 정의 + - 화면 간 네비게이션 테스트 + +3. **API Mock 서버 구축** + - 실제 API 호출 테스트 + - 네트워크 에러 시나리오 + +## 📝 참고 사항 + +### GetIt 사용 시 주의점 +```dart +setUp(() { + getIt = setupTestGetIt(); // 반드시 첫 번째로 + // Mock 서비스 등록 +}); + +tearDown(() { + getIt.reset(); // 반드시 실행 +}); +``` + +### Mock 데이터 생성 +```dart +// Company 목록 +final companies = MockDataHelpers.createMockCompanyList(count: 5); + +// UnifiedEquipment 생성 +final equipment = MockDataHelpers.createMockUnifiedEquipment( + id: 1, + name: '노트북', + status: 'I', // 입고 상태 +); + +// User 모델 생성 +final user = MockDataHelpers.createMockUserModel( + id: 1, + name: '테스트 사용자', + role: 'S', // S: 관리자, M: 멤버 +); + +// User 목록 생성 +final users = MockDataHelpers.createMockUserModelList(count: 10); +``` + +### 테스트 실행 +```bash +# 모든 테스트 실행 +flutter test + +# 특정 파일 테스트 +flutter test test/unit/controllers/company_list_controller_test.dart + +# 커버리지 포함 +flutter test --coverage +``` + +## 🔗 관련 문서 +- [TEST_GUIDE.md](./TEST_GUIDE.md) - 테스트 작성 가이드 +- [CLAUDE.md](./CLAUDE.md) - 프로젝트 개발 규칙 + +--- + +이 문서는 지속적으로 업데이트됩니다. +마지막 업데이트: 2025-07-31 \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index ce278f0..0710065 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -150,6 +150,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.4" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" clock: dependency: transitive description: @@ -182,6 +190,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + coverage: + dependency: "direct dev" + description: + name: coverage + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + url: "https://pub.dev" + source: hosted + version: "1.15.0" crypto: dependency: transitive description: @@ -222,8 +238,24 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - fake_async: + dispose_scope: dependency: transitive + description: + name: dispose_scope + sha256: "48ec38ca2631c53c4f8fa96b294c801e55c335db5e3fb9f82cede150cfe5a2af" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" + url: "https://pub.dev" + source: hosted + version: "2.0.7" + fake_async: + dependency: "direct dev" description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" @@ -267,6 +299,11 @@ packages: url: "https://pub.dev" source: hosted version: "5.2.1" + flutter_driver: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" flutter_lints: dependency: "direct dev" description: @@ -370,6 +407,11 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + fuchsia_remote_debug_protocol: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" get_it: dependency: "direct main" description: @@ -386,6 +428,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + golden_toolkit: + dependency: "direct dev" + description: + name: golden_toolkit + sha256: "8f74adab33154fe7b731395782797021f97d2edc52f7bfb85ff4f1b5c4a215f0" + url: "https://pub.dev" + source: hosted + version: "0.15.0" google_fonts: dependency: "direct main" description: @@ -450,6 +500,11 @@ packages: url: "https://pub.dev" source: hosted version: "2.6.2" + integration_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" intl: dependency: "direct main" description: @@ -578,6 +633,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.4.5" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" + url: "https://pub.dev" + source: hosted + version: "1.0.4" nested: dependency: transitive description: @@ -586,6 +649,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" package_config: dependency: transitive description: @@ -658,6 +729,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + patrol: + dependency: "direct dev" + description: + name: patrol + sha256: "8432f7d71f5d40788804f1a5662f082dc10c6c991ec1160c0cd6e4ecf1aed542" + url: "https://pub.dev" + source: hosted + version: "3.18.0" + patrol_finders: + dependency: transitive + description: + name: patrol_finders + sha256: "4a658d7d560de523f92deb3fa3326c78747ca0bf7e7f4b8788c012463138b628" + url: "https://pub.dev" + source: hosted + version: "2.9.0" + patrol_log: + dependency: transitive + description: + name: patrol_log + sha256: "9fed4143980df1e3bbcfa00d0b443c7d68f04f9132317b7698bbc37f8a5a58c5" + url: "https://pub.dev" + source: hosted + version: "0.5.0" pdf: dependency: "direct main" description: @@ -722,6 +817,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.14.2" + process: + dependency: transitive + description: + name: process + sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" + url: "https://pub.dev" + source: hosted + version: "5.0.3" protobuf: dependency: transitive description: @@ -794,6 +897,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" shelf_web_socket: dependency: transitive description: @@ -823,6 +942,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.5" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" source_span: dependency: transitive description: @@ -863,6 +998,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + sync_http: + dependency: transitive + description: + name: sync_http + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" + source: hosted + version: "0.3.1" term_glyph: dependency: transitive description: @@ -871,6 +1014,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + url: "https://pub.dev" + source: hosted + version: "1.25.15" test_api: dependency: transitive description: @@ -879,6 +1030,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + test_core: + dependency: transitive + description: + name: test_core + sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + url: "https://pub.dev" + source: hosted + version: "0.6.8" timing: dependency: transitive description: @@ -983,6 +1142,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + webdriver: + dependency: transitive + description: + name: webdriver + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" win32: dependency: transitive description: @@ -1017,4 +1192,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.8.0 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index 16f8909..e17e184 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,6 +45,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter flutter_lints: ^5.0.0 # 코드 생성 @@ -54,6 +56,16 @@ dev_dependencies: injectable_generator: ^2.4.1 freezed: ^2.4.6 mockito: ^5.4.5 + + # 테스트 도구 + golden_toolkit: ^0.15.0 + mocktail: ^1.0.3 + fake_async: ^1.3.1 + test: ^1.25.2 + coverage: ^1.7.2 + + # UI 테스트 + patrol: ^3.6.0 flutter: uses-material-design: true diff --git a/test/helpers/mock_data_helpers.dart b/test/helpers/mock_data_helpers.dart new file mode 100644 index 0000000..fcffc21 --- /dev/null +++ b/test/helpers/mock_data_helpers.dart @@ -0,0 +1,614 @@ +import 'package:superport/data/models/auth/auth_user.dart'; +import 'package:superport/data/models/auth/login_response.dart'; +import 'package:superport/data/models/company/company_dto.dart' as company_dto; +// import 'package:superport/data/models/company/branch_dto.dart'; +import 'package:superport/data/models/equipment/equipment_response.dart'; +import 'package:superport/data/models/equipment/equipment_io_response.dart'; +import 'package:superport/data/models/user/user_dto.dart'; +import 'package:superport/data/models/license/license_dto.dart'; +import 'package:superport/data/models/warehouse/warehouse_dto.dart'; +import 'package:superport/data/models/common/paginated_response.dart'; +import 'package:superport/models/company_model.dart'; +import 'package:superport/models/address_model.dart'; +import 'package:superport/models/equipment_unified_model.dart'; +import 'package:superport/models/user_model.dart'; +import 'package:superport/models/warehouse_location_model.dart'; +import 'package:superport/models/license_model.dart'; + +/// 테스트용 Mock 데이터 생성 헬퍼 +class MockDataHelpers { + /// Mock 사용자 생성 + static AuthUser createMockUser({ + int id = 1, + String username = 'testuser', + String email = 'test@example.com', + String name = '테스트 사용자', + String role = 'USER', + }) { + return AuthUser( + id: id, + username: username, + email: email, + name: name, + role: role, + ); + } + + /// Mock 로그인 응답 생성 + static LoginResponse createMockLoginResponse({ + String accessToken = 'test_access_token', + String refreshToken = 'test_refresh_token', + String tokenType = 'Bearer', + int expiresIn = 3600, + AuthUser? user, + }) { + return LoginResponse( + accessToken: accessToken, + refreshToken: refreshToken, + tokenType: tokenType, + expiresIn: expiresIn, + user: user ?? createMockUser(), + ); + } + + /// Mock 회사 생성 + static Company createMockCompany({ + int id = 1, + String name = '테스트 회사', + String? contactName, + String? contactPosition, + String? contactPhone, + String? contactEmail, + String? addressStr, + String? remark, + List? companyTypes, + }) { + return Company( + id: id, + name: name, + address: Address( + zipCode: '06234', + region: addressStr ?? '서울특별시 강남구 테헤란로 123', + detailAddress: '테스트빌딩 5층', + ), + contactName: contactName ?? '홍길동', + contactPosition: contactPosition ?? '대표이사', + contactPhone: contactPhone ?? '02-1234-5678', + contactEmail: contactEmail ?? 'company@test.com', + companyTypes: companyTypes ?? [CompanyType.customer], + remark: remark, + ); + } + + /// Mock 지점 생성 + static Branch createMockBranch({ + int id = 1, + int companyId = 1, + String name = '본사', + String? addressStr, + String? contactName, + String? contactPosition, + String? contactPhone, + String? contactEmail, + }) { + return Branch( + id: id, + companyId: companyId, + name: name, + address: Address( + zipCode: '06234', + region: addressStr ?? '서울특별시 강남구 테헤란로 123', + detailAddress: '테스트빌딩 5층', + ), + contactName: contactName ?? '김지점', + contactPosition: contactPosition ?? '지점장', + contactPhone: contactPhone ?? '02-1234-5678', + contactEmail: contactEmail ?? 'branch@test.com', + ); + } + + /// Mock 장비 생성 + static EquipmentResponse createMockEquipment({ + int id = 1, + String equipmentNumber = 'EQ001', + String? category1, + String? category2, + String? category3, + String manufacturer = '삼성전자', + String? modelName, + String? serialNumber, + String? barcode, + DateTime? purchaseDate, + double purchasePrice = 1000000, + String status = 'AVAILABLE', + int? currentCompanyId, + int? currentBranchId, + int? warehouseLocationId, + String? remark, + String? companyName, + String? branchName, + String? warehouseName, + }) { + return EquipmentResponse( + id: id, + equipmentNumber: equipmentNumber, + category1: category1 ?? '전자기기', + category2: category2, + category3: category3, + manufacturer: manufacturer, + modelName: modelName ?? 'TEST-MODEL-001', + serialNumber: serialNumber ?? 'SN123456789', + barcode: barcode, + purchaseDate: purchaseDate ?? DateTime.now().subtract(const Duration(days: 30)), + purchasePrice: purchasePrice, + status: status, + currentCompanyId: currentCompanyId, + currentBranchId: currentBranchId, + warehouseLocationId: warehouseLocationId, + remark: remark, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + companyName: companyName, + branchName: branchName, + warehouseName: warehouseName, + ); + } + + /// Mock 사용자 DTO 생성 + static UserDto createMockUserDto({ + int id = 1, + String username = 'testuser', + String email = 'test@example.com', + String name = '테스트 사용자', + String? phone, + String role = 'staff', + bool isActive = true, + int? companyId, + int? branchId, + }) { + return UserDto( + id: id, + username: username, + email: email, + name: name, + phone: phone ?? '010-1234-5678', + role: role, + isActive: isActive, + companyId: companyId ?? 1, + branchId: branchId ?? 1, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); + } + + /// Mock 라이선스 생성 + static LicenseDto createMockLicense({ + int id = 1, + String licenseKey = 'TEST-LICENSE-KEY-12345', + String? productName, + String? vendor, + String? licenseType, + int? userCount, + DateTime? purchaseDate, + DateTime? expiryDate, + double? purchasePrice, + int? companyId, + int? branchId, + int? assignedUserId, + String? remark, + bool isActive = true, + }) { + return LicenseDto( + id: id, + licenseKey: licenseKey, + productName: productName ?? '테스트 라이선스', + vendor: vendor ?? '테스트 벤더', + licenseType: licenseType ?? 'SOFTWARE', + userCount: userCount ?? 10, + purchaseDate: purchaseDate ?? DateTime.now().subtract(const Duration(days: 365)), + expiryDate: expiryDate ?? DateTime.now().add(const Duration(days: 365)), + purchasePrice: purchasePrice ?? 500000, + companyId: companyId, + branchId: branchId, + assignedUserId: assignedUserId, + remark: remark, + isActive: isActive, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); + } + + /// Mock 창고 위치 생성 (WarehouseDto가 없으므로 Map 반환) + static Map createMockWarehouse({ + int id = 1, + String code = 'WH001', + String name = '메인 창고', + String type = 'WAREHOUSE', + String? location, + int? capacity, + int? currentOccupancy, + String? manager, + String? contactNumber, + bool isActive = true, + String? notes, + }) { + return { + 'id': id, + 'code': code, + 'name': name, + 'type': type, + 'location': location ?? '서울시 강서구 물류단지', + 'capacity': capacity ?? 1000, + 'currentOccupancy': currentOccupancy ?? 250, + 'manager': manager ?? '김창고', + 'contactNumber': contactNumber ?? '02-9876-5432', + 'isActive': isActive, + 'notes': notes, + 'createdAt': DateTime.now().toIso8601String(), + 'updatedAt': DateTime.now().toIso8601String(), + }; + } + + /// Mock 페이지네이션 응답 생성 + static PaginatedResponse createMockPaginatedResponse({ + required List data, + int page = 1, + int size = 20, + int? totalElements, + int? totalPages, + }) { + final total = totalElements ?? data.length; + final pages = totalPages ?? ((total + size - 1) ~/ size); + + return PaginatedResponse( + items: data, + page: page, + size: size, + totalElements: total, + totalPages: pages, + first: page == 1, + last: page >= pages, + ); + } + + /// Mock 장비 목록 생성 + static List createMockEquipmentList({int count = 10}) { + return List.generate( + count, + (index) => createMockEquipment( + id: index + 1, + equipmentNumber: 'EQ${(index + 1).toString().padLeft(3, '0')}', + category1: '전자기기', + modelName: '테스트 장비 ${index + 1}', + serialNumber: 'SN${DateTime.now().millisecondsSinceEpoch}${index}', + status: index % 3 == 0 ? 'IN_USE' : 'AVAILABLE', + ), + ); + } + + /// Mock UnifiedEquipment 생성 + static UnifiedEquipment createMockUnifiedEquipment({ + int id = 1, + int equipmentId = 1, + String manufacturer = '삼성전자', + String name = '노트북', + String category = 'IT장비', + String subCategory = '컴퓨터', + String subSubCategory = '노트북', + String? serialNumber, + int quantity = 1, + String status = 'I', // I: 입고, O: 출고, R: 대여 + DateTime? date, + String? notes, + }) { + final equipment = Equipment( + id: equipmentId, + manufacturer: manufacturer, + name: name, + category: category, + subCategory: subCategory, + subSubCategory: subSubCategory, + serialNumber: serialNumber ?? 'SN${DateTime.now().millisecondsSinceEpoch}', + quantity: quantity, + inDate: date ?? DateTime.now(), + ); + + return UnifiedEquipment( + id: id, + equipment: equipment, + date: date ?? DateTime.now(), + status: status, + notes: notes, + ); + } + + /// Mock UnifiedEquipment 목록 생성 + static List createMockUnifiedEquipmentList({int count = 10}) { + return List.generate( + count, + (index) => createMockUnifiedEquipment( + id: index + 1, + equipmentId: index + 1, + name: '테스트 장비 ${index + 1}', + status: index % 3 == 0 ? 'O' : 'I', // 일부는 출고, 대부분은 입고 상태 + serialNumber: 'SN${DateTime.now().millisecondsSinceEpoch}${index}', + ), + ); + } + + /// Mock 회사 목록 생성 + static List createMockCompanyList({int count = 10}) { + return List.generate( + count, + (index) => createMockCompany( + id: index + 1, + name: '테스트 회사 ${index + 1}', + contactName: '담당자 ${index + 1}', + contactPosition: '대리', + contactPhone: '02-${1000 + index}-${5678 + index}', + ), + ); + } + + /// Mock 사용자 목록 생성 + static List createMockUserList({int count = 10}) { + return List.generate( + count, + (index) => createMockUserDto( + id: index + 1, + username: 'user${index + 1}', + email: 'user${index + 1}@test.com', + name: '사용자 ${index + 1}', + phone: '010-${1000 + index}-${1000 + index}', + ), + ); + } + + /// Mock 라이선스 목록 생성 + static List createMockLicenseList({int count = 10}) { + return List.generate( + count, + (index) => createMockLicense( + id: index + 1, + productName: '라이선스 ${index + 1}', + licenseKey: 'KEY-${DateTime.now().millisecondsSinceEpoch}-${index}', + licenseType: index % 2 == 0 ? 'SOFTWARE' : 'HARDWARE', + isActive: index % 4 != 0, + ), + ); + } + + /// Mock 창고 목록 생성 + static List createMockWarehouseList({int count = 5}) { + return List.generate( + count, + (index) => createMockWarehouse( + id: index + 1, + code: 'WH${(index + 1).toString().padLeft(3, '0')}', + name: '창고 ${index + 1}', + type: index % 2 == 0 ? 'WAREHOUSE' : 'STORAGE', + currentOccupancy: (index + 1) * 50, + ), + ); + } + + /// Mock Equipment 모델 생성 + static Equipment createMockEquipmentModel({ + int? id, + String manufacturer = '삼성전자', + String name = '노트북', + String category = 'IT장비', + String subCategory = '컴퓨터', + String subSubCategory = '노트북', + String? serialNumber, + String? barcode, + int quantity = 1, + DateTime? inDate, + String? remark, + String? warrantyLicense, + DateTime? warrantyStartDate, + DateTime? warrantyEndDate, + }) { + return Equipment( + id: id ?? 1, + manufacturer: manufacturer, + name: name, + category: category, + subCategory: subCategory, + subSubCategory: subSubCategory, + serialNumber: serialNumber ?? 'SN${DateTime.now().millisecondsSinceEpoch}', + barcode: barcode, + quantity: quantity, + inDate: inDate ?? DateTime.now(), + remark: remark, + warrantyLicense: warrantyLicense, + warrantyStartDate: warrantyStartDate, + warrantyEndDate: warrantyEndDate, + ); + } + + /// Mock Equipment 모델 목록 생성 + static List createMockEquipmentModelList({int count = 10}) { + return List.generate( + count, + (index) => createMockEquipmentModel( + id: index + 1, + name: '테스트 장비 ${index + 1}', + category: index % 2 == 0 ? 'IT장비' : '사무용품', + serialNumber: 'SN${DateTime.now().millisecondsSinceEpoch}${index}', + ), + ); + } + + /// Mock User 모델 생성 (UserDto가 아닌 User 모델) + static User createMockUserModel({ + int? id, + int companyId = 1, + int? branchId, + String name = '테스트 사용자', + String role = 'M', + String? position, + String? email, + List>? phoneNumbers, + String? username, + bool isActive = true, + DateTime? createdAt, + DateTime? updatedAt, + }) { + return User( + id: id ?? 1, + companyId: companyId, + branchId: branchId ?? 1, + name: name, + role: role, + position: position ?? '대리', + email: email ?? 'user@test.com', + phoneNumbers: phoneNumbers ?? [{'type': '기본', 'number': '010-1234-5678'}], + username: username ?? 'testuser', + isActive: isActive, + createdAt: createdAt ?? DateTime.now(), + updatedAt: updatedAt ?? DateTime.now(), + ); + } + + /// Mock User 모델 목록 생성 + static List createMockUserModelList({int count = 10}) { + return List.generate( + count, + (index) => createMockUserModel( + id: index + 1, + name: '사용자 ${index + 1}', + username: 'user${index + 1}', + email: 'user${index + 1}@test.com', + role: index % 3 == 0 ? 'S' : 'M', + isActive: index % 5 != 0, + phoneNumbers: [{'type': '기본', 'number': '010-${1000 + index}-${1000 + index}'}], + ), + ); + } + + /// Mock 장비 입출고 응답 생성 + static EquipmentIoResponse createMockEquipmentIoResponse({ + bool success = true, + String? message, + int transactionId = 1, + int equipmentId = 1, + int quantity = 1, + String transactionType = 'IN', + DateTime? transactionDate, + }) { + return EquipmentIoResponse( + success: success, + message: message ?? (success ? '장비 처리가 완료되었습니다.' : '장비 처리에 실패했습니다.'), + transactionId: transactionId, + equipmentId: equipmentId, + quantity: quantity, + transactionType: transactionType, + transactionDate: transactionDate ?? DateTime.now(), + ); + } + + /// Mock 창고 용량 정보 생성 + static WarehouseCapacityInfo createMockWarehouseCapacityInfo({ + int warehouseId = 1, + int totalCapacity = 1000, + int usedCapacity = 250, + int? availableCapacity, + double? usagePercentage, + int equipmentCount = 50, + }) { + final available = availableCapacity ?? (totalCapacity - usedCapacity); + final percentage = usagePercentage ?? (usedCapacity / totalCapacity * 100); + + return WarehouseCapacityInfo( + warehouseId: warehouseId, + totalCapacity: totalCapacity, + usedCapacity: usedCapacity, + availableCapacity: available, + usagePercentage: percentage, + equipmentCount: equipmentCount, + ); + } + + /// Mock WarehouseLocation 생성 + static WarehouseLocation createMockWarehouseLocation({ + int id = 1, + String name = '메인 창고', + String? addressStr, + String? remark, + }) { + return WarehouseLocation( + id: id, + name: name, + address: Address( + zipCode: '12345', + region: addressStr ?? '서울시 강서구 물류단지', + detailAddress: '창고동 A동', + ), + remark: remark, + ); + } + + /// Mock License 모델 생성 + static License createMockLicenseModel({ + int? id, + String licenseKey = 'TEST-LICENSE-KEY', + String productName = '테스트 라이선스', + String? vendor, + String? licenseType, + int? userCount, + DateTime? purchaseDate, + DateTime? expiryDate, + double? purchasePrice, + int? companyId, + int? branchId, + int? assignedUserId, + String? remark, + bool isActive = true, + }) { + return License( + id: id ?? 1, + licenseKey: licenseKey, + productName: productName, + vendor: vendor ?? '테스트 벤더', + licenseType: licenseType ?? 'SOFTWARE', + userCount: userCount ?? 10, + purchaseDate: purchaseDate ?? DateTime.now().subtract(const Duration(days: 365)), + expiryDate: expiryDate ?? DateTime.now().add(const Duration(days: 365)), + purchasePrice: purchasePrice ?? 500000, + companyId: companyId ?? 1, + branchId: branchId, + assignedUserId: assignedUserId, + remark: remark, + isActive: isActive, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); + } + + /// Mock License 모델 목록 생성 + static List createMockLicenseModelList({int count = 10}) { + return List.generate( + count, + (index) => createMockLicenseModel( + id: index + 1, + productName: '라이선스 ${index + 1}', + licenseKey: 'KEY-${DateTime.now().millisecondsSinceEpoch}-${index}', + licenseType: index % 2 == 0 ? 'SOFTWARE' : 'HARDWARE', + isActive: index % 4 != 0, + ), + ); + } + + /// Mock WarehouseLocation 목록 생성 + static List createMockWarehouseLocationList({int count = 5}) { + return List.generate( + count, + (index) => createMockWarehouseLocation( + id: index + 1, + name: '창고 ${index + 1}', + addressStr: '서울시 ${index % 3 == 0 ? "강서구" : index % 3 == 1 ? "강남구" : "송파구"} 물류단지 ${index + 1}', + ), + ); + } +} \ No newline at end of file diff --git a/test/helpers/mock_services.dart b/test/helpers/mock_services.dart new file mode 100644 index 0000000..f88ab71 --- /dev/null +++ b/test/helpers/mock_services.dart @@ -0,0 +1,525 @@ +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:dartz/dartz.dart'; +import 'package:superport/services/auth_service.dart'; +import 'package:superport/services/company_service.dart'; +import 'package:superport/services/equipment_service.dart'; +import 'package:superport/services/user_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/services/mock_data_service.dart'; +import 'package:superport/core/errors/failures.dart'; + +import 'mock_data_helpers.dart'; +import 'mock_services.mocks.dart'; + +// Mockito 어노테이션으로 Mock 클래스 생성 +@GenerateMocks([ + AuthService, + CompanyService, + EquipmentService, + UserService, + LicenseService, + WarehouseService, + DashboardService, + MockDataService, +]) +void main() {} + +/// Mock 서비스 설정 헬퍼 +class MockServiceHelpers { + /// AuthService Mock 설정 + static void setupAuthServiceMock( + MockAuthService mockAuthService, { + bool isLoggedIn = false, + bool loginSuccess = true, + bool logoutSuccess = true, + }) { + // isLoggedIn + when(mockAuthService.isLoggedIn()) + .thenAnswer((_) async => isLoggedIn); + + // login + if (loginSuccess) { + when(mockAuthService.login(any)) + .thenAnswer((_) async => Right(MockDataHelpers.createMockLoginResponse())); + } else { + when(mockAuthService.login(any)) + .thenAnswer((_) async => Left(AuthenticationFailure( + message: '이메일 또는 비밀번호가 올바르지 않습니다.', + ))); + } + + // logout + if (logoutSuccess) { + when(mockAuthService.logout()) + .thenAnswer((_) async => const Right(null)); + } else { + when(mockAuthService.logout()) + .thenAnswer((_) async => Left(ServerFailure( + message: '로그아웃 중 오류가 발생했습니다.', + ))); + } + + // getCurrentUser + when(mockAuthService.getCurrentUser()) + .thenAnswer((_) async => isLoggedIn ? MockDataHelpers.createMockUser() : null); + + // getAccessToken + when(mockAuthService.getAccessToken()) + .thenAnswer((_) async => isLoggedIn ? 'test_access_token' : null); + + // authStateChanges + when(mockAuthService.authStateChanges) + .thenAnswer((_) => Stream.value(isLoggedIn)); + } + + /// CompanyService Mock 설정 + static void setupCompanyServiceMock( + MockCompanyService mockCompanyService, { + bool getCompaniesSuccess = true, + bool createCompanySuccess = true, + bool updateCompanySuccess = true, + bool deleteCompanySuccess = true, + int companyCount = 10, + }) { + // getCompanies + if (getCompaniesSuccess) { + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => + MockDataHelpers.createMockCompanyList(count: companyCount), + ); + } else { + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenThrow( + ServerFailure(message: '회사 목록을 불러오는 중 오류가 발생했습니다.'), + ); + } + + // createCompany + if (createCompanySuccess) { + when(mockCompanyService.createCompany(any)) + .thenAnswer((_) async => MockDataHelpers.createMockCompany()); + } else { + when(mockCompanyService.createCompany(any)) + .thenThrow(ServerFailure( + message: '회사 등록 중 오류가 발생했습니다.', + )); + } + + // updateCompany + if (updateCompanySuccess) { + when(mockCompanyService.updateCompany(any, any)) + .thenAnswer((_) async => MockDataHelpers.createMockCompany()); + } else { + when(mockCompanyService.updateCompany(any, any)) + .thenThrow(ServerFailure( + message: '회사 정보 수정 중 오류가 발생했습니다.', + )); + } + + // deleteCompany + if (deleteCompanySuccess) { + when(mockCompanyService.deleteCompany(any)) + .thenAnswer((_) async {}); + } else { + when(mockCompanyService.deleteCompany(any)) + .thenThrow(ServerFailure( + message: '회사 삭제 중 오류가 발생했습니다.', + )); + } + + // getCompanyDetail + when(mockCompanyService.getCompanyDetail(any)) + .thenAnswer((_) async => MockDataHelpers.createMockCompany()); + + // checkDuplicateCompany + when(mockCompanyService.checkDuplicateCompany(any)) + .thenAnswer((_) async => false); + + // getCompanyNames + when(mockCompanyService.getCompanyNames()) + .thenAnswer((_) async => [{'id': 1, 'name': '테스트 회사 1'}, {'id': 2, 'name': '테스트 회사 2'}, {'id': 3, 'name': '테스트 회사 3'}]); + } + + /// EquipmentService Mock 설정 + static void setupEquipmentServiceMock( + MockEquipmentService mockEquipmentService, { + bool getEquipmentsSuccess = true, + bool createEquipmentSuccess = true, + bool equipmentInSuccess = true, + bool equipmentOutSuccess = true, + int equipmentCount = 10, + }) { + // getEquipments + if (getEquipmentsSuccess) { + when(mockEquipmentService.getEquipments( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + status: anyNamed('status'), + companyId: anyNamed('companyId'), + warehouseLocationId: anyNamed('warehouseLocationId'), + )).thenAnswer((_) async => + MockDataHelpers.createMockEquipmentModelList(count: equipmentCount), + ); + } else { + when(mockEquipmentService.getEquipments( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + status: anyNamed('status'), + companyId: anyNamed('companyId'), + warehouseLocationId: anyNamed('warehouseLocationId'), + )).thenThrow(ServerFailure( + message: '장비 목록을 불러오는 중 오류가 발생했습니다.', + )); + } + + // createEquipment + if (createEquipmentSuccess) { + when(mockEquipmentService.createEquipment(any)) + .thenAnswer((_) async => MockDataHelpers.createMockEquipmentModel()); + } else { + when(mockEquipmentService.createEquipment(any)) + .thenThrow(ServerFailure( + message: '장비 등록 중 오류가 발생했습니다.', + )); + } + + // equipmentIn + if (equipmentInSuccess) { + when(mockEquipmentService.equipmentIn( + equipmentId: anyNamed('equipmentId'), + quantity: anyNamed('quantity'), + warehouseLocationId: anyNamed('warehouseLocationId'), + notes: anyNamed('notes'), + )).thenAnswer((_) async => MockDataHelpers.createMockEquipmentIoResponse()); + } else { + when(mockEquipmentService.equipmentIn( + equipmentId: anyNamed('equipmentId'), + quantity: anyNamed('quantity'), + warehouseLocationId: anyNamed('warehouseLocationId'), + notes: anyNamed('notes'), + )).thenThrow(ServerFailure( + message: '장비 입고 처리 중 오류가 발생했습니다.', + )); + } + + // equipmentOut + if (equipmentOutSuccess) { + when(mockEquipmentService.equipmentOut( + equipmentId: anyNamed('equipmentId'), + quantity: anyNamed('quantity'), + companyId: anyNamed('companyId'), + branchId: anyNamed('branchId'), + notes: anyNamed('notes'), + )).thenAnswer((_) async => MockDataHelpers.createMockEquipmentIoResponse()); + } else { + when(mockEquipmentService.equipmentOut( + equipmentId: anyNamed('equipmentId'), + quantity: anyNamed('quantity'), + companyId: anyNamed('companyId'), + branchId: anyNamed('branchId'), + notes: anyNamed('notes'), + )).thenThrow(ServerFailure( + message: '장비 출고 처리 중 오류가 발생했습니다.', + )); + } + + // getEquipmentDetail + when(mockEquipmentService.getEquipmentDetail(any)) + .thenAnswer((_) async => MockDataHelpers.createMockEquipmentModel()); + + // getEquipment (alias for getEquipmentDetail) + when(mockEquipmentService.getEquipment(any)) + .thenAnswer((_) async => MockDataHelpers.createMockEquipmentModel()); + + // getEquipmentHistory + when(mockEquipmentService.getEquipmentHistory(any, page: anyNamed('page'), perPage: anyNamed('perPage'))) + .thenAnswer((_) async => []); + + // Additional mock setups can be added here if needed + } + + /// UserService Mock 설정 + static void setupUserServiceMock( + MockUserService mockUserService, { + bool getUsersSuccess = true, + bool createUserSuccess = true, + bool updateUserSuccess = true, + bool deleteUserSuccess = true, + int userCount = 10, + }) { + // getUsers + if (getUsersSuccess) { + when(mockUserService.getUsers( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + isActive: anyNamed('isActive'), + companyId: anyNamed('companyId'), + role: anyNamed('role'), + )).thenAnswer((_) async => + MockDataHelpers.createMockUserModelList(count: userCount), + ); + } else { + when(mockUserService.getUsers( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + isActive: anyNamed('isActive'), + companyId: anyNamed('companyId'), + role: anyNamed('role'), + )).thenThrow(Exception('사용자 목록 조회 실패')); + } + + // createUser + if (createUserSuccess) { + when(mockUserService.createUser( + username: anyNamed('username'), + email: anyNamed('email'), + password: anyNamed('password'), + name: anyNamed('name'), + role: anyNamed('role'), + companyId: anyNamed('companyId'), + branchId: anyNamed('branchId'), + phone: anyNamed('phone'), + position: anyNamed('position'), + )).thenAnswer((_) async => MockDataHelpers.createMockUserModel()); + } else { + when(mockUserService.createUser( + username: anyNamed('username'), + email: anyNamed('email'), + password: anyNamed('password'), + name: anyNamed('name'), + role: anyNamed('role'), + companyId: anyNamed('companyId'), + branchId: anyNamed('branchId'), + phone: anyNamed('phone'), + position: anyNamed('position'), + )).thenThrow(Exception('사용자 생성 실패')); + } + + // updateUser + if (updateUserSuccess) { + when(mockUserService.updateUser( + any, + name: anyNamed('name'), + email: anyNamed('email'), + password: anyNamed('password'), + phone: anyNamed('phone'), + companyId: anyNamed('companyId'), + branchId: anyNamed('branchId'), + role: anyNamed('role'), + position: anyNamed('position'), + )).thenAnswer((_) async => MockDataHelpers.createMockUserModel()); + } else { + when(mockUserService.updateUser( + any, + name: anyNamed('name'), + email: anyNamed('email'), + password: anyNamed('password'), + phone: anyNamed('phone'), + companyId: anyNamed('companyId'), + branchId: anyNamed('branchId'), + role: anyNamed('role'), + position: anyNamed('position'), + )).thenThrow(Exception('사용자 수정 실패')); + } + + // deleteUser + if (deleteUserSuccess) { + when(mockUserService.deleteUser(any)) + .thenAnswer((_) async {}); + } else { + when(mockUserService.deleteUser(any)) + .thenThrow(Exception('사용자 삭제 실패')); + } + + // getUser + when(mockUserService.getUser(any)) + .thenAnswer((_) async => MockDataHelpers.createMockUserModel()); + + // changePassword + when(mockUserService.changePassword(any, any, any)) + .thenAnswer((_) async {}); + + // changeUserStatus + when(mockUserService.changeUserStatus(any, any)) + .thenAnswer((_) async => MockDataHelpers.createMockUserModel()); + + // checkDuplicateUsername + when(mockUserService.checkDuplicateUsername(any)) + .thenAnswer((_) async => false); + } + + /// LicenseService Mock 설정 + static void setupLicenseServiceMock( + MockLicenseService mockLicenseService, { + bool getLicensesSuccess = true, + bool createLicenseSuccess = true, + bool updateLicenseSuccess = true, + bool deleteLicenseSuccess = true, + int licenseCount = 10, + }) { + // getLicenses + if (getLicensesSuccess) { + when(mockLicenseService.getLicenses( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + isActive: anyNamed('isActive'), + companyId: anyNamed('companyId'), + assignedUserId: anyNamed('assignedUserId'), + licenseType: anyNamed('licenseType'), + )).thenAnswer((_) async => + MockDataHelpers.createMockLicenseModelList(count: licenseCount), + ); + } else { + when(mockLicenseService.getLicenses( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + isActive: anyNamed('isActive'), + companyId: anyNamed('companyId'), + assignedUserId: anyNamed('assignedUserId'), + licenseType: anyNamed('licenseType'), + )).thenThrow(ServerFailure( + message: '라이선스 목록을 불러오는 데 실패했습니다', + )); + } + + // createLicense + if (createLicenseSuccess) { + when(mockLicenseService.createLicense(any)) + .thenAnswer((_) async => MockDataHelpers.createMockLicenseModel()); + } else { + when(mockLicenseService.createLicense(any)) + .thenThrow(ServerFailure( + message: '라이선스 생성에 실패했습니다', + )); + } + + // updateLicense + if (updateLicenseSuccess) { + when(mockLicenseService.updateLicense(any)) + .thenAnswer((_) async => MockDataHelpers.createMockLicenseModel()); + } else { + when(mockLicenseService.updateLicense(any)) + .thenThrow(ServerFailure( + message: '라이선스 수정에 실패했습니다', + )); + } + + // deleteLicense + if (deleteLicenseSuccess) { + when(mockLicenseService.deleteLicense(any)) + .thenAnswer((_) async {}); + } else { + when(mockLicenseService.deleteLicense(any)) + .thenThrow(ServerFailure( + message: '라이선스 삭제에 실패했습니다', + )); + } + + // getLicenseById + when(mockLicenseService.getLicenseById(any)) + .thenAnswer((_) async => MockDataHelpers.createMockLicenseModel()); + + // getExpiringLicenses + when(mockLicenseService.getExpiringLicenses( + days: anyNamed('days'), + page: anyNamed('page'), + perPage: anyNamed('perPage'), + )).thenAnswer((_) async => MockDataHelpers.createMockLicenseModelList(count: 3)); + + // assignLicense + when(mockLicenseService.assignLicense(any, any)) + .thenAnswer((_) async => MockDataHelpers.createMockLicenseModel()); + + // unassignLicense + when(mockLicenseService.unassignLicense(any)) + .thenAnswer((_) async => MockDataHelpers.createMockLicenseModel()); + } + + /// WarehouseService Mock 설정 + static void setupWarehouseServiceMock( + MockWarehouseService mockWarehouseService, { + bool getWarehousesSuccess = true, + bool createWarehouseSuccess = true, + bool updateWarehouseSuccess = true, + bool deleteWarehouseSuccess = true, + int warehouseCount = 5, + }) { + // getWarehouses + if (getWarehousesSuccess) { + when(mockWarehouseService.getWarehouseLocations( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => + MockDataHelpers.createMockWarehouseLocationList(count: warehouseCount), + ); + } else { + when(mockWarehouseService.getWarehouseLocations( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + isActive: anyNamed('isActive'), + )).thenThrow(ServerFailure( + message: '창고 위치 목록을 불러오는 데 실패했습니다', + )); + } + + // createWarehouse + if (createWarehouseSuccess) { + when(mockWarehouseService.createWarehouseLocation(any)) + .thenAnswer((_) async => MockDataHelpers.createMockWarehouseLocation()); + } else { + when(mockWarehouseService.createWarehouseLocation(any)) + .thenThrow(ServerFailure( + message: '창고 위치 생성에 실패했습니다', + )); + } + + // updateWarehouse + if (updateWarehouseSuccess) { + when(mockWarehouseService.updateWarehouseLocation(any)) + .thenAnswer((_) async => MockDataHelpers.createMockWarehouseLocation()); + } else { + when(mockWarehouseService.updateWarehouseLocation(any)) + .thenThrow(ServerFailure( + message: '창고 위치 수정에 실패했습니다', + )); + } + + // deleteWarehouse + if (deleteWarehouseSuccess) { + when(mockWarehouseService.deleteWarehouseLocation(any)) + .thenAnswer((_) async {}); + } else { + when(mockWarehouseService.deleteWarehouseLocation(any)) + .thenThrow(ServerFailure( + message: '창고 위치 삭제에 실패했습니다', + )); + } + + // getWarehouseLocationById + when(mockWarehouseService.getWarehouseLocationById(any)) + .thenAnswer((_) async => MockDataHelpers.createMockWarehouseLocation()); + + // getWarehouseEquipment + when(mockWarehouseService.getWarehouseEquipment( + any, + page: anyNamed('page'), + perPage: anyNamed('perPage'), + )).thenAnswer((_) async => []); + + // getWarehouseCapacity + when(mockWarehouseService.getWarehouseCapacity(any)) + .thenAnswer((_) async => MockDataHelpers.createMockWarehouseCapacityInfo()); + } +} \ No newline at end of file diff --git a/test/helpers/mock_services.mocks.dart b/test/helpers/mock_services.mocks.dart new file mode 100644 index 0000000..a9f56ce --- /dev/null +++ b/test/helpers/mock_services.mocks.dart @@ -0,0 +1,1896 @@ +// Mocks generated by Mockito 5.4.5 from annotations +// in superport/test/helpers/mock_services.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i12; + +import 'package:dartz/dartz.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:superport/core/errors/failures.dart' as _i13; +import 'package:superport/data/models/auth/auth_user.dart' as _i17; +import 'package:superport/data/models/auth/login_request.dart' as _i15; +import 'package:superport/data/models/auth/login_response.dart' as _i14; +import 'package:superport/data/models/auth/token_response.dart' as _i16; +import 'package:superport/data/models/company/company_list_dto.dart' as _i19; +import 'package:superport/data/models/dashboard/equipment_status_distribution.dart' + as _i28; +import 'package:superport/data/models/dashboard/expiring_license.dart' as _i29; +import 'package:superport/data/models/dashboard/overview_stats.dart' as _i26; +import 'package:superport/data/models/dashboard/recent_activity.dart' as _i27; +import 'package:superport/data/models/equipment/equipment_history_dto.dart' + as _i5; +import 'package:superport/data/models/equipment/equipment_io_response.dart' + as _i6; +import 'package:superport/data/models/equipment/equipment_list_dto.dart' + as _i21; +import 'package:superport/data/models/warehouse/warehouse_dto.dart' as _i10; +import 'package:superport/models/company_model.dart' as _i3; +import 'package:superport/models/equipment_unified_model.dart' as _i4; +import 'package:superport/models/license_model.dart' as _i8; +import 'package:superport/models/user_model.dart' as _i7; +import 'package:superport/models/warehouse_location_model.dart' as _i9; +import 'package:superport/services/auth_service.dart' as _i11; +import 'package:superport/services/company_service.dart' as _i18; +import 'package:superport/services/dashboard_service.dart' as _i25; +import 'package:superport/services/equipment_service.dart' as _i20; +import 'package:superport/services/license_service.dart' as _i23; +import 'package:superport/services/mock_data_service.dart' as _i30; +import 'package:superport/services/user_service.dart' as _i22; +import 'package:superport/services/warehouse_service.dart' as _i24; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeEither_0 extends _i1.SmartFake implements _i2.Either { + _FakeEither_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCompany_1 extends _i1.SmartFake implements _i3.Company { + _FakeCompany_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBranch_2 extends _i1.SmartFake implements _i3.Branch { + _FakeBranch_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEquipment_3 extends _i1.SmartFake implements _i4.Equipment { + _FakeEquipment_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEquipmentHistoryDto_4 extends _i1.SmartFake + implements _i5.EquipmentHistoryDto { + _FakeEquipmentHistoryDto_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEquipmentIoResponse_5 extends _i1.SmartFake + implements _i6.EquipmentIoResponse { + _FakeEquipmentIoResponse_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeUser_6 extends _i1.SmartFake implements _i7.User { + _FakeUser_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeLicense_7 extends _i1.SmartFake implements _i8.License { + _FakeLicense_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWarehouseLocation_8 extends _i1.SmartFake + implements _i9.WarehouseLocation { + _FakeWarehouseLocation_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeWarehouseCapacityInfo_9 extends _i1.SmartFake + implements _i10.WarehouseCapacityInfo { + _FakeWarehouseCapacityInfo_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AuthService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAuthService extends _i1.Mock implements _i11.AuthService { + MockAuthService() { + _i1.throwOnMissingStub(this); + } + + @override + _i12.Stream get authStateChanges => (super.noSuchMethod( + Invocation.getter(#authStateChanges), + returnValue: _i12.Stream.empty(), + ) as _i12.Stream); + + @override + _i12.Future<_i2.Either<_i13.Failure, _i14.LoginResponse>> login( + _i15.LoginRequest? request) => + (super.noSuchMethod( + Invocation.method( + #login, + [request], + ), + returnValue: + _i12.Future<_i2.Either<_i13.Failure, _i14.LoginResponse>>.value( + _FakeEither_0<_i13.Failure, _i14.LoginResponse>( + this, + Invocation.method( + #login, + [request], + ), + )), + ) as _i12.Future<_i2.Either<_i13.Failure, _i14.LoginResponse>>); + + @override + _i12.Future<_i2.Either<_i13.Failure, void>> logout() => (super.noSuchMethod( + Invocation.method( + #logout, + [], + ), + returnValue: _i12.Future<_i2.Either<_i13.Failure, void>>.value( + _FakeEither_0<_i13.Failure, void>( + this, + Invocation.method( + #logout, + [], + ), + )), + ) as _i12.Future<_i2.Either<_i13.Failure, void>>); + + @override + _i12.Future<_i2.Either<_i13.Failure, _i16.TokenResponse>> refreshToken() => + (super.noSuchMethod( + Invocation.method( + #refreshToken, + [], + ), + returnValue: + _i12.Future<_i2.Either<_i13.Failure, _i16.TokenResponse>>.value( + _FakeEither_0<_i13.Failure, _i16.TokenResponse>( + this, + Invocation.method( + #refreshToken, + [], + ), + )), + ) as _i12.Future<_i2.Either<_i13.Failure, _i16.TokenResponse>>); + + @override + _i12.Future isLoggedIn() => (super.noSuchMethod( + Invocation.method( + #isLoggedIn, + [], + ), + returnValue: _i12.Future.value(false), + ) as _i12.Future); + + @override + _i12.Future<_i17.AuthUser?> getCurrentUser() => (super.noSuchMethod( + Invocation.method( + #getCurrentUser, + [], + ), + returnValue: _i12.Future<_i17.AuthUser?>.value(), + ) as _i12.Future<_i17.AuthUser?>); + + @override + _i12.Future getAccessToken() => (super.noSuchMethod( + Invocation.method( + #getAccessToken, + [], + ), + returnValue: _i12.Future.value(), + ) as _i12.Future); + + @override + _i12.Future getRefreshToken() => (super.noSuchMethod( + Invocation.method( + #getRefreshToken, + [], + ), + returnValue: _i12.Future.value(), + ) as _i12.Future); + + @override + _i12.Future clearSession() => (super.noSuchMethod( + Invocation.method( + #clearSession, + [], + ), + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); +} + +/// A class which mocks [CompanyService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCompanyService extends _i1.Mock implements _i18.CompanyService { + MockCompanyService() { + _i1.throwOnMissingStub(this); + } + + @override + _i12.Future> getCompanies({ + int? page = 1, + int? perPage = 20, + String? search, + bool? isActive, + }) => + (super.noSuchMethod( + Invocation.method( + #getCompanies, + [], + { + #page: page, + #perPage: perPage, + #search: search, + #isActive: isActive, + }, + ), + returnValue: _i12.Future>.value(<_i3.Company>[]), + ) as _i12.Future>); + + @override + _i12.Future<_i3.Company> createCompany(_i3.Company? company) => + (super.noSuchMethod( + Invocation.method( + #createCompany, + [company], + ), + returnValue: _i12.Future<_i3.Company>.value(_FakeCompany_1( + this, + Invocation.method( + #createCompany, + [company], + ), + )), + ) as _i12.Future<_i3.Company>); + + @override + _i12.Future<_i3.Company> getCompanyDetail(int? id) => (super.noSuchMethod( + Invocation.method( + #getCompanyDetail, + [id], + ), + returnValue: _i12.Future<_i3.Company>.value(_FakeCompany_1( + this, + Invocation.method( + #getCompanyDetail, + [id], + ), + )), + ) as _i12.Future<_i3.Company>); + + @override + _i12.Future<_i3.Company> getCompanyWithBranches(int? id) => + (super.noSuchMethod( + Invocation.method( + #getCompanyWithBranches, + [id], + ), + returnValue: _i12.Future<_i3.Company>.value(_FakeCompany_1( + this, + Invocation.method( + #getCompanyWithBranches, + [id], + ), + )), + ) as _i12.Future<_i3.Company>); + + @override + _i12.Future<_i3.Company> updateCompany( + int? id, + _i3.Company? company, + ) => + (super.noSuchMethod( + Invocation.method( + #updateCompany, + [ + id, + company, + ], + ), + returnValue: _i12.Future<_i3.Company>.value(_FakeCompany_1( + this, + Invocation.method( + #updateCompany, + [ + id, + company, + ], + ), + )), + ) as _i12.Future<_i3.Company>); + + @override + _i12.Future deleteCompany(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteCompany, + [id], + ), + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); + + @override + _i12.Future>> getCompanyNames() => + (super.noSuchMethod( + Invocation.method( + #getCompanyNames, + [], + ), + returnValue: _i12.Future>>.value( + >[]), + ) as _i12.Future>>); + + @override + _i12.Future<_i3.Branch> createBranch( + int? companyId, + _i3.Branch? branch, + ) => + (super.noSuchMethod( + Invocation.method( + #createBranch, + [ + companyId, + branch, + ], + ), + returnValue: _i12.Future<_i3.Branch>.value(_FakeBranch_2( + this, + Invocation.method( + #createBranch, + [ + companyId, + branch, + ], + ), + )), + ) as _i12.Future<_i3.Branch>); + + @override + _i12.Future<_i3.Branch> getBranchDetail( + int? companyId, + int? branchId, + ) => + (super.noSuchMethod( + Invocation.method( + #getBranchDetail, + [ + companyId, + branchId, + ], + ), + returnValue: _i12.Future<_i3.Branch>.value(_FakeBranch_2( + this, + Invocation.method( + #getBranchDetail, + [ + companyId, + branchId, + ], + ), + )), + ) as _i12.Future<_i3.Branch>); + + @override + _i12.Future<_i3.Branch> updateBranch( + int? companyId, + int? branchId, + _i3.Branch? branch, + ) => + (super.noSuchMethod( + Invocation.method( + #updateBranch, + [ + companyId, + branchId, + branch, + ], + ), + returnValue: _i12.Future<_i3.Branch>.value(_FakeBranch_2( + this, + Invocation.method( + #updateBranch, + [ + companyId, + branchId, + branch, + ], + ), + )), + ) as _i12.Future<_i3.Branch>); + + @override + _i12.Future deleteBranch( + int? companyId, + int? branchId, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteBranch, + [ + companyId, + branchId, + ], + ), + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); + + @override + _i12.Future> getCompanyBranches(int? companyId) => + (super.noSuchMethod( + Invocation.method( + #getCompanyBranches, + [companyId], + ), + returnValue: _i12.Future>.value(<_i3.Branch>[]), + ) as _i12.Future>); + + @override + _i12.Future> getCompaniesWithBranches() => + (super.noSuchMethod( + Invocation.method( + #getCompaniesWithBranches, + [], + ), + returnValue: _i12.Future>.value( + <_i19.CompanyWithBranches>[]), + ) as _i12.Future>); + + @override + _i12.Future checkDuplicateCompany(String? name) => (super.noSuchMethod( + Invocation.method( + #checkDuplicateCompany, + [name], + ), + returnValue: _i12.Future.value(false), + ) as _i12.Future); + + @override + _i12.Future> searchCompanies(String? query) => + (super.noSuchMethod( + Invocation.method( + #searchCompanies, + [query], + ), + returnValue: _i12.Future>.value(<_i3.Company>[]), + ) as _i12.Future>); + + @override + _i12.Future updateCompanyStatus( + int? id, + bool? isActive, + ) => + (super.noSuchMethod( + Invocation.method( + #updateCompanyStatus, + [ + id, + isActive, + ], + ), + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); +} + +/// A class which mocks [EquipmentService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockEquipmentService extends _i1.Mock implements _i20.EquipmentService { + MockEquipmentService() { + _i1.throwOnMissingStub(this); + } + + @override + _i12.Future> getEquipmentsWithStatus({ + int? page = 1, + int? perPage = 20, + String? status, + int? companyId, + int? warehouseLocationId, + }) => + (super.noSuchMethod( + Invocation.method( + #getEquipmentsWithStatus, + [], + { + #page: page, + #perPage: perPage, + #status: status, + #companyId: companyId, + #warehouseLocationId: warehouseLocationId, + }, + ), + returnValue: _i12.Future>.value( + <_i21.EquipmentListDto>[]), + ) as _i12.Future>); + + @override + _i12.Future> getEquipments({ + int? page = 1, + int? perPage = 20, + String? status, + int? companyId, + int? warehouseLocationId, + }) => + (super.noSuchMethod( + Invocation.method( + #getEquipments, + [], + { + #page: page, + #perPage: perPage, + #status: status, + #companyId: companyId, + #warehouseLocationId: warehouseLocationId, + }, + ), + returnValue: _i12.Future>.value(<_i4.Equipment>[]), + ) as _i12.Future>); + + @override + _i12.Future<_i4.Equipment> createEquipment(_i4.Equipment? equipment) => + (super.noSuchMethod( + Invocation.method( + #createEquipment, + [equipment], + ), + returnValue: _i12.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #createEquipment, + [equipment], + ), + )), + ) as _i12.Future<_i4.Equipment>); + + @override + _i12.Future<_i4.Equipment> getEquipmentDetail(int? id) => (super.noSuchMethod( + Invocation.method( + #getEquipmentDetail, + [id], + ), + returnValue: _i12.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #getEquipmentDetail, + [id], + ), + )), + ) as _i12.Future<_i4.Equipment>); + + @override + _i12.Future<_i4.Equipment> getEquipment(int? id) => (super.noSuchMethod( + Invocation.method( + #getEquipment, + [id], + ), + returnValue: _i12.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #getEquipment, + [id], + ), + )), + ) as _i12.Future<_i4.Equipment>); + + @override + _i12.Future<_i4.Equipment> updateEquipment( + int? id, + _i4.Equipment? equipment, + ) => + (super.noSuchMethod( + Invocation.method( + #updateEquipment, + [ + id, + equipment, + ], + ), + returnValue: _i12.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #updateEquipment, + [ + id, + equipment, + ], + ), + )), + ) as _i12.Future<_i4.Equipment>); + + @override + _i12.Future deleteEquipment(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteEquipment, + [id], + ), + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); + + @override + _i12.Future<_i4.Equipment> changeEquipmentStatus( + int? id, + String? status, + String? reason, + ) => + (super.noSuchMethod( + Invocation.method( + #changeEquipmentStatus, + [ + id, + status, + reason, + ], + ), + returnValue: _i12.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #changeEquipmentStatus, + [ + id, + status, + reason, + ], + ), + )), + ) as _i12.Future<_i4.Equipment>); + + @override + _i12.Future<_i5.EquipmentHistoryDto> addEquipmentHistory( + int? equipmentId, + String? type, + int? quantity, + String? remarks, + ) => + (super.noSuchMethod( + Invocation.method( + #addEquipmentHistory, + [ + equipmentId, + type, + quantity, + remarks, + ], + ), + returnValue: _i12.Future<_i5.EquipmentHistoryDto>.value( + _FakeEquipmentHistoryDto_4( + this, + Invocation.method( + #addEquipmentHistory, + [ + equipmentId, + type, + quantity, + remarks, + ], + ), + )), + ) as _i12.Future<_i5.EquipmentHistoryDto>); + + @override + _i12.Future> getEquipmentHistory( + int? equipmentId, { + int? page = 1, + int? perPage = 20, + }) => + (super.noSuchMethod( + Invocation.method( + #getEquipmentHistory, + [equipmentId], + { + #page: page, + #perPage: perPage, + }, + ), + returnValue: _i12.Future>.value( + <_i5.EquipmentHistoryDto>[]), + ) as _i12.Future>); + + @override + _i12.Future<_i6.EquipmentIoResponse> equipmentIn({ + required int? equipmentId, + required int? quantity, + int? warehouseLocationId, + String? notes, + }) => + (super.noSuchMethod( + Invocation.method( + #equipmentIn, + [], + { + #equipmentId: equipmentId, + #quantity: quantity, + #warehouseLocationId: warehouseLocationId, + #notes: notes, + }, + ), + returnValue: _i12.Future<_i6.EquipmentIoResponse>.value( + _FakeEquipmentIoResponse_5( + this, + Invocation.method( + #equipmentIn, + [], + { + #equipmentId: equipmentId, + #quantity: quantity, + #warehouseLocationId: warehouseLocationId, + #notes: notes, + }, + ), + )), + ) as _i12.Future<_i6.EquipmentIoResponse>); + + @override + _i12.Future<_i6.EquipmentIoResponse> equipmentOut({ + required int? equipmentId, + required int? quantity, + required int? companyId, + int? branchId, + String? notes, + }) => + (super.noSuchMethod( + Invocation.method( + #equipmentOut, + [], + { + #equipmentId: equipmentId, + #quantity: quantity, + #companyId: companyId, + #branchId: branchId, + #notes: notes, + }, + ), + returnValue: _i12.Future<_i6.EquipmentIoResponse>.value( + _FakeEquipmentIoResponse_5( + this, + Invocation.method( + #equipmentOut, + [], + { + #equipmentId: equipmentId, + #quantity: quantity, + #companyId: companyId, + #branchId: branchId, + #notes: notes, + }, + ), + )), + ) as _i12.Future<_i6.EquipmentIoResponse>); +} + +/// A class which mocks [UserService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUserService extends _i1.Mock implements _i22.UserService { + MockUserService() { + _i1.throwOnMissingStub(this); + } + + @override + _i12.Future> getUsers({ + int? page = 1, + int? perPage = 20, + bool? isActive, + int? companyId, + String? role, + }) => + (super.noSuchMethod( + Invocation.method( + #getUsers, + [], + { + #page: page, + #perPage: perPage, + #isActive: isActive, + #companyId: companyId, + #role: role, + }, + ), + returnValue: _i12.Future>.value(<_i7.User>[]), + ) as _i12.Future>); + + @override + _i12.Future<_i7.User> getUser(int? id) => (super.noSuchMethod( + Invocation.method( + #getUser, + [id], + ), + returnValue: _i12.Future<_i7.User>.value(_FakeUser_6( + this, + Invocation.method( + #getUser, + [id], + ), + )), + ) as _i12.Future<_i7.User>); + + @override + _i12.Future<_i7.User> createUser({ + required String? username, + required String? email, + required String? password, + required String? name, + required String? role, + required int? companyId, + int? branchId, + String? phone, + String? position, + }) => + (super.noSuchMethod( + Invocation.method( + #createUser, + [], + { + #username: username, + #email: email, + #password: password, + #name: name, + #role: role, + #companyId: companyId, + #branchId: branchId, + #phone: phone, + #position: position, + }, + ), + returnValue: _i12.Future<_i7.User>.value(_FakeUser_6( + this, + Invocation.method( + #createUser, + [], + { + #username: username, + #email: email, + #password: password, + #name: name, + #role: role, + #companyId: companyId, + #branchId: branchId, + #phone: phone, + #position: position, + }, + ), + )), + ) as _i12.Future<_i7.User>); + + @override + _i12.Future<_i7.User> updateUser( + int? id, { + String? name, + String? email, + String? password, + String? phone, + int? companyId, + int? branchId, + String? role, + String? position, + }) => + (super.noSuchMethod( + Invocation.method( + #updateUser, + [id], + { + #name: name, + #email: email, + #password: password, + #phone: phone, + #companyId: companyId, + #branchId: branchId, + #role: role, + #position: position, + }, + ), + returnValue: _i12.Future<_i7.User>.value(_FakeUser_6( + this, + Invocation.method( + #updateUser, + [id], + { + #name: name, + #email: email, + #password: password, + #phone: phone, + #companyId: companyId, + #branchId: branchId, + #role: role, + #position: position, + }, + ), + )), + ) as _i12.Future<_i7.User>); + + @override + _i12.Future deleteUser(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteUser, + [id], + ), + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); + + @override + _i12.Future<_i7.User> changeUserStatus( + int? id, + bool? isActive, + ) => + (super.noSuchMethod( + Invocation.method( + #changeUserStatus, + [ + id, + isActive, + ], + ), + returnValue: _i12.Future<_i7.User>.value(_FakeUser_6( + this, + Invocation.method( + #changeUserStatus, + [ + id, + isActive, + ], + ), + )), + ) as _i12.Future<_i7.User>); + + @override + _i12.Future changePassword( + int? id, + String? currentPassword, + String? newPassword, + ) => + (super.noSuchMethod( + Invocation.method( + #changePassword, + [ + id, + currentPassword, + newPassword, + ], + ), + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); + + @override + _i12.Future checkDuplicateUsername(String? username) => + (super.noSuchMethod( + Invocation.method( + #checkDuplicateUsername, + [username], + ), + returnValue: _i12.Future.value(false), + ) as _i12.Future); + + @override + _i12.Future> searchUsers({ + required String? query, + int? companyId, + String? status, + String? permissionLevel, + int? page = 1, + int? perPage = 20, + }) => + (super.noSuchMethod( + Invocation.method( + #searchUsers, + [], + { + #query: query, + #companyId: companyId, + #status: status, + #permissionLevel: permissionLevel, + #page: page, + #perPage: perPage, + }, + ), + returnValue: _i12.Future>.value(<_i7.User>[]), + ) as _i12.Future>); + + @override + String? getPhoneForApi(List>? phoneNumbers) => + (super.noSuchMethod(Invocation.method( + #getPhoneForApi, + [phoneNumbers], + )) as String?); +} + +/// A class which mocks [LicenseService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockLicenseService extends _i1.Mock implements _i23.LicenseService { + MockLicenseService() { + _i1.throwOnMissingStub(this); + } + + @override + _i12.Future> getLicenses({ + int? page = 1, + int? perPage = 20, + bool? isActive, + int? companyId, + int? assignedUserId, + String? licenseType, + }) => + (super.noSuchMethod( + Invocation.method( + #getLicenses, + [], + { + #page: page, + #perPage: perPage, + #isActive: isActive, + #companyId: companyId, + #assignedUserId: assignedUserId, + #licenseType: licenseType, + }, + ), + returnValue: _i12.Future>.value(<_i8.License>[]), + ) as _i12.Future>); + + @override + _i12.Future<_i8.License> getLicenseById(int? id) => (super.noSuchMethod( + Invocation.method( + #getLicenseById, + [id], + ), + returnValue: _i12.Future<_i8.License>.value(_FakeLicense_7( + this, + Invocation.method( + #getLicenseById, + [id], + ), + )), + ) as _i12.Future<_i8.License>); + + @override + _i12.Future<_i8.License> createLicense(_i8.License? license) => + (super.noSuchMethod( + Invocation.method( + #createLicense, + [license], + ), + returnValue: _i12.Future<_i8.License>.value(_FakeLicense_7( + this, + Invocation.method( + #createLicense, + [license], + ), + )), + ) as _i12.Future<_i8.License>); + + @override + _i12.Future<_i8.License> updateLicense(_i8.License? license) => + (super.noSuchMethod( + Invocation.method( + #updateLicense, + [license], + ), + returnValue: _i12.Future<_i8.License>.value(_FakeLicense_7( + this, + Invocation.method( + #updateLicense, + [license], + ), + )), + ) as _i12.Future<_i8.License>); + + @override + _i12.Future deleteLicense(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteLicense, + [id], + ), + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); + + @override + _i12.Future<_i8.License> assignLicense( + int? licenseId, + int? userId, + ) => + (super.noSuchMethod( + Invocation.method( + #assignLicense, + [ + licenseId, + userId, + ], + ), + returnValue: _i12.Future<_i8.License>.value(_FakeLicense_7( + this, + Invocation.method( + #assignLicense, + [ + licenseId, + userId, + ], + ), + )), + ) as _i12.Future<_i8.License>); + + @override + _i12.Future<_i8.License> unassignLicense(int? licenseId) => + (super.noSuchMethod( + Invocation.method( + #unassignLicense, + [licenseId], + ), + returnValue: _i12.Future<_i8.License>.value(_FakeLicense_7( + this, + Invocation.method( + #unassignLicense, + [licenseId], + ), + )), + ) as _i12.Future<_i8.License>); + + @override + _i12.Future> getExpiringLicenses({ + int? days = 30, + int? page = 1, + int? perPage = 20, + }) => + (super.noSuchMethod( + Invocation.method( + #getExpiringLicenses, + [], + { + #days: days, + #page: page, + #perPage: perPage, + }, + ), + returnValue: _i12.Future>.value(<_i8.License>[]), + ) as _i12.Future>); + + @override + _i12.Future getTotalLicenses({ + bool? isActive, + int? companyId, + int? assignedUserId, + String? licenseType, + }) => + (super.noSuchMethod( + Invocation.method( + #getTotalLicenses, + [], + { + #isActive: isActive, + #companyId: companyId, + #assignedUserId: assignedUserId, + #licenseType: licenseType, + }, + ), + returnValue: _i12.Future.value(0), + ) as _i12.Future); +} + +/// A class which mocks [WarehouseService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockWarehouseService extends _i1.Mock implements _i24.WarehouseService { + MockWarehouseService() { + _i1.throwOnMissingStub(this); + } + + @override + _i12.Future> getWarehouseLocations({ + int? page = 1, + int? perPage = 20, + bool? isActive, + }) => + (super.noSuchMethod( + Invocation.method( + #getWarehouseLocations, + [], + { + #page: page, + #perPage: perPage, + #isActive: isActive, + }, + ), + returnValue: _i12.Future>.value( + <_i9.WarehouseLocation>[]), + ) as _i12.Future>); + + @override + _i12.Future<_i9.WarehouseLocation> getWarehouseLocationById(int? id) => + (super.noSuchMethod( + Invocation.method( + #getWarehouseLocationById, + [id], + ), + returnValue: + _i12.Future<_i9.WarehouseLocation>.value(_FakeWarehouseLocation_8( + this, + Invocation.method( + #getWarehouseLocationById, + [id], + ), + )), + ) as _i12.Future<_i9.WarehouseLocation>); + + @override + _i12.Future<_i9.WarehouseLocation> createWarehouseLocation( + _i9.WarehouseLocation? location) => + (super.noSuchMethod( + Invocation.method( + #createWarehouseLocation, + [location], + ), + returnValue: + _i12.Future<_i9.WarehouseLocation>.value(_FakeWarehouseLocation_8( + this, + Invocation.method( + #createWarehouseLocation, + [location], + ), + )), + ) as _i12.Future<_i9.WarehouseLocation>); + + @override + _i12.Future<_i9.WarehouseLocation> updateWarehouseLocation( + _i9.WarehouseLocation? location) => + (super.noSuchMethod( + Invocation.method( + #updateWarehouseLocation, + [location], + ), + returnValue: + _i12.Future<_i9.WarehouseLocation>.value(_FakeWarehouseLocation_8( + this, + Invocation.method( + #updateWarehouseLocation, + [location], + ), + )), + ) as _i12.Future<_i9.WarehouseLocation>); + + @override + _i12.Future deleteWarehouseLocation(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteWarehouseLocation, + [id], + ), + returnValue: _i12.Future.value(), + returnValueForMissingStub: _i12.Future.value(), + ) as _i12.Future); + + @override + _i12.Future>> getWarehouseEquipment( + int? warehouseId, { + int? page = 1, + int? perPage = 20, + }) => + (super.noSuchMethod( + Invocation.method( + #getWarehouseEquipment, + [warehouseId], + { + #page: page, + #perPage: perPage, + }, + ), + returnValue: _i12.Future>>.value( + >[]), + ) as _i12.Future>>); + + @override + _i12.Future<_i10.WarehouseCapacityInfo> getWarehouseCapacity(int? id) => + (super.noSuchMethod( + Invocation.method( + #getWarehouseCapacity, + [id], + ), + returnValue: _i12.Future<_i10.WarehouseCapacityInfo>.value( + _FakeWarehouseCapacityInfo_9( + this, + Invocation.method( + #getWarehouseCapacity, + [id], + ), + )), + ) as _i12.Future<_i10.WarehouseCapacityInfo>); + + @override + _i12.Future> getInUseWarehouseLocations() => + (super.noSuchMethod( + Invocation.method( + #getInUseWarehouseLocations, + [], + ), + returnValue: _i12.Future>.value( + <_i9.WarehouseLocation>[]), + ) as _i12.Future>); + + @override + _i12.Future getTotalWarehouseLocations({bool? isActive}) => + (super.noSuchMethod( + Invocation.method( + #getTotalWarehouseLocations, + [], + {#isActive: isActive}, + ), + returnValue: _i12.Future.value(0), + ) as _i12.Future); +} + +/// A class which mocks [DashboardService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDashboardService extends _i1.Mock implements _i25.DashboardService { + MockDashboardService() { + _i1.throwOnMissingStub(this); + } + + @override + _i12.Future<_i2.Either<_i13.Failure, _i26.OverviewStats>> + getOverviewStats() => (super.noSuchMethod( + Invocation.method( + #getOverviewStats, + [], + ), + returnValue: + _i12.Future<_i2.Either<_i13.Failure, _i26.OverviewStats>>.value( + _FakeEither_0<_i13.Failure, _i26.OverviewStats>( + this, + Invocation.method( + #getOverviewStats, + [], + ), + )), + ) as _i12.Future<_i2.Either<_i13.Failure, _i26.OverviewStats>>); + + @override + _i12.Future<_i2.Either<_i13.Failure, List<_i27.RecentActivity>>> + getRecentActivities() => (super.noSuchMethod( + Invocation.method( + #getRecentActivities, + [], + ), + returnValue: _i12.Future< + _i2.Either<_i13.Failure, List<_i27.RecentActivity>>>.value( + _FakeEither_0<_i13.Failure, List<_i27.RecentActivity>>( + this, + Invocation.method( + #getRecentActivities, + [], + ), + )), + ) as _i12 + .Future<_i2.Either<_i13.Failure, List<_i27.RecentActivity>>>); + + @override + _i12.Future<_i2.Either<_i13.Failure, _i28.EquipmentStatusDistribution>> + getEquipmentStatusDistribution() => (super.noSuchMethod( + Invocation.method( + #getEquipmentStatusDistribution, + [], + ), + returnValue: _i12.Future< + _i2.Either<_i13.Failure, + _i28.EquipmentStatusDistribution>>.value( + _FakeEither_0<_i13.Failure, _i28.EquipmentStatusDistribution>( + this, + Invocation.method( + #getEquipmentStatusDistribution, + [], + ), + )), + ) as _i12.Future< + _i2.Either<_i13.Failure, _i28.EquipmentStatusDistribution>>); + + @override + _i12.Future< + _i2.Either<_i13.Failure, List<_i29.ExpiringLicense>>> getExpiringLicenses( + {int? days = 30}) => + (super.noSuchMethod( + Invocation.method( + #getExpiringLicenses, + [], + {#days: days}, + ), + returnValue: _i12 + .Future<_i2.Either<_i13.Failure, List<_i29.ExpiringLicense>>>.value( + _FakeEither_0<_i13.Failure, List<_i29.ExpiringLicense>>( + this, + Invocation.method( + #getExpiringLicenses, + [], + {#days: days}, + ), + )), + ) as _i12.Future<_i2.Either<_i13.Failure, List<_i29.ExpiringLicense>>>); +} + +/// A class which mocks [MockDataService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMockDataService extends _i1.Mock implements _i30.MockDataService { + MockMockDataService() { + _i1.throwOnMissingStub(this); + } + + @override + void initialize() => super.noSuchMethod( + Invocation.method( + #initialize, + [], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i4.EquipmentIn> getAllEquipmentIns() => (super.noSuchMethod( + Invocation.method( + #getAllEquipmentIns, + [], + ), + returnValue: <_i4.EquipmentIn>[], + ) as List<_i4.EquipmentIn>); + + @override + _i4.EquipmentIn? getEquipmentInById(int? id) => + (super.noSuchMethod(Invocation.method( + #getEquipmentInById, + [id], + )) as _i4.EquipmentIn?); + + @override + void addEquipmentIn(_i4.EquipmentIn? equipmentIn) => super.noSuchMethod( + Invocation.method( + #addEquipmentIn, + [equipmentIn], + ), + returnValueForMissingStub: null, + ); + + @override + void updateEquipmentIn(_i4.EquipmentIn? equipmentIn) => super.noSuchMethod( + Invocation.method( + #updateEquipmentIn, + [equipmentIn], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteEquipmentIn(int? id) => super.noSuchMethod( + Invocation.method( + #deleteEquipmentIn, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i4.EquipmentOut> getAllEquipmentOuts() => (super.noSuchMethod( + Invocation.method( + #getAllEquipmentOuts, + [], + ), + returnValue: <_i4.EquipmentOut>[], + ) as List<_i4.EquipmentOut>); + + @override + _i4.EquipmentOut? getEquipmentOutById(int? id) => + (super.noSuchMethod(Invocation.method( + #getEquipmentOutById, + [id], + )) as _i4.EquipmentOut?); + + @override + void changeEquipmentStatus( + int? equipmentInId, + _i4.EquipmentOut? equipmentOut, + ) => + super.noSuchMethod( + Invocation.method( + #changeEquipmentStatus, + [ + equipmentInId, + equipmentOut, + ], + ), + returnValueForMissingStub: null, + ); + + @override + void addEquipmentOut(_i4.EquipmentOut? equipmentOut) => super.noSuchMethod( + Invocation.method( + #addEquipmentOut, + [equipmentOut], + ), + returnValueForMissingStub: null, + ); + + @override + void updateEquipmentOut(_i4.EquipmentOut? equipmentOut) => super.noSuchMethod( + Invocation.method( + #updateEquipmentOut, + [equipmentOut], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteEquipmentOut(int? id) => super.noSuchMethod( + Invocation.method( + #deleteEquipmentOut, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List getAllManufacturers() => (super.noSuchMethod( + Invocation.method( + #getAllManufacturers, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllEquipmentNames() => (super.noSuchMethod( + Invocation.method( + #getAllEquipmentNames, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllCompanyNames() => (super.noSuchMethod( + Invocation.method( + #getAllCompanyNames, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllBranchNames() => (super.noSuchMethod( + Invocation.method( + #getAllBranchNames, + [], + ), + returnValue: [], + ) as List); + + @override + List<_i3.Company> getAllCompanies() => (super.noSuchMethod( + Invocation.method( + #getAllCompanies, + [], + ), + returnValue: <_i3.Company>[], + ) as List<_i3.Company>); + + @override + _i3.Company? getCompanyById(int? id) => (super.noSuchMethod(Invocation.method( + #getCompanyById, + [id], + )) as _i3.Company?); + + @override + _i3.Company? findCompanyByName(String? name) => + (super.noSuchMethod(Invocation.method( + #findCompanyByName, + [name], + )) as _i3.Company?); + + @override + void addCompany(_i3.Company? company) => super.noSuchMethod( + Invocation.method( + #addCompany, + [company], + ), + returnValueForMissingStub: null, + ); + + @override + void updateCompany(_i3.Company? company) => super.noSuchMethod( + Invocation.method( + #updateCompany, + [company], + ), + returnValueForMissingStub: null, + ); + + @override + void updateBranch( + int? companyId, + _i3.Branch? branch, + ) => + super.noSuchMethod( + Invocation.method( + #updateBranch, + [ + companyId, + branch, + ], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteCompany(int? id) => super.noSuchMethod( + Invocation.method( + #deleteCompany, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i7.User> getAllUsers() => (super.noSuchMethod( + Invocation.method( + #getAllUsers, + [], + ), + returnValue: <_i7.User>[], + ) as List<_i7.User>); + + @override + _i7.User? getUserById(int? id) => (super.noSuchMethod(Invocation.method( + #getUserById, + [id], + )) as _i7.User?); + + @override + void addUser(_i7.User? user) => super.noSuchMethod( + Invocation.method( + #addUser, + [user], + ), + returnValueForMissingStub: null, + ); + + @override + void updateUser(_i7.User? user) => super.noSuchMethod( + Invocation.method( + #updateUser, + [user], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteUser(int? id) => super.noSuchMethod( + Invocation.method( + #deleteUser, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i8.License> getAllLicenses() => (super.noSuchMethod( + Invocation.method( + #getAllLicenses, + [], + ), + returnValue: <_i8.License>[], + ) as List<_i8.License>); + + @override + _i8.License? getLicenseById(int? id) => (super.noSuchMethod(Invocation.method( + #getLicenseById, + [id], + )) as _i8.License?); + + @override + void addLicense(_i8.License? license) => super.noSuchMethod( + Invocation.method( + #addLicense, + [license], + ), + returnValueForMissingStub: null, + ); + + @override + void updateLicense(_i8.License? license) => super.noSuchMethod( + Invocation.method( + #updateLicense, + [license], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteLicense(int? id) => super.noSuchMethod( + Invocation.method( + #deleteLicense, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i4.UnifiedEquipment> getAllEquipments() => (super.noSuchMethod( + Invocation.method( + #getAllEquipments, + [], + ), + returnValue: <_i4.UnifiedEquipment>[], + ) as List<_i4.UnifiedEquipment>); + + @override + _i4.UnifiedEquipment? getEquipmentById( + int? id, + String? status, + ) => + (super.noSuchMethod(Invocation.method( + #getEquipmentById, + [ + id, + status, + ], + )) as _i4.UnifiedEquipment?); + + @override + void deleteEquipment( + int? id, + String? status, + ) => + super.noSuchMethod( + Invocation.method( + #deleteEquipment, + [ + id, + status, + ], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i9.WarehouseLocation> getAllWarehouseLocations() => (super.noSuchMethod( + Invocation.method( + #getAllWarehouseLocations, + [], + ), + returnValue: <_i9.WarehouseLocation>[], + ) as List<_i9.WarehouseLocation>); + + @override + _i9.WarehouseLocation? getWarehouseLocationById(int? id) => + (super.noSuchMethod(Invocation.method( + #getWarehouseLocationById, + [id], + )) as _i9.WarehouseLocation?); + + @override + void addWarehouseLocation(_i9.WarehouseLocation? location) => + super.noSuchMethod( + Invocation.method( + #addWarehouseLocation, + [location], + ), + returnValueForMissingStub: null, + ); + + @override + void updateWarehouseLocation(_i9.WarehouseLocation? location) => + super.noSuchMethod( + Invocation.method( + #updateWarehouseLocation, + [location], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteWarehouseLocation(int? id) => super.noSuchMethod( + Invocation.method( + #deleteWarehouseLocation, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List getAllCategories() => (super.noSuchMethod( + Invocation.method( + #getAllCategories, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllSubCategories() => (super.noSuchMethod( + Invocation.method( + #getAllSubCategories, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllSubSubCategories() => (super.noSuchMethod( + Invocation.method( + #getAllSubSubCategories, + [], + ), + returnValue: [], + ) as List); +} diff --git a/test/helpers/simple_mock_services.dart b/test/helpers/simple_mock_services.dart new file mode 100644 index 0000000..b2041cc --- /dev/null +++ b/test/helpers/simple_mock_services.dart @@ -0,0 +1,174 @@ +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:superport/services/auth_service.dart'; +import 'package:superport/services/company_service.dart'; +import 'package:superport/services/equipment_service.dart'; +import 'package:superport/services/mock_data_service.dart'; +import 'package:superport/services/user_service.dart'; +import 'package:superport/data/models/auth/auth_user.dart'; +import 'package:superport/models/user_model.dart'; + +import 'mock_data_helpers.dart'; +import 'simple_mock_services.mocks.dart'; + +// Mockito 어노테이션으로 Mock 클래스 생성 +@GenerateMocks([ + AuthService, + CompanyService, + MockDataService, + EquipmentService, + UserService, +]) +void main() {} + +/// 간단한 Mock 서비스 설정 헬퍼 +class SimpleMockServiceHelpers { + /// AuthService Mock 설정 + static void setupAuthServiceMock( + MockAuthService mockAuthService, { + bool isLoggedIn = false, + }) { + // isLoggedIn + when(mockAuthService.isLoggedIn()) + .thenAnswer((_) async => isLoggedIn); + + // getCurrentUser + when(mockAuthService.getCurrentUser()) + .thenAnswer((_) async => isLoggedIn ? AuthUser( + id: 1, + username: 'test_user', + name: '테스트 사용자', + email: 'test@example.com', + role: 'admin', + ) : null); + } + + /// CompanyService Mock 설정 + static void setupCompanyServiceMock( + MockCompanyService mockCompanyService, { + bool getCompaniesSuccess = true, + bool deleteCompanySuccess = true, + int companyCount = 10, + }) { + // getCompanies + if (getCompaniesSuccess) { + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => + MockDataHelpers.createMockCompanyList(count: companyCount), + ); + } else { + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenThrow( + Exception('회사 목록을 불러오는 중 오류가 발생했습니다.'), + ); + } + + // deleteCompany + if (deleteCompanySuccess) { + when(mockCompanyService.deleteCompany(any)) + .thenAnswer((_) async {}); + } else { + when(mockCompanyService.deleteCompany(any)) + .thenThrow( + Exception('회사 삭제 중 오류가 발생했습니다.'), + ); + } + } + + /// MockDataService Mock 설정 + static void setupMockDataServiceMock( + MockMockDataService mockDataService, { + int companyCount = 10, + int userCount = 10, + }) { + when(mockDataService.getAllCompanies()).thenReturn( + MockDataHelpers.createMockCompanyList(count: companyCount) + ); + + when(mockDataService.deleteCompany(any)).thenReturn(null); + + when(mockDataService.getAllUsers()).thenReturn( + MockDataHelpers.createMockUserModelList(count: userCount) + ); + + when(mockDataService.deleteUser(any)).thenReturn(null); + + when(mockDataService.getCompanyById(any)).thenAnswer((invocation) { + final id = invocation.positionalArguments[0] as int; + final companies = MockDataHelpers.createMockCompanyList(count: companyCount); + return companies.firstWhere( + (c) => c.id == id, + orElse: () => MockDataHelpers.createMockCompany(id: id), + ); + }); + } + + /// UserService Mock 설정 + static void setupUserServiceMock( + MockUserService mockUserService, { + bool getUsersSuccess = true, + bool deleteUserSuccess = true, + bool changeUserStatusSuccess = true, + int userCount = 10, + }) { + // getUsers + if (getUsersSuccess) { + when(mockUserService.getUsers( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + isActive: anyNamed('isActive'), + companyId: anyNamed('companyId'), + role: anyNamed('role'), + )).thenAnswer((_) async => + MockDataHelpers.createMockUserModelList(count: userCount), + ); + } else { + when(mockUserService.getUsers( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + isActive: anyNamed('isActive'), + companyId: anyNamed('companyId'), + role: anyNamed('role'), + )).thenThrow( + Exception('사용자 목록을 불러오는 중 오류가 발생했습니다.'), + ); + } + + // deleteUser + if (deleteUserSuccess) { + when(mockUserService.deleteUser(any)) + .thenAnswer((_) async {}); + } else { + when(mockUserService.deleteUser(any)) + .thenThrow( + Exception('사용자 삭제 중 오류가 발생했습니다.'), + ); + } + + // changeUserStatus + if (changeUserStatusSuccess) { + when(mockUserService.changeUserStatus(any, any)) + .thenAnswer((invocation) async { + final id = invocation.positionalArguments[0] as int; + final isActive = invocation.positionalArguments[1] as bool; + return MockDataHelpers.createMockUserModel( + id: id, + isActive: isActive, + ); + }); + } else { + when(mockUserService.changeUserStatus(any, any)) + .thenThrow( + Exception('사용자 상태 변경 중 오류가 발생했습니다.'), + ); + } + } +} \ No newline at end of file diff --git a/test/helpers/simple_mock_services.mocks.dart b/test/helpers/simple_mock_services.mocks.dart new file mode 100644 index 0000000..c11f5cf --- /dev/null +++ b/test/helpers/simple_mock_services.mocks.dart @@ -0,0 +1,1447 @@ +// Mocks generated by Mockito 5.4.5 from annotations +// in superport/test/helpers/simple_mock_services.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i9; + +import 'package:dartz/dartz.dart' as _i2; +import 'package:mockito/mockito.dart' as _i1; +import 'package:superport/core/errors/failures.dart' as _i10; +import 'package:superport/data/models/auth/auth_user.dart' as _i14; +import 'package:superport/data/models/auth/login_request.dart' as _i12; +import 'package:superport/data/models/auth/login_response.dart' as _i11; +import 'package:superport/data/models/auth/token_response.dart' as _i13; +import 'package:superport/data/models/company/company_list_dto.dart' as _i16; +import 'package:superport/data/models/equipment/equipment_history_dto.dart' + as _i5; +import 'package:superport/data/models/equipment/equipment_io_response.dart' + as _i6; +import 'package:superport/data/models/equipment/equipment_list_dto.dart' + as _i21; +import 'package:superport/models/company_model.dart' as _i3; +import 'package:superport/models/equipment_unified_model.dart' as _i4; +import 'package:superport/models/license_model.dart' as _i18; +import 'package:superport/models/user_model.dart' as _i7; +import 'package:superport/models/warehouse_location_model.dart' as _i19; +import 'package:superport/services/auth_service.dart' as _i8; +import 'package:superport/services/company_service.dart' as _i15; +import 'package:superport/services/equipment_service.dart' as _i20; +import 'package:superport/services/mock_data_service.dart' as _i17; +import 'package:superport/services/user_service.dart' as _i22; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: deprecated_member_use +// ignore_for_file: deprecated_member_use_from_same_package +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: must_be_immutable +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeEither_0 extends _i1.SmartFake implements _i2.Either { + _FakeEither_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCompany_1 extends _i1.SmartFake implements _i3.Company { + _FakeCompany_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBranch_2 extends _i1.SmartFake implements _i3.Branch { + _FakeBranch_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEquipment_3 extends _i1.SmartFake implements _i4.Equipment { + _FakeEquipment_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEquipmentHistoryDto_4 extends _i1.SmartFake + implements _i5.EquipmentHistoryDto { + _FakeEquipmentHistoryDto_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeEquipmentIoResponse_5 extends _i1.SmartFake + implements _i6.EquipmentIoResponse { + _FakeEquipmentIoResponse_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeUser_6 extends _i1.SmartFake implements _i7.User { + _FakeUser_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [AuthService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAuthService extends _i1.Mock implements _i8.AuthService { + MockAuthService() { + _i1.throwOnMissingStub(this); + } + + @override + _i9.Stream get authStateChanges => (super.noSuchMethod( + Invocation.getter(#authStateChanges), + returnValue: _i9.Stream.empty(), + ) as _i9.Stream); + + @override + _i9.Future<_i2.Either<_i10.Failure, _i11.LoginResponse>> login( + _i12.LoginRequest? request) => + (super.noSuchMethod( + Invocation.method( + #login, + [request], + ), + returnValue: + _i9.Future<_i2.Either<_i10.Failure, _i11.LoginResponse>>.value( + _FakeEither_0<_i10.Failure, _i11.LoginResponse>( + this, + Invocation.method( + #login, + [request], + ), + )), + ) as _i9.Future<_i2.Either<_i10.Failure, _i11.LoginResponse>>); + + @override + _i9.Future<_i2.Either<_i10.Failure, void>> logout() => (super.noSuchMethod( + Invocation.method( + #logout, + [], + ), + returnValue: _i9.Future<_i2.Either<_i10.Failure, void>>.value( + _FakeEither_0<_i10.Failure, void>( + this, + Invocation.method( + #logout, + [], + ), + )), + ) as _i9.Future<_i2.Either<_i10.Failure, void>>); + + @override + _i9.Future<_i2.Either<_i10.Failure, _i13.TokenResponse>> refreshToken() => + (super.noSuchMethod( + Invocation.method( + #refreshToken, + [], + ), + returnValue: + _i9.Future<_i2.Either<_i10.Failure, _i13.TokenResponse>>.value( + _FakeEither_0<_i10.Failure, _i13.TokenResponse>( + this, + Invocation.method( + #refreshToken, + [], + ), + )), + ) as _i9.Future<_i2.Either<_i10.Failure, _i13.TokenResponse>>); + + @override + _i9.Future isLoggedIn() => (super.noSuchMethod( + Invocation.method( + #isLoggedIn, + [], + ), + returnValue: _i9.Future.value(false), + ) as _i9.Future); + + @override + _i9.Future<_i14.AuthUser?> getCurrentUser() => (super.noSuchMethod( + Invocation.method( + #getCurrentUser, + [], + ), + returnValue: _i9.Future<_i14.AuthUser?>.value(), + ) as _i9.Future<_i14.AuthUser?>); + + @override + _i9.Future getAccessToken() => (super.noSuchMethod( + Invocation.method( + #getAccessToken, + [], + ), + returnValue: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future getRefreshToken() => (super.noSuchMethod( + Invocation.method( + #getRefreshToken, + [], + ), + returnValue: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future clearSession() => (super.noSuchMethod( + Invocation.method( + #clearSession, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); +} + +/// A class which mocks [CompanyService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockCompanyService extends _i1.Mock implements _i15.CompanyService { + MockCompanyService() { + _i1.throwOnMissingStub(this); + } + + @override + _i9.Future> getCompanies({ + int? page = 1, + int? perPage = 20, + String? search, + bool? isActive, + }) => + (super.noSuchMethod( + Invocation.method( + #getCompanies, + [], + { + #page: page, + #perPage: perPage, + #search: search, + #isActive: isActive, + }, + ), + returnValue: _i9.Future>.value(<_i3.Company>[]), + ) as _i9.Future>); + + @override + _i9.Future<_i3.Company> createCompany(_i3.Company? company) => + (super.noSuchMethod( + Invocation.method( + #createCompany, + [company], + ), + returnValue: _i9.Future<_i3.Company>.value(_FakeCompany_1( + this, + Invocation.method( + #createCompany, + [company], + ), + )), + ) as _i9.Future<_i3.Company>); + + @override + _i9.Future<_i3.Company> getCompanyDetail(int? id) => (super.noSuchMethod( + Invocation.method( + #getCompanyDetail, + [id], + ), + returnValue: _i9.Future<_i3.Company>.value(_FakeCompany_1( + this, + Invocation.method( + #getCompanyDetail, + [id], + ), + )), + ) as _i9.Future<_i3.Company>); + + @override + _i9.Future<_i3.Company> getCompanyWithBranches(int? id) => + (super.noSuchMethod( + Invocation.method( + #getCompanyWithBranches, + [id], + ), + returnValue: _i9.Future<_i3.Company>.value(_FakeCompany_1( + this, + Invocation.method( + #getCompanyWithBranches, + [id], + ), + )), + ) as _i9.Future<_i3.Company>); + + @override + _i9.Future<_i3.Company> updateCompany( + int? id, + _i3.Company? company, + ) => + (super.noSuchMethod( + Invocation.method( + #updateCompany, + [ + id, + company, + ], + ), + returnValue: _i9.Future<_i3.Company>.value(_FakeCompany_1( + this, + Invocation.method( + #updateCompany, + [ + id, + company, + ], + ), + )), + ) as _i9.Future<_i3.Company>); + + @override + _i9.Future deleteCompany(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteCompany, + [id], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future>> getCompanyNames() => + (super.noSuchMethod( + Invocation.method( + #getCompanyNames, + [], + ), + returnValue: _i9.Future>>.value( + >[]), + ) as _i9.Future>>); + + @override + _i9.Future<_i3.Branch> createBranch( + int? companyId, + _i3.Branch? branch, + ) => + (super.noSuchMethod( + Invocation.method( + #createBranch, + [ + companyId, + branch, + ], + ), + returnValue: _i9.Future<_i3.Branch>.value(_FakeBranch_2( + this, + Invocation.method( + #createBranch, + [ + companyId, + branch, + ], + ), + )), + ) as _i9.Future<_i3.Branch>); + + @override + _i9.Future<_i3.Branch> getBranchDetail( + int? companyId, + int? branchId, + ) => + (super.noSuchMethod( + Invocation.method( + #getBranchDetail, + [ + companyId, + branchId, + ], + ), + returnValue: _i9.Future<_i3.Branch>.value(_FakeBranch_2( + this, + Invocation.method( + #getBranchDetail, + [ + companyId, + branchId, + ], + ), + )), + ) as _i9.Future<_i3.Branch>); + + @override + _i9.Future<_i3.Branch> updateBranch( + int? companyId, + int? branchId, + _i3.Branch? branch, + ) => + (super.noSuchMethod( + Invocation.method( + #updateBranch, + [ + companyId, + branchId, + branch, + ], + ), + returnValue: _i9.Future<_i3.Branch>.value(_FakeBranch_2( + this, + Invocation.method( + #updateBranch, + [ + companyId, + branchId, + branch, + ], + ), + )), + ) as _i9.Future<_i3.Branch>); + + @override + _i9.Future deleteBranch( + int? companyId, + int? branchId, + ) => + (super.noSuchMethod( + Invocation.method( + #deleteBranch, + [ + companyId, + branchId, + ], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future> getCompanyBranches(int? companyId) => + (super.noSuchMethod( + Invocation.method( + #getCompanyBranches, + [companyId], + ), + returnValue: _i9.Future>.value(<_i3.Branch>[]), + ) as _i9.Future>); + + @override + _i9.Future> getCompaniesWithBranches() => + (super.noSuchMethod( + Invocation.method( + #getCompaniesWithBranches, + [], + ), + returnValue: _i9.Future>.value( + <_i16.CompanyWithBranches>[]), + ) as _i9.Future>); + + @override + _i9.Future checkDuplicateCompany(String? name) => (super.noSuchMethod( + Invocation.method( + #checkDuplicateCompany, + [name], + ), + returnValue: _i9.Future.value(false), + ) as _i9.Future); + + @override + _i9.Future> searchCompanies(String? query) => + (super.noSuchMethod( + Invocation.method( + #searchCompanies, + [query], + ), + returnValue: _i9.Future>.value(<_i3.Company>[]), + ) as _i9.Future>); + + @override + _i9.Future updateCompanyStatus( + int? id, + bool? isActive, + ) => + (super.noSuchMethod( + Invocation.method( + #updateCompanyStatus, + [ + id, + isActive, + ], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); +} + +/// A class which mocks [MockDataService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMockDataService extends _i1.Mock implements _i17.MockDataService { + MockMockDataService() { + _i1.throwOnMissingStub(this); + } + + @override + void initialize() => super.noSuchMethod( + Invocation.method( + #initialize, + [], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i4.EquipmentIn> getAllEquipmentIns() => (super.noSuchMethod( + Invocation.method( + #getAllEquipmentIns, + [], + ), + returnValue: <_i4.EquipmentIn>[], + ) as List<_i4.EquipmentIn>); + + @override + _i4.EquipmentIn? getEquipmentInById(int? id) => + (super.noSuchMethod(Invocation.method( + #getEquipmentInById, + [id], + )) as _i4.EquipmentIn?); + + @override + void addEquipmentIn(_i4.EquipmentIn? equipmentIn) => super.noSuchMethod( + Invocation.method( + #addEquipmentIn, + [equipmentIn], + ), + returnValueForMissingStub: null, + ); + + @override + void updateEquipmentIn(_i4.EquipmentIn? equipmentIn) => super.noSuchMethod( + Invocation.method( + #updateEquipmentIn, + [equipmentIn], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteEquipmentIn(int? id) => super.noSuchMethod( + Invocation.method( + #deleteEquipmentIn, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i4.EquipmentOut> getAllEquipmentOuts() => (super.noSuchMethod( + Invocation.method( + #getAllEquipmentOuts, + [], + ), + returnValue: <_i4.EquipmentOut>[], + ) as List<_i4.EquipmentOut>); + + @override + _i4.EquipmentOut? getEquipmentOutById(int? id) => + (super.noSuchMethod(Invocation.method( + #getEquipmentOutById, + [id], + )) as _i4.EquipmentOut?); + + @override + void changeEquipmentStatus( + int? equipmentInId, + _i4.EquipmentOut? equipmentOut, + ) => + super.noSuchMethod( + Invocation.method( + #changeEquipmentStatus, + [ + equipmentInId, + equipmentOut, + ], + ), + returnValueForMissingStub: null, + ); + + @override + void addEquipmentOut(_i4.EquipmentOut? equipmentOut) => super.noSuchMethod( + Invocation.method( + #addEquipmentOut, + [equipmentOut], + ), + returnValueForMissingStub: null, + ); + + @override + void updateEquipmentOut(_i4.EquipmentOut? equipmentOut) => super.noSuchMethod( + Invocation.method( + #updateEquipmentOut, + [equipmentOut], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteEquipmentOut(int? id) => super.noSuchMethod( + Invocation.method( + #deleteEquipmentOut, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List getAllManufacturers() => (super.noSuchMethod( + Invocation.method( + #getAllManufacturers, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllEquipmentNames() => (super.noSuchMethod( + Invocation.method( + #getAllEquipmentNames, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllCompanyNames() => (super.noSuchMethod( + Invocation.method( + #getAllCompanyNames, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllBranchNames() => (super.noSuchMethod( + Invocation.method( + #getAllBranchNames, + [], + ), + returnValue: [], + ) as List); + + @override + List<_i3.Company> getAllCompanies() => (super.noSuchMethod( + Invocation.method( + #getAllCompanies, + [], + ), + returnValue: <_i3.Company>[], + ) as List<_i3.Company>); + + @override + _i3.Company? getCompanyById(int? id) => (super.noSuchMethod(Invocation.method( + #getCompanyById, + [id], + )) as _i3.Company?); + + @override + _i3.Company? findCompanyByName(String? name) => + (super.noSuchMethod(Invocation.method( + #findCompanyByName, + [name], + )) as _i3.Company?); + + @override + void addCompany(_i3.Company? company) => super.noSuchMethod( + Invocation.method( + #addCompany, + [company], + ), + returnValueForMissingStub: null, + ); + + @override + void updateCompany(_i3.Company? company) => super.noSuchMethod( + Invocation.method( + #updateCompany, + [company], + ), + returnValueForMissingStub: null, + ); + + @override + void updateBranch( + int? companyId, + _i3.Branch? branch, + ) => + super.noSuchMethod( + Invocation.method( + #updateBranch, + [ + companyId, + branch, + ], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteCompany(int? id) => super.noSuchMethod( + Invocation.method( + #deleteCompany, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i7.User> getAllUsers() => (super.noSuchMethod( + Invocation.method( + #getAllUsers, + [], + ), + returnValue: <_i7.User>[], + ) as List<_i7.User>); + + @override + _i7.User? getUserById(int? id) => (super.noSuchMethod(Invocation.method( + #getUserById, + [id], + )) as _i7.User?); + + @override + void addUser(_i7.User? user) => super.noSuchMethod( + Invocation.method( + #addUser, + [user], + ), + returnValueForMissingStub: null, + ); + + @override + void updateUser(_i7.User? user) => super.noSuchMethod( + Invocation.method( + #updateUser, + [user], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteUser(int? id) => super.noSuchMethod( + Invocation.method( + #deleteUser, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i18.License> getAllLicenses() => (super.noSuchMethod( + Invocation.method( + #getAllLicenses, + [], + ), + returnValue: <_i18.License>[], + ) as List<_i18.License>); + + @override + _i18.License? getLicenseById(int? id) => + (super.noSuchMethod(Invocation.method( + #getLicenseById, + [id], + )) as _i18.License?); + + @override + void addLicense(_i18.License? license) => super.noSuchMethod( + Invocation.method( + #addLicense, + [license], + ), + returnValueForMissingStub: null, + ); + + @override + void updateLicense(_i18.License? license) => super.noSuchMethod( + Invocation.method( + #updateLicense, + [license], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteLicense(int? id) => super.noSuchMethod( + Invocation.method( + #deleteLicense, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i4.UnifiedEquipment> getAllEquipments() => (super.noSuchMethod( + Invocation.method( + #getAllEquipments, + [], + ), + returnValue: <_i4.UnifiedEquipment>[], + ) as List<_i4.UnifiedEquipment>); + + @override + _i4.UnifiedEquipment? getEquipmentById( + int? id, + String? status, + ) => + (super.noSuchMethod(Invocation.method( + #getEquipmentById, + [ + id, + status, + ], + )) as _i4.UnifiedEquipment?); + + @override + void deleteEquipment( + int? id, + String? status, + ) => + super.noSuchMethod( + Invocation.method( + #deleteEquipment, + [ + id, + status, + ], + ), + returnValueForMissingStub: null, + ); + + @override + List<_i19.WarehouseLocation> getAllWarehouseLocations() => + (super.noSuchMethod( + Invocation.method( + #getAllWarehouseLocations, + [], + ), + returnValue: <_i19.WarehouseLocation>[], + ) as List<_i19.WarehouseLocation>); + + @override + _i19.WarehouseLocation? getWarehouseLocationById(int? id) => + (super.noSuchMethod(Invocation.method( + #getWarehouseLocationById, + [id], + )) as _i19.WarehouseLocation?); + + @override + void addWarehouseLocation(_i19.WarehouseLocation? location) => + super.noSuchMethod( + Invocation.method( + #addWarehouseLocation, + [location], + ), + returnValueForMissingStub: null, + ); + + @override + void updateWarehouseLocation(_i19.WarehouseLocation? location) => + super.noSuchMethod( + Invocation.method( + #updateWarehouseLocation, + [location], + ), + returnValueForMissingStub: null, + ); + + @override + void deleteWarehouseLocation(int? id) => super.noSuchMethod( + Invocation.method( + #deleteWarehouseLocation, + [id], + ), + returnValueForMissingStub: null, + ); + + @override + List getAllCategories() => (super.noSuchMethod( + Invocation.method( + #getAllCategories, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllSubCategories() => (super.noSuchMethod( + Invocation.method( + #getAllSubCategories, + [], + ), + returnValue: [], + ) as List); + + @override + List getAllSubSubCategories() => (super.noSuchMethod( + Invocation.method( + #getAllSubSubCategories, + [], + ), + returnValue: [], + ) as List); +} + +/// A class which mocks [EquipmentService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockEquipmentService extends _i1.Mock implements _i20.EquipmentService { + MockEquipmentService() { + _i1.throwOnMissingStub(this); + } + + @override + _i9.Future> getEquipmentsWithStatus({ + int? page = 1, + int? perPage = 20, + String? status, + int? companyId, + int? warehouseLocationId, + }) => + (super.noSuchMethod( + Invocation.method( + #getEquipmentsWithStatus, + [], + { + #page: page, + #perPage: perPage, + #status: status, + #companyId: companyId, + #warehouseLocationId: warehouseLocationId, + }, + ), + returnValue: _i9.Future>.value( + <_i21.EquipmentListDto>[]), + ) as _i9.Future>); + + @override + _i9.Future> getEquipments({ + int? page = 1, + int? perPage = 20, + String? status, + int? companyId, + int? warehouseLocationId, + }) => + (super.noSuchMethod( + Invocation.method( + #getEquipments, + [], + { + #page: page, + #perPage: perPage, + #status: status, + #companyId: companyId, + #warehouseLocationId: warehouseLocationId, + }, + ), + returnValue: _i9.Future>.value(<_i4.Equipment>[]), + ) as _i9.Future>); + + @override + _i9.Future<_i4.Equipment> createEquipment(_i4.Equipment? equipment) => + (super.noSuchMethod( + Invocation.method( + #createEquipment, + [equipment], + ), + returnValue: _i9.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #createEquipment, + [equipment], + ), + )), + ) as _i9.Future<_i4.Equipment>); + + @override + _i9.Future<_i4.Equipment> getEquipmentDetail(int? id) => (super.noSuchMethod( + Invocation.method( + #getEquipmentDetail, + [id], + ), + returnValue: _i9.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #getEquipmentDetail, + [id], + ), + )), + ) as _i9.Future<_i4.Equipment>); + + @override + _i9.Future<_i4.Equipment> getEquipment(int? id) => (super.noSuchMethod( + Invocation.method( + #getEquipment, + [id], + ), + returnValue: _i9.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #getEquipment, + [id], + ), + )), + ) as _i9.Future<_i4.Equipment>); + + @override + _i9.Future<_i4.Equipment> updateEquipment( + int? id, + _i4.Equipment? equipment, + ) => + (super.noSuchMethod( + Invocation.method( + #updateEquipment, + [ + id, + equipment, + ], + ), + returnValue: _i9.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #updateEquipment, + [ + id, + equipment, + ], + ), + )), + ) as _i9.Future<_i4.Equipment>); + + @override + _i9.Future deleteEquipment(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteEquipment, + [id], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future<_i4.Equipment> changeEquipmentStatus( + int? id, + String? status, + String? reason, + ) => + (super.noSuchMethod( + Invocation.method( + #changeEquipmentStatus, + [ + id, + status, + reason, + ], + ), + returnValue: _i9.Future<_i4.Equipment>.value(_FakeEquipment_3( + this, + Invocation.method( + #changeEquipmentStatus, + [ + id, + status, + reason, + ], + ), + )), + ) as _i9.Future<_i4.Equipment>); + + @override + _i9.Future<_i5.EquipmentHistoryDto> addEquipmentHistory( + int? equipmentId, + String? type, + int? quantity, + String? remarks, + ) => + (super.noSuchMethod( + Invocation.method( + #addEquipmentHistory, + [ + equipmentId, + type, + quantity, + remarks, + ], + ), + returnValue: _i9.Future<_i5.EquipmentHistoryDto>.value( + _FakeEquipmentHistoryDto_4( + this, + Invocation.method( + #addEquipmentHistory, + [ + equipmentId, + type, + quantity, + remarks, + ], + ), + )), + ) as _i9.Future<_i5.EquipmentHistoryDto>); + + @override + _i9.Future> getEquipmentHistory( + int? equipmentId, { + int? page = 1, + int? perPage = 20, + }) => + (super.noSuchMethod( + Invocation.method( + #getEquipmentHistory, + [equipmentId], + { + #page: page, + #perPage: perPage, + }, + ), + returnValue: _i9.Future>.value( + <_i5.EquipmentHistoryDto>[]), + ) as _i9.Future>); + + @override + _i9.Future<_i6.EquipmentIoResponse> equipmentIn({ + required int? equipmentId, + required int? quantity, + int? warehouseLocationId, + String? notes, + }) => + (super.noSuchMethod( + Invocation.method( + #equipmentIn, + [], + { + #equipmentId: equipmentId, + #quantity: quantity, + #warehouseLocationId: warehouseLocationId, + #notes: notes, + }, + ), + returnValue: _i9.Future<_i6.EquipmentIoResponse>.value( + _FakeEquipmentIoResponse_5( + this, + Invocation.method( + #equipmentIn, + [], + { + #equipmentId: equipmentId, + #quantity: quantity, + #warehouseLocationId: warehouseLocationId, + #notes: notes, + }, + ), + )), + ) as _i9.Future<_i6.EquipmentIoResponse>); + + @override + _i9.Future<_i6.EquipmentIoResponse> equipmentOut({ + required int? equipmentId, + required int? quantity, + required int? companyId, + int? branchId, + String? notes, + }) => + (super.noSuchMethod( + Invocation.method( + #equipmentOut, + [], + { + #equipmentId: equipmentId, + #quantity: quantity, + #companyId: companyId, + #branchId: branchId, + #notes: notes, + }, + ), + returnValue: _i9.Future<_i6.EquipmentIoResponse>.value( + _FakeEquipmentIoResponse_5( + this, + Invocation.method( + #equipmentOut, + [], + { + #equipmentId: equipmentId, + #quantity: quantity, + #companyId: companyId, + #branchId: branchId, + #notes: notes, + }, + ), + )), + ) as _i9.Future<_i6.EquipmentIoResponse>); +} + +/// A class which mocks [UserService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUserService extends _i1.Mock implements _i22.UserService { + MockUserService() { + _i1.throwOnMissingStub(this); + } + + @override + _i9.Future> getUsers({ + int? page = 1, + int? perPage = 20, + bool? isActive, + int? companyId, + String? role, + }) => + (super.noSuchMethod( + Invocation.method( + #getUsers, + [], + { + #page: page, + #perPage: perPage, + #isActive: isActive, + #companyId: companyId, + #role: role, + }, + ), + returnValue: _i9.Future>.value(<_i7.User>[]), + ) as _i9.Future>); + + @override + _i9.Future<_i7.User> getUser(int? id) => (super.noSuchMethod( + Invocation.method( + #getUser, + [id], + ), + returnValue: _i9.Future<_i7.User>.value(_FakeUser_6( + this, + Invocation.method( + #getUser, + [id], + ), + )), + ) as _i9.Future<_i7.User>); + + @override + _i9.Future<_i7.User> createUser({ + required String? username, + required String? email, + required String? password, + required String? name, + required String? role, + required int? companyId, + int? branchId, + String? phone, + String? position, + }) => + (super.noSuchMethod( + Invocation.method( + #createUser, + [], + { + #username: username, + #email: email, + #password: password, + #name: name, + #role: role, + #companyId: companyId, + #branchId: branchId, + #phone: phone, + #position: position, + }, + ), + returnValue: _i9.Future<_i7.User>.value(_FakeUser_6( + this, + Invocation.method( + #createUser, + [], + { + #username: username, + #email: email, + #password: password, + #name: name, + #role: role, + #companyId: companyId, + #branchId: branchId, + #phone: phone, + #position: position, + }, + ), + )), + ) as _i9.Future<_i7.User>); + + @override + _i9.Future<_i7.User> updateUser( + int? id, { + String? name, + String? email, + String? password, + String? phone, + int? companyId, + int? branchId, + String? role, + String? position, + }) => + (super.noSuchMethod( + Invocation.method( + #updateUser, + [id], + { + #name: name, + #email: email, + #password: password, + #phone: phone, + #companyId: companyId, + #branchId: branchId, + #role: role, + #position: position, + }, + ), + returnValue: _i9.Future<_i7.User>.value(_FakeUser_6( + this, + Invocation.method( + #updateUser, + [id], + { + #name: name, + #email: email, + #password: password, + #phone: phone, + #companyId: companyId, + #branchId: branchId, + #role: role, + #position: position, + }, + ), + )), + ) as _i9.Future<_i7.User>); + + @override + _i9.Future deleteUser(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteUser, + [id], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future<_i7.User> changeUserStatus( + int? id, + bool? isActive, + ) => + (super.noSuchMethod( + Invocation.method( + #changeUserStatus, + [ + id, + isActive, + ], + ), + returnValue: _i9.Future<_i7.User>.value(_FakeUser_6( + this, + Invocation.method( + #changeUserStatus, + [ + id, + isActive, + ], + ), + )), + ) as _i9.Future<_i7.User>); + + @override + _i9.Future changePassword( + int? id, + String? currentPassword, + String? newPassword, + ) => + (super.noSuchMethod( + Invocation.method( + #changePassword, + [ + id, + currentPassword, + newPassword, + ], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + + @override + _i9.Future checkDuplicateUsername(String? username) => + (super.noSuchMethod( + Invocation.method( + #checkDuplicateUsername, + [username], + ), + returnValue: _i9.Future.value(false), + ) as _i9.Future); + + @override + _i9.Future> searchUsers({ + required String? query, + int? companyId, + String? status, + String? permissionLevel, + int? page = 1, + int? perPage = 20, + }) => + (super.noSuchMethod( + Invocation.method( + #searchUsers, + [], + { + #query: query, + #companyId: companyId, + #status: status, + #permissionLevel: permissionLevel, + #page: page, + #perPage: perPage, + }, + ), + returnValue: _i9.Future>.value(<_i7.User>[]), + ) as _i9.Future>); + + @override + String? getPhoneForApi(List>? phoneNumbers) => + (super.noSuchMethod(Invocation.method( + #getPhoneForApi, + [phoneNumbers], + )) as String?); +} diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart new file mode 100644 index 0000000..5204ae4 --- /dev/null +++ b/test/helpers/test_helpers.dart @@ -0,0 +1,296 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:provider/provider.dart'; + +/// 테스트용 GetIt 인스턴스 초기화 +GetIt setupTestGetIt() { + final getIt = GetIt.instance; + + // 기존 등록된 서비스들 모두 제거 + getIt.reset(); + + return getIt; +} + +/// 테스트용 위젯 래퍼 +/// 모든 위젯 테스트에서 필요한 기본 설정을 제공 +class TestWidgetWrapper extends StatelessWidget { + final Widget child; + final List? providers; + final NavigatorObserver? navigatorObserver; + final Map? routes; + final String? initialRoute; + + const TestWidgetWrapper({ + Key? key, + required this.child, + this.providers, + this.navigatorObserver, + this.routes, + this.initialRoute, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + Widget wrappedChild = MaterialApp( + title: 'Test App', + theme: ThemeData( + primarySwatch: Colors.blue, + useMaterial3: true, + ), + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('ko', 'KR'), + Locale('en', 'US'), + ], + home: Scaffold(body: child), + routes: routes ?? {}, + initialRoute: initialRoute, + navigatorObservers: navigatorObserver != null ? [navigatorObserver!] : [], + ); + + // Provider가 있는 경우 래핑 + if (providers != null && providers!.isNotEmpty) { + return MultiProvider( + providers: providers!, + child: wrappedChild, + ); + } + + return wrappedChild; + } +} + +/// 위젯을 테스트 환경에서 펌프하는 헬퍼 함수 +Future pumpTestWidget( + WidgetTester tester, + Widget widget, { + List? providers, + NavigatorObserver? navigatorObserver, + Map? routes, + String? initialRoute, +}) async { + await tester.pumpWidget( + TestWidgetWrapper( + child: widget, + providers: providers, + navigatorObserver: navigatorObserver, + routes: routes, + initialRoute: initialRoute, + ), + ); +} + +/// 비동기 작업을 기다리고 위젯을 리빌드하는 헬퍼 +Future pumpAndSettleWithTimeout( + WidgetTester tester, { + Duration timeout = const Duration(seconds: 10), +}) async { + await tester.pump(); + await tester.pumpAndSettle(timeout); +} + +/// TextField에 텍스트를 입력하는 헬퍼 +Future enterTextByLabel( + WidgetTester tester, + String label, + String text, +) async { + final textFieldFinder = find.ancestor( + of: find.text(label), + matching: find.byType(TextFormField), + ); + + if (textFieldFinder.evaluate().isEmpty) { + // 라벨로 찾지 못한 경우, 가까운 TextFormField 찾기 + final labelWidget = find.text(label); + final textField = find.byType(TextFormField).first; + await tester.enterText(textField, text); + } else { + await tester.enterText(textFieldFinder, text); + } +} + +/// 버튼을 찾고 탭하는 헬퍼 +Future tapButtonByText( + WidgetTester tester, + String buttonText, +) async { + final buttonFinder = find.widgetWithText(ElevatedButton, buttonText); + + if (buttonFinder.evaluate().isEmpty) { + // ElevatedButton이 아닌 경우 다른 버튼 타입 시도 + final textButtonFinder = find.widgetWithText(TextButton, buttonText); + if (textButtonFinder.evaluate().isNotEmpty) { + await tester.tap(textButtonFinder); + return; + } + + final outlinedButtonFinder = find.widgetWithText(OutlinedButton, buttonText); + if (outlinedButtonFinder.evaluate().isNotEmpty) { + await tester.tap(outlinedButtonFinder); + return; + } + + // 아무 버튼도 찾지 못한 경우 텍스트만으로 시도 + await tester.tap(find.text(buttonText)); + } else { + await tester.tap(buttonFinder); + } +} + +/// 스낵바 메시지 검증 헬퍼 +void expectSnackBar(WidgetTester tester, String message) { + expect( + find.descendant( + of: find.byType(SnackBar), + matching: find.text(message), + ), + findsOneWidget, + ); +} + +/// 로딩 인디케이터 검증 헬퍼 +void expectLoading(WidgetTester tester, {bool isLoading = true}) { + expect( + find.byType(CircularProgressIndicator), + isLoading ? findsOneWidget : findsNothing, + ); +} + +/// 에러 메시지 검증 헬퍼 +void expectErrorMessage(WidgetTester tester, String errorMessage) { + expect(find.text(errorMessage), findsOneWidget); +} + +/// 화면 전환 대기 헬퍼 +Future waitForNavigation(WidgetTester tester) async { + await tester.pump(); + await tester.pump(const Duration(milliseconds: 300)); // 애니메이션 대기 +} + +/// 다이얼로그 검증 헬퍼 +void expectDialog(WidgetTester tester, {String? title, String? content}) { + expect(find.byType(Dialog), findsOneWidget); + + if (title != null) { + expect( + find.descendant( + of: find.byType(Dialog), + matching: find.text(title), + ), + findsOneWidget, + ); + } + + if (content != null) { + expect( + find.descendant( + of: find.byType(Dialog), + matching: find.text(content), + ), + findsOneWidget, + ); + } +} + +/// 다이얼로그 닫기 헬퍼 +Future closeDialog(WidgetTester tester) async { + // 다이얼로그 외부 탭하여 닫기 + await tester.tapAt(const Offset(10, 10)); + await tester.pump(); +} + +/// 스크롤하여 위젯 찾기 헬퍼 +Future scrollUntilVisible( + WidgetTester tester, + Finder finder, { + double delta = 300, + int maxScrolls = 10, + Finder? scrollable, +}) async { + final scrollableFinder = scrollable ?? find.byType(Scrollable).first; + + for (int i = 0; i < maxScrolls; i++) { + if (finder.evaluate().isNotEmpty) { + return; + } + + await tester.drag(scrollableFinder, Offset(0, -delta)); + await tester.pump(); + } +} + +/// 테이블이나 리스트에서 특정 행 찾기 헬퍼 +Finder findRowContaining(String text) { + return find.ancestor( + of: find.text(text), + matching: find.byType(Row), + ); +} + +/// 폼 필드 검증 헬퍼 +void expectFormFieldError(WidgetTester tester, String fieldLabel, String errorText) { + final formField = find.ancestor( + of: find.text(fieldLabel), + matching: find.byType(TextFormField), + ); + + final errorFinder = find.descendant( + of: formField, + matching: find.text(errorText), + ); + + expect(errorFinder, findsOneWidget); +} + +/// 드롭다운 선택 헬퍼 +Future selectDropdownItem( + WidgetTester tester, + String dropdownLabel, + String itemText, +) async { + // 드롭다운 찾기 + final dropdown = find.ancestor( + of: find.text(dropdownLabel), + matching: find.byType(DropdownButtonFormField), + ); + + // 드롭다운 열기 + await tester.tap(dropdown); + await tester.pump(); + + // 아이템 선택 + await tester.tap(find.text(itemText).last); + await tester.pump(); +} + +/// 날짜 선택 헬퍼 +Future selectDate( + WidgetTester tester, + String dateFieldLabel, + DateTime date, +) async { + // 날짜 필드 탭 + final dateField = find.ancestor( + of: find.text(dateFieldLabel), + matching: find.byType(TextFormField), + ); + + await tester.tap(dateField); + await tester.pump(); + + // 날짜 선택 (간단한 구현, 실제로는 더 복잡할 수 있음) + await tester.tap(find.text(date.day.toString())); + await tester.pump(); + + // 확인 버튼 탭 + await tester.tap(find.text('확인')); + await tester.pump(); +} \ No newline at end of file diff --git a/test/unit/controllers/company_list_controller_test.dart b/test/unit/controllers/company_list_controller_test.dart new file mode 100644 index 0000000..ded1c32 --- /dev/null +++ b/test/unit/controllers/company_list_controller_test.dart @@ -0,0 +1,113 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/mockito.dart'; +import 'package:superport/screens/company/controllers/company_list_controller.dart'; +import 'package:superport/services/company_service.dart'; + +import '../../helpers/test_helpers.dart'; +import '../../helpers/simple_mock_services.dart'; +import '../../helpers/simple_mock_services.mocks.dart'; +import '../../helpers/mock_data_helpers.dart'; + +void main() { + late CompanyListController controller; + late MockMockDataService mockDataService; + late MockCompanyService mockCompanyService; + late GetIt getIt; + + setUp(() { + getIt = setupTestGetIt(); + mockDataService = MockMockDataService(); + mockCompanyService = MockCompanyService(); + + // GetIt에 CompanyService 등록 + getIt.registerSingleton(mockCompanyService); + + // Mock 설정 + SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService); + SimpleMockServiceHelpers.setupCompanyServiceMock(mockCompanyService); + + controller = CompanyListController(dataService: mockDataService); + }); + + tearDown(() { + controller.dispose(); + getIt.reset(); + }); + + group('CompanyListController 단위 테스트', () { + test('검색 키워드 업데이트 테스트', () async { + // Act + await controller.updateSearchKeyword('테스트'); + + // Assert + expect(controller.searchKeyword, '테스트'); + }); + + test('회사 선택/해제 테스트', () { + // Act + controller.toggleCompanySelection(1); + expect(controller.selectedCompanyIds.contains(1), true); + + controller.toggleCompanySelection(1); + expect(controller.selectedCompanyIds.contains(1), false); + }); + + test('전체 선택/해제 테스트', () { + // Arrange + controller.companies = MockDataHelpers.createMockCompanyList(count: 3); + controller.filteredCompanies = controller.companies; + + // Act - 전체 선택 + controller.toggleSelectAll(); + expect(controller.selectedCompanyIds.length, 3); + + // Act - 전체 해제 + controller.toggleSelectAll(); + expect(controller.selectedCompanyIds.isEmpty, true); + }); + + test('필터 적용 테스트', () { + // Arrange + controller.companies = MockDataHelpers.createMockCompanyList(count: 5); + controller.searchKeyword = '회사 1'; + + // Act + controller.applyFilters(); + + // Assert + expect(controller.filteredCompanies.length, 1); + expect(controller.filteredCompanies.first.name, '테스트 회사 1'); + }); + + test('회사 삭제 테스트', () async { + // Arrange + controller.companies = MockDataHelpers.createMockCompanyList(count: 3); + controller.filteredCompanies = controller.companies; + + // Act + final result = await controller.deleteCompany(1); + + // Assert + expect(result, true); + expect(controller.companies.length, 2); + expect(controller.companies.any((c) => c.id == 1), false); + verify(mockCompanyService.deleteCompany(1)).called(1); + }); + + test('에러 처리 테스트', () async { + // Arrange + SimpleMockServiceHelpers.setupCompanyServiceMock( + mockCompanyService, + getCompaniesSuccess: false, + ); + + // Act + await controller.loadData(); + + // Assert + expect(controller.error, isNotNull); + expect(controller.isLoading, false); + }); + }); +} \ No newline at end of file diff --git a/test/unit/controllers/equipment_list_controller_test.dart b/test/unit/controllers/equipment_list_controller_test.dart new file mode 100644 index 0000000..c07b3cf --- /dev/null +++ b/test/unit/controllers/equipment_list_controller_test.dart @@ -0,0 +1,132 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/mockito.dart'; +import 'package:superport/screens/equipment/controllers/equipment_list_controller.dart'; +import 'package:superport/services/equipment_service.dart'; + +import '../../helpers/test_helpers.dart'; +import '../../helpers/simple_mock_services.dart'; +import '../../helpers/simple_mock_services.mocks.dart'; +import '../../helpers/mock_data_helpers.dart'; + +void main() { + late EquipmentListController controller; + late MockMockDataService mockDataService; + late MockEquipmentService mockEquipmentService; + late GetIt getIt; + + setUp(() { + getIt = setupTestGetIt(); + mockDataService = MockMockDataService(); + mockEquipmentService = MockEquipmentService(); + + // GetIt에 EquipmentService 등록 + getIt.registerSingleton(mockEquipmentService); + + // Mock 설정 + when(mockDataService.getAllEquipments()).thenReturn( + MockDataHelpers.createMockUnifiedEquipmentList(count: 5) + ); + + // EquipmentService mock 설정 + when(mockEquipmentService.getEquipments( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + status: anyNamed('status'), + companyId: anyNamed('companyId'), + warehouseLocationId: anyNamed('warehouseLocationId'), + )).thenAnswer((_) async => []); + + when(mockEquipmentService.deleteEquipment(any)) + .thenAnswer((_) async {}); + + controller = EquipmentListController(dataService: mockDataService); + }); + + tearDown(() { + controller.dispose(); + getIt.reset(); + }); + + group('EquipmentListController 단위 테스트', () { + test('장비 선택/해제 테스트', () { + // Arrange + final equipment = MockDataHelpers.createMockUnifiedEquipment(id: 1); + + // Act + controller.selectEquipment(equipment.id, equipment.status, true); + expect(controller.selectedEquipmentIds.contains('${equipment.id}:${equipment.status}'), true); + + controller.selectEquipment(equipment.id, equipment.status, false); + expect(controller.selectedEquipmentIds.contains('${equipment.id}:${equipment.status}'), false); + }); + + test('전체 선택 테스트', () { + // Arrange + controller.equipments = MockDataHelpers.createMockUnifiedEquipmentList(count: 3); + + // 수동으로 전체 선택 + for (final equipment in controller.equipments) { + controller.selectEquipment(equipment.id, equipment.status, true); + } + + // Assert + expect(controller.selectedEquipmentIds.length, 3); + }); + + test('상태 필터 변경 테스트', () async { + // Act + await controller.changeStatusFilter('IN_STOCK'); + + // Assert + expect(controller.selectedStatusFilter, 'IN_STOCK'); + }); + + test('장비 삭제 테스트', () async { + // Arrange + controller.equipments = MockDataHelpers.createMockUnifiedEquipmentList(count: 3); + final equipmentToDelete = controller.equipments.first; + + // Act + final result = await controller.deleteEquipment(equipmentToDelete); + + // Assert + expect(result, true); + expect(controller.equipments.length, 2); + expect(controller.equipments.any((e) => e.id == equipmentToDelete.id), false); + verify(mockEquipmentService.deleteEquipment(equipmentToDelete.equipment.id!)).called(1); + }); + + test('선택된 장비 수 테스트', () { + // Arrange + controller.equipments = MockDataHelpers.createMockUnifiedEquipmentList(count: 5); + + // 3개만 선택 + controller.selectEquipment(1, 'I', true); + controller.selectEquipment(2, 'I', true); + controller.selectEquipment(3, 'I', true); + + // Assert + expect(controller.getSelectedEquipmentCount(), 3); + expect(controller.getSelectedInStockCount(), 3); + }); + + test('에러 처리 테스트', () async { + // Arrange + when(mockEquipmentService.getEquipments( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + status: anyNamed('status'), + companyId: anyNamed('companyId'), + warehouseLocationId: anyNamed('warehouseLocationId'), + )).thenThrow(Exception('장비 목록을 불러오는 중 오류가 발생했습니다.')); + + // Act + await controller.loadData(); + + // Assert + expect(controller.error, isNotNull); + expect(controller.isLoading, false); + }); + }); +} \ No newline at end of file diff --git a/test/unit/controllers/user_list_controller_test.dart b/test/unit/controllers/user_list_controller_test.dart new file mode 100644 index 0000000..9db5769 --- /dev/null +++ b/test/unit/controllers/user_list_controller_test.dart @@ -0,0 +1,255 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/mockito.dart'; +import 'package:superport/screens/user/controllers/user_list_controller.dart'; +import 'package:superport/services/user_service.dart'; +import 'package:superport/models/company_model.dart'; + +import '../../helpers/test_helpers.dart'; +import '../../helpers/simple_mock_services.dart'; +import '../../helpers/simple_mock_services.mocks.dart'; +import '../../helpers/mock_data_helpers.dart'; + +void main() { + late UserListController controller; + late MockMockDataService mockDataService; + late MockUserService mockUserService; + late GetIt getIt; + + setUp(() { + getIt = setupTestGetIt(); + mockDataService = MockMockDataService(); + mockUserService = MockUserService(); + + // GetIt에 UserService 등록 + getIt.registerSingleton(mockUserService); + + // Mock 설정 + SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService); + SimpleMockServiceHelpers.setupUserServiceMock(mockUserService); + + controller = UserListController(dataService: mockDataService); + }); + + tearDown(() { + controller.dispose(); + getIt.reset(); + }); + + group('UserListController 단위 테스트', () { + test('초기 상태 확인', () { + expect(controller.users, isEmpty); + expect(controller.isLoading, false); + expect(controller.error, isNull); + expect(controller.hasMoreData, true); + expect(controller.searchQuery, ''); + expect(controller.filterCompanyId, isNull); + expect(controller.filterRole, isNull); + expect(controller.filterIsActive, isNull); + }); + + test('사용자 목록 로드 테스트', () async { + // Act + await controller.loadUsers(); + + // Assert + expect(controller.users, isNotEmpty); + expect(controller.users.length, 10); + expect(controller.isLoading, false); + expect(controller.error, isNull); + verify(mockUserService.getUsers( + page: 1, + perPage: 20, + isActive: null, + companyId: null, + role: null, + )).called(1); + }); + + test('검색 쿼리 설정 및 검색 테스트', () async { + // Act + controller.setSearchQuery('사용자 1'); + + // Assert + expect(controller.searchQuery, '사용자 1'); + await Future.delayed(Duration(milliseconds: 100)); // loadUsers 완료 대기 + verify(mockUserService.getUsers( + page: 1, + perPage: 20, + isActive: null, + companyId: null, + role: null, + )).called(1); + }); + + test('필터 설정 테스트', () async { + // Act + controller.setFilters( + companyId: 1, + role: 'S', + isActive: true, + ); + + // Assert + expect(controller.filterCompanyId, 1); + expect(controller.filterRole, 'S'); + expect(controller.filterIsActive, true); + await Future.delayed(Duration(milliseconds: 100)); // loadUsers 완료 대기 + verify(mockUserService.getUsers( + page: 1, + perPage: 20, + isActive: true, + companyId: 1, + role: 'S', + )).called(1); + }); + + test('필터 초기화 테스트', () async { + // Arrange + controller.setFilters( + companyId: 1, + role: 'S', + isActive: true, + ); + await Future.delayed(Duration(milliseconds: 100)); + + // Act + controller.clearFilters(); + + // Assert + expect(controller.filterCompanyId, isNull); + expect(controller.filterRole, isNull); + expect(controller.filterIsActive, isNull); + expect(controller.searchQuery, ''); + }); + + test('사용자 삭제 테스트', () async { + // Arrange + await controller.loadUsers(); + final initialUserCount = controller.users.length; + bool deletedCallbackCalled = false; + + // Act + await controller.deleteUser( + 1, + () => deletedCallbackCalled = true, + (error) => fail('삭제 실패: $error'), + ); + + // Assert + expect(deletedCallbackCalled, true); + expect(controller.users.length, initialUserCount - 1); + expect(controller.users.any((u) => u.id == 1), false); + verify(mockUserService.deleteUser(1)).called(1); + }); + + test('사용자 상태 변경 테스트', () async { + // Arrange + await controller.loadUsers(); + + // Act + await controller.changeUserStatus( + 1, + false, + (error) => fail('상태 변경 실패: $error'), + ); + + // Assert + final updatedUser = controller.users.firstWhere((u) => u.id == 1); + expect(updatedUser.isActive, false); + verify(mockUserService.changeUserStatus(1, false)).called(1); + }); + + test('에러 처리 테스트', () async { + // Arrange + SimpleMockServiceHelpers.setupUserServiceMock( + mockUserService, + getUsersSuccess: false, + ); + + // Act + await controller.loadUsers(); + + // Assert + expect(controller.error, isNotNull); + expect(controller.isLoading, false); + expect(controller.users, isEmpty); + }); + + test('페이지네이션 - 더 불러오기 테스트', () async { + // Arrange + // 첫 번째 페이지와 두 번째 페이지에 대해 다른 응답 설정 + when(mockUserService.getUsers( + page: 1, + perPage: 20, + isActive: anyNamed('isActive'), + companyId: anyNamed('companyId'), + role: anyNamed('role'), + )).thenAnswer((_) async => + MockDataHelpers.createMockUserModelList(count: 20), + ); + + when(mockUserService.getUsers( + page: 2, + perPage: 20, + isActive: anyNamed('isActive'), + companyId: anyNamed('companyId'), + role: anyNamed('role'), + )).thenAnswer((_) async => + MockDataHelpers.createMockUserModelList(count: 10), + ); + + await controller.loadUsers(); + final initialCount = controller.users.length; + + // Act + await controller.loadMore(); + + // Assert + expect(controller.users.length, greaterThan(initialCount)); + verify(mockUserService.getUsers( + page: 2, + perPage: 20, + isActive: null, + companyId: null, + role: null, + )).called(1); + }); + + test('Mock 모드에서 필터링 테스트', () async { + // Arrange + controller.toggleApiMode(); // Mock 모드로 전환 + + // Act + controller.setFilters(companyId: 1, role: 'S'); + await Future.delayed(Duration(milliseconds: 100)); + + // Assert + // Mock 모드에서는 getAllUsers를 통해 전체 데이터를 가져온 후 필터링 + verify(mockDataService.getAllUsers()).called(greaterThanOrEqualTo(1)); + }); + + test('지점명 조회 테스트', () { + // Arrange + final mockCompany = Company( + id: 1, + name: '테스트 회사', + branches: [ + Branch( + id: 1, + companyId: 1, + name: '본사', + ), + ], + ); + when(mockDataService.getCompanyById(1)).thenReturn(mockCompany); + + // Act + final branchName = controller.getBranchName(1, 1); + + // Assert + expect(branchName, '본사'); + verify(mockDataService.getCompanyById(1)).called(1); + }); + }); +} \ No newline at end of file diff --git a/test/widget/screens/company_list_widget_test.dart b/test/widget/screens/company_list_widget_test.dart new file mode 100644 index 0000000..1f4a39b --- /dev/null +++ b/test/widget/screens/company_list_widget_test.dart @@ -0,0 +1,413 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:get_it/get_it.dart'; +import 'package:mockito/mockito.dart'; +import 'package:provider/provider.dart'; +import 'package:superport/screens/company/company_list_redesign.dart'; +import 'package:superport/screens/company/controllers/company_list_controller.dart'; +import 'package:superport/services/company_service.dart'; +import 'package:superport/services/auth_service.dart'; +import 'package:superport/services/mock_data_service.dart'; +import 'package:superport/models/company_model.dart'; +import 'package:superport/models/address_model.dart'; +import 'package:superport/core/errors/failures.dart'; + +import '../../helpers/test_helpers.dart'; +import '../../helpers/simple_mock_services.dart'; +import '../../helpers/simple_mock_services.mocks.dart'; +import '../../helpers/mock_data_helpers.dart'; + +void main() { + late MockCompanyService mockCompanyService; + late MockAuthService mockAuthService; + late MockMockDataService mockDataService; + late GetIt getIt; + + setUp(() { + // GetIt 초기화 + getIt = setupTestGetIt(); + + // Mock 서비스 생성 + mockCompanyService = MockCompanyService(); + mockAuthService = MockAuthService(); + mockDataService = MockMockDataService(); + + // Mock 서비스 등록 + getIt.registerSingleton(mockCompanyService); + getIt.registerSingleton(mockAuthService); + getIt.registerSingleton(mockDataService); + + // 기본 Mock 설정 + SimpleMockServiceHelpers.setupAuthServiceMock(mockAuthService, isLoggedIn: true); + SimpleMockServiceHelpers.setupCompanyServiceMock(mockCompanyService); + SimpleMockServiceHelpers.setupMockDataServiceMock(mockDataService); + }); + + tearDown(() { + getIt.reset(); + }); + + group('회사 목록 화면 Widget 테스트', () { + testWidgets('초기 화면 렌더링 테스트', (WidgetTester tester) async { + // Arrange & Act + await pumpTestWidget( + tester, + const CompanyListRedesign(), + ); + + await pumpAndSettleWithTimeout(tester); + + // Assert + expect(find.text('회사 관리'), findsOneWidget); // 앱바 타이틀 + expect(find.byType(TextField), findsOneWidget); // 검색 필드 + expect(find.byIcon(Icons.add), findsOneWidget); // 추가 버튼 + expect(find.byType(DataTable), findsOneWidget); // 데이터 테이블 + }); + + testWidgets('회사 목록 로딩 및 표시 테스트', (WidgetTester tester) async { + // Arrange + final mockCompanies = MockDataHelpers.createMockCompanyList(count: 5); + + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => mockCompanies); + + // Act + await pumpTestWidget( + tester, + const CompanyListRedesign(), + ); + + await pumpAndSettleWithTimeout(tester); + + // Assert + // 각 회사가 테이블에 표시되는지 확인 + for (int i = 0; i < 5; i++) { + expect(find.text('테스트 회사 ${i + 1}'), findsOneWidget); + expect(find.text('담당자 ${i + 1}'), findsOneWidget); + expect(find.text('02-${1000 + i}-${5678 + i}'), findsOneWidget); + } + }); + + testWidgets('회사 검색 기능 테스트', (WidgetTester tester) async { + // Arrange + final allCompanies = MockDataHelpers.createMockCompanyList(count: 10); + final searchedCompanies = [allCompanies[0]]; // 검색 결과로 첫 번째 회사만 + + // 초기 로드 + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => allCompanies); + + // Act + await pumpTestWidget( + tester, + const CompanyListRedesign(), + ); + + await pumpAndSettleWithTimeout(tester); + + // 검색어 입력 + final searchField = find.byType(TextField); + await tester.enterText(searchField, '테스트 회사 1'); + + // 검색 결과 설정 + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: '테스트 회사 1', + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => searchedCompanies); + + // 디바운스 대기 + await tester.pump(const Duration(milliseconds: 600)); + await pumpAndSettleWithTimeout(tester); + + // Assert + expect(find.text('테스트 회사 1'), findsOneWidget); + expect(find.text('테스트 회사 2'), findsNothing); + }); + + testWidgets('회사 추가 버튼 클릭 테스트', (WidgetTester tester) async { + // Arrange + bool navigated = false; + + await pumpTestWidget( + tester, + const CompanyListRedesign(), + routes: { + '/company/add': (context) { + navigated = true; + return const Scaffold(body: Text('회사 추가 화면')); + }, + }, + ); + + await pumpAndSettleWithTimeout(tester); + + // Act + final addButton = find.byIcon(Icons.add); + await tester.tap(addButton); + await pumpAndSettleWithTimeout(tester); + + // Assert + expect(navigated, true); + expect(find.text('회사 추가 화면'), findsOneWidget); + }); + + testWidgets('회사 삭제 다이얼로그 테스트', (WidgetTester tester) async { + // Arrange + final companies = MockDataHelpers.createMockCompanyList(count: 1); + + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => companies); + + when(mockCompanyService.deleteCompany(any)) + .thenAnswer((_) async => null); + + // Act + await pumpTestWidget( + tester, + const CompanyListRedesign(), + ); + + await pumpAndSettleWithTimeout(tester); + + // 삭제 버튼 찾기 및 클릭 + final deleteButton = find.byIcon(Icons.delete).first; + await tester.tap(deleteButton); + await pumpAndSettleWithTimeout(tester); + + // Assert - 삭제 확인 다이얼로그 + expectDialog(tester, title: '삭제 확인', content: '이 회사 정보를 삭제하시겠습니까?'); + + // 삭제 확인 + await tapButtonByText(tester, '삭제'); + await pumpAndSettleWithTimeout(tester); + + // 삭제 메서드 호출 확인 + verify(mockCompanyService.deleteCompany(1)).called(1); + }); + + testWidgets('회사 정보 수정 화면 이동 테스트', (WidgetTester tester) async { + // Arrange + final companies = MockDataHelpers.createMockCompanyList(count: 1); + bool navigated = false; + int? companyId; + + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => companies); + + // Act + await pumpTestWidget( + tester, + const CompanyListRedesign(), + routes: { + '/company/edit': (context) { + navigated = true; + companyId = ModalRoute.of(context)!.settings.arguments as int?; + return const Scaffold(body: Text('회사 수정 화면')); + }, + }, + ); + + await pumpAndSettleWithTimeout(tester); + + // 수정 버튼 찾기 및 클릭 + final editButton = find.byIcon(Icons.edit).first; + await tester.tap(editButton); + await pumpAndSettleWithTimeout(tester); + + // Assert + expect(navigated, true); + expect(companyId, 1); + expect(find.text('회사 수정 화면'), findsOneWidget); + }); + + testWidgets('회사 목록 페이지네이션 테스트', (WidgetTester tester) async { + // Arrange + final firstPageCompanies = MockDataHelpers.createMockCompanyList(count: 20); + final secondPageCompanies = MockDataHelpers.createMockCompanyList(count: 5) + .map((c) => MockDataHelpers.createMockCompany( + id: c.id! + 20, + name: '추가 회사 ${c.id}', + )) + .toList(); + + // 첫 페이지 + when(mockCompanyService.getCompanies( + page: 1, + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => firstPageCompanies); + + // 두 번째 페이지 + when(mockCompanyService.getCompanies( + page: 2, + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => secondPageCompanies); + + // Act + await pumpTestWidget( + tester, + const CompanyListRedesign(), + ); + + await pumpAndSettleWithTimeout(tester); + + // 스크롤하여 더 많은 데이터 로드 + final scrollable = find.byType(SingleChildScrollView).first; + await tester.drag(scrollable, const Offset(0, -500)); + await pumpAndSettleWithTimeout(tester); + + // Assert + verify(mockCompanyService.getCompanies( + page: 1, + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).called(greaterThanOrEqualTo(1)); + }); + + testWidgets('에러 처리 테스트', (WidgetTester tester) async { + // Arrange + SimpleMockServiceHelpers.setupCompanyServiceMock( + mockCompanyService, + getCompaniesSuccess: false, + ); + + // Act + await pumpTestWidget( + tester, + const CompanyListRedesign(), + ); + + await pumpAndSettleWithTimeout(tester); + + // Assert + expect(find.text('회사 목록을 불러오는 중 오류가 발생했습니다.'), findsOneWidget); + }); + + testWidgets('로딩 상태 표시 테스트', (WidgetTester tester) async { + // Arrange + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async { + await Future.delayed(const Duration(seconds: 2)); + return MockDataHelpers.createMockCompanyList(count: 5); + }); + + // Act + await pumpTestWidget( + tester, + const CompanyListRedesign(), + ); + + await tester.pump(); // 로딩 시작 + + // Assert - 로딩 인디케이터 표시 + expectLoading(tester, isLoading: true); + + // 로딩 완료 대기 + await pumpAndSettleWithTimeout(tester); + + // Assert - 로딩 인디케이터 사라짐 + expectLoading(tester, isLoading: false); + }); + + testWidgets('회사 선택 체크박스 테스트', (WidgetTester tester) async { + // Arrange + final companies = MockDataHelpers.createMockCompanyList(count: 3); + + when(mockCompanyService.getCompanies( + page: anyNamed('page'), + perPage: anyNamed('perPage'), + search: anyNamed('search'), + isActive: anyNamed('isActive'), + )).thenAnswer((_) async => companies); + + // Act + await pumpTestWidget( + tester, + const CompanyListRedesign(), + ); + + await pumpAndSettleWithTimeout(tester); + + // 첫 번째 체크박스 선택 + final firstCheckbox = find.byType(Checkbox).at(1); // 헤더 체크박스 제외 + await tester.tap(firstCheckbox); + await pumpAndSettleWithTimeout(tester); + + // 전체 선택 체크박스 클릭 + final selectAllCheckbox = find.byType(Checkbox).first; + await tester.tap(selectAllCheckbox); + await pumpAndSettleWithTimeout(tester); + + // Assert + // 모든 체크박스가 선택되었는지 확인하는 로직 추가 + final checkboxes = find.byType(Checkbox); + expect(checkboxes, findsNWidgets(4)); // 헤더 + 3개 회사 + }); + }); + + group('회사 컨트롤러 단위 테스트', () { + test('검색 키워드 업데이트 테스트', () async { + // Arrange + final controller = CompanyListController(dataService: MockMockDataService()); + + // Act + controller.updateSearchKeyword('테스트'); + + // Assert + expect(controller.searchKeyword, '테스트'); + }); + + test('회사 선택/해제 테스트', () { + // Arrange + final controller = CompanyListController(dataService: MockMockDataService()); + + // Act + controller.toggleCompanySelection(1); + expect(controller.selectedCompanyIds.contains(1), true); + + controller.toggleCompanySelection(1); + expect(controller.selectedCompanyIds.contains(1), false); + }); + + test('전체 선택/해제 테스트', () { + // Arrange + final controller = CompanyListController(dataService: MockMockDataService()); + controller.companies = MockDataHelpers.createMockCompanyList(count: 3); + controller.filteredCompanies = controller.companies; + + // Act - 전체 선택 + controller.toggleSelectAll(); + expect(controller.selectedCompanyIds.length, 3); + + // Act - 전체 해제 + controller.toggleSelectAll(); + expect(controller.selectedCompanyIds.isEmpty, true); + }); + }); +} \ No newline at end of file