From 39c6d9dce49c773f1a69be4b695665d7b59a8df8 Mon Sep 17 00:00:00 2001 From: RyuJongJin <266630912+RyuJongJin@users.noreply.github.com> Date: Tue, 20 Oct 2026 11:25:14 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8B=9C=ED=97=98:=20WebDAV=20=C2=B7=20SMB=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=94=EA=B8=B0=EB=8F=84=20=EC=8B=A4=EC=A0=9C=20rsy?= =?UTF-8?q?nc=20=EA=B2=B0=EA=B3=BC=20=ED=8A=B8=EB=A6=AC=EC=99=80=20?= =?UTF-8?q?=EA=B0=99=EC=9D=8C=20(28=20=EC=9D=98=20=EC=9E=A5=EB=A9=B4=20?= =?UTF-8?q?=C2=B7=20=EC=98=B5=EC=85=98=2028=EA=B0=9C,=20=EA=B2=80=ED=86=A0?= =?UTF-8?q?=20=EB=8C=80=EA=B8=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 관리자 지시 (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 --- test/ftp_test.dart | 2 +- test/sftp_test.dart | 2 +- test/smb_test.dart | 31 +++++++++++++++++++++++++++++++ test/support/rsync_scenes.dart | 2 +- test/webdav_test.dart | 16 ++++++++++++++++ 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/test/ftp_test.dart b/test/ftp_test.dart index 1d0243d..a50dae4 100644 --- a/test/ftp_test.dart +++ b/test/ftp_test.dart @@ -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')]); diff --git a/test/sftp_test.dart b/test/sftp_test.dart index a2a8551..637be7f 100644 --- a/test/sftp_test.dart +++ b/test/sftp_test.dart @@ -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)]); diff --git a/test/smb_test.dart b/test/smb_test.dart index 2765a5f..3d5eb4f 100644 --- a/test/smb_test.dart +++ b/test/smb_test.dart @@ -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 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 = []; + 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')); + }); } diff --git a/test/support/rsync_scenes.dart b/test/support/rsync_scenes.dart index fae5ad7..a29c053 100644 --- a/test/support/rsync_scenes.dart +++ b/test/support/rsync_scenes.dart @@ -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; } diff --git a/test/webdav_test.dart b/test/webdav_test.dart index 94d2134..048ecf0 100644 --- a/test/webdav_test.dart +++ b/test/webdav_test.dart @@ -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 = []; + 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))); }