계정 정보 다이얼로그 추가 및 전체 목록 페치 개선
This commit is contained in:
34
test/features/auth/domain/entities/auth_permission_test.dart
Normal file
34
test/features/auth/domain/entities/auth_permission_test.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:superport_v2/core/permissions/permission_manager.dart';
|
||||
import 'package:superport_v2/features/auth/domain/entities/auth_permission.dart';
|
||||
|
||||
void main() {
|
||||
group('AuthPermission.toPermissionMap', () {
|
||||
test('백엔드 표준 문자열을 프런트 권한으로 매핑한다', () {
|
||||
final permission = AuthPermission(
|
||||
resource: '/approvals',
|
||||
actions: ['read', 'update', 'approve'],
|
||||
);
|
||||
|
||||
final result = permission.toPermissionMap();
|
||||
|
||||
expect(result, contains('/approvals'));
|
||||
final actions = result['/approvals']!;
|
||||
expect(actions.contains(PermissionAction.view), isTrue);
|
||||
expect(actions.contains(PermissionAction.edit), isTrue);
|
||||
expect(actions.contains(PermissionAction.approve), isTrue);
|
||||
});
|
||||
|
||||
test('알 수 없는 문자열은 무시해 빈 권한으로 반환한다', () {
|
||||
final permission = AuthPermission(
|
||||
resource: '/dashboard',
|
||||
actions: ['unknown', 'legacy'],
|
||||
);
|
||||
|
||||
final result = permission.toPermissionMap();
|
||||
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -60,7 +60,7 @@ void main() {
|
||||
verify(
|
||||
() => repository.list(
|
||||
page: 1,
|
||||
pageSize: 200,
|
||||
pageSize: any<int>(named: 'pageSize'),
|
||||
query: null,
|
||||
parentId: null,
|
||||
isActive: null,
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import 'package:superport_v2/features/masters/product/presentation/widgets/vendor_autocomplete_field.dart';
|
||||
import 'package:superport_v2/features/masters/vendor/domain/entities/vendor.dart';
|
||||
|
||||
void main() {
|
||||
group('VendorAutocompleteField', () {
|
||||
testWidgets('선택한 항목이 입력창에 반영된다', (tester) async {
|
||||
final vendors = [
|
||||
Vendor(
|
||||
id: 1,
|
||||
vendorCode: 'V001',
|
||||
vendorName: '테스트 제조사',
|
||||
isActive: true,
|
||||
isDeleted: false,
|
||||
),
|
||||
];
|
||||
int? selectedId;
|
||||
|
||||
await tester.pumpWidget(
|
||||
ShadApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: VendorAutocompleteField(
|
||||
initialOptions: vendors,
|
||||
onSelected: (id) => selectedId = id,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// 포커스 후 옵션 선택
|
||||
await tester.tap(find.byType(ShadInput));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// RawAutocomplete 옵션은 Overlay에 그려진다.
|
||||
await tester.tap(find.text('테스트 제조사 (V001)'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(selectedId, equals(1));
|
||||
|
||||
final editableText = tester.widget<EditableText>(find.byType(EditableText));
|
||||
expect(editableText.controller.text, '테스트 제조사 (V001)');
|
||||
});
|
||||
});
|
||||
}
|
||||
196
test/widgets/app_shell_test.dart
Normal file
196
test/widgets/app_shell_test.dart
Normal file
@@ -0,0 +1,196 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart' as lucide;
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import 'package:superport_v2/core/constants/app_sections.dart';
|
||||
import 'package:superport_v2/core/permissions/permission_manager.dart';
|
||||
import 'package:superport_v2/core/theme/superport_shad_theme.dart';
|
||||
import 'package:superport_v2/core/theme/theme_controller.dart';
|
||||
import 'package:superport_v2/core/services/token_storage.dart';
|
||||
import 'package:superport_v2/features/auth/application/auth_service.dart';
|
||||
import 'package:superport_v2/features/auth/domain/entities/auth_permission.dart';
|
||||
import 'package:superport_v2/features/auth/domain/entities/auth_session.dart';
|
||||
import 'package:superport_v2/features/auth/domain/entities/authenticated_user.dart';
|
||||
import 'package:superport_v2/features/auth/domain/entities/login_request.dart';
|
||||
import 'package:superport_v2/features/auth/domain/repositories/auth_repository.dart';
|
||||
import 'package:superport_v2/widgets/app_shell.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() {
|
||||
GetIt.I.reset();
|
||||
});
|
||||
|
||||
testWidgets('계정 버튼을 누르면 계정 정보 다이얼로그가 표시된다', (tester) async {
|
||||
final session = _buildSession();
|
||||
final authService = _createAuthService(session);
|
||||
GetIt.I.registerSingleton<AuthService>(authService);
|
||||
addTearDown(authService.dispose);
|
||||
await authService.login(
|
||||
const LoginRequest(identifier: 'user@example.com', password: 'secret'),
|
||||
);
|
||||
|
||||
await _pumpAppShell(tester);
|
||||
|
||||
await tester.tap(find.byIcon(lucide.LucideIcons.userRound));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('계정 정보'), findsOneWidget);
|
||||
expect(find.text('김승인'), findsWidgets);
|
||||
expect(find.text('E2025001'), findsOneWidget);
|
||||
expect(find.text('물류팀'), findsOneWidget);
|
||||
expect(find.textContaining('/approvals'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('다이얼로그에서 로그아웃을 누르면 세션이 초기화되고 로그인 화면으로 이동한다', (tester) async {
|
||||
final session = _buildSession();
|
||||
final authService = _createAuthService(session);
|
||||
GetIt.I.registerSingleton<AuthService>(authService);
|
||||
addTearDown(authService.dispose);
|
||||
await authService.login(
|
||||
const LoginRequest(identifier: 'user@example.com', password: 'secret'),
|
||||
);
|
||||
|
||||
await _pumpAppShell(tester);
|
||||
|
||||
expect(authService.session, isNotNull);
|
||||
|
||||
await tester.tap(find.byIcon(lucide.LucideIcons.userRound));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('로그아웃'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(authService.session, isNull);
|
||||
expect(find.text('로그인 페이지'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pumpAppShell(WidgetTester tester) async {
|
||||
final themeController = ThemeController();
|
||||
final permissionManager = PermissionManager();
|
||||
final router = GoRouter(
|
||||
initialLocation: dashboardRoutePath,
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: loginRoutePath,
|
||||
builder: (context, state) => const _LoginPlaceholder(),
|
||||
),
|
||||
ShellRoute(
|
||||
builder: (context, state, child) =>
|
||||
AppShell(currentLocation: state.uri.toString(), child: child),
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: dashboardRoutePath,
|
||||
builder: (context, state) => const _DashboardPlaceholder(),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
addTearDown(themeController.dispose);
|
||||
addTearDown(permissionManager.dispose);
|
||||
addTearDown(router.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
PermissionScope(
|
||||
manager: permissionManager,
|
||||
child: ThemeControllerScope(
|
||||
controller: themeController,
|
||||
child: ShadApp.router(
|
||||
routerConfig: router,
|
||||
theme: SuperportShadTheme.light(),
|
||||
darkTheme: SuperportShadTheme.dark(),
|
||||
debugShowCheckedModeBanner: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
AuthSession _buildSession() {
|
||||
return AuthSession(
|
||||
accessToken: 'access-token',
|
||||
refreshToken: 'refresh-token',
|
||||
expiresAt: DateTime.parse('2025-10-21T09:00:00Z'),
|
||||
user: const AuthenticatedUser(
|
||||
id: 7,
|
||||
name: '김승인',
|
||||
employeeNo: 'E2025001',
|
||||
email: 'approver@example.com',
|
||||
primaryGroupId: 3,
|
||||
primaryGroupName: '물류팀',
|
||||
),
|
||||
permissions: const [
|
||||
AuthPermission(resource: '/dashboard', actions: ['read']),
|
||||
AuthPermission(resource: '/approvals', actions: ['read', 'update']),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
AuthService _createAuthService(AuthSession session) {
|
||||
final repository = _FakeAuthRepository(session);
|
||||
final tokenStorage = _MemoryTokenStorage();
|
||||
return AuthService(repository: repository, tokenStorage: tokenStorage);
|
||||
}
|
||||
|
||||
class _FakeAuthRepository implements AuthRepository {
|
||||
_FakeAuthRepository(this.session);
|
||||
|
||||
final AuthSession session;
|
||||
|
||||
@override
|
||||
Future<AuthSession> login(LoginRequest request) async => session;
|
||||
|
||||
@override
|
||||
Future<AuthSession> refresh(String refreshToken) async => session;
|
||||
}
|
||||
|
||||
class _MemoryTokenStorage implements TokenStorage {
|
||||
String? _access;
|
||||
String? _refresh;
|
||||
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
_access = null;
|
||||
_refresh = null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> readAccessToken() async => _access;
|
||||
|
||||
@override
|
||||
Future<String?> readRefreshToken() async => _refresh;
|
||||
|
||||
@override
|
||||
Future<void> writeAccessToken(String? token) async {
|
||||
_access = token;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> writeRefreshToken(String? token) async {
|
||||
_refresh = token;
|
||||
}
|
||||
}
|
||||
|
||||
class _DashboardPlaceholder extends StatelessWidget {
|
||||
const _DashboardPlaceholder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(body: Center(child: Text('대시보드 본문')));
|
||||
}
|
||||
}
|
||||
|
||||
class _LoginPlaceholder extends StatelessWidget {
|
||||
const _LoginPlaceholder();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Scaffold(body: Center(child: Text('로그인 페이지')));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user