JJ_MKVMaker/test/explorer_webdav_test.dart

219 lines
12 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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/app/settings.dart';
import 'package:jj_mkvmaker/core/secret_gate.dart';
import 'package:jj_mkvmaker/core/webdav.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/app_shell.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 'explorer_test.dart' show act, doubleTap, settle, calm;
import 'support/dav_server.dart';
class _Shell extends NoopShell {
final opened = <String>[];
@override
Future<bool> openWith(String path, {bool choose = false}) async {
opened.add(path);
return true;
}
}
void main() {
late Directory tmp, remote;
late String left;
late TestDavServer server;
late _Shell shell;
late AppController c;
setUp(() async {
tmp = Directory.systemTemp.createTempSync('jj_explorer_dav_');
left = p.join(tmp.path, 'left');
remote = Directory(p.join(tmp.path, 'remote'))..createSync();
File(p.join(left, 'doc.txt'))
..createSync(recursive: true)
..writeAsStringSync('hello');
File(p.join(left, 'sub', 'inner.txt'))
..createSync(recursive: true)
..writeAsStringSync('inner');
File(p.join(remote.path, '원격', 'note.txt'))
..createSync(recursive: true)
..writeAsStringSync('remote');
server = TestDavServer(remote);
await server.start();
shell = _Shell();
// 이 PC 의 드라이브 (네트워크 드라이브 등) 가 많으면 탭이 밀려 "WebDAV" 탭이 화면 밖으로 간다 - 시험 폴더의 드라이브만
final root = p.rootPrefix(tmp.path);
ExplorerPage.debugVolumes = () => [(root, root.replaceAll(RegExp(r'[\\/]+$'), ''))];
c = AppController(PlatformServices(
mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService(), shell: shell));
});
tearDown(() async {
ExplorerPage.debugVolumes = null;
DavRegistry.configure([]);
await server.stop();
tmp.deleteSync(recursive: true);
});
Future<void> pump(WidgetTester tester, Widget page, {Size size = const Size(1600, 900)}) async {
HttpOverrides.global = null; // testWidgets 는 HttpClient 를 막아 둔다 - 이 시험은 실제 (로컬) 서버에 연결
tester.view.physicalSize = size;
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
await tester.runAsync(() => tester.pumpWidget(MaterialApp(home: page)));
}
testWidgets('파일 탐색기: WebDAV 탭 → 서버 추가 (연결 확인) → 목록 · 열기 (받아서) · 로컬 → WebDAV 복사', (tester) async {
c.settings.explorerPaths = [left, left];
await pump(tester, ExplorerPage(c: c));
await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty);
// 서버가 없으면 위쪽 저장 장치 옆에 "WebDAV" 탭 (창마다)
expect(find.text('WebDAV'), findsNWidgets(2));
await act(tester, () => tester.tap(find.text('WebDAV').last));
await calm(tester);
expect(find.text('WebDAV 서버 추가'), findsOneWidget);
await tester.enterText(find.widgetWithText(TextField, '이름'), '집 NAS');
await tester.enterText(find.widgetWithText(TextField, '주소'), server.url);
await tester.enterText(find.widgetWithText(TextField, '아이디'), 'user');
await tester.enterText(find.widgetWithText(TextField, '비밀번호'), 'pass');
await tester.pump();
await act(tester, () => tester.tap(find.text('연결 확인')));
await settle(tester, () => find.textContaining('연결됨').evaluate().isNotEmpty);
expect(find.textContaining('연결됨: 항목 1개'), findsOneWidget);
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '저장')));
// 저장하면 설정에 남고 오른쪽 창이 그 서버로
await settle(tester, () => find.text('원격').evaluate().isNotEmpty);
expect(c.settings.webdavServers.single.url, server.url);
final id = c.settings.webdavServers.single.id;
expect(find.text('집 NAS'), findsNWidgets(3)); // 두 창의 탭 + 오른쪽 창의 맨 위 줄
expect(find.text('WebDAV'), findsNothing);
// WebDAV 폴더 펼치기 · 파일 두 번 누르기 = 임시 폴더로 받아 기본 앱으로
await act(tester, () => tester.tap(find.text('원격')));
await settle(tester, () => find.text('note.txt').evaluate().isNotEmpty);
await doubleTap(tester, find.text('note.txt'));
await settle(tester, () => shell.opened.isNotEmpty);
expect(p.split(shell.opened.single), containsAllInOrder(['jj_webdav', id, '원격', 'note.txt']));
expect(File(shell.opened.single).readAsStringSync(), 'remote');
// 왼쪽 doc.txt → 오른쪽 (WebDAV 원격 폴더) 으로 복사
await act(tester, () => tester.tap(find.text('원격')));
await settle(tester, () => false, rounds: 15);
await act(tester, () => tester.tap(find.text('doc.txt')));
await settle(tester, () => false, rounds: 15);
await act(tester, () => tester.tap(find.text('복사').first));
await calm(tester);
expect(find.textContaining('☁ 집 NAS/원격'), findsOneWidget);
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '복사')));
final up = File(p.join(remote.path, '원격', 'doc.txt'));
await settle(tester, () => up.existsSync() && find.byType(LinearProgressIndicator).evaluate().isEmpty);
expect(up.readAsStringSync(), 'hello');
});
testWidgets('178: 설정 옵션에 --remove-source-files 가 있으면 체크 켬 · [이동] · 경고, 끄면 정말 원본을 남긴다', (tester) async {
final s = DavServer(id: 'nas', name: '집 NAS', url: server.url, user: 'user', password: 'pass');
c.settings
..webdavServers = [s]
..rsyncOptions = '-av --remove-source-files'
..rsyncPaths = [left, 'dav://nas/'];
DavRegistry.configure([s]);
// 1280×720 (낮은 창) 에서도 체크 · 경고가 버튼에 가려지지 않게
await pump(tester, ExplorerPage(c: c, rsync: true), size: const Size(1280, 720));
await settle(tester, () => find.text('sub').evaluate().isNotEmpty && find.text('원격').evaluate().isNotEmpty);
await act(tester, () => tester.tap(find.text('sub')));
await act(tester, () => tester.tap(find.text('원격')));
await settle(tester, () => false, rounds: 5);
await act(tester, () => tester.tap(find.text('좌 → 우')));
await settle(tester, () => find.textContaining('비교하는 중').evaluate().isEmpty, rounds: 400);
// 보이는 것 = 실행될 것: 체크 켬 · [이동] · 경고 · 명령 줄에 한 번
CheckboxListTile box() => tester.widget<CheckboxListTile>(find.byType(CheckboxListTile));
expect(box().value, isTrue);
expect(find.widgetWithText(FilledButton, '이동'), findsOneWidget);
expect(find.textContaining('원본 파일이 지워집니다 (이동). 환경 설정의 rsync 옵션에'), findsOneWidget);
expect(find.text('rsync -av --remove-source-files'), findsOneWidget);
final buttonsTop = tester.getRect(find.widgetWithText(FilledButton, '이동')).top;
expect(tester.getRect(find.byType(CheckboxListTile)).bottom, lessThan(buttonsTop));
expect(tester.getRect(find.textContaining('원본 파일이 지워집니다')).bottom, lessThan(buttonsTop));
// 끄면 [실행] · 옵션 줄에서도 빠진다
await tester.tap(find.byType(CheckboxListTile));
await settle(tester, () => find.textContaining('비교하는 중').evaluate().isEmpty, rounds: 400);
expect(box().value, isFalse);
expect(find.widgetWithText(FilledButton, '실행'), findsOneWidget);
expect(find.text('rsync -av'), findsOneWidget);
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '실행')));
final got = File(p.join(remote.path, '원격', 'inner.txt'));
await settle(tester, () => got.existsSync() && find.byType(LinearProgressIndicator).evaluate().isEmpty, rounds: 200);
expect(got.readAsStringSync(), 'inner');
expect(File(p.join(left, 'sub', 'inner.txt')).existsSync(), isTrue, reason: '체크를 끄면 원본은 그대로 (복사)');
});
testWidgets('73: WebDAV 폴더도 즐겨찾기로 연다 - 비밀번호가 있는 서버는 마스터 문 (124) 을 지난다', (tester) async {
final s = DavServer(id: 'nas', name: '집 NAS', url: server.url, user: 'user', password: 'pass');
var asked = 0, allow = false;
SecretGate.check = (force) async {
asked++;
return allow;
};
addTearDown(() => SecretGate.check = null);
c.settings
..webdavServers = [s]
..explorerPaths = [left, left]
..folderBookmarks = [const FolderBookmark(name: 'NAS 원격', path: 'dav://nas/원격')];
DavRegistry.configure([s]);
await pump(tester, ExplorerPage(c: c));
await settle(tester, () => find.text('doc.txt').evaluate().isNotEmpty);
Future<void> openBookmark() async {
await act(tester, () => tester.tap(find.byTooltip('즐겨찾기').first));
// pumpAndSettle 이 아니라 창이 뜰 때까지 - 목록을 읽는 중 (진행 막대) 이면 무거울 때 시간 초과
await settle(tester, () => find.text('NAS 원격').evaluate().isNotEmpty);
await act(tester, () => tester.tap(find.text('NAS 원격')));
}
// 마스터 문을 지나지 못하면 열지 않고 알린다 (저장된 비밀번호를 쓰지 않음)
await openBookmark();
await settle(tester, () => find.textContaining('마스터 비밀번호가 필요합니다').evaluate().isNotEmpty, rounds: 200);
expect(asked, greaterThan(0));
expect(find.text('note.txt'), findsNothing);
// 지나면 그 폴더 목록
allow = true;
await openBookmark();
await settle(tester, () => find.text('note.txt').evaluate().isNotEmpty, rounds: 300);
expect(find.text('note.txt'), findsOneWidget);
});
testWidgets('Rsync 화면: 로컬 ↔ WebDAV 폴더 → (앱이 직접 맞춤 · rsync 필요 없음)', (tester) async {
final s = DavServer(id: 'nas', name: '집 NAS', url: server.url, user: 'user', password: 'pass');
c.settings
..webdavServers = [s]
..rsyncPaths = [left, 'dav://nas/'];
DavRegistry.configure([s]);
await pump(tester, ExplorerPage(c: c, rsync: true));
await settle(tester, () => find.text('sub').evaluate().isNotEmpty && find.text('원격').evaluate().isNotEmpty);
expect(find.byIcon(Icons.cloud), findsOneWidget); // 오른쪽 창 맨 위 줄
await act(tester, () => tester.tap(find.text('sub')));
await act(tester, () => tester.tap(find.text('원격')));
await settle(tester, () => false, rounds: 5);
await act(tester, () => tester.tap(find.text('좌 → 우')));
await settle(tester, () => find.textContaining('비교하는 중').evaluate().isEmpty, rounds: 400);
expect(find.text('→ ☁ 집 NAS › 원격'), findsOneWidget); // 109: 짧은 경로
// 72: 비교 - 보낼 것 · 받는 쪽 (WebDAV) 에만 있는 것 (지우기 없으니 그대로 둠)
expect(find.textContaining('→ + inner.txt'), findsOneWidget);
expect(find.textContaining('= note.txt'), findsOneWidget);
expect(find.textContaining('WebDAV · SFTP · SMB · FTP: rsync 대신 앱이 직접 맞춥니다'), findsOneWidget);
// M2: 옵션에 거르기가 없으면 거르기 경고는 없다
expect(find.textContaining('거르기 (--exclude'), findsNothing);
await act(tester, () => tester.tap(find.widgetWithText(FilledButton, '실행')));
final got = File(p.join(remote.path, '원격', 'inner.txt'));
await settle(tester, () => got.existsSync() && find.byType(LinearProgressIndicator).evaluate().isEmpty, rounds: 200);
expect(got.readAsStringSync(), 'inner');
expect(File(p.join(remote.path, '원격', 'note.txt')).existsSync(), isTrue); // 지우기 없이
expect(c.settings.copyTasks.single.dest, 'dav://nas/원격'); // 모니터링에 남음
});
}