## 주요 변경사항 ### 아키텍처 개선 - Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리) - Use Case 패턴 도입으로 비즈니스 로직 캡슐화 - Repository 패턴으로 데이터 접근 추상화 - 의존성 주입 구조 개선 ### 상태 관리 최적화 - 모든 Controller에서 불필요한 상태 관리 로직 제거 - 페이지네이션 로직 통일 및 간소화 - 에러 처리 로직 개선 (에러 메시지 한글화) - 로딩 상태 관리 최적화 ### Mock 서비스 제거 - MockDataService 완전 제거 - 모든 화면을 실제 API 전용으로 전환 - 불필요한 Mock 관련 코드 정리 ### UI/UX 개선 - Overview 화면 대시보드 기능 강화 - 라이선스 만료 알림 위젯 추가 - 사이드바 네비게이션 개선 - 일관된 UI 컴포넌트 사용 ### 코드 품질 - 중복 코드 제거 및 함수 추출 - 파일별 책임 분리 명확화 - 테스트 코드 업데이트 ## 영향 범위 - 모든 화면의 Controller 리팩토링 - API 통신 레이어 구조 개선 - 에러 처리 및 로깅 시스템 개선 ## 향후 계획 - 단위 테스트 커버리지 확대 - 통합 테스트 시나리오 추가 - 성능 모니터링 도구 통합
297 lines
10 KiB
Dart
297 lines
10 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:get_it/get_it.dart';
|
||
import 'package:superport/data/datasources/remote/api_client.dart';
|
||
import 'package:superport/services/auth_service.dart';
|
||
import 'package:superport/services/license_service.dart';
|
||
import 'package:superport/services/company_service.dart';
|
||
import 'package:superport/models/license_model.dart';
|
||
import 'package:superport/models/company_model.dart';
|
||
import 'package:superport/models/address_model.dart';
|
||
import 'package:superport/data/models/auth/login_request.dart';
|
||
import 'dart:math';
|
||
import 'real_api/test_helper.dart';
|
||
|
||
void main() {
|
||
late GetIt getIt;
|
||
late AuthService authService;
|
||
late LicenseService licenseService;
|
||
late CompanyService companyService;
|
||
|
||
setUpAll(() async {
|
||
// RealApiTestHelper를 사용하여 Mock Storage와 함께 테스트 환경 설정
|
||
await RealApiTestHelper.setupTestEnvironment();
|
||
|
||
// GetIt 인스턴스 가져오기
|
||
getIt = GetIt.instance;
|
||
|
||
// 서비스 초기화
|
||
authService = getIt<AuthService>();
|
||
licenseService = getIt<LicenseService>();
|
||
companyService = getIt<CompanyService>();
|
||
|
||
// 로그인
|
||
print('🔐 로그인 중...');
|
||
final loginResult = await authService.login(
|
||
LoginRequest(
|
||
email: 'admin@superport.kr',
|
||
password: 'admin123!',
|
||
),
|
||
);
|
||
|
||
loginResult.fold(
|
||
(failure) => throw Exception('로그인 실패: $failure'),
|
||
(response) => print('✅ 로그인 성공: ${response.user.email}'),
|
||
);
|
||
});
|
||
|
||
tearDownAll(() async {
|
||
await authService.logout();
|
||
await RealApiTestHelper.teardownTestEnvironment();
|
||
});
|
||
|
||
group('라이센스 관리 통합 테스트', () {
|
||
late Company testCompany;
|
||
final random = Random();
|
||
|
||
setUpAll(() async {
|
||
// 테스트용 회사 조회 또는 생성
|
||
print('🏢 테스트용 회사 준비 중...');
|
||
final companies = await companyService.getCompanies();
|
||
if (companies.isNotEmpty) {
|
||
testCompany = companies.first;
|
||
print('✅ 기존 회사 사용: ${testCompany.name}');
|
||
} else {
|
||
// 회사가 없으면 생성
|
||
testCompany = await companyService.createCompany(
|
||
Company(
|
||
name: 'Test Company ${random.nextInt(10000)}',
|
||
address: Address(
|
||
detailAddress: '서울시 강남구 테헤란로 123',
|
||
),
|
||
contactName: '테스트 담당자',
|
||
contactPhone: '010-1234-5678',
|
||
contactEmail: 'test@test.com',
|
||
),
|
||
);
|
||
print('✅ 새 회사 생성: ${testCompany.name}');
|
||
}
|
||
});
|
||
|
||
test('1. 라이센스 목록 조회', () async {
|
||
print('\n📋 라이센스 목록 조회 테스트...');
|
||
|
||
final licenses = await licenseService.getLicenses();
|
||
|
||
print('✅ 라이센스 ${licenses.length}개 조회 성공');
|
||
expect(licenses, isA<List<License>>());
|
||
});
|
||
|
||
test('2. 라이센스 생성', () async {
|
||
print('\n➕ 라이센스 생성 테스트...');
|
||
|
||
final newLicense = License(
|
||
licenseKey: 'TEST-${DateTime.now().millisecondsSinceEpoch}',
|
||
productName: 'Flutter Test Product',
|
||
vendor: 'Test Vendor',
|
||
licenseType: 'subscription',
|
||
userCount: 10,
|
||
purchaseDate: DateTime.now(),
|
||
expiryDate: DateTime.now().add(Duration(days: 365)),
|
||
purchasePrice: 100000.0,
|
||
companyId: testCompany.id,
|
||
remark: '통합 테스트용 라이센스',
|
||
isActive: true,
|
||
);
|
||
|
||
final createdLicense = await licenseService.createLicense(newLicense);
|
||
|
||
print('✅ 라이센스 생성 성공: ${createdLicense.licenseKey}');
|
||
expect(createdLicense.id, isNotNull);
|
||
expect(createdLicense.licenseKey, equals(newLicense.licenseKey));
|
||
expect(createdLicense.companyId, equals(testCompany.id));
|
||
});
|
||
|
||
test('3. 라이센스 상세 조회', () async {
|
||
print('\n🔍 라이센스 상세 조회 테스트...');
|
||
|
||
// 먼저 목록을 조회하여 ID 획득
|
||
final licenses = await licenseService.getLicenses();
|
||
if (licenses.isEmpty) {
|
||
print('⚠️ 조회할 라이센스가 없습니다.');
|
||
return;
|
||
}
|
||
|
||
final targetId = licenses.first.id!;
|
||
final license = await licenseService.getLicenseById(targetId);
|
||
|
||
print('✅ 라이센스 상세 조회 성공: ${license.licenseKey}');
|
||
expect(license.id, equals(targetId));
|
||
expect(license.licenseKey, isNotEmpty);
|
||
});
|
||
|
||
test('4. 라이센스 수정', () async {
|
||
print('\n✏️ 라이센스 수정 테스트...');
|
||
|
||
// 먼저 라이센스 생성
|
||
final newLicense = License(
|
||
licenseKey: 'UPDATE-TEST-${DateTime.now().millisecondsSinceEpoch}',
|
||
productName: 'Original Product',
|
||
vendor: 'Original Vendor',
|
||
licenseType: 'perpetual',
|
||
userCount: 5,
|
||
purchaseDate: DateTime.now(),
|
||
expiryDate: DateTime.now().add(Duration(days: 180)),
|
||
purchasePrice: 50000.0,
|
||
companyId: testCompany.id,
|
||
remark: '수정 테스트용',
|
||
isActive: true,
|
||
);
|
||
|
||
final createdLicense = await licenseService.createLicense(newLicense);
|
||
print('✅ 수정할 라이센스 생성: ${createdLicense.licenseKey}');
|
||
|
||
// 라이센스 수정
|
||
final updatedLicense = License(
|
||
id: createdLicense.id,
|
||
licenseKey: createdLicense.licenseKey,
|
||
productName: 'Updated Product',
|
||
vendor: 'Updated Vendor',
|
||
licenseType: 'subscription',
|
||
userCount: 20,
|
||
purchaseDate: createdLicense.purchaseDate,
|
||
expiryDate: DateTime.now().add(Duration(days: 730)),
|
||
purchasePrice: 200000.0,
|
||
companyId: testCompany.id,
|
||
remark: '수정됨',
|
||
isActive: true,
|
||
);
|
||
|
||
final result = await licenseService.updateLicense(updatedLicense);
|
||
|
||
print('✅ 라이센스 수정 성공');
|
||
expect(result.productName, equals('Updated Product'));
|
||
expect(result.vendor, equals('Updated Vendor'));
|
||
expect(result.userCount, equals(20));
|
||
});
|
||
|
||
test('5. 라이센스 삭제', () async {
|
||
print('\n🗑️ 라이센스 삭제 테스트...');
|
||
|
||
// 삭제할 라이센스 생성
|
||
final newLicense = License(
|
||
licenseKey: 'DELETE-TEST-${DateTime.now().millisecondsSinceEpoch}',
|
||
productName: 'Delete Test Product',
|
||
vendor: 'Delete Test Vendor',
|
||
licenseType: 'trial',
|
||
userCount: 1,
|
||
purchaseDate: DateTime.now(),
|
||
expiryDate: DateTime.now().add(Duration(days: 30)),
|
||
purchasePrice: 0.0,
|
||
companyId: testCompany.id,
|
||
remark: '삭제 테스트용',
|
||
isActive: true,
|
||
);
|
||
|
||
final createdLicense = await licenseService.createLicense(newLicense);
|
||
print('✅ 삭제할 라이센스 생성: ${createdLicense.licenseKey}');
|
||
|
||
// 라이센스 삭제
|
||
await licenseService.deleteLicense(createdLicense.id!);
|
||
print('✅ 라이센스 삭제 성공');
|
||
|
||
// 삭제 확인
|
||
try {
|
||
await licenseService.getLicenseById(createdLicense.id!);
|
||
fail('삭제된 라이센스가 여전히 조회됩니다');
|
||
} catch (e) {
|
||
print('✅ 삭제 확인: 라이센스가 정상적으로 삭제되었습니다');
|
||
}
|
||
});
|
||
|
||
test('6. 만료 예정 라이센스 조회', () async {
|
||
print('\n⏰ 만료 예정 라이센스 조회 테스트...');
|
||
|
||
// 30일 이내 만료 예정 라이센스 생성
|
||
final expiringLicense = License(
|
||
licenseKey: 'EXPIRING-${DateTime.now().millisecondsSinceEpoch}',
|
||
productName: 'Soon Expiring Product',
|
||
vendor: 'Test Vendor',
|
||
licenseType: 'subscription',
|
||
userCount: 5,
|
||
purchaseDate: DateTime.now(),
|
||
expiryDate: DateTime.now().add(Duration(days: 15)), // 15일 후 만료
|
||
purchasePrice: 10000.0,
|
||
companyId: testCompany.id,
|
||
remark: '만료 예정 테스트',
|
||
isActive: true,
|
||
);
|
||
|
||
await licenseService.createLicense(expiringLicense);
|
||
print('✅ 만료 예정 라이센스 생성 (15일 후 만료)');
|
||
|
||
final expiringLicenses = await licenseService.getExpiringLicenses(days: 30);
|
||
|
||
print('✅ 만료 예정 라이센스 ${expiringLicenses.length}개 조회');
|
||
expect(expiringLicenses, isA<List<License>>());
|
||
});
|
||
|
||
test('7. 에러 처리 테스트', () async {
|
||
print('\n❌ 에러 처리 테스트...');
|
||
|
||
// 잘못된 ID로 조회
|
||
try {
|
||
await licenseService.getLicenseById(999999);
|
||
fail('존재하지 않는 라이센스 조회가 성공했습니다');
|
||
} catch (e) {
|
||
print('✅ 잘못된 ID 에러 처리 성공: $e');
|
||
}
|
||
|
||
// 필수 필드 누락
|
||
try {
|
||
final invalidLicense = License(
|
||
licenseKey: '', // 빈 라이센스 키
|
||
productName: 'Invalid Product',
|
||
companyId: testCompany.id,
|
||
);
|
||
await licenseService.createLicense(invalidLicense);
|
||
fail('유효하지 않은 라이센스 생성이 성공했습니다');
|
||
} catch (e) {
|
||
print('✅ 유효성 검증 에러 처리 성공: $e');
|
||
}
|
||
});
|
||
|
||
test('8. 페이지네이션 테스트', () async {
|
||
print('\n📄 페이지네이션 테스트...');
|
||
|
||
// 첫 페이지
|
||
final page1 = await licenseService.getLicenses(page: 1, perPage: 5);
|
||
print('✅ 1페이지: ${page1.length}개 라이센스');
|
||
|
||
// 두 번째 페이지
|
||
final page2 = await licenseService.getLicenses(page: 2, perPage: 5);
|
||
print('✅ 2페이지: ${page2.length}개 라이센스');
|
||
|
||
expect(page1.length, lessThanOrEqualTo(5));
|
||
expect(page2.length, lessThanOrEqualTo(5));
|
||
});
|
||
|
||
test('9. 필터링 테스트', () async {
|
||
print('\n🔎 필터링 테스트...');
|
||
|
||
// 활성 라이센스만 조회
|
||
final activeLicenses = await licenseService.getLicenses(isActive: true);
|
||
print('✅ 활성 라이센스: ${activeLicenses.length}개');
|
||
|
||
// 특정 회사 라이센스만 조회
|
||
final companyLicenses = await licenseService.getLicenses(
|
||
companyId: testCompany.id,
|
||
);
|
||
print('✅ ${testCompany.name} 라이센스: ${companyLicenses.length}개');
|
||
|
||
expect(activeLicenses, isA<List<License>>());
|
||
expect(companyLicenses, isA<List<License>>());
|
||
});
|
||
});
|
||
|
||
print('\n🎉 모든 라이센스 통합 테스트 완료!');
|
||
} |