결재 템플릿 단계 적용 구현

- ApprovalTemplate 엔티티·DTO·원격 리포지토리 추가
- ApprovalController에 템플릿 로딩/적용 상태와 assignSteps 호출 연동
- ApprovalPage 단계 탭에 템플릿 선택 UI 및 적용 확인 다이얼로그 구현
- 템플릿 적용 단위 테스트와 IMPLEMENTATION_TASKS 현황 갱신
This commit is contained in:
JiWoong Sul
2025-09-25 00:21:12 +09:00
parent b6e50464d2
commit c3010965ad
63 changed files with 10179 additions and 1436 deletions

View File

@@ -0,0 +1,193 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/features/masters/group/domain/entities/group.dart';
import 'package:superport_v2/features/masters/group/domain/repositories/group_repository.dart';
import 'package:superport_v2/features/masters/group/presentation/controllers/group_controller.dart';
class _MockGroupRepository extends Mock implements GroupRepository {}
class _FakeGroupInput extends Fake implements GroupInput {}
void main() {
late GroupController controller;
late _MockGroupRepository repository;
final sampleGroup = Group(
id: 1,
groupName: '관리자',
description: '전체 권한',
isDefault: true,
isActive: true,
isDeleted: false,
);
PaginatedResult<Group> createResult({List<Group>? items}) {
final list = items ?? [sampleGroup];
return PaginatedResult<Group>(
items: list,
page: 1,
pageSize: 20,
total: list.length,
);
}
setUpAll(() {
registerFallbackValue(_FakeGroupInput());
});
setUp(() {
repository = _MockGroupRepository();
controller = GroupController(repository: repository);
});
group('fetch', () {
test('정상 조회 시 결과가 갱신된다', () async {
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
isDefault: any(named: 'isDefault'),
isActive: any(named: 'isActive'),
),
).thenAnswer((_) async => createResult());
await controller.fetch();
expect(controller.isLoading, isFalse);
expect(controller.errorMessage, isNull);
expect(controller.result?.items, isNotEmpty);
verify(
() => repository.list(
page: 1,
pageSize: 20,
query: null,
isDefault: null,
isActive: null,
),
).called(1);
});
test('에러 발생 시 errorMessage에 저장한다', () async {
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
isDefault: any(named: 'isDefault'),
isActive: any(named: 'isActive'),
),
).thenThrow(Exception('fail'));
await controller.fetch();
expect(controller.isLoading, isFalse);
expect(controller.errorMessage, isNotNull);
});
test('필터 상태를 파라미터에 반영한다', () async {
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
isDefault: any(named: 'isDefault'),
isActive: any(named: 'isActive'),
),
).thenAnswer((_) async => createResult());
controller.updateQuery('admin');
controller.updateDefaultFilter(GroupDefaultFilter.defaultOnly);
controller.updateStatusFilter(GroupStatusFilter.inactiveOnly);
await controller.fetch();
verify(
() => repository.list(
page: 1,
pageSize: 20,
query: 'admin',
isDefault: true,
isActive: false,
),
).called(1);
});
});
test('필터 업데이트 메서드', () {
controller.updateQuery('abc');
controller.updateDefaultFilter(GroupDefaultFilter.nonDefault);
controller.updateStatusFilter(GroupStatusFilter.activeOnly);
expect(controller.query, 'abc');
expect(controller.defaultFilter, GroupDefaultFilter.nonDefault);
expect(controller.statusFilter, GroupStatusFilter.activeOnly);
});
group('mutations', () {
setUp(() {
when(
() => repository.list(
page: any(named: 'page'),
pageSize: any(named: 'pageSize'),
query: any(named: 'query'),
isDefault: any(named: 'isDefault'),
isActive: any(named: 'isActive'),
),
).thenAnswer((_) async => createResult());
});
final input = GroupInput(groupName: '관리자');
test('create 성공 시 목록 재조회', () async {
when(() => repository.create(any())).thenAnswer((_) async => sampleGroup);
final created = await controller.create(input);
expect(created, isNotNull);
verify(() => repository.create(any())).called(1);
verify(
() => repository.list(
page: 1,
pageSize: 20,
query: null,
isDefault: null,
isActive: null,
),
).called(greaterThanOrEqualTo(1));
});
test('update 성공 시 현재 페이지 유지', () async {
when(
() => repository.update(any(), any()),
).thenAnswer((_) async => sampleGroup);
final updated = await controller.update(1, input);
expect(updated, isNotNull);
verify(() => repository.update(1, any())).called(1);
});
test('delete 성공 시 true 반환', () async {
when(() => repository.delete(any())).thenAnswer((_) async {});
final deleted = await controller.delete(1);
expect(deleted, isTrue);
verify(() => repository.delete(1)).called(1);
});
test('restore 성공 시 그룹 반환', () async {
when(
() => repository.restore(any()),
).thenAnswer((_) async => sampleGroup);
final restored = await controller.restore(1);
expect(restored, isNotNull);
verify(() => repository.restore(1)).called(1);
});
});
}