JJ_MKVMaker/test/cleanup_test.dart

178 lines
8.1 KiB
Dart

import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:jj_mkvmaker/app/app_controller.dart';
import 'package:jj_mkvmaker/app/cleanup.dart';
import 'package:jj_mkvmaker/app/download_manager.dart';
import 'package:jj_mkvmaker/core/download_detect.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/downloader.dart';
import 'package:jj_mkvmaker/services/model_store.dart';
import 'package:jj_mkvmaker/services/platform_services.dart';
import 'package:path/path.dart' as p;
/// 앱 임시 폴더를 시험 폴더로
class _Storage extends DesktopStorageService {
final String tmp;
_Storage(this.tmp);
@override
Future<String> tempDirectory() async => tmp;
}
class _Backend implements DownloadBackend {
@override
DownloadKind get kind => DownloadKind.video;
@override
Future<(String, List<PlaylistEntry>)?> expandPlaylist(String url) async => null;
@override
Future<void> start(DownloadTask t, void Function() changed) async => t.state = DownloadState.downloading;
@override
Future<void> pause(DownloadTask t) async {}
@override
Future<void> cancel(DownloadTask t) async {}
@override
Future<void> shutdown() async {}
}
void main() {
late Directory root;
late AppController c;
late String tmp, data, models, sysTmp, logs;
File make(String path, [int size = 100]) => File(path)
..parent.createSync(recursive: true)
..writeAsBytesSync(List.filled(size, 1));
setUp(() {
root = Directory.systemTemp.createTempSync('jj_clean_');
tmp = p.join(root.path, 'apptmp');
data = p.join(root.path, 'data');
models = p.join(root.path, 'models');
sysTmp = p.join(root.path, 'systemp');
logs = p.join(data, 'Logs');
c = AppController(PlatformServices(
mediaTool: ProcessMediaTool('x', 'y'),
storage: _Storage(tmp),
models: ModelStore(models),
));
c.settings.download.root = p.join(root.path, 'dl');
c.logFile = p.join(logs, 'app.log');
// 지울 것
make(p.join(tmp, 'ai_123', 'audio.wav'), 5000);
make(p.join(tmp, 'jj_adjust_after_1.png'));
make(p.join(data, 'settings.json.4567.tmp'));
make(p.join(c.settings.ytDlpDir, '영상 [abc].mp4.part'), 3000);
make(p.join(c.settings.ytDlpDir, '영상 [abc].f137.mp4'), 2000);
make(p.join(c.settings.ytDlpDir, '영상 [abc].mp4.ytdl'));
make(p.join(c.settings.ytDlpDir, '재생목록', '두번째 [def].webm.part-Frag12'));
make(p.join(c.settings.aria2Dir, 'movie.mkv.aria2'));
make(p.join(models, 'whisper', 'ggml-small.bin.part'), 4000);
make(p.join(sysTmp, 'jj_mkvmaker_update_2026.10.04_001', 'JJ_MKVMaker.zip'), 6000);
make(p.join(sysTmp, 'jj_ffmpeg.download'));
make(p.join(logs, 'app.log.1'), 700);
// 남길 것
make(p.join(c.settings.ytDlpDir, '영상 [abc].mp4'), 9000);
make(p.join(c.settings.ytDlpDir, 'jj_mkv', '영상 [abc].mkv'));
make(p.join(c.settings.aria2Dir, 'movie.mkv'));
make(p.join(models, 'whisper', 'ggml-base.bin'));
make(p.join(data, 'settings.json'));
make(p.join(sysTmp, 'other_app.tmp'));
make(p.join(logs, 'app.log'));
});
tearDown(() => root.deleteSync(recursive: true));
test('찾기: 묶음별로 남은 조각만 찾고, 받은 동영상 · MKV · 모델 · 설정 · 지금 기록은 건드리지 않는다', () async {
final groups = await Cleaner(c, dataDir: data, systemTemp: sysTmp).scan();
Map<String, List<String>> names = {
for (final g in groups) g.id: [for (final i in g.items) p.basename(i.path)]..sort(),
};
expect(names['work'], ['ai_123', 'jj_adjust_after_1.png', 'settings.json.4567.tmp']);
expect(names['download'], [
'movie.mkv.aria2',
'두번째 [def].webm.part-Frag12',
'영상 [abc].f137.mp4',
'영상 [abc].mp4.part',
'영상 [abc].mp4.ytdl',
]);
expect(names['models'], ['ggml-small.bin.part']);
expect(names['update'], ['jj_ffmpeg.download', 'jj_mkvmaker_update_2026.10.04_001']);
expect(names['logs'], ['app.log.1']);
expect(groups.firstWhere((g) => g.id == 'work').bytes, 5000 + 100 + 100);
expect(groups.every((g) => g.skipped == null), isTrue);
final (n, bytes) = await Cleaner(c, dataDir: data, systemTemp: sysTmp).clean(groups);
expect(n, 3 + 5 + 1 + 2 + 1);
expect(bytes, greaterThan(20000));
// 남길 것은 그대로
for (final keep in [
p.join(c.settings.ytDlpDir, '영상 [abc].mp4'),
p.join(c.settings.ytDlpDir, 'jj_mkv', '영상 [abc].mkv'),
p.join(c.settings.aria2Dir, 'movie.mkv'),
p.join(models, 'whisper', 'ggml-base.bin'),
p.join(data, 'settings.json'),
p.join(sysTmp, 'other_app.tmp'),
p.join(logs, 'app.log'),
]) {
expect(File(keep).existsSync(), isTrue, reason: keep);
}
// 다시 찾으면 없음
final again = await Cleaner(c, dataDir: data, systemTemp: sysTmp).scan();
expect(again.every((g) => g.items.isEmpty), isTrue);
});
test('다운로드 중 (일시정지 포함) 이면 받다 만 다운로드는 건너뛴다', () async {
final d = DownloadManager(backends: [_Backend()], settings: () => c.settings, readClipboard: () async => null);
addTearDown(d.dispose);
d.addPage('https://www.youtube.com/watch?v=abc');
await Future<void>.delayed(Duration.zero);
expect(d.activeCount, 1);
final groups = await Cleaner(c, downloads: d, dataDir: data, systemTemp: sysTmp).scan();
final dl = groups.firstWhere((g) => g.id == 'download');
expect(dl.skipped, isNotNull);
await Cleaner(c, downloads: d, dataDir: data, systemTemp: sysTmp).clean(groups);
expect(File(p.join(c.settings.ytDlpDir, '영상 [abc].mp4.part')).existsSync(), isTrue, reason: '이어받기용 조각은 그대로');
expect(Directory(p.join(tmp, 'ai_123')).existsSync(), isFalse, reason: '다른 묶음은 정리');
});
test('크기 글', () {
expect(Cleaner.sizeText(512), '512 B');
expect(Cleaner.sizeText(2048), '2 KB');
expect(Cleaner.sizeText(5 * 1024 * 1024), '5.0 MB');
expect(Cleaner.sizeText(3 * 1024 * 1024 * 1024), '3.0 GB');
});
test('관리자 지시 (10/20): 앱이 켜질 때 지난 업데이트가 남긴 것 (풀어 둔 새 판 · .ps1 · .log) 만 지운다 - 막 생긴 것 · 다른 jj 이름은 남김', () {
final tmp = Directory.systemTemp.createTempSync('jj_upd_left_');
addTearDown(() => tmp.deleteSync(recursive: true));
final old = DateTime.now().subtract(const Duration(hours: 1));
void make(String rel, {bool fresh = false}) {
final f = File(p.join(tmp.path, rel))..createSync(recursive: true);
if (!fresh) f.setLastModifiedSync(old);
}
make(p.join('jj_mkvmaker_update_2026.10.18_979', 'extract', 'jj_mkvmaker.exe'));
make('jj_mkvmaker_update.ps1');
make('jj_mkvmaker_update.log', fresh: true); // 교체 스크립트가 막 쓴 것
// 앱 자신이 업데이트 때 만든 이름만 지운다 - 다른 jj 이름 (앱 임시 폴더 · 도구 받기 · Chrome 엔진 · 비슷한 이름) 은 건드리지 않음
final others = ['jj_mkvmaker_updater.txt', p.join('jj_mkvmaker', 'a.wav'), 'jj_ffmpeg.download', p.join('jj_cef_1', 'x'), p.join('jj_tool_1', 'x'),
p.join('jj_mkvmaker_update', 'x'), 'jj_mkvmaker_update.txt', p.join('jj_other_update_1', 'x')];
for (final o in others) {
make(o);
}
// 폴더는 안에 파일을 만들어 바뀐 때가 지금이다 - 5분 뒤로 보고 지우는지 본다
final dir = Directory(p.join(tmp.path, 'jj_mkvmaker_update_2026.10.18_979'));
final gone = removeUpdateLeftovers(tmp.path, now: DateTime.now().add(const Duration(minutes: 5)));
expect(gone..sort(), ['jj_mkvmaker_update.log', 'jj_mkvmaker_update.ps1', 'jj_mkvmaker_update_2026.10.18_979'],
reason: '5분 뒤로 보면 막 생긴 기록도 2분이 지남');
expect(dir.existsSync(), isFalse);
for (final o in others) {
expect(File(p.join(tmp.path, o)).existsSync(), isTrue, reason: '$o 는 남김');
}
make('jj_mkvmaker_update.log', fresh: true);
expect(removeUpdateLeftovers(tmp.path), isEmpty, reason: '2분이 안 된 것은 남김 (스크립트가 끝나는 중일 수 있음)');
});
}