승인 단계 삭제 복구 흐름 구현하고 API 정렬 문서 추가

This commit is contained in:
JiWoong Sul
2025-10-01 15:51:01 +09:00
parent 5578bf443f
commit 67fc319c3c
16 changed files with 671 additions and 38 deletions

View File

@@ -221,4 +221,70 @@ void main() {
expect(result, isNull);
expect(controller.errorMessage, isNotNull);
});
test('deleteStep 성공 시 단계가 비활성화된다', () async {
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
statusId: any(named: 'statusId'),
approverId: any(named: 'approverId'),
approvalId: any(named: 'approvalId'),
),
).thenAnswer((_) async => createResult([sampleRecord]));
when(() => repository.delete(any())).thenAnswer((_) async {});
await controller.fetch();
final success = await controller.deleteStep(sampleRecord.step.id!);
expect(success, isTrue);
expect(controller.result?.items.first.step.isDeleted, isTrue);
});
test('deleteStep 실패 시 false를 반환하고 에러를 기록한다', () async {
when(() => repository.delete(any())).thenThrow(Exception('fail'));
final success = await controller.deleteStep(999);
expect(success, isFalse);
expect(controller.errorMessage, isNotNull);
});
test('restoreStep 성공 시 단계가 활성화된다', () async {
final deletedRecord = sampleRecord.copyWith(
step: sampleRecord.step.copyWith(isDeleted: true),
);
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
statusId: any(named: 'statusId'),
approverId: any(named: 'approverId'),
approvalId: any(named: 'approvalId'),
),
).thenAnswer((_) async => createResult([deletedRecord]));
when(() => repository.restore(any())).thenAnswer((_) async => sampleRecord);
await controller.fetch();
final restored = await controller.restoreStep(sampleRecord.step.id!);
expect(restored, isNotNull);
expect(controller.result?.items.first.step.isDeleted, isFalse);
});
test('restoreStep 실패 시 null을 반환하고 에러를 기록한다', () async {
when(() => repository.restore(any())).thenThrow(Exception('fail'));
final restored = await controller.restoreStep(100);
expect(restored, isNull);
expect(controller.errorMessage, isNotNull);
});
}

View File

@@ -6,6 +6,7 @@ import 'package:mocktail/mocktail.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport_v2/core/common/models/paginated_result.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/step/domain/entities/approval_step_input.dart';
import 'package:superport_v2/features/approvals/step/domain/entities/approval_step_record.dart';
@@ -16,13 +17,19 @@ class _MockApprovalStepRepository extends Mock
implements ApprovalStepRepository {}
Widget _buildApp(Widget child) {
final manager = PermissionManager(
overrides: {'/approvals/steps': PermissionAction.values.toSet()},
);
return MaterialApp(
home: ShadTheme(
data: ShadThemeData(
colorScheme: const ShadSlateColorScheme.light(),
brightness: Brightness.light,
home: PermissionScope(
manager: manager,
child: ShadTheme(
data: ShadThemeData(
colorScheme: const ShadSlateColorScheme.light(),
brightness: Brightness.light,
),
child: Scaffold(body: child),
),
child: Scaffold(body: child),
),
);
}
@@ -263,4 +270,92 @@ void main() {
findsOneWidget,
);
});
testWidgets('삭제 버튼 확인 후 저장소 삭제를 호출한다', (tester) async {
dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true\n');
repository = _MockApprovalStepRepository();
GetIt.I.registerLazySingleton<ApprovalStepRepository>(() => repository);
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
statusId: any(named: 'statusId'),
approverId: any(named: 'approverId'),
approvalId: any(named: 'approvalId'),
),
).thenAnswer(
(_) async => PaginatedResult<ApprovalStepRecord>(
items: [record],
page: 1,
pageSize: 20,
total: 1,
),
);
when(() => repository.delete(any())).thenAnswer((_) async {});
await tester.pumpWidget(_buildApp(const ApprovalStepPage()));
await tester.pump();
await tester.pumpAndSettle();
final deleteFinder = find.byKey(const ValueKey('step_delete_501_1'));
final deleteButton = tester.widget<ShadButton>(deleteFinder);
deleteButton.onPressed?.call();
await tester.pump();
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(ShadButton, '삭제').last);
await tester.pump();
await tester.pumpAndSettle();
verify(() => repository.delete(501)).called(1);
});
testWidgets('복구 버튼 확인 후 저장소 복구를 호출한다', (tester) async {
dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true\n');
repository = _MockApprovalStepRepository();
GetIt.I.registerLazySingleton<ApprovalStepRepository>(() => repository);
final deletedRecord = record.copyWith(
step: record.step.copyWith(isDeleted: true),
);
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
statusId: any(named: 'statusId'),
approverId: any(named: 'approverId'),
approvalId: any(named: 'approvalId'),
),
).thenAnswer(
(_) async => PaginatedResult<ApprovalStepRecord>(
items: [deletedRecord],
page: 1,
pageSize: 20,
total: 1,
),
);
when(() => repository.restore(any())).thenAnswer((_) async => record);
await tester.pumpWidget(_buildApp(const ApprovalStepPage()));
await tester.pump();
await tester.pumpAndSettle();
final restoreFinder = find.byKey(const ValueKey('step_restore_501_1'));
final restoreButton = tester.widget<ShadButton>(restoreFinder);
restoreButton.onPressed?.call();
await tester.pump();
await tester.pumpAndSettle();
await tester.tap(find.widgetWithText(ShadButton, '복구').last);
await tester.pump();
await tester.pumpAndSettle();
verify(() => repository.restore(501)).called(1);
});
}