- approvals 모듈에서 APPROVAL_ACCESS_DENIED 응답을 포착하여 ApprovalAccessDeniedException으로 변환하고 접근 거부 시 토스트·대시보드 리다이렉트를 처리 - approval history 조회가 서버 action id에 맞춰 필터링되도록 repository·controller·테스트를 보강 - 재고 트랜잭션 상태 전이 API 호출에 note를 전달하도록 repository·컨트롤러·통합/단위 테스트를 업데이트 - 승인 플로우 QA 체크리스트 및 연동 문서를 최신 계약과 테스트 흐름으로 업데이트
5.9 KiB
5.9 KiB
ApiClient 설계서 (Dio 기반, Superport 스타일)
Implementation Snapshot (2025-10-31)
- ✅
ApiClient/ApiErrorMapper/AuthInterceptor가 구현되어 모든 원격 저장소가 공통 경로를 사용한다 (lib/core/network/api_client.dart,lib/core/network/api_error.dart,lib/core/network/interceptors/auth_interceptor.dart). - ✅ DI/환경 설정은
Environment.initialize()이후lib/injection_container.dart에서 ApiClient와 TokenStorage, 인터셉터를 등록한다. - ✅ 단위 테스트가 경로·쿼리·에러 매핑·토큰 재발급 동작을 검증한다 (
test/core/network/api_client_test.dart,test/core/network/auth_interceptor_test.dart). - ✅ 문서/코드가
doc/stock_approval_system_api_v4.md계약과 동기화됐으며, 엔드포인트별 Remote Repository 테스트가include·필터 직렬화를 검증한다.
1) 목표
- 단일 진입점 ApiClient(Dio 래퍼)로 모든 네트워크 호출 일원화
- 환경 변수 기반 BaseURL/타임아웃/로그 레벨 설정
- 인증 토큰 주입, 401 자동 처리(토큰 갱신 → 재시도), 에러 매핑 일관화
- 목록/단건 표준 응답 구조에 맞춘 헬퍼 제공 (구현 완료)
2) 의존성
dio: ^5.x,get_it: ^7.x,flutter_secure_storage,intl,pretty_dio_logger(dev) —pubspec.yaml에 반영됨.- 테스트:
mocktail,flutter_test(already configured).
3) 환경 변수
API_BASE_URL,API_CONNECT_TIMEOUT_MS,API_RECEIVE_TIMEOUT_MS,LOG_LEVEL- 로드 순서:
await Environment.initialize()→injection_container.dart에서ApiClient생성 .env.*파일에 샘플 값이 포함되어 있으며,FeatureFlags.initialize()이전에 호출된다.
4) 인증 스택
- 로그인/토큰 재발급 플로우는 Superport 백엔드와 동일 (
POST /auth/login,POST /auth/refresh). AuthInterceptor가 저장소에서 토큰을 읽어 Authorization 헤더를 주입하고, 401 발생 시 리프레시 콜백을 호출해 한 번만 재시도한다 (lib/core/network/interceptors/auth_interceptor.dart:45).TokenStorage는 플랫폼별 저장소(web/local) 구현을 제공한다 (lib/core/network/services/token_storage.dart).
5) 에러 매핑 정책
ApiErrorMapper가DioException을Failure로 변환해 코드/메시지를 표준화한다 (lib/core/network/api_error.dart).- HTTP 상태별 매핑: 400(검증), 401(세션 만료), 403(권한), 404(리소스 없음), 409(충돌), 422(업무 규칙), 500+(서버 오류).
- 테스트
test/core/network/api_client_test.dart:62가 매핑 결과를 검증한다.
6) 쿼리 헬퍼와 규약
ApiClient.buildQuery가 페이지네이션(page,page_size), 정렬(sort,order), 검색(q), 증분(updated_since), include, 맞춤 필터를 직렬화한다 (lib/core/network/api_client.dart:25).buildPath가 세그먼트를 안전하게 결합한다 (lib/core/network/api_client.dart:14).- 모든 Remote Repository는 해당 헬퍼를 사용하도록 테스트로 강제된다 (예:
test/features/approvals/data/approval_repository_remote_test.dart:42).
7) ApiClient 인터페이스
class ApiClient {
Future<Response<T>> get<T>(String path, {Map<String, dynamic>? query, Options? options, CancelToken? cancelToken});
Future<Response<T>> post<T>(String path, {dynamic data, Map<String, dynamic>? query, Options? options, CancelToken? cancelToken});
Future<Response<T>> patch<T>(String path, {dynamic data, Map<String, dynamic>? query, Options? options, CancelToken? cancelToken});
Future<Response<T>> delete<T>(String path, {dynamic data, Map<String, dynamic>? query, Options? options, CancelToken? cancelToken});
static Map<String, dynamic> buildQuery({...});
static String buildPath(Object base, [Iterable<Object?> segments = const []]);
}
8) 인터셉터 구성
AuthInterceptor: 토큰 주입 + 401 재시도, 동시 갱신 방지 큐 적용.LoggingInterceptor: 개발 모드에서만 pretty 출력 (lib/core/network/interceptors/logging_interceptor.dart).RetryInterceptor는 필요 시 idempotent 요청 재시도를 담당한다 (lib/core/network/interceptors/retry_interceptor.dart).
9) 표준 응답 파서
- 목록:
{ items, page, page_size, total }→PaginatedResult<T>(lib/core/common/models/paginated_result.dart). - 단건:
{ data: {...} }→ApiClient.unwrapAsMap/unwrap헬퍼가 추출한다 (lib/core/network/api_client.dart:91).
10) 사용 예시
final response = await _api.get<Map<String, dynamic>>(
ApiRoutes.approvals,
query: ApiClient.buildQuery(page: page, pageSize: pageSize, include: ['requested_by']),
);
return ApprovalDto.parsePaginated(response.data ?? const {});
11) 보안/스토리지
- 웹: localStorage, 모바일: secure storage —
TokenStorage가 추상화. - 민감정보 로깅 금지, 개발 모드에서만
pretty_dio_logger활성화. - 쿠키 기반 인증 시
dio.options.extra['withCredentials']=true를 사용하도록 확장 가능.
12) 테스트 전략
- 단위 테스트:
test/core/network/api_client_test.dart,test/core/network/auth_interceptor_test.dart에서 경로·쿼리·에러·토큰 재시도를 검증. - 기능 테스트: 각 Remote Repository 테스트가 파라미터 직렬화를 검증하고, 통합 테스트(
integration_test/approvals_flow_test.dart)가 실제 플로우를 검증한다.
13) 구현 체크리스트
dio및 보조 패키지 의존성을 추가했다.ApiClient/AuthInterceptor/ApiErrorMapper를 구현하고 테스트했다.Environment.initialize()이후 DI에서 ApiClient와 인터셉터를 등록한다 (lib/injection_container.dart:60).- 모든 Remote Repository가 ApiClient를 사용하도록 마이그레이션했다.
- 에러/토큰/재시도 정책을 위젯 및 도메인 테스트에 연결했다.
- 문서와 코드가 동기화되었으며, 변경 시
tool/sync_stock_docs.sh --check를 사용한다.