232 lines
8.7 KiB
Dart
232 lines
8.7 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:friendly_map/modules/core/core.dart';
|
|
import 'package:friendly_map/modules/transport/transport.dart';
|
|
|
|
import 'support/envelopes.dart';
|
|
|
|
const a = '+821011112222';
|
|
const b = '+821022223333';
|
|
|
|
Future<List<Envelope>> collect(Transport t, Future<void> Function() act) async {
|
|
final got = <Envelope>[];
|
|
final sub = t.incoming.listen(got.add);
|
|
await act();
|
|
await Future<void>.delayed(const Duration(milliseconds: 50));
|
|
await sub.cancel();
|
|
return got;
|
|
}
|
|
|
|
/// 보관함 서버를 띄운다. 윈도우의 python3 는 스토어 바로가기일 수 있어 python, py 순으로도 시도한다.
|
|
Future<(Process, String)> startServer(List<String> extra) async {
|
|
for (final exe in ['python3', 'python', 'py']) {
|
|
Process? p;
|
|
try {
|
|
p = await Process.start(exe, ['mailbox_server.py', '--host', '127.0.0.1', '--port', '0', ...extra],
|
|
workingDirectory: 'server');
|
|
final line = await p.stdout.transform(utf8.decoder).transform(const LineSplitter()).first
|
|
.timeout(const Duration(seconds: 5));
|
|
return (p, 'http://127.0.0.1:${line.split(' ').last}');
|
|
} catch (_) {
|
|
p?.kill();
|
|
}
|
|
}
|
|
throw StateError('파이썬을 찾지 못함');
|
|
}
|
|
|
|
void main() {
|
|
group('메모리 연결', () {
|
|
test('켜져 있으면 바로 도착', () async {
|
|
final hub = MemoryHub();
|
|
final ta = MemoryTransport(a, hub), tb = MemoryTransport(b, hub);
|
|
await ta.start();
|
|
await tb.start();
|
|
final got = await collect(tb, () async => expect(await ta.send(envelope('1')), true));
|
|
expect(got.single.id, '1');
|
|
});
|
|
|
|
test('꺼져 있으면 보관함에 쌓였다가 가져옴', () async {
|
|
final hub = MemoryHub();
|
|
final ta = MemoryTransport(a, hub), tb = MemoryTransport(b, hub);
|
|
await ta.start();
|
|
expect(await ta.send(envelope('1')), true);
|
|
await tb.start();
|
|
final got = await collect(tb, () async => expect(await tb.fetchPending(), 1));
|
|
expect(got.single.id, '1');
|
|
expect(await tb.fetchPending(), 0);
|
|
});
|
|
|
|
test('보관함을 끄면 못 보냄', () async {
|
|
final hub = MemoryHub()..mailboxEnabled = false;
|
|
final ta = MemoryTransport(a, hub);
|
|
MemoryTransport(b, hub);
|
|
await ta.start();
|
|
expect(await ta.send(envelope('1')), false);
|
|
});
|
|
});
|
|
|
|
group('묶음 연결', () {
|
|
test('앞 방식이 못 보내면 다음 방식으로 보냄', () async {
|
|
final hub1 = MemoryHub()..mailboxEnabled = false;
|
|
final hub2 = MemoryHub();
|
|
final first = MemoryTransport(a, hub1);
|
|
final second = MemoryTransport(a, hub2);
|
|
final tb = MemoryTransport(b, hub2);
|
|
final c = CompositeTransport([first, second]);
|
|
await c.start();
|
|
await tb.start();
|
|
MemoryTransport(b, hub1);
|
|
final got = await collect(tb, () async => expect(await c.send(envelope('1')), true));
|
|
expect(got.single.id, '1');
|
|
});
|
|
|
|
test('모두 못 보내면 false, 받은 것은 하나로 합쳐 줌', () async {
|
|
final hub = MemoryHub()..mailboxEnabled = false;
|
|
final t = MemoryTransport(a, hub);
|
|
final c = CompositeTransport([t]);
|
|
await c.start();
|
|
expect(await c.send(envelope('1')), false);
|
|
final peer = MemoryTransport(b, hub);
|
|
await peer.start();
|
|
final got = await collect(c, () async => await peer.send(envelope('2', from: b, to: a)));
|
|
expect(got.single.id, '2');
|
|
});
|
|
});
|
|
|
|
group('직접 연결 (같은 네트워크)', () {
|
|
test('주소를 알면 바로 보내고, 모르면 false', () async {
|
|
DirectAddress? where;
|
|
final recv = DirectTransport(resolve: (_) async => null);
|
|
final send = DirectTransport(resolve: (_) async => where);
|
|
await recv.start();
|
|
await send.start();
|
|
expect(await send.send(envelope('1')), false);
|
|
where = DirectAddress('127.0.0.1', recv.boundPort);
|
|
final got = await collect(recv, () async => expect(await send.send(envelope('2')), true));
|
|
expect(got.single.id, '2');
|
|
await recv.stop();
|
|
await send.stop();
|
|
});
|
|
});
|
|
|
|
group('외부 보관함 연결', () {
|
|
Process? server;
|
|
String? base;
|
|
|
|
setUpAll(() async {
|
|
try {
|
|
(server, base) = await startServer([]);
|
|
} catch (_) {
|
|
server = null; // 파이썬이 없으면 이 묶음은 건너뜀
|
|
}
|
|
});
|
|
tearDownAll(() => server?.kill());
|
|
|
|
test('보내고, 가져오고, 지움', () async {
|
|
if (server == null) return;
|
|
final ta = MailboxTransport(baseUrl: base!, myPhone: a, token: 'TA');
|
|
final tb = MailboxTransport(baseUrl: base!, myPhone: b, token: 'TB');
|
|
expect(await ta.send(envelope('1')), true);
|
|
final got = await collect(tb, () async => expect(await tb.fetchPending(), 1));
|
|
expect(got.single.body, 'SEALED');
|
|
expect(await tb.fetchPending(), 0); // 받았다고 알렸으니 사라짐
|
|
await ta.stop();
|
|
await tb.stop();
|
|
});
|
|
|
|
test('다른 비밀값으로는 우편함을 못 염', () async {
|
|
if (server == null) return;
|
|
final first = MailboxTransport(baseUrl: base!, myPhone: '+821033334444', token: 'T1');
|
|
final other = MailboxTransport(baseUrl: base!, myPhone: '+821033334444', token: 'T2');
|
|
await first.fetchPending();
|
|
expect(await other.fetchPending(), 0);
|
|
});
|
|
|
|
test('서버 시각을 알려 주고, 서버가 꺼져 있으면 보내기 실패', () async {
|
|
if (server == null) return;
|
|
final t = MailboxTransport(baseUrl: base!, myPhone: a, token: 'TA');
|
|
expect((await t.referenceStamp())!, greaterThan(170000000000));
|
|
final dead = MailboxTransport(
|
|
baseUrl: 'http://127.0.0.1:1', myPhone: a, token: 'TA', timeout: const Duration(seconds: 1));
|
|
expect(await dead.send(envelope('1')), false);
|
|
expect(await dead.referenceStamp(), null);
|
|
});
|
|
});
|
|
|
|
group('늦게 만드는 연결', () {
|
|
test('시작 전에는 보내기 실패, 시작하면 안쪽 연결로 전달', () async {
|
|
final hub = MemoryHub();
|
|
final late = LateTransport(() async => MemoryTransport(a, hub));
|
|
final tb = MemoryTransport(b, hub);
|
|
await tb.start();
|
|
expect(await late.send(envelope('0')), false);
|
|
await late.start();
|
|
final got = await collect(tb, () async => expect(await late.send(envelope('1')), true));
|
|
expect(got.length, 1);
|
|
await late.stop();
|
|
});
|
|
|
|
test('안쪽 연결이 없으면(null) 조용히 아무것도 안 함', () async {
|
|
final late = LateTransport(() async => null);
|
|
await late.start();
|
|
expect(await late.send(envelope('1')), false);
|
|
expect(await late.fetchPending(), 0);
|
|
expect(await late.referenceStamp(), null);
|
|
});
|
|
|
|
test('받은 봉투를 바깥으로 흘려 준다', () async {
|
|
final hub = MemoryHub();
|
|
final late = LateTransport(() async => MemoryTransport(a, hub));
|
|
final tb = MemoryTransport(b, hub);
|
|
await late.start();
|
|
await tb.start();
|
|
final got = await collect(late, () async => expect(await tb.send(envelope('2', to: a)), true));
|
|
expect(got.length, 1);
|
|
});
|
|
});
|
|
|
|
group('문자 확인', () {
|
|
Process? server;
|
|
String? base;
|
|
late File smsLog;
|
|
final token = 'c' * 32;
|
|
|
|
setUpAll(() async {
|
|
try {
|
|
smsLog = File('${Directory.systemTemp.createTempSync('fm_sms').path}/sms.log');
|
|
(server, base) = await startServer(['--sms-log', smsLog.path]);
|
|
} catch (_) {
|
|
server = null;
|
|
}
|
|
});
|
|
tearDownAll(() => server?.kill());
|
|
|
|
String lastCode() => RegExp(r'\b(\d{6})\b').allMatches(smsLog.readAsStringSync()).last.group(1)!;
|
|
|
|
test('요청하면 문자가 가고, 맞는 번호를 넣으면 확인되고 틀리면 거절', () async {
|
|
if (server == null) return;
|
|
final v = MailboxVerifier(baseUrl: base!, myPhone: a, token: token);
|
|
expect(await v.requestCode(), VerifyStart.sent);
|
|
expect(await v.confirm('000000' == lastCode() ? '111111' : '000000'), false);
|
|
expect(await v.confirm(lastCode()), true);
|
|
});
|
|
|
|
test('1시간에 3번이 넘으면 잠시 막힘', () async {
|
|
if (server == null) return;
|
|
final v = MailboxVerifier(baseUrl: base!, myPhone: b, token: token);
|
|
for (var i = 0; i < 3; i++) {
|
|
expect(await v.requestCode(), VerifyStart.sent);
|
|
}
|
|
expect(await v.requestCode(), VerifyStart.rateLimited);
|
|
});
|
|
|
|
test('서버가 없으면 실패, 문자 확인을 안 쓰는 서버면 쓸 수 없음', () async {
|
|
expect(await MailboxVerifier(baseUrl: 'http://127.0.0.1:1', myPhone: a, token: token).requestCode(), VerifyStart.failed);
|
|
expect(await LatePhoneVerifier(() async => null).requestCode(), VerifyStart.unavailable);
|
|
expect(await LatePhoneVerifier(() async => null).confirm('123456'), false);
|
|
});
|
|
});
|
|
}
|