import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/app/download_manager.dart'; import 'package:jj_mkvmaker/app/settings.dart'; import 'package:jj_mkvmaker/core/download_detect.dart'; import 'package:jj_mkvmaker/platform/windows/aria2_backend.dart'; import 'package:jj_mkvmaker/services/downloader.dart'; import 'package:jj_mkvmaker/ui/downloads_page.dart'; class _Backend implements DownloadBackend { @override DownloadKind get kind => DownloadKind.torrent; final started = []; var cancelled = 0; @override Future<(String, List)?> expandPlaylist(String url) async => null; @override Future start(DownloadTask t, void Function() changed) async { started.add(t.source); t.state = DownloadState.downloading; } @override Future pause(DownloadTask t) async {} @override Future cancel(DownloadTask t) async { cancelled++; t.state = DownloadState.cancelled; } @override Future shutdown() async {} } /// 28 확인 (10/20): 마그넷이 이 PC (IPv6 는 Tailscale fd7a:… 뿐) 에서 시작되지 않던 것 - 인터넷 IPv6 판단 · 멈춘 줄 안내 void main() { InternetAddress a(String s) => InternetAddress(s); test('인터넷 IPv6: 전역 2000::/3 만 - Tailscale (fd7a:115c:a1e0::) · ULA · 링크 로컬 · Teredo · 6to4 는 아님', () { expect(hasInternetIpv6([a('fd7a:115c:a1e0::1234')]), isFalse, reason: 'Tailscale 뿐'); expect(hasInternetIpv6([a('fe80::1'), a('fdab::5')]), isFalse); expect(hasInternetIpv6([a('2001:0:4136:e378::1')]), isFalse, reason: 'Teredo'); expect(hasInternetIpv6([a('2002:c000:204::1')]), isFalse, reason: '6to4'); expect(hasInternetIpv6([a('fd7a:115c:a1e0::1'), a('2406:5900:1:2::10')]), isTrue); expect(hasInternetIpv6([a('192.168.0.5')]), isFalse); }); test('인터넷 IPv4: 루프백 · 169.254 · Tailscale 100.64/10 은 빼고 (사설 주소는 공유기 뒤라 넣음)', () { expect(hasInternetIpv4([a('127.0.0.1'), a('169.254.3.4'), a('100.80.195.34')]), isFalse); expect(hasInternetIpv4([a('192.168.88.105')]), isTrue); expect(hasInternetIpv4([a('100.128.0.1')]), isTrue, reason: '100.64/10 밖'); }); test('설정: 시작되지 않으면 알리기 - 처음 60초, 모르는 값은 60', () { expect(AppSettings().torrentStallSeconds, 60); expect(AppSettings.fromJson({'torrentStallSeconds': 0}).torrentStallSeconds, 0); expect(AppSettings.fromJson({'torrentStallSeconds': 7}).torrentStallSeconds, 60); }); testWidgets('멈춘 마그넷 줄: "트래커 · 피어에 닿지 않습니다" · 이유 후보 · [다시 시도] (지금 것을 지우고 새로 넣음)', (tester) async { final b = _Backend(); final s = AppSettings()..dnsMode = 'custom'; final d = DownloadManager(backends: [b], settings: () => s); final t = d.add('magnet:?xt=urn:btih:7acf8fb590b2060dd9c3146ef770169d593433b0')!; await tester.pump(); t.state = DownloadState.downloading; t.extra ..['gid'] = 'abc' ..['stallSince'] = DateTime.now().millisecondsSinceEpoch - 61000 ..['stalled'] = true ..['stallReasons'] = ['ipv6only', 'firewall', 'dns']; await tester.pumpWidget(MaterialApp(home: DownloadsPage(d: d))); await tester.pump(); expect(find.textContaining(RegExp(r'트래커 · 피어에 닿지 않습니다 \(6[1-3]초째\)')), findsOneWidget); expect(find.textContaining('인터넷 IPv4 가 없음 (IPv6 만) · 방화벽 · 공유기가 토렌트를 막음 · DNS 강제'), findsOneWidget); await tester.tap(find.text('다시 시도')); await tester.pump(); expect(b.cancelled, 1); expect(t.extra.containsKey('gid'), isFalse, reason: '새로 추가'); expect(t.extra.containsKey('stalled'), isFalse); await tester.pump(const Duration(milliseconds: 100)); expect(b.started, hasLength(2), reason: '다시 시작'); await tester.pumpWidget(const SizedBox()); await tester.pump(const Duration(seconds: 1)); }); }