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/secret_gate.dart'; import 'package:jj_mkvmaker/core/sftp.dart'; import 'package:jj_mkvmaker/core/smb.dart'; import 'package:jj_mkvmaker/core/webdav.dart'; /// modular_sync #6 F11 약속: 원격 등록 4벌 · 마스터 문 4벌을 한 몸통 (RemoteRegistry · requireSecretGate) 으로. /// ① 다시 접속하는 바뀜은 예전과 같게: 지움 · 비밀이 아닌 칸 (주소 · 사용자 …) · 비밀 값 (SFTP 는 키 · 키 암호까지) /// ② 문 (보안): 비밀 값이 있는데 마스터 확인 없이 열리는 길이 없음 - 거절이면 연결 전에 DavLockedException, 비밀 값이 없으면 묻지 않음 void main() { tearDown(() { SecretGate.check = null; DavRegistry.configure(const []); SftpRegistry.configure(const []); SmbRegistry.configure(const []); FtpRegistry.configure(const []); SmbRegistry.requireSecure = false; }); /// 등록 하나의 규칙: 같으면 같은 클라이언트 · [changes] 마다 새 클라이언트 · 지우면 [missing] 오류 void rules(String name, S base, List changes, void Function(List) configure, RemoteClient Function(String) client, Matcher missing) { test('$name: 같으면 같은 클라이언트 · 바뀌면 새로 · 지우면 설정 없음', () { configure([base]); final first = client('x'); configure([base]); expect(identical(client('x'), first), isTrue, reason: '그대로면 다시 접속하지 않음'); var prev = first; for (final (i, c) in changes.indexed) { configure([base]); prev = client('x'); configure([c]); expect(identical(client('x'), prev), isFalse, reason: '바뀜 $i 이면 새 클라이언트'); } configure(const []); expect(() => client('x'), throwsA(missing)); }); } const dav = DavServer(id: 'x', name: 'n', url: 'http://127.0.0.1:1/dav', user: 'u', password: 'p'); rules('WebDAV', dav, [dav.copyWith(url: 'http://127.0.0.1:2/dav'), dav.copyWith(user: 'v'), dav.copyWith(password: 'q'), dav.copyWith(insecure: true)], DavRegistry.configure, DavRegistry.client, isA()); const sftp = SftpServer(id: 'x', name: 'n', host: '127.0.0.1', port: 1, user: 'u', password: 'p', privateKey: 'k', keyPassphrase: 'kp'); rules('SFTP', sftp, [sftp.copyWith(host: 'h2'), sftp.copyWith(user: 'v'), sftp.copyWith(password: 'q'), sftp.copyWith(privateKey: 'k2'), sftp.copyWith(keyPassphrase: 'kp2')], SftpRegistry.configure, SftpRegistry.client, isA()); const smb = SmbServer(id: 'x', name: 'n', host: '127.0.0.1', port: 1, share: 's', user: 'u', password: 'p'); rules('SMB', smb, [smb.copyWith(host: 'h2'), smb.copyWith(share: 't'), smb.copyWith(user: 'v'), smb.copyWith(password: 'q')], SmbRegistry.configure, SmbRegistry.client, isA()); const ftp = FtpServer(id: 'x', name: 'n', host: '127.0.0.1', port: 1, user: 'u', password: 'p', security: 'none'); rules('FTP', ftp, [ftp.copyWith(host: 'h2'), ftp.copyWith(user: 'v'), ftp.copyWith(password: 'q'), ftp.copyWith(security: 'explicit')], FtpRegistry.configure, FtpRegistry.client, isA()); test('SMB: 서명 · 암호화 필수를 바꾸면 열린 연결을 버림', () { SmbRegistry.configure(const [smb]); final first = SmbRegistry.client('x'); SmbRegistry.requireSecure = true; expect(identical(SmbRegistry.client('x'), first), isFalse); }); test('문: 비밀 값이 있으면 4종 모두 마스터 확인을 지나야 함 - 거절이면 연결 전에 DavLockedException', () async { var asked = 0; SecretGate.check = (_) async { asked++; return false; }; final clients = [DavClient(dav), SftpRemote(sftp), SmbRemote(smb), FtpRemote(ftp)]; for (final c in clients) { await expectLater(c.list('/'), throwsA(isA()), reason: '${c.runtimeType}'); c.close(); } expect(asked, clients.length, reason: '서버마다 물었음'); // SFTP 는 개인 키만 있어도 비밀 값 final keyOnly = SftpRemote(const SftpServer(id: 'y', name: 'n', host: '127.0.0.1', port: 1, user: 'u', privateKey: 'k')); await expectLater(keyOnly.list('/'), throwsA(isA())); keyOnly.close(); }); test('문: 비밀 값이 없으면 묻지 않는다 (막히는 것은 마스터가 아니라 그 서버의 까닭)', () async { var asked = 0; SecretGate.check = (_) async { asked++; return false; }; for (final c in [ DavClient(const DavServer(id: 'z', name: 'n', url: 'http://127.0.0.1:1/dav')), SftpRemote(const SftpServer(id: 'z', name: 'n', host: '127.0.0.1', port: 1, user: 'u')), FtpRemote(const FtpServer(id: 'z', name: 'n', host: '127.0.0.1', port: 1, security: 'none')), ]) { await expectLater(c.list('/'), throwsA(isNot(isA())), reason: '${c.runtimeType}'); c.close(); } expect(asked, 0); }); test('SecretGate.passIf: 비밀 값이 있을 때만 묻고 그 답을 따름 · 없으면 묻지 않고 지나감 · force 를 넘김', () async { final asked = []; var answer = false; SecretGate.check = (force) async { asked.add(force); return answer; }; expect(await SecretGate.passIf(false), isTrue); expect(asked, isEmpty); expect(await SecretGate.passIf(true), isFalse); answer = true; expect(await SecretGate.passIf(true, force: true), isTrue); expect(asked, [false, true]); }); }