JJ_MKVMaker/integration_test/tor_dns_real_test.dart

138 lines
6.2 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/app/bookmarks_controller.dart';
import 'package:jj_mkvmaker/app/component_store.dart';
import 'package:jj_mkvmaker/app/net_guard.dart';
import 'package:jj_mkvmaker/app/tor_bundle.dart';
import 'package:jj_mkvmaker/app/tor_service.dart';
import 'package:jj_mkvmaker/platform/windows/desktop_storage_service.dart';
import 'package:jj_mkvmaker/platform/windows/process_media_tool.dart';
import 'package:jj_mkvmaker/services/platform_services.dart';
import 'package:jj_mkvmaker/ui/app_actions.dart';
import 'package:jj_mkvmaker/ui/browser_page.dart';
import 'package:jj_mkvmaker/ui/tor_page.dart';
import 'package:path/path.dart' as p;
/// 실제 망 (Windows): DNS 강제 (8.8.8.8) 로 앱의 HTTP · 일반 브라우저 (Edge) 가 이름을 푸는지, 닿지 않는 DNS 는 실패로 알리는지,
/// Tor 컴포넌트 (공식 번들, SHA-256 확인) 를 받아 Tor 화면이 check.torproject.org 의 "Congratulations" 까지 가는지.
/// 데이터는 JJ_MKVMAKER_DATA (없으면 임시 폴더) 에만 - 사용자 앱 데이터를 건드리지 않는다.
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('DNS 강제 · Tor 화면 실제 연결', (tester) async {
final data = Platform.environment['JJ_MKVMAKER_DATA'] ?? Directory.systemTemp.createTempSync('jj_tor_real_').path;
Directory(data).createSync(recursive: true);
ComponentStore.dataDirectory = data;
ComponentStore.shared = ComponentStore(data);
TorService.dataDir = data;
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
c.settings.webViewDataDir = p.join(data, 'webview');
final logs = <String>[];
final answered = <String>[];
final g = NetGuard.instance
..config = (() => c.settings.dnsConfig)
..log = logs.add;
g.resolver.onAnswer = (h, s) => answered.add('$h@$s');
TorService.instance.log = logs.add;
expect(c.settings.dnsMode, 'custom');
expect(c.settings.dnsPrimary, '8.8.8.8');
await tester.runAsync(g.ensure);
HttpOverrides.global = JjHttpOverrides();
Future<void> wait(int ms) async {
for (var i = 0; i < ms ~/ 200; i++) {
await tester.runAsync(() => Future<void>.delayed(const Duration(milliseconds: 200)));
await tester.pump();
}
}
// 1. 앱의 HTTP (HttpClient) → 앱 안 프록시 → 8.8.8.8
final code = await tester.runAsync(() async {
final cl = HttpClient();
final r = await (await cl.getUrl(Uri.parse('https://www.google.com/generate_204'))).close();
await r.drain<void>();
cl.close();
return r.statusCode;
});
// ignore: avoid_print
print('RESULT HttpClient google 204: $code, 답한 DNS: $answered');
expect(code, 204);
expect(answered, contains('www.google.com@8.8.8.8'));
// 2. 일반 브라우저 (Edge WebView2) 도 같은 프록시로
final bm = BookmarksController(p.join(data, 'bm.json'));
await tester.pumpWidget(MaterialApp(
builder: (context, child) => AppScope(controller: c, bookmarks: bm, child: child!),
home: BrowserPage(c: c, bookmarks: bm, initialUrl: 'https://example.com/'),
));
String title = '';
for (var i = 0; i < 60 && !title.contains('Example'); i++) {
await wait(500);
title = '${await tester.runAsync(() async => BrowserPage.debugNav?.evaluate('document.title'))}';
}
// ignore: avoid_print
print('RESULT Edge example.com 제목: $title, 답한 DNS: ${answered.where((a) => a.startsWith('example.com'))}');
expect(title, contains('Example'));
expect(answered, contains('example.com@8.8.8.8'));
await tester.pumpWidget(const SizedBox());
await wait(1000);
// 3. 닿지 않는 DNS (TEST-NET) → 기기 DNS 로 바꾸지 않고 실패 + 알림
c.settings
..dnsPrimary = '192.0.2.1'
..dnsSecondary = '';
g.resolver.clearCache();
final err = await tester.runAsync(() async {
final cl = HttpClient();
try {
final r = await (await cl.getUrl(Uri.parse('https://www.wikipedia.org/'))).close();
await r.drain<void>();
return 'HTTP ${r.statusCode}';
} catch (e) {
return '$e';
} finally {
cl.close(force: true);
}
});
// ignore: avoid_print
print('RESULT 닿지 않는 DNS: $err / 알림: ${g.failure.value?.reason}');
expect(err, contains('502'));
expect(g.failure.value, isNotNull);
c.settings
..dnsPrimary = '8.8.8.8'
..dnsSecondary = '8.8.4.4';
g.resolver.clearCache();
// 4. Tor 컴포넌트 받기 (공식 번들 · SHA-256) → Tor 화면 → check.torproject.org
await tester.runAsync(() => TorBundle.installWindows());
expect(TorBundle.windowsExe, isNotNull);
expect(TorBundle.installedVersion(), TorBundle.version);
c.settings
..components = [...c.settings.components, 'tor']
..torNoticeSeen = true;
await tester.pumpWidget(MaterialApp(
builder: (context, child) => AppScope(controller: c, bookmarks: bm, child: child!),
home: TorPage(c: c),
));
var body = '';
for (var i = 0; i < 360 && !body.contains('Congratulations'); i++) {
await wait(500);
if (TorPage.debugNav != null) body = '${await tester.runAsync(() async => TorPage.debugNav!.evaluate('document.body ? document.body.innerText : ""'))}';
}
final rtc = await tester.runAsync(() async => TorPage.debugNav?.evaluate('typeof RTCPeerConnection'));
// ignore: avoid_print
print('RESULT Tor 상태: ${TorService.instance.status.value.phase} / RTCPeerConnection: $rtc\n'
'RESULT check.torproject.org: ${body.split('\n').where((l) => l.trim().isNotEmpty).take(3).join(' | ')}');
// ignore: avoid_print
print('RESULT 기록: ${logs.where((l) => l.contains('Tor')).join(' | ')}');
expect(body, contains('Congratulations'));
expect(rtc, 'undefined');
await tester.pumpWidget(const SizedBox());
await tester.runAsync(() => TorService.instance.stop());
await wait(1000);
}, timeout: const Timeout(Duration(minutes: 10)));
}