- ShadTable: ensure full-width via LayoutBuilder+ConstrainedBox minWidth - BaseListScreen: default data area padding = 0 for table edge-to-edge - Vendor/Model/User/Company/Inventory/Zipcode: set columnSpanExtent per column and add final filler column to absorb remaining width; pin date/status/actions widths; ensure date text is single-line - Equipment: unify card/border style; define fixed column widths + filler; increase checkbox column to 56px to avoid overflow - Rent list: migrate to ShadTable.list with fixed widths + filler column - Rent form dialog: prevent infinite width by bounding ShadProgress with SizedBox and remove Expanded from option rows; add safe selectedOptionBuilder - Admin list: fix const with non-const argument in table column extents - Services/Controller: remove hardcoded perPage=10; use BaseListController perPage; trust server meta (total/totalPages) in equipment pagination - widgets/shad_table: ConstrainedBox(minWidth=viewport) so table stretches Run: flutter analyze → 0 errors (warnings remain).
520 lines
16 KiB
Dart
520 lines
16 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:get_it/get_it.dart';
|
|
import 'package:dio/dio.dart';
|
|
import 'package:superport/data/datasources/remote/api_client.dart';
|
|
import 'package:superport/services/company_service.dart';
|
|
import 'package:superport/services/user_service.dart';
|
|
// License service removed - Sprint 5 migration to Maintenance system
|
|
import 'package:superport/services/warehouse_service.dart';
|
|
import 'package:superport/services/equipment_service.dart';
|
|
import 'package:superport/services/auth_service.dart';
|
|
import 'package:superport/data/models/auth/login_request.dart';
|
|
import 'package:superport/models/company_model.dart';
|
|
import 'package:superport/models/user_model.dart';
|
|
// License model removed - Sprint 5 migration to Maintenance system
|
|
import 'package:superport/models/warehouse_location_model.dart';
|
|
import 'package:superport/models/equipment_unified_model.dart';
|
|
import 'package:superport/core/utils/debug_logger.dart';
|
|
import '../real_api/test_helper.dart';
|
|
|
|
/// 인터랙티브 검색 기능 자동 테스트 및 수정
|
|
///
|
|
/// 각 화면의 검색 기능을 체계적으로 테스트하고
|
|
/// 발견된 문제를 자동으로 수정합니다.
|
|
class InteractiveSearchTest {
|
|
final ApiClient apiClient;
|
|
final GetIt getIt;
|
|
|
|
// 테스트 대상 서비스들
|
|
late CompanyService companyService;
|
|
late UserService userService;
|
|
late LicenseService licenseService;
|
|
late WarehouseService warehouseService;
|
|
late EquipmentService equipmentService;
|
|
late AuthService authService;
|
|
|
|
// 테스트 데이터
|
|
final List<Map<String, dynamic>> testResults = [];
|
|
|
|
InteractiveSearchTest({
|
|
required this.apiClient,
|
|
required this.getIt,
|
|
});
|
|
|
|
/// 서비스 초기화 및 인증
|
|
Future<void> initialize() async {
|
|
print('\n${'=' * 60}');
|
|
print('인터랙티브 검색 기능 테스트 시작');
|
|
print('${'=' * 60}\n');
|
|
|
|
// 서비스 초기화
|
|
companyService = getIt<CompanyService>();
|
|
userService = getIt<UserService>();
|
|
licenseService = getIt<LicenseService>();
|
|
warehouseService = getIt<WarehouseService>();
|
|
equipmentService = getIt<EquipmentService>();
|
|
authService = getIt<AuthService>();
|
|
|
|
// 인증
|
|
await _ensureAuthenticated();
|
|
}
|
|
|
|
/// 인증 확인
|
|
Future<void> _ensureAuthenticated() async {
|
|
try {
|
|
final isAuthenticated = await authService.isLoggedIn();
|
|
|
|
if (!isAuthenticated) {
|
|
print('로그인 시도...');
|
|
final loginRequest = LoginRequest(
|
|
email: 'admin@example.com',
|
|
password: 'password123',
|
|
);
|
|
await authService.login(loginRequest);
|
|
print('로그인 성공');
|
|
}
|
|
} catch (e) {
|
|
print('인증 실패: $e');
|
|
// throw e;
|
|
}
|
|
}
|
|
|
|
/// 모든 검색 기능 테스트 실행
|
|
Future<void> runAllTests() async {
|
|
await initialize();
|
|
|
|
// 1. Company 검색 테스트
|
|
await testCompanySearch();
|
|
|
|
// 2. User 검색 테스트
|
|
await testUserSearch();
|
|
|
|
// 3. License 검색 테스트
|
|
await testLicenseSearch();
|
|
|
|
// 4. Warehouse Location 검색 테스트
|
|
await testWarehouseLocationSearch();
|
|
|
|
// 5. Equipment 검색 테스트 (현재 미구현)
|
|
await testEquipmentSearch();
|
|
|
|
// 결과 출력
|
|
_printTestResults();
|
|
}
|
|
|
|
/// Company 검색 기능 테스트
|
|
Future<void> testCompanySearch() async {
|
|
print('\n--- Company 검색 기능 테스트 ---');
|
|
final result = <String, dynamic>{
|
|
'screen': 'Company',
|
|
'tests': [],
|
|
};
|
|
|
|
try {
|
|
// 1. 빈 검색어 테스트
|
|
print('테스트 1: 빈 검색어로 전체 목록 조회');
|
|
var companies = await companyService.getCompanies(
|
|
page: 1,
|
|
perPage: 10,
|
|
search: null,
|
|
);
|
|
result['tests'].add({
|
|
'name': '빈 검색어 조회',
|
|
'status': companies != null ? 'PASS' : 'FAIL',
|
|
'count': companies.items.length ?? 0,
|
|
});
|
|
print(' 결과: ${companies.items.length ?? 0}개 회사 조회됨');
|
|
|
|
// 2. 특정 검색어 테스트
|
|
if (companies.items.isNotEmpty) {
|
|
final testCompany = companies.items.first;
|
|
final searchKeyword = testCompany.name.substring(0, testCompany.name.length > 3 ? 3 : testCompany.name.length);
|
|
|
|
print('테스트 2: "$searchKeyword" 검색어로 조회');
|
|
companies = await companyService.getCompanies(
|
|
page: 1,
|
|
perPage: 10,
|
|
search: searchKeyword,
|
|
);
|
|
|
|
final hasMatch = companies.items.any((c) =>
|
|
c.name.toLowerCase().contains(searchKeyword.toLowerCase())
|
|
) ?? false;
|
|
|
|
result['tests'].add({
|
|
'name': '검색어 필터링',
|
|
'status': hasMatch ? 'PASS' : 'FAIL',
|
|
'keyword': searchKeyword,
|
|
'count': companies.items.length ?? 0,
|
|
});
|
|
print(' 결과: ${companies.items.length ?? 0}개 회사 조회됨 (매칭: $hasMatch)');
|
|
}
|
|
|
|
// 3. 특수문자 검색 테스트
|
|
print('테스트 3: 특수문자 포함 검색');
|
|
try {
|
|
companies = await companyService.getCompanies(
|
|
page: 1,
|
|
perPage: 10,
|
|
search: '@#\$%^&*',
|
|
);
|
|
result['tests'].add({
|
|
'name': '특수문자 검색',
|
|
'status': 'PASS',
|
|
'count': companies.items.length ?? 0,
|
|
});
|
|
print(' 결과: 에러 없이 처리됨');
|
|
} catch (e) {
|
|
result['tests'].add({
|
|
'name': '특수문자 검색',
|
|
'status': 'FAIL',
|
|
'error': e.toString(),
|
|
});
|
|
print(' 결과: 에러 발생 - $e');
|
|
}
|
|
|
|
// 4. 긴 검색어 테스트
|
|
print('테스트 4: 매우 긴 검색어');
|
|
final longKeyword = 'a' * 100;
|
|
try {
|
|
companies = await companyService.getCompanies(
|
|
page: 1,
|
|
perPage: 10,
|
|
search: longKeyword,
|
|
);
|
|
result['tests'].add({
|
|
'name': '긴 검색어',
|
|
'status': 'PASS',
|
|
'keywordLength': longKeyword.length,
|
|
});
|
|
print(' 결과: 에러 없이 처리됨');
|
|
} catch (e) {
|
|
result['tests'].add({
|
|
'name': '긴 검색어',
|
|
'status': 'FAIL',
|
|
'error': e.toString(),
|
|
});
|
|
print(' 결과: 에러 발생 - $e');
|
|
}
|
|
|
|
// 5. 한글 검색 테스트
|
|
print('테스트 5: 한글 검색어');
|
|
try {
|
|
companies = await companyService.getCompanies(
|
|
page: 1,
|
|
perPage: 10,
|
|
search: '테스트',
|
|
);
|
|
result['tests'].add({
|
|
'name': '한글 검색',
|
|
'status': 'PASS',
|
|
'count': companies.items.length ?? 0,
|
|
});
|
|
print(' 결과: ${companies.items.length ?? 0}개 회사 조회됨');
|
|
} catch (e) {
|
|
result['tests'].add({
|
|
'name': '한글 검색',
|
|
'status': 'FAIL',
|
|
'error': e.toString(),
|
|
});
|
|
print(' 결과: 에러 발생 - $e');
|
|
}
|
|
|
|
result['overall'] = 'PASS';
|
|
} catch (e) {
|
|
result['overall'] = 'FAIL';
|
|
result['error'] = e.toString();
|
|
print('Company 검색 테스트 실패: $e');
|
|
}
|
|
|
|
testResults.add(result);
|
|
}
|
|
|
|
/// User 검색 기능 테스트
|
|
Future<void> testUserSearch() async {
|
|
print('\n--- User 검색 기능 테스트 ---');
|
|
final result = <String, dynamic>{
|
|
'screen': 'User',
|
|
'tests': [],
|
|
};
|
|
|
|
try {
|
|
// 1. 빈 검색어 테스트
|
|
print('테스트 1: 빈 검색어로 전체 목록 조회');
|
|
var users = await userService.getUsers(
|
|
page: 1,
|
|
perPage: 10,
|
|
);
|
|
result['tests'].add({
|
|
'name': '빈 검색어 조회',
|
|
'status': users != null ? 'PASS' : 'FAIL',
|
|
'count': users.items.length ?? 0,
|
|
});
|
|
print(' 결과: ${users.items.length ?? 0}명 사용자 조회됨');
|
|
|
|
// 2. 이름으로 검색
|
|
if (users.items.isNotEmpty) {
|
|
final testUser = users.items.first;
|
|
final searchKeyword = testUser.name.substring(0, testUser.name.length > 2 ? 2 : testUser.name.length);
|
|
|
|
print('테스트 2: "$searchKeyword" 검색어로 조회');
|
|
// UserService에 search 파라미터 지원 확인 필요
|
|
// 현재 UserService API를 확인해야 함
|
|
result['tests'].add({
|
|
'name': '이름 검색',
|
|
'status': 'PENDING',
|
|
'note': 'UserService API 확인 필요',
|
|
});
|
|
}
|
|
|
|
result['overall'] = 'PARTIAL';
|
|
} catch (e) {
|
|
result['overall'] = 'FAIL';
|
|
result['error'] = e.toString();
|
|
print('User 검색 테스트 실패: $e');
|
|
}
|
|
|
|
testResults.add(result);
|
|
}
|
|
|
|
/// License 검색 기능 테스트
|
|
Future<void> testLicenseSearch() async {
|
|
print('\n--- License 검색 기능 테스트 ---');
|
|
final result = <String, dynamic>{
|
|
'screen': 'License',
|
|
'tests': [],
|
|
};
|
|
|
|
try {
|
|
// 1. 빈 검색어 테스트
|
|
print('테스트 1: 빈 검색어로 전체 목록 조회');
|
|
var licenses = await licenseService.getLicenses(
|
|
page: 1,
|
|
perPage: 10,
|
|
);
|
|
result['tests'].add({
|
|
'name': '빈 검색어 조회',
|
|
'status': licenses != null ? 'PASS' : 'FAIL',
|
|
'count': licenses?.items.length ?? 0,
|
|
});
|
|
print(' 결과: ${licenses?.items.length ?? 0}개 라이선스 조회됨');
|
|
|
|
result['overall'] = 'PARTIAL';
|
|
} catch (e) {
|
|
result['overall'] = 'FAIL';
|
|
result['error'] = e.toString();
|
|
print('License 검색 테스트 실패: $e');
|
|
}
|
|
|
|
testResults.add(result);
|
|
}
|
|
|
|
/// Warehouse Location 검색 기능 테스트
|
|
Future<void> testWarehouseLocationSearch() async {
|
|
print('\n--- Warehouse Location 검색 기능 테스트 ---');
|
|
final result = <String, dynamic>{
|
|
'screen': 'WarehouseLocation',
|
|
'tests': [],
|
|
};
|
|
|
|
try {
|
|
// 1. 빈 검색어 테스트
|
|
print('테스트 1: 빈 검색어로 전체 목록 조회');
|
|
var warehouses = await warehouseService.getWarehouseLocations(
|
|
page: 1,
|
|
perPage: 10,
|
|
);
|
|
result['tests'].add({
|
|
'name': '빈 검색어 조회',
|
|
'status': warehouses != null ? 'PASS' : 'FAIL',
|
|
'count': warehouses.items.length ?? 0,
|
|
});
|
|
print(' 결과: ${warehouses.items.length ?? 0}개 창고 위치 조회됨');
|
|
|
|
result['overall'] = 'PARTIAL';
|
|
} catch (e) {
|
|
result['overall'] = 'FAIL';
|
|
result['error'] = e.toString();
|
|
print('Warehouse Location 검색 테스트 실패: $e');
|
|
}
|
|
|
|
testResults.add(result);
|
|
}
|
|
|
|
/// Equipment 검색 기능 테스트
|
|
Future<void> testEquipmentSearch() async {
|
|
print('\n--- Equipment 검색 기능 테스트 ---');
|
|
final result = <String, dynamic>{
|
|
'screen': 'Equipment',
|
|
'tests': [],
|
|
};
|
|
|
|
try {
|
|
// 1. 빈 검색어 테스트
|
|
print('테스트 1: 빈 검색어로 전체 목록 조회');
|
|
var equipments = await equipmentService.getEquipmentsWithStatus(
|
|
page: 1,
|
|
perPage: 10,
|
|
search: null,
|
|
);
|
|
result['tests'].add({
|
|
'name': '빈 검색어 조회',
|
|
'status': equipments != null ? 'PASS' : 'FAIL',
|
|
'count': equipments.items.length ?? 0,
|
|
});
|
|
print(' 결과: ${equipments.items.length ?? 0}개 장비 조회됨');
|
|
|
|
// 2. 특정 검색어 테스트
|
|
if (equipments.items.isNotEmpty) {
|
|
final testEquipment = equipments.items.first;
|
|
final searchKeyword = testEquipment.manufacturer?.substring(0,
|
|
testEquipment.manufacturer!.length > 3 ? 3 : testEquipment.manufacturer!.length) ?? 'test';
|
|
|
|
print('테스트 2: "$searchKeyword" 검색어로 조회');
|
|
equipments = await equipmentService.getEquipmentsWithStatus(
|
|
page: 1,
|
|
perPage: 10,
|
|
search: searchKeyword,
|
|
);
|
|
|
|
final hasMatch = equipments.items.any((e) =>
|
|
(e.manufacturer?.toLowerCase().contains(searchKeyword.toLowerCase()) ?? false) ||
|
|
(e.modelName?.toLowerCase().contains(searchKeyword.toLowerCase()) ?? false) ||
|
|
(e.equipmentNumber.toLowerCase().contains(searchKeyword.toLowerCase()) ?? false)
|
|
) ?? false;
|
|
|
|
result['tests'].add({
|
|
'name': '검색어 필터링',
|
|
'status': hasMatch ? 'PASS' : 'FAIL',
|
|
'keyword': searchKeyword,
|
|
'count': equipments.items.length ?? 0,
|
|
});
|
|
print(' 결과: ${equipments.items.length ?? 0}개 장비 조회됨 (매칭: $hasMatch)');
|
|
}
|
|
|
|
// 3. 특수문자 검색 테스트
|
|
print('테스트 3: 특수문자 포함 검색');
|
|
try {
|
|
equipments = await equipmentService.getEquipmentsWithStatus(
|
|
page: 1,
|
|
perPage: 10,
|
|
search: '@#\$%^&*',
|
|
);
|
|
result['tests'].add({
|
|
'name': '특수문자 검색',
|
|
'status': 'PASS',
|
|
'count': equipments.items.length ?? 0,
|
|
});
|
|
print(' 결과: 에러 없이 처리됨');
|
|
} catch (e) {
|
|
result['tests'].add({
|
|
'name': '특수문자 검색',
|
|
'status': 'FAIL',
|
|
'error': e.toString(),
|
|
});
|
|
print(' 결과: 에러 발생 - $e');
|
|
}
|
|
|
|
// 4. 한글 검색 테스트
|
|
print('테스트 4: 한글 검색어');
|
|
try {
|
|
equipments = await equipmentService.getEquipmentsWithStatus(
|
|
page: 1,
|
|
perPage: 10,
|
|
search: '테스트',
|
|
);
|
|
result['tests'].add({
|
|
'name': '한글 검색',
|
|
'status': 'PASS',
|
|
'count': equipments.items.length ?? 0,
|
|
});
|
|
print(' 결과: ${equipments.items.length ?? 0}개 장비 조회됨');
|
|
} catch (e) {
|
|
result['tests'].add({
|
|
'name': '한글 검색',
|
|
'status': 'FAIL',
|
|
'error': e.toString(),
|
|
});
|
|
print(' 결과: 에러 발생 - $e');
|
|
}
|
|
|
|
result['overall'] = 'PASS';
|
|
result['needsImplementation'] = false;
|
|
} catch (e) {
|
|
result['overall'] = 'FAIL';
|
|
result['error'] = e.toString();
|
|
print('Equipment 검색 테스트 실패: $e');
|
|
}
|
|
|
|
testResults.add(result);
|
|
}
|
|
|
|
/// 테스트 결과 출력
|
|
void _printTestResults() {
|
|
print('\n${'=' * 60}');
|
|
print('테스트 결과 요약');
|
|
print('${'=' * 60}\n');
|
|
|
|
for (final result in testResults) {
|
|
print('화면: ${result['screen']}');
|
|
print('전체 결과: ${result['overall']}');
|
|
|
|
if (result['tests'] != null) {
|
|
for (final test in result['tests']) {
|
|
print(' - ${test['name']}: ${test['status']}');
|
|
if (test['note'] != null) {
|
|
print(' 참고: ${test['note']}');
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result['needsImplementation'] == true) {
|
|
print(' ⚠️ 구현 필요!');
|
|
}
|
|
|
|
print('');
|
|
}
|
|
|
|
// 수정이 필요한 항목 식별
|
|
print('수정 필요 항목:');
|
|
if (testResults.any((r) => r['screen'] == 'Equipment' && r['overall'] == 'PASS')) {
|
|
print('✅ Equipment 화면: 검색 기능 구현 완료!');
|
|
} else {
|
|
print('❌ Equipment 화면: 검색 기능 오류');
|
|
}
|
|
print('⚠️ User/License: API 응답 형식 문제 수정 필요');
|
|
}
|
|
}
|
|
|
|
/// 테스트 실행
|
|
const bool RUN_EXTERNAL_TESTS = bool.fromEnvironment('RUN_EXTERNAL_TESTS');
|
|
void main() async {
|
|
if (!RUN_EXTERNAL_TESTS) {
|
|
test('External tests disabled', () {}, skip: 'Enable with --dart-define=RUN_EXTERNAL_TESTS=true');
|
|
return;
|
|
}
|
|
// 실제 API 환경 설정
|
|
await RealApiTestHelper.setupTestEnvironment();
|
|
final getIt = GetIt.instance;
|
|
|
|
group('인터랙티브 검색 기능 테스트', () {
|
|
setUpAll(() async {
|
|
// 로그인 및 토큰 설정
|
|
await RealApiTestHelper.loginAndGetToken();
|
|
});
|
|
|
|
tearDownAll(() async {
|
|
await RealApiTestHelper.teardownTestEnvironment();
|
|
});
|
|
|
|
test('모든 화면의 검색 기능 테스트', () async {
|
|
final tester = InteractiveSearchTest(
|
|
apiClient: getIt.get<ApiClient>(),
|
|
getIt: getIt,
|
|
);
|
|
|
|
await tester.runAllTests();
|
|
}, timeout: Timeout(Duration(minutes: 5)));
|
|
});
|
|
}
|