refactor: Repository 패턴 적용 및 Clean Architecture 완성
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled

## 주요 변경사항

### 🏗️ Architecture
- Repository 패턴 전면 도입 (인터페이스/구현체 분리)
- Domain Layer에 Repository 인터페이스 정의
- Data Layer에 Repository 구현체 배치
- UseCase 의존성을 Service에서 Repository로 전환

### 📦 Dependency Injection
- GetIt 기반 DI Container 재구성 (lib/injection_container.dart)
- Repository 인터페이스와 구현체 등록
- Service와 Repository 공존 (마이그레이션 기간)

### 🔄 Migration Status
완료:
- License 모듈 (6개 UseCase)
- Warehouse Location 모듈 (5개 UseCase)

진행중:
- Auth 모듈 (2/5 UseCase)
- Company 모듈 (1/6 UseCase)

대기:
- User 모듈 (7개 UseCase)
- Equipment 모듈 (4개 UseCase)

### 🎯 Controller 통합
- 중복 Controller 제거 (with_usecase 버전)
- 단일 Controller로 통합
- UseCase 패턴 직접 적용

### 🧹 코드 정리
- 임시 파일 제거 (test_*.md, task.md)
- Node.js 아티팩트 제거 (package.json)
- 불필요한 테스트 파일 정리

###  테스트 개선
- Real API 중심 테스트 구조
- Mock 제거, 실제 API 엔드포인트 사용
- 통합 테스트 프레임워크 강화

## 기술적 영향
- 의존성 역전 원칙 적용
- 레이어 간 결합도 감소
- 테스트 용이성 향상
- 확장성 및 유지보수성 개선

## 다음 단계
1. User/Equipment 모듈 Repository 마이그레이션
2. Service Layer 점진적 제거
3. 캐싱 전략 구현
4. 성능 최적화
This commit is contained in:
JiWoong Sul
2025-08-11 20:14:10 +09:00
parent d64aa26157
commit 731dcd816b
105 changed files with 5225 additions and 3941 deletions

View File

@@ -221,7 +221,7 @@ class WarehouseAutomatedTest extends BaseScreenTest {
perPage: 20,
);
// PaginatedResponse의 items를 반환하여 List처럼 사용할 수 있도록 함
return result.items;
return result;
}
@override
@@ -312,9 +312,9 @@ class WarehouseTestData {
final purposes = ['물류', '보관', '배송', '집하', '분류', '냉동', '냉장', '특수', '일반', '대형'];
final suffixes = ['창고', '센터', '물류센터', '보관소', '집하장'];
final type = types[random.nextInt(types.items.length)];
final purpose = purposes[random.nextInt(purposes.items.length)];
final suffix = suffixes[random.nextInt(suffixes.items.length)];
final type = types[random.nextInt(types.length)];
final purpose = purposes[random.nextInt(purposes.length)];
final suffix = suffixes[random.nextInt(suffixes.length)];
final timestamp = DateTime.now().millisecondsSinceEpoch;
return '$type $purpose$suffix - TEST$timestamp';
@@ -331,9 +331,9 @@ class WarehouseTestData {
'산업단지', '물류단지', '유통단지', '첨단산업단지', '일반산업단지', '국가산업단지'
];
final city = cities[random.nextInt(cities.items.length)];
final district = districts[random.nextInt(districts.items.length)];
final industrial = industrialAreas[random.nextInt(industrialAreas.items.length)];
final city = cities[random.nextInt(cities.length)];
final district = districts[random.nextInt(districts.length)];
final industrial = industrialAreas[random.nextInt(industrialAreas.length)];
final number = random.nextInt(500) + 1;
final detail = '$industrial $number블록 ${random.nextInt(10) + 1}';
@@ -362,7 +362,7 @@ class WarehouseTestData {
final featureCount = random.nextInt(3) + 1; // 1-3개 특징
for (int i = 0; i < featureCount; i++) {
final feature = features[random.nextInt(features.items.length)];
final feature = features[random.nextInt(features.length)];
if (!selectedFeatures.contains(feature)) {
selectedFeatures.add(feature);
}
@@ -376,8 +376,8 @@ class WarehouseTestData {
final lastNames = ['', '', '', '', '', '', '', '', '', ''];
final firstNames = ['창고장', '소장', '센터장', '팀장', '과장', '부장', '이사', '실장'];
final lastName = lastNames[random.nextInt(lastNames.items.length)];
final firstName = firstNames[random.nextInt(firstNames.items.length)];
final lastName = lastNames[random.nextInt(lastNames.length)];
final firstName = firstNames[random.nextInt(firstNames.length)];
return '$lastName$firstName';
}
@@ -385,7 +385,7 @@ class WarehouseTestData {
// 연락처 생성기
static String generateContact() {
final areaCodes = ['02', '031', '032', '033', '041', '042', '043', '051', '052', '053'];
final areaCode = areaCodes[random.nextInt(areaCodes.items.length)];
final areaCode = areaCodes[random.nextInt(areaCodes.length)];
final middle = random.nextInt(9000) + 1000;
final last = random.nextInt(9000) + 1000;
return '$areaCode-$middle-$last';
@@ -394,7 +394,7 @@ class WarehouseTestData {
// 창고 용량 생성기 (평방미터)
static int generateCapacity() {
final capacities = [500, 1000, 1500, 2000, 3000, 5000, 10000, 15000, 20000];
return capacities[random.nextInt(capacities.items.length)];
return capacities[random.nextInt(capacities.length)];
}
}
@@ -421,7 +421,7 @@ extension on WarehouseAutomatedTest {
await _testWarehouseUpdate(createdWarehouse.id);
// 6. 창고 검색 테스트
await _testWarehouseSearch(createdWarehouse.name.split(' ').items.first);
await _testWarehouseSearch(createdWarehouse.name.split(' ').first);
// 7. 활성/비활성 필터링 테스트
await _testActiveFiltering();
@@ -603,12 +603,12 @@ extension on WarehouseAutomatedTest {
try {
// search 파라미터가 지원되는지 확인
final searchResults = await warehouseService.searchWarehouseLocations(
keyword: searchKeyword.split(' ').items.first, // 첫 단어만 사용
keyword: searchKeyword.split(' ').first, // 첫 단어만 사용
page: 1,
perPage: 10,
);
_log('검색 결과: ${searchResults.items.length}개 창고');
_log('검색 결과: ${searchResults.length}개 창고');
testContext.setData('searchResults', searchResults);
testContext.setData('searchSuccess', true);
} catch (e) {
@@ -620,13 +620,13 @@ extension on WarehouseAutomatedTest {
page: 1,
perPage: 50,
);
final allWarehouses = allWarehousesResult.items;
final allWarehouses = allWarehousesResult;
final filtered = allWarehouses.items.where((w) =>
final filtered = allWarehouses.where((w) =>
w.name.toLowerCase().contains(searchKeyword.toLowerCase())
).toList();
_log('필터링 결과: ${filtered.items.length}개 창고');
_log('필터링 결과: ${filtered.length}개 창고');
testContext.setData('searchResults', filtered);
testContext.setData('searchSuccess', true);
} catch (e2) {
@@ -647,8 +647,8 @@ extension on WarehouseAutomatedTest {
perPage: 10,
isActive: true,
);
final activeWarehouses = activeWarehousesResult.items;
_log('활성 창고: ${activeWarehouses.items.length}');
final activeWarehouses = activeWarehousesResult;
_log('활성 창고: ${activeWarehouses.length}');
// 비활성 창고만 조회
_log('비활성 창고 조회 중...');
@@ -657,8 +657,8 @@ extension on WarehouseAutomatedTest {
perPage: 10,
isActive: false,
);
final inactiveWarehouses = inactiveWarehousesResult.items;
_log('비활성 창고: ${inactiveWarehouses.items.length}');
final inactiveWarehouses = inactiveWarehousesResult;
_log('비활성 창고: ${inactiveWarehouses.length}');
testContext.setData('activeWarehouses', activeWarehouses);
testContext.setData('inactiveWarehouses', inactiveWarehouses);
@@ -856,7 +856,7 @@ extension on WarehouseAutomatedTest {
);
// expect(diagnosis.errorType, equals(ErrorType.missingRequiredField));
_log('진단 결과: ${diagnosis.missingFields?.items.length ?? 0}개 필드 누락');
_log('진단 결과: ${diagnosis.missingFields?.length ?? 0}개 필드 누락');
// 자동 수정
final fixResult = await autoFixer.attemptAutoFix(diagnosis);
@@ -903,7 +903,7 @@ extension on WarehouseAutomatedTest {
// 1. 창고별 장비 목록 조회 (초기 상태)
_log('창고별 장비 목록 조회 중...');
final initialEquipment = await warehouseService.getWarehouseEquipment(warehouse.id);
_log('초기 장비 수: ${initialEquipment.items.length}');
_log('초기 장비 수: ${initialEquipment.length}');
// 2. 장비 입고 시뮬레이션 (실제로는 Equipment 서비스를 통해 수행)
_log('장비 입고 프로세스는 Equipment 서비스에서 처리됩니다');
@@ -911,17 +911,17 @@ extension on WarehouseAutomatedTest {
// 3. 사용 중인 창고 목록 조회
_log('사용 중인 창고 목록 조회 중...');
final inUseWarehouses = await warehouseService.getInUseWarehouseLocations();
_log('사용 중인 창고 수: ${inUseWarehouses.items.length}');
_log('사용 중인 창고 수: ${inUseWarehouses.length}');
// 장비가 있는 창고는 사용 중으로 표시되어야 함
if (initialEquipment.items.isNotEmpty) {
final isInUse = inUseWarehouses.items.any((w) => w.id == warehouse.id);
if (initialEquipment.isNotEmpty) {
final isInUse = inUseWarehouses.any((w) => w.id == warehouse.id);
// expect(isInUse, isTrue, reason: '장비가 있는 창고가 사용 중으로 표시되지 않았습니다');
}
testContext.setData('equipmentIntegrationSuccess', true);
testContext.setData('initialEquipmentCount', initialEquipment.items.length);
testContext.setData('inUseWarehouseCount', inUseWarehouses.items.length);
testContext.setData('initialEquipmentCount', initialEquipment.length);
testContext.setData('inUseWarehouseCount', inUseWarehouses.length);
} catch (e) {
_log('장비 연동 중 오류 발생: $e');
@@ -952,7 +952,7 @@ extension on WarehouseAutomatedTest {
page: 1,
perPage: 100,
);
_log('전체 창고 수: ${allWarehouses.items.length}');
_log('전체 창고 수: ${allWarehouses.length}');
// 2. 활성 창고만 필터링
_log('활성 창고만 필터링...');
@@ -961,7 +961,7 @@ extension on WarehouseAutomatedTest {
perPage: 100,
isActive: true,
);
_log('활성 창고 수: ${activeWarehouses.items.length}');
_log('활성 창고 수: ${activeWarehouses.length}');
// 3. 비활성 창고 필터링
_log('비활성 창고 필터링...');
@@ -970,21 +970,21 @@ extension on WarehouseAutomatedTest {
perPage: 100,
isActive: false,
);
_log('비활성 창고 수: ${inactiveWarehouses.items.length}');
_log('비활성 창고 수: ${inactiveWarehouses.length}');
// 4. 사용 중인 창고 목록
_log('사용 중인 창고 목록 조회...');
final inUseWarehouses = await warehouseService.getInUseWarehouseLocations();
_log('사용 중인 창고 수: ${inUseWarehouses.items.length}');
_log('사용 중인 창고 수: ${inUseWarehouses.length}');
// 검증: 활성 + 비활성 = 전체 (대략적으로)
// 페이지네이션 때문에 정확히 일치하지 않을 수 있음
testContext.setData('inUseManagementSuccess', true);
testContext.setData('totalWarehouses', allWarehouses.items.length);
testContext.setData('activeWarehouses', activeWarehouses.items.length);
testContext.setData('inactiveWarehouses', inactiveWarehouses.items.length);
testContext.setData('inUseWarehouses', inUseWarehouses.items.length);
testContext.setData('totalWarehouses', allWarehouses.length);
testContext.setData('activeWarehouses', activeWarehouses.length);
testContext.setData('inactiveWarehouses', inactiveWarehouses.length);
testContext.setData('inUseWarehouses', inUseWarehouses.length);
} catch (e) {
_log('사용 중인 창고 관리 중 오류 발생: $e');
@@ -1024,8 +1024,8 @@ extension WarehouseServiceExtension on WarehouseService {
// 실제 검색 API가 있다면 사용
// 없다면 전체 목록을 가져와서 필터링
final allResult = await getWarehouseLocations(page: page, perPage: perPage * 5);
final all = allResult.items;
return all.where((w) =>
final all = allResult;
return all.items.where((w) =>
w.name.toLowerCase().contains(keyword.toLowerCase()) ||
(w.address.toString().toLowerCase().contains(keyword.toLowerCase()))
).toList();