// ignore_for_file: avoid_print import 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:superport/services/license_service.dart'; import 'package:superport/services/company_service.dart'; import 'package:superport/services/user_service.dart'; import 'package:superport/models/license_model.dart'; import '../base/base_screen_test.dart'; import '../../framework/models/test_models.dart'; import '../../framework/models/error_models.dart'; import '../../framework/models/report_models.dart' as report_models; /// 라이선스(License) 화면 자동화 테스트 /// /// 이 테스트는 라이선스 관리 전체 프로세스를 자동으로 실행하고, /// 에러 발생 시 자동으로 진단하고 수정합니다. class LicenseScreenTest extends BaseScreenTest { late LicenseService licenseService; late CompanyService companyService; late UserService userService; LicenseScreenTest({ required super.apiClient, required super.getIt, required super.testContext, required super.errorDiagnostics, required super.autoFixer, required super.dataGenerator, required super.reportCollector, }); @override ScreenMetadata getScreenMetadata() { return ScreenMetadata( screenName: 'LicenseScreen', controllerType: LicenseService, relatedEndpoints: [ ApiEndpoint( path: '/api/v1/licenses', method: 'POST', description: '라이선스 생성', ), ApiEndpoint( path: '/api/v1/licenses', method: 'GET', description: '라이선스 목록 조회', ), ApiEndpoint( path: '/api/v1/licenses/{id}', method: 'GET', description: '라이선스 상세 조회', ), ApiEndpoint( path: '/api/v1/licenses/{id}', method: 'PUT', description: '라이선스 수정', ), ApiEndpoint( path: '/api/v1/licenses/{id}', method: 'DELETE', description: '라이선스 삭제', ), ApiEndpoint( path: '/api/v1/licenses/{id}/assign', method: 'POST', description: '라이선스 할당', ), ApiEndpoint( path: '/api/v1/licenses/{id}/unassign', method: 'POST', description: '라이선스 할당 해제', ), ApiEndpoint( path: '/api/v1/licenses/expiring', method: 'GET', description: '만료 예정 라이선스 조회', ), ], screenCapabilities: { 'license_management': { 'crud': true, 'expiry_management': true, 'license_key_validation': true, 'duplicate_check': true, 'user_assignment': true, 'search': true, 'pagination': true, 'status_filter': true, 'type_filter': true, }, }, ); } @override Future initializeServices() async { licenseService = getIt(); companyService = getIt(); userService = getIt(); } @override dynamic getService() => licenseService; @override String getResourceType() => 'license'; @override Map getDefaultFilters() { return { 'isActive': true, // 기본적으로 활성 라이선스만 필터링 }; } @override Future> detectCustomFeatures(ScreenMetadata metadata) async { final features = []; // 라이선스 관리 기능 테스트 features.add(TestableFeature( featureName: 'License Management', type: FeatureType.custom, testCases: [ // 정상 라이선스 생성 시나리오 TestCase( name: 'Normal license creation', execute: (data) async { await performNormalLicenseCreation(data); }, verify: (data) async { await verifyNormalLicenseCreation(data); }, ), // 만료일 관리 시나리오 - 만료 임박 TestCase( name: 'Expiring license management', execute: (data) async { await performExpiringLicenseManagement(data); }, verify: (data) async { await verifyExpiringLicenseManagement(data); }, ), // 만료일 관리 시나리오 - 만료된 라이선스 TestCase( name: 'Expired license management', execute: (data) async { await performExpiredLicenseManagement(data); }, verify: (data) async { await verifyExpiredLicenseManagement(data); }, ), // 라이선스 키 유효성 검증 시나리오 TestCase( name: 'License key validation', execute: (data) async { await performLicenseKeyValidation(data); }, verify: (data) async { await verifyLicenseKeyValidation(data); }, ), // 중복 라이선스 키 처리 시나리오 TestCase( name: 'Duplicate license key handling', execute: (data) async { await performDuplicateLicenseKeyHandling(data); }, verify: (data) async { await verifyDuplicateLicenseKeyHandling(data); }, ), // 필수 필드 누락 시나리오 TestCase( name: 'Missing required fields', execute: (data) async { await performMissingRequiredFields(data); }, verify: (data) async { await verifyMissingRequiredFields(data); }, ), // 라이선스 타입별 테스트 - 영구 라이선스 TestCase( name: 'Perpetual license type test', execute: (data) async { await performPerpetualLicenseTest(data); }, verify: (data) async { await verifyPerpetualLicenseTest(data); }, ), // 라이선스 타입별 테스트 - 기간제 라이선스 TestCase( name: 'Term license type test', execute: (data) async { await performTermLicenseTest(data); }, verify: (data) async { await verifyTermLicenseTest(data); }, ), // 사용자 할당/해제 시나리오 TestCase( name: 'User assignment and unassignment', execute: (data) async { await performUserAssignment(data); }, verify: (data) async { await verifyUserAssignment(data); }, ), ], metadata: { 'description': '라이선스 관리 프로세스 자동화 테스트', }, )); return features; } /// 정상 라이선스 생성 프로세스 Future performNormalLicenseCreation(TestData data) async { _log('=== 정상 라이선스 생성 프로세스 시작 ==='); try { // 1. 라이선스 데이터 자동 생성 _log('라이선스 데이터 자동 생성 중...'); final licenseData = await dataGenerator.generate( GenerationStrategy( dataType: License, fields: [ FieldGeneration( fieldName: 'licenseKey', valueType: String, strategy: 'unique', prefix: 'LIC-', ), FieldGeneration( fieldName: 'productName', valueType: String, strategy: 'predefined', values: ['Microsoft Office', 'Adobe Creative Suite', 'JetBrains IDE', 'Visual Studio', 'Slack', 'Zoom'], ), FieldGeneration( fieldName: 'vendor', valueType: String, strategy: 'predefined', values: ['Microsoft', 'Adobe', 'JetBrains', 'Atlassian', 'Oracle', 'Salesforce'], ), FieldGeneration( fieldName: 'licenseType', valueType: String, strategy: 'predefined', values: ['구독형', '영구', '평가판', '교육용', '기업용'], ), FieldGeneration( fieldName: 'userCount', valueType: int, strategy: 'fixed', value: 10, ), FieldGeneration( fieldName: 'purchaseDate', valueType: DateTime, strategy: 'date', format: 'yyyy-MM-dd', ), FieldGeneration( fieldName: 'expiryDate', valueType: DateTime, strategy: 'date', format: 'yyyy-MM-dd', ), FieldGeneration( fieldName: 'purchasePrice', valueType: double, strategy: 'fixed', value: 100000.0, ), ], relationships: [], constraints: { 'licenseKey': ['required', 'minLength:5'], 'productName': ['required'], 'userCount': ['min:1'], }, ), ); _log('생성된 라이선스 데이터: ${licenseData.toJson()}'); // 2. 회사 ID 확보 final companyId = testContext.getData('testCompanyId') as int?; if (companyId != null) { licenseData.data['companyId'] = companyId; } // 3. 라이선스 생성 _log('라이선스 생성 API 호출 중...'); License? createdLicense; try { final licenseReq = licenseData.data as Map; // License 객체 생성 final newLicense = License( licenseKey: licenseReq['licenseKey'] ?? 'TEST-${DateTime.now().millisecondsSinceEpoch}', productName: licenseReq['productName'] ?? '테스트 제품', vendor: licenseReq['vendor'], licenseType: licenseReq['licenseType'], userCount: licenseReq['userCount'] ?? 1, purchaseDate: licenseReq['purchaseDate'] ?? DateTime.now(), expiryDate: licenseReq['expiryDate'], purchasePrice: licenseReq['purchasePrice']?.toDouble(), companyId: licenseReq['companyId'], branchId: licenseReq['branchId'], remark: '자동화 테스트로 생성된 라이선스', ); createdLicense = await licenseService.createLicense(newLicense); _log('라이선스 생성 성공: ID=${createdLicense.id}'); testContext.addCreatedResourceId('license', createdLicense.id.toString()); } catch (e) { _log('라이선스 생성 실패: $e'); // 에러 진단 final diagnosis = await errorDiagnostics.diagnose( ApiError( endpoint: '/api/v1/licenses', method: 'POST', statusCode: 400, message: e.toString(), requestBody: licenseData.toJson(), timestamp: DateTime.now(), requestUrl: '/api/v1/licenses', requestMethod: 'POST', ), ); _log('에러 진단 결과: ${diagnosis.errorType} - ${diagnosis.description}'); // 자동 수정 final fixResult = await autoFixer.attemptAutoFix(diagnosis); if (!fixResult.success) { throw Exception('자동 수정 실패: ${fixResult.error}'); } // 수정된 데이터로 재시도 _log('수정된 데이터로 재시도...'); final fixedLicense = License( licenseKey: 'FIXED-${LicenseTestData.generateLicenseKey()}', productName: 'Fixed ${LicenseTestData.generateProductName()}', vendor: LicenseTestData.generateVendor(), licenseType: 'perpetual', userCount: 10, purchaseDate: DateTime.now(), remark: '자동 수정된 라이선스', ); createdLicense = await licenseService.createLicense(fixedLicense); _log('라이선스 생성 성공 (재시도): ID=${createdLicense.id}'); testContext.addCreatedResourceId('license', createdLicense.id.toString()); } // 4. 생성된 라이선스 조회 _log('생성된 라이선스 조회 중...'); final licenseDetail = await licenseService.getLicenseById(createdLicense.id!); _log('라이선스 상세 조회 성공: ${licenseDetail.productName}'); testContext.setData('createdLicense', createdLicense); testContext.setData('licenseDetail', licenseDetail); testContext.setData('processSuccess', true); } catch (e) { _log('예상치 못한 오류 발생: $e'); testContext.setData('processSuccess', false); testContext.setData('lastError', e.toString()); } } /// 정상 라이선스 생성 검증 Future verifyNormalLicenseCreation(TestData data) async { final processSuccess = testContext.getData('processSuccess') ?? false; expect(processSuccess, isTrue, reason: '라이선스 생성 프로세스가 실패했습니다'); final createdLicense = testContext.getData('createdLicense') as License?; expect(createdLicense, isNotNull, reason: '라이선스가 생성되지 않았습니다'); expect(createdLicense!.id, isNotNull, reason: '라이선스 ID가 없습니다'); expect(createdLicense.licenseKey, isNotEmpty, reason: '라이선스 키가 비어있습니다'); _log('✓ 정상 라이선스 생성 프로세스 검증 완료'); } /// 만료 임박 라이선스 관리 시나리오 Future performExpiringLicenseManagement(TestData data) async { _log('=== 만료 임박 라이선스 관리 시나리오 시작 ==='); try { // 1. 30일 후 만료되는 라이선스 생성 final expiryDate = DateTime.now().add(Duration(days: 15)); // 15일 후 만료 final expiringLicense = License( licenseKey: 'EXPIRING-${LicenseTestData.generateLicenseKey()}', productName: '곧 만료될 제품', vendor: LicenseTestData.generateVendor(), licenseType: 'subscription', userCount: 5, purchaseDate: DateTime.now().subtract(Duration(days: 350)), expiryDate: expiryDate, purchasePrice: 500000, companyId: testContext.getData('testCompanyId'), remark: '만료 임박 테스트용 라이선스', ); final created = await licenseService.createLicense(expiringLicense); testContext.addCreatedResourceId('license', created.id.toString()); _log('만료 임박 라이선스 생성 완료: ${created.id}, 만료일: ${expiryDate.toIso8601String()}'); // 2. 만료 예정 라이선스 조회 _log('만료 예정 라이선스 조회 중...'); final expiringLicenses = await licenseService.getExpiringLicenses(days: 30); _log('30일 이내 만료 예정 라이선스: ${expiringLicenses.items.length}개'); // 3. 방금 생성한 라이선스가 포함되어 있는지 확인 final hasOurLicense = expiringLicenses.items.any((l) => l.id == created.id); testContext.setData('expiringLicenseCreated', created); testContext.setData('expiringLicensesList', expiringLicenses); testContext.setData('hasOurExpiringLicense', hasOurLicense); testContext.setData('expiringSuccess', true); } catch (e) { _log('만료 임박 라이선스 관리 중 오류: $e'); testContext.setData('expiringSuccess', false); testContext.setData('expiringError', e.toString()); } } /// 만료 임박 라이선스 관리 검증 Future verifyExpiringLicenseManagement(TestData data) async { final success = testContext.getData('expiringSuccess') ?? false; expect(success, isTrue, reason: '만료 임박 라이선스 관리가 실패했습니다'); final expiringLicenses = testContext.getData('expiringLicensesList') as List?; expect(expiringLicenses, isNotNull, reason: '만료 예정 라이선스 목록을 가져오지 못했습니다'); // API가 우리가 생성한 라이선스를 정확히 반환하는지는 타이밍에 따라 다를 수 있음 // 하지만 만료 예정 라이선스 조회 기능 자체는 작동해야 함 expect(expiringLicenses, isA>(), reason: '올바른 형식의 목록이 아닙니다'); _log('✓ 만료 임박 라이선스 관리 시나리오 검증 완료'); } /// 만료된 라이선스 관리 시나리오 Future performExpiredLicenseManagement(TestData data) async { _log('=== 만료된 라이선스 관리 시나리오 시작 ==='); try { // 1. 이미 만료된 라이선스 생성 final expiredDate = DateTime.now().subtract(Duration(days: 30)); // 30일 전 만료 final expiredLicense = License( licenseKey: 'EXPIRED-${LicenseTestData.generateLicenseKey()}', productName: '만료된 제품', vendor: LicenseTestData.generateVendor(), licenseType: 'subscription', userCount: 3, purchaseDate: DateTime.now().subtract(Duration(days: 395)), expiryDate: expiredDate, purchasePrice: 300000, companyId: testContext.getData('testCompanyId'), remark: '만료된 라이선스 테스트', isActive: false, // 만료된 라이선스는 비활성화 ); final created = await licenseService.createLicense(expiredLicense); testContext.addCreatedResourceId('license', created.id.toString()); _log('만료된 라이선스 생성 완료: ${created.id}, 만료일: ${expiredDate.toIso8601String()}'); // 2. 비활성 라이선스 필터링 테스트 _log('비활성 라이선스 조회 중...'); final inactiveLicenses = await licenseService.getLicenses( page: 1, perPage: 100, isActive: false, ); _log('비활성 라이선스: ${inactiveLicenses.items.length}개'); // 3. 만료된 라이선스가 비활성 목록에 있는지 확인 final hasExpiredLicense = inactiveLicenses.items.any((l) => l.id == created.id); testContext.setData('expiredLicenseCreated', created); testContext.setData('inactiveLicensesList', inactiveLicenses); testContext.setData('hasExpiredLicense', hasExpiredLicense); testContext.setData('expiredSuccess', true); } catch (e) { _log('만료된 라이선스 관리 중 오류: $e'); testContext.setData('expiredSuccess', false); testContext.setData('expiredError', e.toString()); } } /// 만료된 라이선스 관리 검증 Future verifyExpiredLicenseManagement(TestData data) async { final success = testContext.getData('expiredSuccess') ?? false; expect(success, isTrue, reason: '만료된 라이선스 관리가 실패했습니다'); final inactiveLicenses = testContext.getData('inactiveLicensesList') as List?; expect(inactiveLicenses, isNotNull, reason: '비활성 라이선스 목록을 가져오지 못했습니다'); _log('✓ 만료된 라이선스 관리 시나리오 검증 완료'); } /// 라이선스 키 유효성 검증 시나리오 Future performLicenseKeyValidation(TestData data) async { _log('=== 라이선스 키 유효성 검증 시나리오 시작 ==='); final invalidKeys = [ '', // 빈 키 'A', // 너무 짧은 키 'ABC-', // 불완전한 형식 'TEST KEY WITH SPACES', // 공백 포함 '테스트-라이선스-키', // 한글 포함 'A' * 300, // 너무 긴 키 ]; int validationErrors = 0; for (final invalidKey in invalidKeys) { try { final invalidLicense = License( licenseKey: invalidKey, productName: '유효성 검증 테스트', vendor: 'Test Vendor', licenseType: 'test', userCount: 1, purchaseDate: DateTime.now(), ); await licenseService.createLicense(invalidLicense); _log('⚠️ 잘못된 키가 허용됨: "$invalidKey"'); } catch (e) { _log('✓ 예상된 검증 에러 발생: "$invalidKey" - ${e.toString().split('\n').items.first}'); validationErrors++; } } // 유효한 키 테스트 final validKeys = [ 'ABCD-EFGH-IJKL-MNOP', 'TEST123456789', 'LICENSE-2024-001', 'PRO_VERSION_1.0', ]; int validKeysAccepted = 0; for (final validKey in validKeys) { try { final validLicense = License( licenseKey: validKey, productName: '유효한 라이선스', vendor: 'Test Vendor', licenseType: 'test', userCount: 1, purchaseDate: DateTime.now(), ); final created = await licenseService.createLicense(validLicense); testContext.addCreatedResourceId('license', created.id.toString()); _log('✓ 유효한 키 허용됨: "$validKey"'); validKeysAccepted++; } catch (e) { _log('⚠️ 유효한 키가 거부됨: "$validKey" - ${e.toString()}'); } } testContext.setData('invalidKeysRejected', validationErrors); testContext.setData('validKeysAccepted', validKeysAccepted); testContext.setData('keyValidationSuccess', true); } /// 라이선스 키 유효성 검증 확인 Future verifyLicenseKeyValidation(TestData data) async { final success = testContext.getData('keyValidationSuccess') ?? false; expect(success, isTrue, reason: '키 유효성 검증이 실행되지 않았습니다'); final invalidKeysRejected = testContext.getData('invalidKeysRejected') ?? 0; final validKeysAccepted = testContext.getData('validKeysAccepted') ?? 0; // 적어도 일부 잘못된 키는 거부되어야 함 expect(invalidKeysRejected, greaterThan(0), reason: '잘못된 키가 전혀 거부되지 않았습니다'); // 적어도 일부 유효한 키는 허용되어야 함 expect(validKeysAccepted, greaterThan(0), reason: '유효한 키가 전혀 허용되지 않았습니다'); _log('✓ 라이선스 키 유효성 검증 시나리오 검증 완료'); } /// 중복 라이선스 키 처리 시나리오 Future performDuplicateLicenseKeyHandling(TestData data) async { _log('=== 중복 라이선스 키 처리 시나리오 시작 ==='); try { // 1. 첫 번째 라이선스 생성 final uniqueKey = 'UNIQUE-${DateTime.now().millisecondsSinceEpoch}'; final firstLicense = License( licenseKey: uniqueKey, productName: '첫 번째 제품', vendor: 'Test Vendor', licenseType: 'perpetual', userCount: 10, purchaseDate: DateTime.now(), ); final created = await licenseService.createLicense(firstLicense); testContext.addCreatedResourceId('license', created.id.toString()); _log('첫 번째 라이선스 생성 성공: ${created.id}'); // 2. 동일한 키로 두 번째 라이선스 생성 시도 final duplicateLicense = License( licenseKey: uniqueKey, // 동일한 키 사용 productName: '두 번째 제품', vendor: 'Another Vendor', licenseType: 'subscription', userCount: 5, purchaseDate: DateTime.now(), ); bool duplicateRejected = false; try { await licenseService.createLicense(duplicateLicense); _log('⚠️ 중복 라이선스 키가 허용되었습니다'); } catch (e) { _log('✓ 예상된 중복 에러 발생: ${e.toString().split('\n').items.first}'); duplicateRejected = true; } testContext.setData('firstLicenseId', created.id); testContext.setData('duplicateKey', uniqueKey); testContext.setData('duplicateRejected', duplicateRejected); testContext.setData('duplicateSuccess', true); } catch (e) { _log('중복 라이선스 키 처리 중 오류: $e'); testContext.setData('duplicateSuccess', false); testContext.setData('duplicateError', e.toString()); } } /// 중복 라이선스 키 처리 검증 Future verifyDuplicateLicenseKeyHandling(TestData data) async { final success = testContext.getData('duplicateSuccess') ?? false; expect(success, isTrue, reason: '중복 라이선스 키 처리가 실행되지 않았습니다'); // 중복 키가 거부되었는지 확인 // 일부 시스템은 중복을 허용할 수 있으므로 경고만 표시 final duplicateRejected = testContext.getData('duplicateRejected') ?? false; if (!duplicateRejected) { _log('⚠️ 경고: 시스템이 중복 라이선스 키를 허용합니다'); } _log('✓ 중복 라이선스 키 처리 시나리오 검증 완료'); } /// 필수 필드 누락 시나리오 Future performMissingRequiredFields(TestData data) async { _log('=== 필수 필드 누락 시나리오 시작 ==='); // 필수 필드가 누락된 라이선스 데이터 try { final incompleteLicense = License( licenseKey: '', // 빈 라이선스 키 productName: null, // null 제품명 vendor: null, licenseType: null, userCount: null, purchaseDate: null, expiryDate: null, purchasePrice: null, ); await licenseService.createLicense(incompleteLicense); fail('필수 필드가 누락된 데이터로 라이선스가 생성되어서는 안 됩니다'); } catch (e) { _log('예상된 에러 발생: $e'); // 에러 진단 final diagnosis = await errorDiagnostics.diagnose( ApiError( endpoint: '/api/v1/licenses', method: 'POST', statusCode: 400, message: e.toString(), requestBody: { 'licenseKey': '', 'productName': null, }, timestamp: DateTime.now(), requestUrl: '/api/v1/licenses', requestMethod: 'POST', ), ); expect(diagnosis.errorType, equals(ErrorType.missingRequiredField)); _log('진단 결과: ${diagnosis.missingFields?.items.length ?? 0}개 필드 누락'); // 자동 수정 final fixResult = await autoFixer.attemptAutoFix(diagnosis); if (!fixResult.success) { throw Exception('자동 수정 실패: ${fixResult.error}'); } // 수정된 데이터로 재시도 _log('수정된 데이터로 재시도...'); final fixedLicense = License( licenseKey: 'AUTO-FIXED-${LicenseTestData.generateLicenseKey()}', productName: 'Auto Fixed Product', vendor: 'Auto Fix Vendor', licenseType: 'perpetual', userCount: 1, purchaseDate: DateTime.now(), ); final created = await licenseService.createLicense(fixedLicense); testContext.addCreatedResourceId('license', created.id.toString()); testContext.setData('missingFieldsFixed', true); testContext.setData('fixedLicense', created); } } /// 필수 필드 누락 시나리오 검증 Future verifyMissingRequiredFields(TestData data) async { final missingFieldsFixed = testContext.getData('missingFieldsFixed') ?? false; expect(missingFieldsFixed, isTrue, reason: '필수 필드 누락 문제가 해결되지 않았습니다'); final fixedLicense = testContext.getData('fixedLicense'); expect(fixedLicense, isNotNull, reason: '수정된 라이선스가 생성되지 않았습니다'); _log('✓ 필수 필드 누락 시나리오 검증 완료'); } /// 영구 라이선스 타입 테스트 Future performPerpetualLicenseTest(TestData data) async { _log('=== 영구 라이선스 타입 테스트 시작 ==='); try { // 영구 라이선스는 만료일이 없음 final perpetualLicense = License( licenseKey: 'PERPETUAL-${LicenseTestData.generateLicenseKey()}', productName: '영구 라이선스 제품', vendor: 'Perpetual Vendor', licenseType: 'perpetual', userCount: 999, // 무제한을 나타내는 큰 수 purchaseDate: DateTime.now(), expiryDate: null, // 만료일 없음 purchasePrice: 5000000, remark: '영구 라이선스 - 만료 없음', ); final created = await licenseService.createLicense(perpetualLicense); testContext.addCreatedResourceId('license', created.id.toString()); _log('영구 라이선스 생성 성공: ${created.id}'); // 생성된 라이선스 확인 final retrieved = await licenseService.getLicenseById(created.id!); testContext.setData('perpetualLicense', created); testContext.setData('retrievedPerpetual', retrieved); testContext.setData('perpetualSuccess', true); } catch (e) { _log('영구 라이선스 생성 중 오류: $e'); testContext.setData('perpetualSuccess', false); testContext.setData('perpetualError', e.toString()); } } /// 영구 라이선스 타입 검증 Future verifyPerpetualLicenseTest(TestData data) async { final success = testContext.getData('perpetualSuccess') ?? false; expect(success, isTrue, reason: '영구 라이선스 생성이 실패했습니다'); final perpetualLicense = testContext.getData('perpetualLicense') as License?; expect(perpetualLicense, isNotNull, reason: '영구 라이선스가 생성되지 않았습니다'); expect(perpetualLicense!.licenseType, equals('perpetual'), reason: '라이선스 타입이 올바르지 않습니다'); expect(perpetualLicense.expiryDate, isNull, reason: '영구 라이선스에 만료일이 설정되었습니다'); _log('✓ 영구 라이선스 타입 테스트 검증 완료'); } /// 기간제 라이선스 타입 테스트 Future performTermLicenseTest(TestData data) async { _log('=== 기간제 라이선스 타입 테스트 시작 ==='); try { // 1년 기간제 라이선스 final startDate = DateTime.now(); final endDate = startDate.add(Duration(days: 365)); final termLicense = License( licenseKey: 'TERM-${LicenseTestData.generateLicenseKey()}', productName: '기간제 라이선스 제품', vendor: 'Term Vendor', licenseType: 'subscription', userCount: 20, purchaseDate: startDate, expiryDate: endDate, // 1년 후 만료 purchasePrice: 1200000, // 연간 구독료 remark: '1년 기간제 라이선스', ); final created = await licenseService.createLicense(termLicense); testContext.addCreatedResourceId('license', created.id.toString()); _log('기간제 라이선스 생성 성공: ${created.id}'); _log('시작일: ${startDate.toIso8601String()}'); _log('만료일: ${endDate.toIso8601String()}'); // 생성된 라이선스 확인 final retrieved = await licenseService.getLicenseById(created.id!); testContext.setData('termLicense', created); testContext.setData('retrievedTerm', retrieved); testContext.setData('termSuccess', true); } catch (e) { _log('기간제 라이선스 생성 중 오류: $e'); testContext.setData('termSuccess', false); testContext.setData('termError', e.toString()); } } /// 기간제 라이선스 타입 검증 Future verifyTermLicenseTest(TestData data) async { final success = testContext.getData('termSuccess') ?? false; expect(success, isTrue, reason: '기간제 라이선스 생성이 실패했습니다'); final termLicense = testContext.getData('termLicense') as License?; expect(termLicense, isNotNull, reason: '기간제 라이선스가 생성되지 않았습니다'); expect(termLicense!.licenseType, equals('subscription'), reason: '라이선스 타입이 올바르지 않습니다'); expect(termLicense.expiryDate, isNotNull, reason: '기간제 라이선스에 만료일이 없습니다'); // 만료일이 구매일보다 나중인지 확인 if (termLicense.purchaseDate != null && termLicense.expiryDate != null) { expect( termLicense.expiryDate!.isAfter(termLicense.purchaseDate!), isTrue, reason: '만료일이 구매일보다 이전입니다', ); } _log('✓ 기간제 라이선스 타입 테스트 검증 완료'); } /// 사용자 할당/해제 시나리오 Future performUserAssignment(TestData data) async { _log('=== 사용자 할당/해제 시나리오 시작 ==='); try { // 1. 라이선스 생성 final license = License( licenseKey: 'ASSIGN-${LicenseTestData.generateLicenseKey()}', productName: '사용자 할당 테스트 제품', vendor: 'Assignment Vendor', licenseType: 'user', userCount: 1, // 단일 사용자 라이선스 purchaseDate: DateTime.now(), expiryDate: DateTime.now().add(Duration(days: 365)), purchasePrice: 500000, ); final created = await licenseService.createLicense(license); testContext.addCreatedResourceId('license', created.id.toString()); _log('할당용 라이선스 생성 완료: ${created.id}'); // 2. 사용자 목록 조회 (할당할 사용자 찾기) final users = await userService.getUsers(page: 1, perPage: 10); if (users.items.isNotEmpty) { final targetUser = users.items.first; _log('할당 대상 사용자: ${targetUser.name} (ID: ${targetUser.id})'); // 3. 라이선스 할당 _log('라이선스 할당 중...'); final assignedLicense = await licenseService.assignLicense(created.id!, targetUser.id!); _log('라이선스 할당 성공'); expect(assignedLicense.assignedUserId, equals(targetUser.id), reason: '사용자 ID가 일치하지 않습니다'); // 4. 할당 해제 _log('라이선스 할당 해제 중...'); final unassignedLicense = await licenseService.unassignLicense(created.id!); _log('라이선스 할당 해제 성공'); expect(unassignedLicense.assignedUserId, isNull, reason: '사용자 ID가 제거되지 않았습니다'); testContext.setData('assignmentSuccess', true); testContext.setData('assignedUserId', targetUser.id); } else { _log('할당할 사용자가 없습니다. 할당 테스트를 건너뜁니다.'); testContext.setData('assignmentSuccess', true); testContext.setData('noUsersAvailable', true); } } catch (e) { _log('사용자 할당/해제 중 오류: $e'); testContext.setData('assignmentSuccess', false); testContext.setData('assignmentError', e.toString()); } } /// 사용자 할당/해제 검증 Future verifyUserAssignment(TestData data) async { final success = testContext.getData('assignmentSuccess') ?? false; expect(success, isTrue, reason: '사용자 할당/해제가 실패했습니다'); final noUsersAvailable = testContext.getData('noUsersAvailable') ?? false; if (!noUsersAvailable) { final assignedUserId = testContext.getData('assignedUserId'); expect(assignedUserId, isNotNull, reason: '사용자가 할당되지 않았습니다'); } _log('✓ 사용자 할당/해제 시나리오 검증 완료'); } // BaseScreenTest의 추상 메서드 구현 @override Future performCreateOperation(TestData data) async { final licenseData = data.data; final license = License( licenseKey: licenseData['licenseKey'] ?? 'TEST-${DateTime.now().millisecondsSinceEpoch}', productName: licenseData['productName'] ?? 'Test Product', vendor: licenseData['vendor'], licenseType: licenseData['licenseType'] ?? 'perpetual', userCount: licenseData['userCount'] ?? 1, purchaseDate: licenseData['purchaseDate'] ?? DateTime.now(), expiryDate: licenseData['expiryDate'], purchasePrice: licenseData['purchasePrice']?.toDouble(), companyId: licenseData['companyId'], branchId: licenseData['branchId'], remark: licenseData['remark'], ); return await licenseService.createLicense(license); } @override Future performReadOperation(TestData data) async { return await licenseService.getLicenses( page: data.data['page'] ?? 1, perPage: data.data['perPage'] ?? 20, isActive: data.data['isActive'], companyId: data.data['companyId'], licenseType: data.data['licenseType'], ); } @override Future performUpdateOperation(dynamic resourceId, Map updateData) async { // 먼저 기존 라이선스 정보 조회 final existing = await licenseService.getLicenseById(resourceId as int); // 업데이트할 라이선스 객체 생성 final updated = License( id: existing.id, licenseKey: existing.licenseKey, // 키는 변경 불가 productName: updateData['productName'] ?? existing.productName, vendor: updateData['vendor'] ?? existing.vendor, licenseType: updateData['licenseType'] ?? existing.licenseType, userCount: updateData['userCount'] ?? existing.userCount, purchaseDate: updateData['purchaseDate'] ?? existing.purchaseDate, expiryDate: updateData['expiryDate'] ?? existing.expiryDate, purchasePrice: updateData['purchasePrice']?.toDouble() ?? existing.purchasePrice, companyId: existing.companyId, branchId: existing.branchId, assignedUserId: existing.assignedUserId, remark: updateData['remark'] ?? existing.remark, isActive: updateData['isActive'] ?? existing.isActive, ); return await licenseService.updateLicense(updated); } @override Future performDeleteOperation(dynamic resourceId) async { await licenseService.deleteLicense(resourceId as int); } @override dynamic extractResourceId(dynamic resource) { return (resource as License).id; } // 헬퍼 메서드 void _log(String message) { final timestamp = DateTime.now().toString(); debugPrint('[$timestamp] [License] $message'); // 리포트 수집기에도 로그 추가 reportCollector.addStep( report_models.StepReport( stepName: 'License Management', timestamp: DateTime.now(), success: !message.contains('실패') && !message.contains('에러'), message: message, details: {}, ), ); } } /// 라이선스 테스트 데이터 생성 유틸리티 class LicenseTestData { static final random = Random(); // 라이선스 키 생성기 static String generateLicenseKey() { final prefixes = ['PRO', 'ENT', 'STD', 'TRIAL', 'DEV', 'PROD']; final prefix = prefixes[random.nextInt(prefixes.items.length)]; final timestamp = DateTime.now().millisecondsSinceEpoch.toString().substring(6); final randomPart = random.nextInt(9999).toString().padLeft(4, '0'); return '$prefix-$timestamp-$randomPart'; } // 제품명 생성기 static String generateProductName() { final products = [ 'Microsoft Office 365', 'Adobe Creative Cloud', 'AutoCAD 2024', 'Visual Studio Enterprise', 'IntelliJ IDEA Ultimate', 'Slack Business+', 'Zoom Pro', 'Jira Software', 'GitHub Enterprise', 'Docker Enterprise', 'VMware vSphere', 'Salesforce CRM', 'SAP S/4HANA', 'Oracle Database', 'MongoDB Enterprise', ]; return products[random.nextInt(products.items.length)]; } // 벤더명 생성기 static String generateVendor() { final vendors = [ 'Microsoft', 'Adobe', 'Autodesk', 'JetBrains', 'Atlassian', 'Oracle', 'SAP', 'IBM', 'VMware', 'Salesforce', 'Google', 'Amazon', 'Docker', 'Red Hat', 'Elastic', ]; return vendors[random.nextInt(vendors.items.length)]; } // 라이선스 타입 생성기 static String generateLicenseType() { final types = ['perpetual', 'subscription', 'trial', 'oem', 'academic', 'nfr']; return types[random.nextInt(types.items.length)]; } // 구매일 생성기 (과거 2년 이내) static DateTime generatePurchaseDate() { final daysAgo = random.nextInt(730); // 최대 2년 전 return DateTime.now().subtract(Duration(days: daysAgo)); } // 만료일 생성기 (구매일 기준 1-3년 후, 또는 null) static DateTime? generateExpiryDate() { // 30% 확률로 영구 라이선스 (만료일 없음) if (random.nextDouble() < 0.3) { return null; } // 나머지는 미래 날짜 final daysFromNow = random.nextInt(1095) - 365; // -365 ~ +730일 return DateTime.now().add(Duration(days: daysFromNow)); } } // 테스트 실행을 위한 main 함수 void main() { group('License Screen Automated Test', () { test('This is a screen test class, not a standalone test', () { // 이 클래스는 BaseScreenTest를 상속받아 프레임워크를 통해 실행됩니다 // 직접 실행하려면 run_license_test.dart를 사용하세요 expect(true, isTrue); }); }); }