Files
superport_v2/flutter_test_config.dart
JiWoong Sul d76f765814 feat(approvals): Approval Flow v2 프런트엔드 전면 개편
- 환경/라우터 모듈에 approval_flow_v2 토글을 추가하고 FeatureFlags 초기화를 연결 (.env*, lib/core/**)
- ApiClient 빌더·ApiRoutes 확장과 ApprovalRepositoryRemote 리팩터링으로 include·액션 시그니처를 정합화
- ApprovalFlow·ApprovalDraft 엔티티/레포/유즈케이스를 도입해 서버 초안과 단계 액션(승인·회수·재상신)을 지원
- Approval 컨트롤러·히스토리·템플릿 페이지와 공유 위젯을 재작성해 감사 로그·회수 UX·템플릿 CRUD를 반영
- Inbound/Outbound/Rental 컨트롤러·페이지에 결재 섹션을 삽입하고 대시보드 pending 카드 요약을 갱신
- SuperportDialog·FormField 등 공통 위젯을 보강하고 승인 위젯 가이드를 추가해 UI 가이드를 정리
- 결재/재고 테스트 픽스처와 단위·위젯·통합 테스트를 확장하고 flutter_test_config로 스테이징 호스트를 허용
- Approval Flow 레포트/플랜 문서를 업데이트하고 ApprovalFlow_System_Integration_and_ChangePlan.md를 추가
- 실행: flutter analyze, flutter test
2025-10-31 01:05:39 +09:00

147 lines
3.6 KiB
Dart

import 'dart:async';
import 'dart:io';
/// 스테이징 결재 더블(approval double) 연결 시 인증서 우회를 위한 HttpOverrides.
class _ApprovalStagingHttpOverrides extends HttpOverrides {
_ApprovalStagingHttpOverrides(this._patterns);
final List<_HostPattern> _patterns;
bool _isAllowedHost(String host) {
final normalized = host.trim().toLowerCase();
if (normalized.isEmpty) {
return false;
}
for (final pattern in _patterns) {
if (pattern.matches(normalized)) {
return true;
}
}
return false;
}
@override
HttpClient createHttpClient(SecurityContext? context) {
final client = super.createHttpClient(context);
client.badCertificateCallback = (cert, host, port) {
// 허용된 호스트에 대해서만 자기서명 인증서를 허용한다.
return _patterns.isNotEmpty && _isAllowedHost(host);
};
return client;
}
}
/// 호스트 패턴(정확 일치 또는 서픽스 매칭)을 표현한다.
class _HostPattern {
_HostPattern._(this.value, this.isSuffix);
factory _HostPattern.parse(String raw) {
final trimmed = raw.trim().toLowerCase();
if (trimmed.startsWith('*.')) {
return _HostPattern._(trimmed.substring(2), true);
}
if (trimmed.startsWith('.')) {
return _HostPattern._(trimmed.substring(1), true);
}
return _HostPattern._(trimmed, false);
}
final String value;
final bool isSuffix;
bool get isValid => value.isNotEmpty;
bool matches(String host) {
if (!isValid) {
return false;
}
if (isSuffix) {
return host == value || host.endsWith('.$value');
}
return host == value;
}
}
bool _parseBoolDynamic(String? raw) {
if (raw == null) {
return false;
}
switch (raw.trim().toLowerCase()) {
case '1':
case 'y':
case 'yes':
case 'true':
case 'on':
return true;
default:
return false;
}
}
List<_HostPattern> _collectAllowedPatterns() {
final env = Platform.environment;
final tokens = <String>[];
void addTokens(String? source) {
if (source == null || source.trim().isEmpty) {
return;
}
tokens.addAll(source.split(','));
}
addTokens(env['APPROVAL_DOUBLE_ALLOWED_HOSTS']);
addTokens(
const String.fromEnvironment(
'APPROVAL_DOUBLE_ALLOWED_HOSTS',
defaultValue: '',
),
);
final baseUrl =
env['API_BASE_URL'] ??
const String.fromEnvironment('API_BASE_URL', defaultValue: '');
final uri = Uri.tryParse(baseUrl);
if (uri != null && uri.host.isNotEmpty) {
tokens.add(uri.host);
}
final patterns = <_HostPattern>[];
for (final token in tokens) {
final pattern = _HostPattern.parse(token);
if (pattern.isValid) {
patterns.add(pattern);
}
}
return patterns;
}
/// Flutter 테스트 진입점.
///
/// - `USE_APPROVAL_STAGING_DOUBLE` 토글이 true일 때 HttpOverrides를 등록한다.
/// - `APPROVAL_DOUBLE_ALLOWED_HOSTS` 또는 `API_BASE_URL` 호스트를 인증 허용 목록에 추가한다.
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
final runOverride =
_parseBoolDynamic(Platform.environment['USE_APPROVAL_STAGING_DOUBLE']) ||
const bool.fromEnvironment(
'USE_APPROVAL_STAGING_DOUBLE',
defaultValue: false,
);
HttpOverrides? previous;
if (runOverride) {
final patterns = _collectAllowedPatterns();
if (patterns.isNotEmpty) {
previous = HttpOverrides.current;
HttpOverrides.global = _ApprovalStagingHttpOverrides(patterns);
}
}
try {
await testMain();
} finally {
if (runOverride) {
HttpOverrides.global = previous;
}
}
}