import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/app/app_controller.dart'; import 'package:jj_mkvmaker/core/sftp.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/explorer_page.dart'; import 'package:path/path.dart' as p; import 'support/sftp_server.dart'; /// 88: 파일 탐색기의 SFTP 탭 - 처음 연결하면 서버 키 지문을 보이고 [지문 확인하고 연결] → [믿고 연결] 뒤 목록 (시험 서버 paramiko) void main() { late Directory tmp; SftpTestServer? server; setUp(() => tmp = Directory.systemTemp.createTempSync('jj_sftp88_ui_')); tearDown(() async { SftpRegistry.configure(const []); await server?.stop(); server = null; for (var i = 0;; i++) { try { if (tmp.existsSync()) tmp.deleteSync(recursive: true); break; } on FileSystemException { if (i > 30) rethrow; await Future.delayed(const Duration(milliseconds: 100)); } } }); // 횟수가 아니라 실제 시간으로 기다린다 (전체 시험이 함께 돌아 무거우면 paramiko 서버의 키 교환이 몇십 초 걸린다 - 관리자 지적 10/18) Future settle(WidgetTester tester, bool Function() done, {Duration limit = const Duration(seconds: 90)}) async { final end = DateTime.now().add(limit); while (DateTime.now().isBefore(end)) { await tester.runAsync(() => Future.delayed(const Duration(milliseconds: 30))); await tester.pump(); if (done()) break; } await tester.pump(const Duration(milliseconds: 300)); } testWidgets('처음 연결: 지문 안내 → [지문 확인하고 연결] → [믿고 연결] → 목록 · 지문은 설정에 저장', (tester) async { final root = Directory(p.join(tmp.path, 'root'))..createSync(); File(p.join(root.path, 'hello.txt')).writeAsStringSync('hi'); final s = await tester.runAsync(() => SftpTestServer.start(root: root.path, hostKeyFile: p.join(tmp.path, 'hk'))); server = s; final local = Directory(p.join(tmp.path, 'local'))..createSync(); final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); await tester.runAsync(() => c.updateSettings((x) => x ..sftpServers = [SftpServer(id: 's1', name: 'NAS', host: '127.0.0.1', port: s!.port, user: 'jj', password: 'pw')] ..explorer.paths = ['sftp://s1/', local.path])); tester.view.physicalSize = const Size(1600, 900); tester.view.devicePixelRatio = 1; addTearDown(tester.view.reset); await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: ExplorerPage(c: c)))); await settle(tester, () => find.text('지문 확인하고 연결').evaluate().isNotEmpty); expect(find.textContaining('처음 연결하는 서버입니다'), findsWidgets); expect(find.textContaining(s!.fingerprint), findsWidgets, reason: '안내에 지문'); expect(find.text('hello.txt'), findsNothing, reason: '믿기 전에는 열지 않는다'); // 탭: 🔑 아이콘 · 서버 이름 expect(find.byIcon(Icons.vpn_key_outlined), findsWidgets); await tester.tap(find.text('지문 확인하고 연결')); await settle(tester, () => find.text('믿고 연결').evaluate().isNotEmpty); expect(find.textContaining(s.fingerprint), findsWidgets, reason: '확인 창에도 지문'); await tester.tap(find.text('믿고 연결')); await settle(tester, () => find.text('hello.txt').evaluate().isNotEmpty); expect(find.text('hello.txt'), findsOneWidget); expect(c.settings.sftpServers.single.hostKeyFingerprint, s.fingerprint); expect(c.settings.sftpServers.single.hostKeyType, 'ssh-ed25519'); // 화면 내리기 · 연결 닫기 await tester.pumpWidget(const SizedBox()); await tester.runAsync(() async => SftpRegistry.configure(const [])); await tester.pump(const Duration(seconds: 1)); }, skip: !sftpTestServerAvailable); }