시험: WebDAV · SMB 맞추기도 실제 rsync 결과 트리와 같음 (28 의 장면 · 옵션 28개, 검토 대기)

관리자 지시 (10/20) 2번 - 위키 974 목록 8 "원격 동기화도 거르기를 따름" 의 📱 ⏸ 가운데 기기 없이 할 수 있는 것:
28 이 손으로 한 WebDAV 21 장면 비교를 시험으로 남기고 (시험 WebDAV 서버), SMB 는 가짜 채널로 같은 28개.
네 원격 (WebDAV · SFTP · FTP · SMB) 모두 결과 트리 (폴더 · 파일 · 글) 가 실제 rsync 와 같음 - 코드 변경 없음.
rsync_scenes 의 bundledRsync 를 sceneRsync 로 (live_sync.dart 의 같은 이름과 겹침).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
next6
RyuJongJin 2026-10-20 11:25:14 +09:00
parent 83e9fdd676
commit 39c6d9dce4
5 changed files with 50 additions and 3 deletions

View File

@ -236,7 +236,7 @@ void main() {
}, skip: hasPy ? false : '시험용 파이썬 없음');
test('관리자 지시 (10/20): FTP 맞추기 결과 = 실제 rsync 결과 (28 의 장면 · 옵션 28개)', () async {
final rsync = bundledRsync;
final rsync = sceneRsync;
if (rsync == null) return markTestSkipped('앱에 든 rsync 가 없음 (tool\\fetch_rsync.ps1)');
await start('plain');
FtpRegistry.configure([srv(security: 'none')]);

View File

@ -211,7 +211,7 @@ void main() {
}, skip: hasServer ? false : 'SFTP 시험 서버 (paramiko) 없음');
test('관리자 지시 (10/20): SFTP 맞추기 결과 = 실제 rsync 결과 (28 의 장면 · 옵션 28개)', () async {
final rsync = bundledRsync;
final rsync = sceneRsync;
if (rsync == null) return markTestSkipped('앱에 든 rsync 가 없음 (tool\\fetch_rsync.ps1)');
await start();
SftpRegistry.configure([srv(fp: fingerprint)]);

View File

@ -12,6 +12,7 @@ import 'package:path/path.dart' as p;
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';
/// 88: Android SMB (smbj) 의 Dart 쪽 - 플랫폼 채널을 가짜 서버로 (실제 smbj · 서버 연결은 Android 기기 확인 몫)
class _FakeSmb {
@ -263,4 +264,34 @@ void main() {
expect(fake.files.containsKey(r'dst\old.txt'), isFalse);
expect((fake.files.containsKey(r'dst\gone\g.tmp'), fake.files.containsKey(r'dst\gone\g.txt')), (true, false));
});
test('관리자 지시 (10/20): SMB 맞추기 결과 = 실제 rsync 결과 (28 의 장면 · 옵션 28개, 가짜 채널)', () async {
final rsync = sceneRsync;
if (rsync == null) return markTestSkipped('앱에 든 rsync 가 없음 (tool\\fetch_rsync.ps1)');
SmbRegistry.configure(const [server]);
final tmp = Directory.systemTemp.createTempSync('jj_smbscene_');
addTearDown(() => tmp.deleteSync(recursive: true));
final src = p.join(tmp.path, 'src'), want = p.join(tmp.path, 'rsync'), seed = p.join(tmp.path, 'seed');
// 가짜 공유의 scene 폴더 → 트리 (treeOf 와 같은 모양)
Map<String, String> fakeTree() => {
for (final e in fake.files.entries)
if (e.key.startsWith(r'scene\'))
e.key.substring(6).replaceAll(r'\', '/'): e.value == null ? '/' : utf8.decode(e.value!),
};
final bad = <String>[];
for (final opts in sceneOptions) {
writeScene(src, want);
final w = await realRsyncTree(rsync, src, want, opts);
writeScene(src, seed);
fake.files
..clear()
..['scene'] = null;
for (final e in treeOf(seed).entries) {
fake.files['scene\\${e.key.replaceAll('/', r'\')}'] = e.value == '/' ? null : utf8.encode(e.value);
}
await FileOps().mirror(src, 'smb://m1/scene', delete: optionsDelete(opts), filter: RsyncFilter.parse(opts));
final d = treeDiff(w, fakeTree());
if (d.isNotEmpty) bad.add('$opts\n ${d.join('\n ')}');
}
expect(bad, isEmpty, reason: bad.join('\n'));
});
}

View File

@ -54,7 +54,7 @@ const sceneOptions = [
];
/// 앱에 들어 있는 rsync (Windows). 없으면 null
String? get bundledRsync {
String? get sceneRsync {
final f = p.join(Directory.current.path, 'third_party', 'rsync', 'windows', 'rsync.exe');
return Platform.isWindows && File(f).existsSync() ? f : null;
}

View File

@ -16,6 +16,7 @@ import 'package:path/path.dart' as p;
import 'support/dav_server.dart';
import 'package:jj_mkvmaker/core/sync_preview.dart';
import 'package:jj_mkvmaker/core/rsync_filter.dart';
import 'support/rsync_scenes.dart';
void main() {
late Directory tmp, remote, local;
@ -302,4 +303,19 @@ void main() {
expect(Directory(real('cache')).existsSync(), isFalse);
expect(Directory(real('gone')).existsSync(), isFalse);
});
test('관리자 지시 (10/20): WebDAV 맞추기 결과 = 실제 rsync 결과 (28 의 장면 · 옵션 28개)', () async {
final rsync = sceneRsync;
if (rsync == null) return markTestSkipped('앱에 든 rsync 가 없음 (tool\\fetch_rsync.ps1)');
final src = p.join(local.path, 'scene_src'), want = p.join(local.path, 'scene_rsync'), got = p.join(remote.path, 'scene');
final bad = <String>[];
for (final opts in sceneOptions) {
writeScene(src, want);
final w = await realRsyncTree(rsync, src, want, opts);
writeScene(src, got);
await FileOps().mirror(src, 'dav://$id/scene', delete: optionsDelete(opts), filter: RsyncFilter.parse(opts));
final d = treeDiff(w, treeOf(got));
if (d.isNotEmpty) bad.add('$opts\n ${d.join('\n ')}');
}
expect(bad, isEmpty, reason: bad.join('\n'));
}, timeout: const Timeout(Duration(minutes: 5)));
}