Files
superport/test/integration/automated/user_real_api_test.dart
JiWoong Sul 49b203d366
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
feat(ui): full‑width ShadTable across app; fix rent dialog width; correct equipment pagination
- 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).
2025-09-09 22:38:08 +09:00

474 lines
16 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter_test/flutter_test.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'test_result.dart';
import 'dart:math';
/// 사용자 관리 실제 API 테스트
///
/// 테스트 항목:
/// 1. 사용자 목록 조회
/// 2. 사용자 생성 (관리자/일반)
/// 3. 사용자 상세 조회
/// 4. 사용자 정보 수정
/// 5. 비밀번호 변경
/// 6. 사용자 권한 변경
/// 7. 사용자 비활성화/활성화
/// 8. 사용자 삭제
/// 9. 사용자 검색
/// 10. 권한별 접근 제어 테스트
Future<TestResult> runUserTests({
required Dio dio,
required String authToken,
bool verbose = false,
}) async {
const String baseUrl = 'http://43.201.34.104:8080/api/v1';
final Random random = Random();
final Stopwatch stopwatch = Stopwatch()..start();
int totalTests = 0;
int passedTests = 0;
final List<String> failedTestNames = [];
// 테스트용 사용자 데이터
final testUserData = {
'names': ['김철수', '이영희', '박민수', '정수진', '최동욱'],
'departments': ['개발팀', '영업팀', '마케팅팀', '인사팀', '운영팀'],
'positions': ['대리', '과장', '차장', '부장', '팀장'],
'emails': ['test1@superport.kr', 'test2@superport.kr', 'test3@superport.kr'],
'phones': ['010-1234-5678', '010-2345-6789', '010-3456-7890'],
};
group('🧑‍💼 사용자 관리 API 테스트', () {
// 생성된 사용자 ID 저장
final List<int> createdUserIds = [];
test('1. 사용자 목록 조회', () async {
totalTests++;
if (verbose) debugPrint('\n📋 사용자 목록 조회 테스트...');
try {
final response = await dio.get(
'$baseUrl/users',
queryParameters: {
'page': 1,
'per_page': 20,
},
);
if (response.statusCode == 200) {
final users = response.data['data'] ?? [];
if (verbose) {
debugPrint('✅ 사용자 ${users.length}개 조회 성공');
}
passedTests++;
} else {
failedTestNames.add('사용자 목록 조회');
if (verbose) debugPrint('❌ 사용자 목록 조회 실패: ${response.statusCode}');
}
} catch (e) {
failedTestNames.add('사용자 목록 조회');
if (verbose) debugPrint('❌ 사용자 목록 조회 오류: $e');
}
});
test('2. 사용자 생성 (일반 사용자)', () async {
totalTests++;
if (verbose) debugPrint('\n 일반 사용자 생성 테스트...');
final timestamp = DateTime.now().millisecondsSinceEpoch;
final nameIndex = random.nextInt(testUserData['names']!.length);
final deptIndex = random.nextInt(testUserData['departments']!.length);
try {
final newUser = {
'username': 'user_$timestamp',
'email': 'user_$timestamp@superport.kr',
'password': 'Password123!',
'name': testUserData['names']![nameIndex],
'department': testUserData['departments']![deptIndex],
'position': testUserData['positions']![random.nextInt(testUserData['positions']!.length)],
'phone': '010-${1000 + random.nextInt(9000)}-${1000 + random.nextInt(9000)}',
'role': 'user', // 일반 사용자
};
final response = await dio.post(
'$baseUrl/users',
data: newUser,
);
if (response.statusCode == 200 || response.statusCode == 201) {
final createdUser = response.data['data'];
if (createdUser != null && createdUser['id'] != null) {
createdUserIds.add(createdUser['id']);
if (verbose) {
debugPrint('✅ 일반 사용자 생성 성공: ${createdUser['name']} (${createdUser['email']})');
}
passedTests++;
} else {
failedTestNames.add('사용자 생성 (일반)');
if (verbose) debugPrint('❌ 사용자 생성 응답에 ID가 없음');
}
} else {
failedTestNames.add('사용자 생성 (일반)');
if (verbose) debugPrint('❌ 사용자 생성 실패: ${response.statusCode}');
}
} catch (e) {
failedTestNames.add('사용자 생성 (일반)');
if (verbose) debugPrint('❌ 사용자 생성 오류: $e');
}
});
test('3. 사용자 생성 (관리자)', () async {
totalTests++;
if (verbose) debugPrint('\n 관리자 생성 테스트...');
final timestamp = DateTime.now().millisecondsSinceEpoch;
try {
final adminUser = {
'username': 'admin_$timestamp',
'email': 'admin_$timestamp@superport.kr',
'password': 'Admin123!@#',
'name': '관리자_$timestamp',
'department': '시스템관리팀',
'position': '팀장',
'phone': '010-9999-${1000 + random.nextInt(9000)}',
'role': 'admin', // 관리자
};
final response = await dio.post(
'$baseUrl/users',
data: adminUser,
);
if (response.statusCode == 200 || response.statusCode == 201) {
final createdUser = response.data['data'];
if (createdUser != null && createdUser['id'] != null) {
createdUserIds.add(createdUser['id']);
if (verbose) {
debugPrint('✅ 관리자 생성 성공: ${createdUser['name']}');
}
passedTests++;
} else {
failedTestNames.add('사용자 생성 (관리자)');
if (verbose) debugPrint('❌ 관리자 생성 응답에 ID가 없음');
}
} else {
failedTestNames.add('사용자 생성 (관리자)');
if (verbose) debugPrint('❌ 관리자 생성 실패: ${response.statusCode}');
}
} catch (e) {
failedTestNames.add('사용자 생성 (관리자)');
if (verbose) debugPrint('❌ 관리자 생성 오류: $e');
}
});
test('4. 사용자 상세 조회', () async {
totalTests++;
if (verbose) debugPrint('\n🔍 사용자 상세 조회 테스트...');
if (createdUserIds.isEmpty) {
failedTestNames.add('사용자 상세 조회');
if (verbose) debugPrint('⚠️ 조회할 사용자가 없음');
return;
}
try {
final userId = createdUserIds.first;
final response = await dio.get('$baseUrl/users/$userId');
if (response.statusCode == 200) {
final user = response.data['data'];
if (user != null) {
if (verbose) {
debugPrint('✅ 사용자 상세 조회 성공: ${user['name']} (${user['email']})');
}
passedTests++;
} else {
failedTestNames.add('사용자 상세 조회');
if (verbose) debugPrint('❌ 사용자 상세 조회 응답 비어있음');
}
} else {
failedTestNames.add('사용자 상세 조회');
if (verbose) debugPrint('❌ 사용자 상세 조회 실패: ${response.statusCode}');
}
} catch (e) {
failedTestNames.add('사용자 상세 조회');
if (verbose) debugPrint('❌ 사용자 상세 조회 오류: $e');
}
});
test('5. 사용자 정보 수정', () async {
totalTests++;
if (verbose) debugPrint('\n✏️ 사용자 정보 수정 테스트...');
if (createdUserIds.isEmpty) {
failedTestNames.add('사용자 정보 수정');
if (verbose) debugPrint('⚠️ 수정할 사용자가 없음');
return;
}
try {
final userId = createdUserIds.first;
final updatedData = {
'name': '수정된이름_${random.nextInt(1000)}',
'department': '수정된부서',
'position': '수정된직급',
'phone': '010-9999-9999',
};
final response = await dio.put(
'$baseUrl/users/$userId',
data: updatedData,
);
if (response.statusCode == 200) {
if (verbose) {
debugPrint('✅ 사용자 정보 수정 성공');
}
passedTests++;
} else {
failedTestNames.add('사용자 정보 수정');
if (verbose) debugPrint('❌ 사용자 정보 수정 실패: ${response.statusCode}');
}
} catch (e) {
failedTestNames.add('사용자 정보 수정');
if (verbose) debugPrint('❌ 사용자 정보 수정 오류: $e');
}
});
test('6. 비밀번호 변경', () async {
totalTests++;
if (verbose) debugPrint('\n🔐 비밀번호 변경 테스트...');
if (createdUserIds.isEmpty) {
failedTestNames.add('비밀번호 변경');
if (verbose) debugPrint('⚠️ 대상 사용자가 없음');
return;
}
try {
final userId = createdUserIds.first;
final passwordData = {
'current_password': 'Password123!',
'new_password': 'NewPassword456!',
'confirm_password': 'NewPassword456!',
};
final response = await dio.post(
'$baseUrl/users/$userId/change-password',
data: passwordData,
);
if (response.statusCode == 200) {
if (verbose) {
debugPrint('✅ 비밀번호 변경 성공');
}
passedTests++;
} else {
failedTestNames.add('비밀번호 변경');
if (verbose) debugPrint('❌ 비밀번호 변경 실패: ${response.statusCode}');
}
} catch (e) {
failedTestNames.add('비밀번호 변경');
if (verbose) debugPrint('❌ 비밀번호 변경 오류: $e');
}
});
test('7. 사용자 권한 변경', () async {
totalTests++;
if (verbose) debugPrint('\n👤 사용자 권한 변경 테스트...');
if (createdUserIds.length < 2) {
failedTestNames.add('사용자 권한 변경');
if (verbose) debugPrint('⚠️ 권한 변경할 사용자가 부족');
return;
}
try {
final userId = createdUserIds[1]; // 두 번째 사용자
final roleData = {
'role': 'admin', // user -> admin으로 변경
};
final response = await dio.patch(
'$baseUrl/users/$userId/role',
data: roleData,
);
if (response.statusCode == 200 || response.statusCode == 204) {
if (verbose) {
debugPrint('✅ 사용자 권한 변경 성공');
}
passedTests++;
} else {
failedTestNames.add('사용자 권한 변경');
if (verbose) debugPrint('❌ 사용자 권한 변경 실패: ${response.statusCode}');
}
} catch (e) {
failedTestNames.add('사용자 권한 변경');
if (verbose) debugPrint('❌ 사용자 권한 변경 오류: $e');
}
});
test('8. 사용자 비활성화/활성화', () async {
totalTests++;
if (verbose) debugPrint('\n🔄 사용자 비활성화/활성화 테스트...');
if (createdUserIds.isEmpty) {
failedTestNames.add('사용자 비활성화/활성화');
if (verbose) debugPrint('⚠️ 대상 사용자가 없음');
return;
}
try {
final userId = createdUserIds.first;
// 비활성화
var response = await dio.patch(
'$baseUrl/users/$userId/status',
data: {'is_active': false},
);
if (response.statusCode == 200 || response.statusCode == 204) {
if (verbose) debugPrint('✅ 사용자 비활성화 성공');
// 다시 활성화
response = await dio.patch(
'$baseUrl/users/$userId/status',
data: {'is_active': true},
);
if (response.statusCode == 200 || response.statusCode == 204) {
if (verbose) debugPrint('✅ 사용자 활성화 성공');
passedTests++;
} else {
failedTestNames.add('사용자 비활성화/활성화');
if (verbose) debugPrint('❌ 사용자 활성화 실패: ${response.statusCode}');
}
} else {
failedTestNames.add('사용자 비활성화/활성화');
if (verbose) debugPrint('❌ 사용자 비활성화 실패: ${response.statusCode}');
}
} catch (e) {
failedTestNames.add('사용자 비활성화/활성화');
if (verbose) debugPrint('❌ 사용자 비활성화/활성화 오류: $e');
}
});
test('9. 사용자 검색', () async {
totalTests++;
if (verbose) debugPrint('\n🔍 사용자 검색 테스트...');
try {
final response = await dio.get(
'$baseUrl/users/search',
queryParameters: {
'q': '관리자',
'page': 1,
'per_page': 10,
},
);
if (response.statusCode == 200) {
final results = response.data['data'] ?? [];
if (verbose) {
debugPrint('✅ 사용자 검색 성공: ${results.length}개 결과');
}
passedTests++;
} else {
failedTestNames.add('사용자 검색');
if (verbose) debugPrint('❌ 사용자 검색 실패: ${response.statusCode}');
}
} catch (e) {
failedTestNames.add('사용자 검색');
if (verbose) debugPrint('❌ 사용자 검색 오류: $e');
}
});
test('10. 사용자 삭제', () async {
totalTests++;
if (verbose) debugPrint('\n🗑️ 사용자 삭제 테스트...');
if (createdUserIds.isEmpty) {
failedTestNames.add('사용자 삭제');
if (verbose) debugPrint('⚠️ 삭제할 사용자가 없음');
return;
}
try {
// 모든 생성된 사용자 삭제
for (final userId in createdUserIds) {
final response = await dio.delete('$baseUrl/users/$userId');
if (response.statusCode == 200 || response.statusCode == 204) {
if (verbose) debugPrint('✅ 사용자 ID $userId 삭제 성공');
} else {
if (verbose) debugPrint('❌ 사용자 ID $userId 삭제 실패: ${response.statusCode}');
}
}
passedTests++;
} catch (e) {
failedTestNames.add('사용자 삭제');
if (verbose) debugPrint('❌ 사용자 삭제 오류: $e');
}
});
});
stopwatch.stop();
return TestResult(
name: '사용자 관리 API',
totalTests: totalTests,
passedTests: passedTests,
failedTests: totalTests - passedTests,
failedTestNames: failedTestNames,
executionTime: stopwatch.elapsed,
);
}
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;
}
// 테스트용 Dio 인스턴스 생성
final dio = Dio();
dio.options.connectTimeout = const Duration(seconds: 10);
dio.options.receiveTimeout = const Duration(seconds: 10);
// 로그인
const baseUrl = 'http://43.201.34.104:8080/api/v1';
debugPrint('🔐 로그인 중...');
try {
final loginResponse = await dio.post(
'$baseUrl/auth/login',
data: {
'email': 'admin@example.com',
'password': 'password123',
},
);
final token = loginResponse.data['data']['access_token'];
dio.options.headers['Authorization'] = 'Bearer $token';
debugPrint('✅ 로그인 성공\n');
// 사용자 테스트 실행
final result = await runUserTests(
dio: dio,
authToken: token,
verbose: true,
);
debugPrint('\n${result.summary}');
} catch (e) {
debugPrint('❌ 로그인 실패: $e');
} finally {
dio.close();
}
}