WIP 시험: FTP (pyftpdlib) · SMB (가짜 채널) 맞추기도 거르기를 따르고 미리 보기 = 실제 (44407c3 보충) (검토 대기)
관리자 지시 (10/19): 원격 맞추기 (_mirrorV) 의 거르기를 WebDAV · SFTP 에 이어 FTP · SMB 로도 확인.
- ftp_test (새 1, pyftpdlib 평문 시험 서버): WebDAV 때와 같은 거르기 5가지 (--exclude 파일 · 폴더 / --delete-excluded / --include 와 --exclude 차례 /
--filter "- …" "- /…" / 지우기 없음) 마다 미리 보기의 + · ~ 는 실제로 있고 − 는 실제로 없어지며, 실제로 지워진 것은 모두 미리 보기 지울 목록에.
자세히: 거른 것은 보내지 않음 · 받는 쪽의 거른 것 지킴 · 거른 것이 든 폴더는 남기고 안의 것만 지움.
- smb_test (새 1, 가짜 SMB 채널): 같은 규칙 - 미리 보기 지울 목록 {old.txt, gone/g.txt} · 새로 {a.txt} 와 실제 결과가 같음.
코드는 바꾸지 않음 (시험만).
시험: ftp_test · smb_test 17 통과, 임시 폴더 흔적 0, analyze 문제 없음.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
next4
parent
44407c36de
commit
484991cdda
|
|
@ -10,6 +10,9 @@ import 'package:jj_mkvmaker/services/secret_store.dart';
|
|||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'support/sftp_server.dart' show sftpTestPython;
|
||||
import 'package:jj_mkvmaker/core/file_ops.dart';
|
||||
import 'package:jj_mkvmaker/core/rsync_filter.dart';
|
||||
import 'package:jj_mkvmaker/core/sync_preview.dart';
|
||||
|
||||
/// l (관리자 지시 10/17): FTP · FTPS (dart:io 로 직접). 실제 서버 시험은 tool/ftp_test_server.py (pyftpdlib) -
|
||||
/// 시험용 파이썬 (sftp 시험과 같은 venv) 에 pyftpdlib 가 없으면 건너뛴다
|
||||
|
|
@ -161,6 +164,76 @@ void main() {
|
|||
expect(bad, isA<FtpCertException>().having((x) => x.changed, 'changed', isTrue));
|
||||
}, skip: hasPy ? false : '시험용 파이썬 없음');
|
||||
|
||||
test('관리자 지시 (10/19): FTP 맞추기도 거르기 5가지를 따르고 미리 보기 = 실제', () async {
|
||||
await start('plain');
|
||||
FtpRegistry.configure([srv(security: 'none')]);
|
||||
final src = Directory(p.join(tmp.path, 'fsrc'));
|
||||
void fresh() {
|
||||
for (final d in [src, Directory(p.join(root.path, 'dst'))]) {
|
||||
if (d.existsSync()) d.deleteSync(recursive: true);
|
||||
}
|
||||
void lf(String rel, String s) => File(p.join(src.path, rel))
|
||||
..parent.createSync(recursive: true)
|
||||
..writeAsStringSync(s);
|
||||
void rf(String rel, String s) => File(p.join(root.path, 'dst', rel))
|
||||
..parent.createSync(recursive: true)
|
||||
..writeAsStringSync(s);
|
||||
lf('a.txt', 'a');
|
||||
lf('skip.tmp', 's');
|
||||
lf('cache/c.bin', 'c');
|
||||
lf('keep/x.txt', 'x');
|
||||
lf('keep/y.log', 'y');
|
||||
rf('only.tmp', 'o');
|
||||
rf('old.txt', 'old');
|
||||
rf('cache/remote.bin', 'r');
|
||||
rf('gone/g.tmp', 'g');
|
||||
rf('gone/g.txt', 'g');
|
||||
}
|
||||
|
||||
String real(String rel) => p.joinAll([root.path, 'dst', ...rel.split('/').where((s) => s.isNotEmpty)]);
|
||||
for (final (opts, delete) in [
|
||||
('-av --exclude=*.tmp --exclude=cache/', true),
|
||||
('-av --exclude=*.tmp --exclude=cache/ --delete-excluded', true),
|
||||
('-av --include=keep/ --include=keep/*.txt --exclude=keep/* --exclude=*.tmp', true),
|
||||
('-av --filter="- *.log" --filter="- /cache"', true),
|
||||
('-av --exclude=*.tmp', false),
|
||||
]) {
|
||||
fresh();
|
||||
final filter = RsyncFilter.parse(opts);
|
||||
final before = <String>{};
|
||||
void scan(Directory d, String rel) {
|
||||
for (final e in d.listSync()) {
|
||||
final r = rel.isEmpty ? p.basename(e.path) : '$rel/${p.basename(e.path)}';
|
||||
before.add(r);
|
||||
if (e is Directory) scan(e, r);
|
||||
}
|
||||
}
|
||||
|
||||
scan(Directory(real('')), '');
|
||||
final pv = await previewSync(src.path, 'ftp://f1/dst', toRight: true, delete: delete, filter: filter);
|
||||
await FileOps().mirror(src.path, 'ftp://f1/dst', delete: delete, filter: filter);
|
||||
for (final x in pv) {
|
||||
final there = FileSystemEntity.typeSync(real(x.rel)) != FileSystemEntityType.notFound;
|
||||
if (x.action == PreviewAction.add || x.action == PreviewAction.update) expect(there, isTrue, reason: '+/~ ${x.rel} ($opts)');
|
||||
if (x.action == PreviewAction.delete) expect(there, isFalse, reason: '− ${x.rel} ($opts)');
|
||||
}
|
||||
final shown = {for (final x in pv) if (x.action == PreviewAction.delete) x.rel};
|
||||
for (final r in before) {
|
||||
if (FileSystemEntity.typeSync(real(r)) == FileSystemEntityType.notFound) {
|
||||
expect(shown.any((d) => r == d || r.startsWith('$d/')), isTrue, reason: '실제로 지워진 $r 는 미리 보기에 ($opts)');
|
||||
}
|
||||
}
|
||||
}
|
||||
// 하나 자세히: 받는 쪽의 거른 것 지키기 · 거른 것이 든 폴더는 남기고 안의 것만
|
||||
fresh();
|
||||
await FileOps().mirror(src.path, 'ftp://f1/dst', delete: true, filter: RsyncFilter.parse('--exclude=*.tmp --exclude=cache/'));
|
||||
expect(File(real('skip.tmp')).existsSync(), isFalse);
|
||||
expect(File(real('only.tmp')).existsSync(), isTrue);
|
||||
expect(File(real('cache/remote.bin')).existsSync(), isTrue);
|
||||
expect(File(real('old.txt')).existsSync(), isFalse);
|
||||
expect((File(real('gone/g.tmp')).existsSync(), File(real('gone/g.txt')).existsSync()), (true, false));
|
||||
}, skip: hasPy ? false : '시험용 파이썬 없음');
|
||||
|
||||
test('동영상 스트리밍 다리: ftp:// 경로의 부분 읽기 (FtpRemote.openReadFrom - REST 로 그 자리부터)', () async {
|
||||
await start('plain');
|
||||
FtpRegistry.configure([srv(security: 'none')]);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import 'package:jj_mkvmaker/core/vfs.dart';
|
|||
import 'package:jj_mkvmaker/services/secret_store.dart';
|
||||
import 'package:jj_mkvmaker/ui/smb_settings.dart';
|
||||
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';
|
||||
|
||||
/// 88: Android SMB (smbj) 의 Dart 쪽 - 플랫폼 채널을 가짜 서버로 (실제 smbj · 서버 연결은 Android 기기 확인 몫)
|
||||
class _FakeSmb {
|
||||
|
|
@ -227,4 +230,37 @@ void main() {
|
|||
expect(fake.lastArgs['requireSecure'], isTrue);
|
||||
expect(e, isA<SmbException>().having((x) => x.kind, 'kind', SmbFailure.insecure).having((x) => x.message, 'm', contains('SMB 서명 · 암호화 필수')));
|
||||
});
|
||||
test('관리자 지시 (10/19): SMB 맞추기 (_mirrorV) 도 거르기를 따르고 미리 보기 = 실제 (가짜 채널)', () async {
|
||||
SmbRegistry.configure(const [server]);
|
||||
final tmp = Directory.systemTemp.createTempSync('jj_smbfilter_');
|
||||
addTearDown(() => tmp.deleteSync(recursive: true));
|
||||
void lf(String rel, String s) => File(p.join(tmp.path, rel))
|
||||
..parent.createSync(recursive: true)
|
||||
..writeAsStringSync(s);
|
||||
lf('a.txt', 'a');
|
||||
lf('skip.tmp', 's');
|
||||
lf('cache/c.bin', 'c');
|
||||
fake.files.addAll({
|
||||
'dst': null,
|
||||
r'dst\only.tmp': utf8.encode('o'),
|
||||
r'dst\old.txt': utf8.encode('old'),
|
||||
r'dst\cache': null,
|
||||
r'dst\cache\remote.bin': utf8.encode('r'),
|
||||
r'dst\gone': null,
|
||||
r'dst\gone\g.tmp': utf8.encode('g'),
|
||||
r'dst\gone\g.txt': utf8.encode('g'),
|
||||
});
|
||||
final filter = RsyncFilter.parse('-av --exclude=*.tmp --exclude=cache/');
|
||||
final pv = await previewSync(tmp.path, 'smb://m1/dst', toRight: true, delete: true, filter: filter);
|
||||
expect({for (final x in pv) if (x.action == PreviewAction.delete) x.rel}, {'old.txt', 'gone/g.txt'});
|
||||
expect({for (final x in pv) if (x.action == PreviewAction.add) x.rel}, {'a.txt'});
|
||||
await FileOps().mirror(tmp.path, 'smb://m1/dst', delete: true, filter: filter);
|
||||
expect(fake.files.containsKey(r'dst\a.txt'), isTrue);
|
||||
expect(fake.files.containsKey(r'dst\skip.tmp'), isFalse, reason: '거른 것은 보내지 않음');
|
||||
expect(fake.files.containsKey(r'dst\cache\c.bin'), isFalse);
|
||||
expect(fake.files.containsKey(r'dst\only.tmp'), isTrue, reason: '받는 쪽의 거른 것은 지킴');
|
||||
expect(fake.files.containsKey(r'dst\cache\remote.bin'), isTrue);
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue