import 'package:flutter/material.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_it/get_it.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:superport_v2/core/common/models/paginated_result.dart'; import 'package:superport_v2/core/config/environment.dart'; import 'package:superport_v2/core/permissions/permission_manager.dart'; import 'package:superport_v2/features/approvals/domain/entities/approval.dart'; import 'package:superport_v2/features/approvals/domain/entities/approval_template.dart'; import 'package:superport_v2/features/approvals/domain/repositories/approval_repository.dart'; import 'package:superport_v2/features/approvals/domain/repositories/approval_template_repository.dart'; import 'package:superport_v2/features/approvals/presentation/pages/approval_page.dart'; import '../../helpers/test_app.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true'); await Environment.initialize(); dotenv.env['FEATURE_APPROVALS_ENABLED'] = 'true'; }); tearDown(() async { await GetIt.I.reset(); }); Future pumpApprovalPage( WidgetTester tester, PermissionManager manager, ) async { await tester.pumpWidget( buildTestApp(const ApprovalPage(), permissionManager: manager), ); await tester.pump(const Duration(milliseconds: 200)); await tester.pumpAndSettle(); } testWidgets('결재 단계 액션은 승인 권한이 없으면 비활성화된다', (tester) async { final repo = _StubApprovalRepository(); final templateRepo = _StubApprovalTemplateRepository(); GetIt.I.registerSingleton(repo); GetIt.I.registerSingleton(templateRepo); final permissionManager = PermissionManager( overrides: { '/approvals/requests': {PermissionAction.view}, }, ); final view = tester.view; view.physicalSize = const Size(1280, 800); view.devicePixelRatio = 1.0; addTearDown(() { view.resetPhysicalSize(); view.resetDevicePixelRatio(); }); await pumpApprovalPage(tester, permissionManager); final rowFinder = find.byKey(const ValueKey('approval_row_1')); expect(rowFinder, findsOneWidget); await tester.tap(rowFinder); await tester.pumpAndSettle(); final tabContext = tester.element(find.byType(TabBar)); final tabController = DefaultTabController.of(tabContext); tabController.animateTo(1); await tester.pumpAndSettle(); final approveButton = tester.widget( find.byKey(const ValueKey('step_action_100_approve')), ); expect(approveButton.onPressed, isNull); expect(find.text('결재 권한이 없어 단계 행위를 실행할 수 없습니다.'), findsOneWidget); }); testWidgets('승인 권한이 있으면 단계 액션을 실행할 수 있다', (tester) async { final repo = _StubApprovalRepository(); final templateRepo = _StubApprovalTemplateRepository(); GetIt.I.registerSingleton(repo); GetIt.I.registerSingleton(templateRepo); final permissionManager = PermissionManager( overrides: { '/approvals/requests': { PermissionAction.view, PermissionAction.approve, }, }, ); await pumpApprovalPage(tester, permissionManager); final rowFinder = find.byKey(const ValueKey('approval_row_1')); expect(rowFinder, findsOneWidget); await tester.tap(rowFinder); await tester.pumpAndSettle(); final tabContext = tester.element(find.byType(TabBar)); final tabController = DefaultTabController.of(tabContext); tabController.animateTo(1); await tester.pumpAndSettle(); final approveButton = tester.widget( find.byKey(const ValueKey('step_action_100_approve')), ); expect(approveButton.onPressed, isNotNull); expect(find.text('결재 권한이 없어 단계 행위를 실행할 수 없습니다.'), findsNothing); }); } class _StubApprovalRepository implements ApprovalRepository { _StubApprovalRepository(); final ApprovalStatus _pendingStatus = ApprovalStatus(id: 1, name: '승인대기'); final ApprovalApprover _approver = ApprovalApprover( id: 10, employeeNo: 'E010', name: '김승인', ); late final ApprovalStep _step = ApprovalStep( id: 100, stepOrder: 1, approver: _approver, status: _pendingStatus, assignedAt: DateTime(2024, 1, 1), ); late final Approval _approval = Approval( id: 1, approvalNo: 'AP-001', transactionNo: 'TRX-001', status: _pendingStatus, currentStep: _step, requester: ApprovalRequester(id: 20, employeeNo: 'E020', name: '요청자'), requestedAt: DateTime(2024, 1, 1), steps: [_step], histories: const [], ); @override Future> list({ int page = 1, int pageSize = 20, String? query, String? status, DateTime? from, DateTime? to, bool includeHistories = false, bool includeSteps = false, }) async { return PaginatedResult( items: [_approval], page: 1, pageSize: 20, total: 1, ); } @override Future fetchDetail( int id, { bool includeSteps = true, bool includeHistories = true, }) async { return _approval; } @override Future> listActions({bool activeOnly = true}) async { return [ ApprovalAction(id: 1, name: 'approve'), ApprovalAction(id: 2, name: 'reject'), ApprovalAction(id: 3, name: 'comment'), ]; } @override Future performStepAction(ApprovalStepActionInput input) async { return _approval; } @override Future assignSteps(ApprovalStepAssignmentInput input) async { return _approval; } @override Future create(ApprovalInput input) { throw UnimplementedError(); } @override Future delete(int id) { throw UnimplementedError(); } @override Future restore(int id) { throw UnimplementedError(); } @override Future update(int id, ApprovalInput input) { throw UnimplementedError(); } } class _StubApprovalTemplateRepository implements ApprovalTemplateRepository { _StubApprovalTemplateRepository(); final ApprovalTemplate _template = ApprovalTemplate( id: 1, code: 'TMP-001', name: '표준 1단계', isActive: true, steps: [ ApprovalTemplateStep( stepOrder: 1, approver: ApprovalTemplateApprover( id: 10, employeeNo: 'E010', name: '김승인', ), ), ], ); @override Future> list({ int page = 1, int pageSize = 20, String? query, bool? isActive, }) async { return PaginatedResult( items: [_template], page: 1, pageSize: 20, total: 1, ); } @override Future fetchDetail( int id, { bool includeSteps = true, }) async { return _template; } @override Future create( ApprovalTemplateInput input, { List steps = const [], }) { throw UnimplementedError(); } @override Future delete(int id) { throw UnimplementedError(); } @override Future restore(int id) { throw UnimplementedError(); } @override Future update( int id, ApprovalTemplateInput input, { List? steps, }) { throw UnimplementedError(); } }