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:
@@ -200,7 +200,7 @@ void main() {
|
||||
|
||||
testWidgets('신규 등록 성공', (tester) async {
|
||||
final view = tester.view;
|
||||
view.physicalSize = const Size(1280, 800);
|
||||
view.physicalSize = const Size(1600, 900);
|
||||
view.devicePixelRatio = 1.0;
|
||||
addTearDown(() {
|
||||
view.resetPhysicalSize();
|
||||
@@ -260,13 +260,26 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final dialog = find.byType(Dialog);
|
||||
final editableTexts = find.descendant(
|
||||
of: dialog,
|
||||
matching: find.byType(EditableText),
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_employee')),
|
||||
'A010',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_name')),
|
||||
'신규 사용자',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_password')),
|
||||
'Aa1!abcd',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_email')),
|
||||
'new@superport.com',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_phone')),
|
||||
'010-1111-2222',
|
||||
);
|
||||
|
||||
await tester.enterText(editableTexts.at(0), 'A010');
|
||||
await tester.enterText(editableTexts.at(1), '신규 사용자');
|
||||
|
||||
final selectFinder = find.descendant(
|
||||
of: dialog,
|
||||
@@ -290,9 +303,149 @@ void main() {
|
||||
|
||||
expect(capturedInput, isNotNull);
|
||||
expect(capturedInput?.employeeNo, 'A010');
|
||||
expect(capturedInput?.password, 'Aa1!abcd');
|
||||
expect(capturedInput?.forcePasswordChange, isTrue);
|
||||
expect(capturedInput?.email, 'new@superport.com');
|
||||
expect(capturedInput?.mobileNo, '010-1111-2222');
|
||||
expect(find.byType(Dialog), findsNothing);
|
||||
expect(find.text('A010'), findsOneWidget);
|
||||
verify(() => userRepository.create(any())).called(1);
|
||||
});
|
||||
|
||||
testWidgets('비밀번호 정책을 위반하면 에러가 노출되고 생성이 중단된다', (tester) async {
|
||||
final view = tester.view;
|
||||
view.physicalSize = const Size(1280, 800);
|
||||
view.devicePixelRatio = 1.0;
|
||||
addTearDown(() {
|
||||
view.resetPhysicalSize();
|
||||
view.resetDevicePixelRatio();
|
||||
});
|
||||
|
||||
when(
|
||||
() => userRepository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
groupId: any(named: 'groupId'),
|
||||
isActive: any(named: 'isActive'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => PaginatedResult<UserAccount>(
|
||||
items: const [],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const UserPage()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('신규 등록'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final dialog = find.byType(Dialog);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_employee')),
|
||||
'A011',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_name')),
|
||||
'정책 위반',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_password')),
|
||||
'abc',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_email')),
|
||||
'invalid@superport.com',
|
||||
);
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('user_form_phone')),
|
||||
'010-3333-4444',
|
||||
);
|
||||
|
||||
final selectFinder = find.descendant(
|
||||
of: dialog,
|
||||
matching: find.byType(ShadSelect<int?>),
|
||||
);
|
||||
final selectElement = tester.element(selectFinder);
|
||||
final renderBox = selectElement.renderObject as RenderBox;
|
||||
final globalCenter = renderBox.localToGlobal(
|
||||
renderBox.size.center(Offset.zero),
|
||||
);
|
||||
await tester.tapAt(globalCenter);
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
await tester.tap(find.text('관리자', skipOffstage: false).first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('등록'));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.textContaining('최소 8자 이상 입력해야 합니다.'), findsOneWidget);
|
||||
verifyNever(() => userRepository.create(any()));
|
||||
});
|
||||
|
||||
testWidgets('비밀번호 재설정 버튼을 통해 확인 후 API 호출', (tester) async {
|
||||
final view = tester.view;
|
||||
view.physicalSize = const Size(1280, 800);
|
||||
view.devicePixelRatio = 1.0;
|
||||
addTearDown(() {
|
||||
view.resetPhysicalSize();
|
||||
view.resetDevicePixelRatio();
|
||||
});
|
||||
|
||||
when(
|
||||
() => userRepository.list(
|
||||
page: any(named: 'page'),
|
||||
pageSize: any(named: 'pageSize'),
|
||||
query: any(named: 'query'),
|
||||
groupId: any(named: 'groupId'),
|
||||
isActive: any(named: 'isActive'),
|
||||
),
|
||||
).thenAnswer(
|
||||
(_) async => PaginatedResult<UserAccount>(
|
||||
items: [
|
||||
UserAccount(
|
||||
id: 1,
|
||||
employeeNo: 'A001',
|
||||
employeeName: '홍길동',
|
||||
group: UserGroup(id: 1, groupName: '관리자'),
|
||||
),
|
||||
],
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 1,
|
||||
),
|
||||
);
|
||||
when(() => userRepository.resetPassword(any())).thenAnswer(
|
||||
(_) async => UserAccount(
|
||||
id: 1,
|
||||
employeeNo: 'A001',
|
||||
employeeName: '홍길동',
|
||||
group: UserGroup(id: 1, groupName: '관리자'),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_buildApp(const UserPage()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final resetFinder = find
|
||||
.widgetWithIcon(ShadButton, LucideIcons.refreshCcw)
|
||||
.first;
|
||||
final resetButton = tester.widget<ShadButton>(resetFinder);
|
||||
resetButton.onPressed?.call();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(Dialog), findsOneWidget);
|
||||
expect(find.text('재설정'), findsOneWidget);
|
||||
await tester.tap(find.text('재설정'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
verify(() => userRepository.resetPassword(1)).called(1);
|
||||
expect(find.text('비밀번호 재설정'), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user