import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/core/download_detect.dart'; import 'package:jj_mkvmaker/platform/windows/ytdlp_backend.dart'; import 'package:jj_mkvmaker/services/downloader.dart'; import 'package:path/path.dart' as p; /// 184-2: 확장자가 없는 주소의 동영상이 아닌 파일 - yt-dlp 가 다 받은 뒤 후처리에서 실패해도 받은 파일을 원래 이름으로 남기고 완료 void main() { test('원래 이름: 물음표 뒤 파일 이름 → 경로 끝 → 받은 이름에서 [id] · .unknown_video 빼기', () { expect(nonVideoFileName('https://a.com/download.php?id=3&file=docs/report.zip', 'x [1].unknown_video'), 'report.zip'); expect(nonVideoFileName('https://a.com/files/setup.exe?token=abc', 'x'), 'setup.exe'); expect(nonVideoFileName('https://a.com/get.php?id=3', r'C:\d\get [get].unknown_video'), 'get'); expect(nonVideoFileName('https://a.com/dl/12345', r'C:\d\12345 [12345].bin'), '12345.bin'); }); test('후처리 "동영상이 아님" 오류만 고른다', () { expect(isNonVideoPostprocessError('ERROR: Postprocessing: Error opening input files: Invalid data found when processing input'), isTrue); expect(isNonVideoPostprocessError('ERROR: [generic] Unable to download webpage: HTTP Error 404'), isFalse); }); test('yt-dlp 가 받은 뒤 후처리에서 실패 → 원래 이름으로 남기고 완료, 같은 이름은 덮지 않음', () async { final dir = Directory.systemTemp.createTempSync('jj_plain_'); addTearDown(() => dir.deleteSync(recursive: true)); File(p.join(dir.path, 'report.zip')).writeAsStringSync('old'); // 가짜 yt-dlp: 파일을 만들고 "Destination" 을 알린 뒤 후처리 오류로 끝 final script = File(p.join(dir.path, 'fake_ytdlp.py')) ..writeAsStringSync(r''' import os, sys a = sys.argv[1:] d = a[a.index('-P') + 1] f = os.path.join(d, 'download [download].unknown_video') open(f, 'w').write('PK') print('[generic] Extracting URL: ' + a[-1], flush=True) print('[download] Destination: ' + f, flush=True) sys.stderr.write('ERROR: Postprocessing: Error opening input files: Invalid data found when processing input\n') sys.exit(1) '''); final out = Directory(p.join(dir.path, 'out'))..createSync(); File(p.join(out.path, 'report.zip')).writeAsStringSync('old'); final backend = YtDlpBackend(ytdlp: 'python', prefixArgs: [script.path], deno: ''); final t = DownloadTask( id: '1', kind: DownloadKind.video, source: 'https://a.com/download.php?file=report.zip', dir: out.path); await backend.start(t, () {}); expect(t.state, DownloadState.done, reason: t.error); expect(t.files, [p.join(out.path, 'report (2).zip')]); expect(File(t.files.single).readAsStringSync(), 'PK'); expect(File(p.join(out.path, 'report.zip')).readAsStringSync(), 'old', reason: '있던 파일은 덮지 않음'); expect(File(p.join(out.path, 'download [download].unknown_video')).existsSync(), isFalse); }); test('사이트 추출기 (youtube 등) 의 후처리 실패는 바꾸지 않는다 - 망가진 동영상을 "완료" 로 두지 않음', () async { final dir = Directory.systemTemp.createTempSync('jj_plain_'); addTearDown(() => dir.deleteSync(recursive: true)); final script = File(p.join(dir.path, 'fake_ytdlp.py')) ..writeAsStringSync(r''' import os, sys a = sys.argv[1:] d = a[a.index('-P') + 1] f = os.path.join(d, 'clip [abc].f137.mp4') open(f, 'w').write('V') print('[youtube] Extracting URL: ' + a[-1], flush=True) print('[download] Destination: ' + f, flush=True) sys.stderr.write('ERROR: Postprocessing: Error opening input files: Invalid data found when processing input\n') sys.exit(1) '''); final backend = YtDlpBackend(ytdlp: 'python', prefixArgs: [script.path], deno: ''); final t = DownloadTask( id: '1', kind: DownloadKind.video, source: 'https://www.youtube.com/watch?v=abc&file=a.zip', dir: dir.path); await backend.start(t, () {}); expect(t.state, DownloadState.failed); expect(t.error, contains('Postprocessing')); expect(File(p.join(dir.path, 'clip [abc].f137.mp4')).existsSync(), isTrue, reason: '이름도 바꾸지 않음'); }); test('받은 파일이 없으면 그대로 실패', () async { final dir = Directory.systemTemp.createTempSync('jj_plain_'); addTearDown(() => dir.deleteSync(recursive: true)); final script = File(p.join(dir.path, 'fake_ytdlp.py')) ..writeAsStringSync(r''' import sys sys.stderr.write('ERROR: Postprocessing: Invalid data found when processing input\n') sys.exit(1) '''); final backend = YtDlpBackend(ytdlp: 'python', prefixArgs: [script.path], deno: ''); final t = DownloadTask(id: '1', kind: DownloadKind.video, source: 'https://a.com/x', dir: dir.path); await backend.start(t, () {}); expect(t.state, DownloadState.failed); }); }