Files
superport/test/integration/automated/user_real_api_test.dart
JiWoong Sul 731dcd816b
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
refactor: Repository 패턴 적용 및 Clean Architecture 완성
## 주요 변경사항

### 🏗️ 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. 성능 최적화
2025-08-11 20:14:10 +09:00

468 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,
);
}
void main() async {
// 테스트용 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@superport.kr',
'password': 'admin123!',
},
);
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();
}
}