feat(user): 사용자 자기정보 편집과 관리자 재설정 플로우를 연동
- lib/widgets/app_shell.dart에서 내 정보 다이얼로그를 추가하고 UserRepository.updateMe·비밀번호 변경 로직을 연결 - lib/features/masters/user/* 모듈에 phone·forcePasswordChange·passwordUpdatedAt 필드를 반영하고 reset-password/update-me API를 사용 - lib/core/validation/password_rules.dart을 신설해 비밀번호 정책 검증을 공통화하고 신규 위젯·테스트에서 재사용 - doc/stock_approval_system_api_v4.md 등 문서를 users 스펙 개편 내용으로 갱신하고 user_management_plan.md를 추가 - test/widgets/app_shell_test.dart 등에서 자기정보 수정·비밀번호 재설정 시나리오를 검증하고 기존 테스트를 보강
This commit is contained in:
@@ -4,6 +4,7 @@ import 'package:mocktail/mocktail.dart';
|
||||
|
||||
import 'package:superport_v2/core/network/api_client.dart';
|
||||
import 'package:superport_v2/features/masters/user/data/repositories/user_repository_remote.dart';
|
||||
import 'package:superport_v2/features/masters/user/domain/entities/user.dart';
|
||||
|
||||
class _MockApiClient extends Mock implements ApiClient {}
|
||||
|
||||
@@ -14,6 +15,9 @@ void main() {
|
||||
setUpAll(() {
|
||||
registerFallbackValue(Options());
|
||||
registerFallbackValue(CancelToken());
|
||||
registerFallbackValue(
|
||||
Response<dynamic>(requestOptions: RequestOptions(path: '/fallback')),
|
||||
);
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
@@ -35,13 +39,15 @@ void main() {
|
||||
'items': [
|
||||
{
|
||||
'id': 1,
|
||||
'employee_no': 'E-001',
|
||||
'employee_name': '홍길동',
|
||||
'employee_id': 'E-001',
|
||||
'name': '홍길동',
|
||||
'group': {'id': 2, 'group_name': '관리자'},
|
||||
'force_password_change': false,
|
||||
'password_updated_at': '2025-01-10T09:00:00Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
requestOptions: RequestOptions(path: '/api/v1/employees'),
|
||||
requestOptions: RequestOptions(path: '/api/v1/users'),
|
||||
statusCode: 200,
|
||||
),
|
||||
);
|
||||
@@ -59,7 +65,150 @@ void main() {
|
||||
final path = captured[0] as String;
|
||||
final query = captured[1] as Map<String, dynamic>;
|
||||
|
||||
expect(path, equals('/api/v1/employees'));
|
||||
expect(path, equals('/api/v1/users'));
|
||||
expect(query['include'], 'group');
|
||||
});
|
||||
|
||||
test('create 호출 시 employee_id 파라미터를 전달한다', () async {
|
||||
final response = Response<Map<String, dynamic>>(
|
||||
data: {
|
||||
'data': {
|
||||
'id': 10,
|
||||
'employee_id': 'E2025001',
|
||||
'name': '김승인',
|
||||
'email': 'approver@example.com',
|
||||
'phone': '+82-10-1111-2222',
|
||||
'group': {'id': 1, 'group_name': '관리자'},
|
||||
'force_password_change': true,
|
||||
'is_active': true,
|
||||
},
|
||||
},
|
||||
requestOptions: RequestOptions(path: '/api/v1/users'),
|
||||
statusCode: 201,
|
||||
);
|
||||
|
||||
when(
|
||||
() => apiClient.post<Map<String, dynamic>>(
|
||||
any(),
|
||||
data: any(named: 'data'),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
when(
|
||||
() => apiClient.unwrapAsMap(response),
|
||||
).thenReturn(response.data!['data'] as Map<String, dynamic>);
|
||||
|
||||
final input = UserInput(
|
||||
employeeNo: 'E2025001',
|
||||
employeeName: '김승인',
|
||||
groupId: 1,
|
||||
email: 'approver@example.com',
|
||||
mobileNo: '+82-10-1111-2222',
|
||||
password: 'TempPass!1',
|
||||
);
|
||||
|
||||
final user = await repository.create(input);
|
||||
|
||||
expect(user.employeeNo, 'E2025001');
|
||||
expect(user.forcePasswordChange, isTrue);
|
||||
|
||||
verify(
|
||||
() => apiClient.post<Map<String, dynamic>>(
|
||||
'/api/v1/users',
|
||||
data: any(named: 'data'),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('updateMe 호출 시 /users/me 엔드포인트를 사용한다', () async {
|
||||
final response = Response<Map<String, dynamic>>(
|
||||
data: {
|
||||
'data': {
|
||||
'id': 7,
|
||||
'employee_id': 'E2025001',
|
||||
'name': '김승인',
|
||||
'email': 'approver@example.com',
|
||||
'phone': '+82-10-1111-2222',
|
||||
'force_password_change': false,
|
||||
},
|
||||
},
|
||||
requestOptions: RequestOptions(path: '/api/v1/users/me'),
|
||||
statusCode: 200,
|
||||
);
|
||||
|
||||
when(
|
||||
() => apiClient.patch<Map<String, dynamic>>(
|
||||
any(),
|
||||
data: any(named: 'data'),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
when(
|
||||
() => apiClient.unwrapAsMap(response),
|
||||
).thenReturn(response.data!['data'] as Map<String, dynamic>);
|
||||
|
||||
final payload = UserProfileUpdateInput(
|
||||
email: 'approver@example.com',
|
||||
phone: '+82-10-1111-2222',
|
||||
password: 'NewPass!23',
|
||||
currentPassword: 'TempPass!1',
|
||||
);
|
||||
|
||||
final user = await repository.updateMe(payload);
|
||||
|
||||
expect(user.email, 'approver@example.com');
|
||||
verify(
|
||||
() => apiClient.patch<Map<String, dynamic>>(
|
||||
'/api/v1/users/me',
|
||||
data: payload.toPayload(),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
|
||||
test('resetPassword 호출 시 /users/{id}/reset-password 엔드포인트를 사용한다', () async {
|
||||
final response = Response<Map<String, dynamic>>(
|
||||
data: {
|
||||
'data': {
|
||||
'id': 7,
|
||||
'employee_id': 'E2025001',
|
||||
'email': 'approver@example.com',
|
||||
'force_password_change': true,
|
||||
'password_updated_at': '2025-03-11T02:05:00Z',
|
||||
},
|
||||
},
|
||||
requestOptions: RequestOptions(path: '/api/v1/users/7/reset-password'),
|
||||
statusCode: 200,
|
||||
);
|
||||
|
||||
when(
|
||||
() => apiClient.post<Map<String, dynamic>>(
|
||||
any(),
|
||||
data: any(named: 'data'),
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).thenAnswer((_) async => response);
|
||||
when(
|
||||
() => apiClient.unwrapAsMap(response),
|
||||
).thenReturn(response.data!['data'] as Map<String, dynamic>);
|
||||
|
||||
final user = await repository.resetPassword(7);
|
||||
|
||||
expect(user.forcePasswordChange, isTrue);
|
||||
expect(user.passwordUpdatedAt, isNotNull);
|
||||
verify(
|
||||
() => apiClient.post<Map<String, dynamic>>(
|
||||
'/api/v1/users/7/reset-password',
|
||||
data: null,
|
||||
options: any(named: 'options'),
|
||||
cancelToken: any(named: 'cancelToken'),
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user