99 lines
4.7 KiB
Dart
99 lines
4.7 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:jj_mkvmaker/core/ftp/ftp_remote.dart';
|
|
import 'package:jj_mkvmaker/core/ftp/ftp_server.dart';
|
|
import 'package:jj_mkvmaker/core/ftp/ftp_session.dart';
|
|
import 'package:path/path.dart' as p;
|
|
|
|
import 'support/sftp_server.dart' show sftpTestPython;
|
|
|
|
/// 나누기 ftp 약속 시험: lib/core/ftp/ 모듈마다 맨 위 약속 - 실제 서버 (tool/ftp_test_server.py, pyftpdlib) · 실제 시험 폴더.
|
|
/// 시험용 파이썬이 없으면 서버 시험은 건너뛴다 (ftp_test 와 같음)
|
|
const _pw = 'ftp-Pw-7Rk2xQ9t';
|
|
|
|
void main() {
|
|
final hasPy = File(sftpTestPython).existsSync();
|
|
late Directory tmp;
|
|
Process? proc;
|
|
late int port;
|
|
late String fp;
|
|
|
|
Future<void> start(String mode) async {
|
|
final pr = await Process.start(sftpTestPython, ['tool/ftp_test_server.py', '--root', tmp.path, '--mode', mode, '--password', _pw]);
|
|
proc = pr;
|
|
final lines = pr.stdout.transform(utf8.decoder).transform(const LineSplitter()).asBroadcastStream();
|
|
pr.stderr.drain<void>();
|
|
port = int.parse((await lines.firstWhere((l) => l.startsWith('PORT ')).timeout(const Duration(seconds: 20))).split(' ')[1]);
|
|
final f = (await lines.firstWhere((l) => l.startsWith('FINGERPRINT')).timeout(const Duration(seconds: 5))).split(' ');
|
|
fp = f.length > 1 ? f[1] : '';
|
|
}
|
|
|
|
setUp(() => tmp = Directory.systemTemp.createTempSync('jj_ftpm_'));
|
|
tearDown(() async {
|
|
final pr = proc;
|
|
proc = null;
|
|
if (pr != null) {
|
|
pr.kill();
|
|
await pr.exitCode.timeout(const Duration(seconds: 5), onTimeout: () => -1);
|
|
}
|
|
for (var i = 0; tmp.existsSync(); i++) {
|
|
try {
|
|
tmp.deleteSync(recursive: true);
|
|
} on FileSystemException {
|
|
if (i > 30) rethrow;
|
|
await Future<void>.delayed(const Duration(milliseconds: 100));
|
|
}
|
|
}
|
|
});
|
|
|
|
FtpServer srv(String security, {String cert = '', String password = _pw}) =>
|
|
FtpServer(id: 'm1', name: '', host: '127.0.0.1', port: port, user: 'jj', password: password, security: security, certFingerprint: cert);
|
|
Future<InternetAddress?> resolve(String h) async => InternetAddress.tryParse(h);
|
|
|
|
test('ftp_server: JSON 에 비밀 값 없음 · 모르는 값은 FTPS · 이름', () {
|
|
const s = FtpServer(id: 'a', name: ' ', host: 'nas', password: 'secret', security: 'none');
|
|
expect(jsonEncode(s.toJson()), isNot(contains('secret')));
|
|
expect(FtpServer.fromJson(s.toJson()).security, 'none');
|
|
expect(FtpServer.fromJson({'id': 'x', 'security': '?'}).security, 'explicit');
|
|
expect((s.label, s.tls, s.copyWith(security: 'implicit').tls), ('nas', false, true));
|
|
});
|
|
|
|
test('ftp_session: 처음 보는 인증서는 지문과 함께 막음 → 믿으면 로그인 · 명령 · 줄바꿈 명령은 보내지 않음', () async {
|
|
await start('explicit');
|
|
final first = await FtpSession.open(srv('explicit'), const Duration(seconds: 10), resolve);
|
|
final e = await first.login().then<Object?>((_) => null, onError: (Object x) => x);
|
|
first.close();
|
|
expect(e, isA<FtpCertException>().having((x) => x.fingerprint, 'fingerprint', fp).having((x) => x.kind, 'kind', FtpFailure.certUnknown));
|
|
final s = await FtpSession.open(srv('explicit', cert: fp), const Duration(seconds: 10), resolve);
|
|
addTearDown(s.close);
|
|
await s.login();
|
|
expect(s.secure, isTrue);
|
|
expect(s.home, '/');
|
|
expect((await s.cmd('NOOP')).$1, 200);
|
|
expect(() => s.send('DELE a\r\nDELE b'), throwsA(isA<FtpException>()));
|
|
}, skip: hasPy ? false : '시험용 파이썬 없음');
|
|
|
|
test('ftp_remote: 쓰기 · 목록 · 정보 · 부분 읽기 · 옮기기 · 지우기 (평문) · 틀린 비밀번호는 auth', () async {
|
|
await start('plain');
|
|
final c = FtpRemote(srv('none'));
|
|
addTearDown(c.close);
|
|
await c.mkdirs('/가/나');
|
|
await c.write('/가/나/a.txt', Stream.value(utf8.encode('0123456789')));
|
|
expect(File(p.join(tmp.path, '가', '나', 'a.txt')).readAsStringSync(), '0123456789');
|
|
expect((await c.list('/가/나')).map((x) => (x.rel, x.size)), [('/가/나/a.txt', 10)]);
|
|
expect(await c.stat('/없음'), isNull);
|
|
final part = await (await c.openReadFrom('/가/나/a.txt', offset: 4, length: 3)).expand((x) => x).toList();
|
|
expect(utf8.decode(part), '456');
|
|
await c.move('/가/나/a.txt', '/가/b.txt');
|
|
expect(File(p.join(tmp.path, '가', 'b.txt')).existsSync(), isTrue);
|
|
await c.delete('/가');
|
|
expect(Directory(p.join(tmp.path, '가')).existsSync(), isFalse);
|
|
final bad = FtpRemote(srv('none', password: 'wrong-pw'));
|
|
addTearDown(bad.close);
|
|
await expectLater(bad.list('/'), throwsA(isA<FtpException>().having((x) => x.kind, 'kind', FtpFailure.auth)));
|
|
}, skip: hasPy ? false : '시험용 파이썬 없음');
|
|
}
|