import 'dart:convert'; import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:jj_mkvmaker/core/ftp.dart'; import 'package:jj_mkvmaker/core/remote_fs.dart'; import 'package:jj_mkvmaker/core/sftp.dart'; import 'package:jj_mkvmaker/core/webdav.dart'; import 'package:path/path.dart' as p; import 'support/dav_server.dart'; import 'support/sftp_server.dart'; /// modular_sync #6 RemoteFs 약속 (mkdirs, F11): 서버 종류와 상관없이 같게 - 없는 위 폴더까지 만들고 · 이미 폴더면 그대로 · /// 같은 이름의 파일이 있으면 RemoteNameIsFile (파일은 그대로). 예전에는 FTP 는 조용히 넘어가고 WebDAV 는 서버 오류였다. /// 실제 시험 서버: WebDAV (TestDavServer) · FTP (pyftpdlib) · SFTP (asyncssh). SMB 는 Android 에서만 돈다 (기기 확인) void main() { late Directory tmp, root; setUp(() { tmp = Directory.systemTemp.createTempSync('jj_rmkdirs_'); root = Directory(p.join(tmp.path, 'root'))..createSync(); File(p.join(root.path, 'file')).writeAsStringSync('keep'); }); tearDown(() async { for (var i = 0;; i++) { try { if (tmp.existsSync()) tmp.deleteSync(recursive: true); break; } on FileSystemException { if (i > 30) rethrow; await Future.delayed(const Duration(milliseconds: 100)); } } }); Future contract(RemoteClient c) async { await c.mkdirs('/a/b/c'); expect(Directory(p.join(root.path, 'a', 'b', 'c')).existsSync(), isTrue, reason: '위 폴더까지'); await c.mkdirs('/a/b'); await expectLater(c.mkdirs('/file'), throwsA(isA())); await expectLater(c.mkdirs('/file/sub'), throwsA(isA()), reason: '위 폴더 자리가 파일'); expect(File(p.join(root.path, 'file')).readAsStringSync(), 'keep', reason: '파일은 그대로'); } test('WebDAV', () async { final server = TestDavServer(root); await server.start(); addTearDown(server.stop); await contract(DavClient(DavServer(id: 'd', name: 'd', url: server.url, user: 'user', password: 'pass'))); }); test('FTP (pyftpdlib)', () async { if (!sftpTestServerAvailable) return markTestSkipped('시험용 python 없음'); final pr = await Process.start(sftpTestPython, ['tool/ftp_test_server.py', '--root', root.path, '--mode', 'plain', '--password', 'pw-rm']); addTearDown(() async { pr.kill(); await pr.exitCode.timeout(const Duration(seconds: 5), onTimeout: () => -1); }); pr.stderr.drain(); final lines = pr.stdout.transform(utf8.decoder).transform(const LineSplitter()).asBroadcastStream(); final port = int.parse((await lines.firstWhere((l) => l.startsWith('PORT ')).timeout(const Duration(seconds: 20))).split(' ')[1]); final c = FtpRemote(FtpServer(id: 'f', name: 'f', host: '127.0.0.1', port: port, user: 'jj', password: 'pw-rm', security: 'none')); addTearDown(c.close); await contract(c); }); test('SFTP (asyncssh)', () async { if (!sftpTestServerAvailable) return markTestSkipped('시험용 python 없음'); final server = await SftpTestServer.start(root: root.path, hostKeyFile: p.join(tmp.path, 'hostkey')); addTearDown(server.stop); final c = SftpRemote(SftpServer(id: 's', name: 's', host: '127.0.0.1', port: server.port, user: 'jj', password: 'pw', hostKeyFingerprint: server.fingerprint)); addTearDown(c.close); await contract(c); }); }