diff --git a/test/ftp_test.dart b/test/ftp_test.dart index a50dae4..8f2d24c 100644 --- a/test/ftp_test.dart +++ b/test/ftp_test.dart @@ -14,6 +14,11 @@ import 'package:jj_mkvmaker/core/file_ops.dart'; import 'package:jj_mkvmaker/core/rsync_filter.dart'; import 'package:jj_mkvmaker/core/sync_preview.dart'; import 'support/rsync_scenes.dart'; +import 'package:jj_mkvmaker/app/app_controller.dart'; +import 'package:jj_mkvmaker/core/playlist.dart' show PlaylistMode; +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'; /// l (관리자 지시 10/17): FTP · FTPS (dart:io 로 직접). 실제 서버 시험은 tool/ftp_test_server.py (pyftpdlib) - /// 시험용 파이썬 (sftp 시험과 같은 venv) 에 pyftpdlib 가 없으면 건너뛴다 @@ -263,6 +268,45 @@ void main() { expect(await range(7, null), '789'); }, skip: hasPy ? false : '시험용 파이썬 없음'); + test('관리자 지시 (10/20): FTP 동영상 - 같은 폴더 목록 · 다리로 받지 않고 재생 (Range · 건너뛰기) · 다음 화 · 그 자막', () async { + await start('plain'); + FtpRegistry.configure([srv(security: 'none')]); + Directory(p.join(root.path, '영상')).createSync(); + File(p.join(root.path, '영상', '1화.mkv')).writeAsStringSync('0123456789'); + File(p.join(root.path, '영상', '2화.mkv')).writeAsStringSync('abcdefghij'); + File(p.join(root.path, '영상', '2화.ko.srt')).writeAsStringSync('1\n00:00:01,000 --> 00:00:02,000\n둘째\n'); + File(p.join(root.path, '영상', '메모.txt')).writeAsStringSync('t'); + addTearDown(RemoteStreamBridge.close); + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.playlistMode = PlaylistMode.folder; + final plan = await c.preparePlayback(['ftp://f1/영상/1화.mkv']); + expect(plan!.$1, ['ftp://f1/영상/1화.mkv', 'ftp://f1/영상/2화.mkv'], reason: '같은 폴더의 동영상만 차례로 (다음 화)'); + + final bridge = await RemoteStreamBridge.get(); + final u1 = bridge.url('ftp://f1/영상/1화.mkv'), u2 = bridge.url('ftp://f1/영상/2화.mkv'); + expect(u1, startsWith('http://127.0.0.1:')); + expect(u1, isNot(contains('pw')), reason: '주소에 비밀번호 없음'); + Future<(int, String)> get(String url, [String? range]) async { + final http = HttpClient(); + try { + final req = await http.getUrl(Uri.parse(url)); + if (range != null) req.headers.set('Range', range); + final res = await req.close(); + return (res.statusCode, await res.transform(utf8.decoder).join()); + } finally { + http.close(force: true); + } + } + + expect(await get(u1), (200, '0123456789')); + expect(await get(u1, 'bytes=4-6'), (206, '456'), reason: '건너뛰기 - 그 자리부터'); + expect(await get(u1, 'bytes=7-'), (206, '789')); + expect(await get(u2), (200, 'abcdefghij'), reason: '다음 화도 받지 않고 바로'); + final subs = await c.externalSubtitlesFor('ftp://f1/영상/2화.mkv'); + expect(subs.map(p.basename), ['2화.ko.srt']); + expect(File(subs.single).readAsStringSync(), contains('둘째')); + }, skip: hasPy ? false : '시험용 파이썬 없음'); + test('FTPS (암시, 990 꼴)', () async { await start('implicit'); final c = FtpRemote(srv(security: 'implicit', cert: fp)); diff --git a/test/smb_test.dart b/test/smb_test.dart index 3d5eb4f..7d8dfdf 100644 --- a/test/smb_test.dart +++ b/test/smb_test.dart @@ -13,6 +13,12 @@ import 'package:jj_mkvmaker/core/file_ops.dart'; import 'package:jj_mkvmaker/core/rsync_filter.dart'; import 'package:jj_mkvmaker/core/sync_preview.dart'; import 'support/rsync_scenes.dart'; +import 'package:jj_mkvmaker/app/app_controller.dart'; +import 'package:jj_mkvmaker/core/playlist.dart' show PlaylistMode; +import 'package:jj_mkvmaker/core/remote_stream.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'; /// 88: Android SMB (smbj) 의 Dart 쪽 - 플랫폼 채널을 가짜 서버로 (실제 smbj · 서버 연결은 Android 기기 확인 몫) class _FakeSmb { @@ -294,4 +300,48 @@ void main() { } expect(bad, isEmpty, reason: bad.join('\n')); }); + + test('관리자 지시 (10/20): SMB 동영상 - 같은 폴더 목록 · 다리로 받지 않고 재생 (Range · 건너뛰기) · 다음 화 · 그 자막', () async { + SmbRegistry.configure(const [server]); + // 위젯 시험 바인딩은 HttpClient 를 가짜 (늘 400) 로 바꾼다 - 이 시험은 이 기기 안의 다리 (127.0.0.1) 에 실제로 묻는다 + final saved = HttpOverrides.current; + HttpOverrides.global = null; + addTearDown(() => HttpOverrides.global = saved); + fake.files.addAll({ + '영상': null, + '영상\\1화.mkv': utf8.encode('0123456789'), + '영상\\2화.mkv': utf8.encode('abcdefghij'), + '영상\\2화.ko.srt': utf8.encode('1\n00:00:01,000 --> 00:00:02,000\n둘째\n'), + '영상\\메모.txt': utf8.encode('t'), + }); + addTearDown(RemoteStreamBridge.close); + final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService())); + c.settings.playlistMode = PlaylistMode.folder; + final plan = await c.preparePlayback(['smb://m1/영상/1화.mkv']); + expect(plan!.$1, ['smb://m1/영상/1화.mkv', 'smb://m1/영상/2화.mkv'], reason: '같은 폴더의 동영상만 차례로 (다음 화)'); + + final bridge = await RemoteStreamBridge.get(); + final u1 = bridge.url('smb://m1/영상/1화.mkv'), u2 = bridge.url('smb://m1/영상/2화.mkv'); + expect(u1, startsWith('http://127.0.0.1:')); + expect(u1, isNot(contains('pw')), reason: '주소에 비밀번호 없음'); + Future<(int, String)> get(String url, [String? range]) async { + final http = HttpClient(); + try { + final req = await http.getUrl(Uri.parse(url)); + if (range != null) req.headers.set('Range', range); + final res = await req.close(); + return (res.statusCode, await res.transform(utf8.decoder).join()); + } finally { + http.close(force: true); + } + } + + expect(await get(u1), (200, '0123456789')); + expect(await get(u1, 'bytes=4-6'), (206, '456'), reason: '건너뛰기 - 그 자리부터'); + expect(await get(u1, 'bytes=7-'), (206, '789')); + expect(await get(u2), (200, 'abcdefghij'), reason: '다음 화도 받지 않고 바로'); + final subs = await c.externalSubtitlesFor('smb://m1/영상/2화.mkv'); + expect(subs.map(p.basename), ['2화.ko.srt']); + expect(File(subs.single).readAsStringSync(), contains('둘째')); + }); }