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 = []; 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 testExecutable(FutureOr 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; } } }