219 lines
12 KiB
Dart
219 lines
12 KiB
Dart
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/원격'); // 모니터링에 남음
|
||
});
|
||
}
|