API v4 계약 반영하고 보고서·입출고 화면 실연동 강화

This commit is contained in:
JiWoong Sul
2025-10-16 14:57:07 +09:00
parent 7e0f7b1c55
commit d5c99627db
34 changed files with 1767 additions and 327 deletions

View File

@@ -0,0 +1,150 @@
import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:superport_v2/core/network/api_client.dart';
import 'package:superport_v2/features/approvals/data/repositories/approval_repository_remote.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
class _MockApiClient extends Mock implements ApiClient {}
void main() {
late ApiClient apiClient;
late ApprovalRepositoryRemote repository;
setUpAll(() {
registerFallbackValue(Options());
registerFallbackValue(CancelToken());
});
setUp(() {
apiClient = _MockApiClient();
repository = ApprovalRepositoryRemote(apiClient: apiClient);
});
Map<String, dynamic> buildStep({
required int id,
required int order,
required int statusId,
String statusName = '대기',
}) {
return {
'id': id,
'approval_id': 5001,
'step_order': order,
'approver': {
'id': 20 + order,
'employee_no': 'E${order.toString().padLeft(4, '0')}',
'employee_name': '승인자$order',
},
'step_status': {
'id': statusId,
'status_name': statusName,
'is_blocking_next': true,
'is_terminal': false,
},
'assigned_at': '2025-09-18T06:00:00Z',
'decided_at': order == 1 ? '2025-09-18T08:05:00Z' : null,
'note': order == 1 ? '승인합니다.' : null,
};
}
Map<String, dynamic> buildHistory({
required int id,
required String timestamp,
}) {
return {
'id': id,
'approval_action_id': 1,
'approval_action_name': '승인',
'action_at': timestamp,
'note': '이력$id',
'from_status': {
'id': 1,
'status_name': '대기',
'is_blocking_next': true,
'is_terminal': false,
},
'to_status': {
'id': 2,
'status_name': '진행중',
'is_blocking_next': true,
'is_terminal': false,
},
};
}
test('performStepAction은 응답의 steps와 histories를 병합한다', () async {
const path = '/api/v1/approval-steps/7001/actions';
when(
() => apiClient.post<Map<String, dynamic>>(
path,
data: any(named: 'data'),
options: any(named: 'options'),
cancelToken: any(named: 'cancelToken'),
),
).thenAnswer(
(_) async => Response<Map<String, dynamic>>(
data: {
'data': {
'approval': {
'id': 5001,
'approval_no': 'APP-2025-0001',
'status': {'id': 2, 'status_name': '진행중'},
'current_step': {'id': 7002, 'step_order': 2},
'requester': {
'id': 7,
'employee_no': 'E0007',
'employee_name': '김요청',
},
'requested_at': '2025-09-18T06:00:00Z',
'steps': [buildStep(id: 7001, order: 1, statusId: 1)],
'histories': [
buildHistory(id: 91000, timestamp: '2025-09-18T07:00:00Z'),
],
},
'steps': [
buildStep(id: 7001, order: 1, statusId: 2, statusName: '진행중'),
buildStep(id: 7002, order: 2, statusId: 1),
],
'step': buildStep(
id: 7001,
order: 1,
statusId: 2,
statusName: '진행중',
),
'next_step': buildStep(
id: 7002,
order: 2,
statusId: 3,
statusName: '대기',
),
'history': buildHistory(
id: 91001,
timestamp: '2025-09-18T08:05:00Z',
),
},
},
statusCode: 200,
requestOptions: RequestOptions(path: path),
),
);
final result = await repository.performStepAction(
ApprovalStepActionInput(stepId: 7001, actionId: 1, note: '승인합니다.'),
);
expect(result.id, 5001);
expect(result.steps.length, 2);
final firstStep = result.steps.firstWhere((step) => step.id == 7001);
expect(firstStep.status.id, 2);
expect(firstStep.status.name, '진행중');
final secondStep = result.steps.firstWhere((step) => step.id == 7002);
expect(secondStep.status.id, 3);
expect(result.histories.length, 2);
expect(result.histories.last.id, 91001);
});
}