import 'package:flutter_test/flutter_test.dart'; import 'package:friendly_map/modules/app/app.dart'; import 'package:friendly_map/modules/platform/platform.dart'; import 'package:friendly_map/modules/storage/storage.dart'; import 'package:friendly_map/modules/transport/transport.dart'; import 'support/app_device.dart'; const pa = '+821011112222'; const pb = '+821022223333'; Future<(AppDevice, AppDevice)> pair({String? modeB}) async { final hub = MemoryHub(); final a = await AppDevice.create(hub, '010-1111-2222', pa, '가'); final b = await AppDevice.create(hub, '010-2222-3333', pb, '나'); if (modeB != null) await b.app.settings.update(exchangeMode: modeB); return (a, b); } Future befriend(AppDevice a, AppDevice b) async { await a.app.friends.add(b.phone, b.name); await AppDevice.settleAll(); await b.app.friends.accept(a.phone); await AppDevice.settleAll(); } void main() { group('처음 시작', () { test('번호·이름을 정하면 종류별 기본 명함 5장이 생기고, 개인 명함만 새 친구에게 기본 공유', () async { final (a, _) = await pair(); final cards = await a.app.cards.mine(); expect(cards.map((c) => c.kind).toList(), ['personal', 'company', 'school', 'group', 'family']); expect(cards.where((c) => c.shareDefault).map((c) => c.kind).toList(), ['personal']); expect(decodeFields(cards.first.fieldsJson)['name'], '가'); expect((await a.app.profile.me())!.phone, pa); }); test('올바르지 않은 번호·이름은 거절', () async { final (a, _) = await pair(); expect(await a.app.profile.setUp('abc', '가'), null); expect(await a.app.profile.setUp('010-1111-2222', ' '), null); }); }); group('친구 추가와 명함 교환', () { test('요청 후 수락하면 서로 키를 알고, 개인 명함이 서로에게 간다', () async { final (a, b) = await pair(); final added = await a.app.friends.add(pb, '나'); expect(added!.publicKey, null); await AppDevice.settleAll(); final req = (await b.app.friends.requests()).single; expect(req.phone, pa); expect(req.name, '가'); await b.app.friends.accept(pa); await AppDevice.settleAll(); final bOfA = (await a.app.friends.person(added.id))!; expect(bOfA.publicKey, b.key); final aOfB = (await b.app.friends.people()).single; expect(aOfB.publicKey, a.key); final got = await b.app.deps.storage.receivedCards.byPerson(aOfB.id); expect(got.single.kind, 'personal'); expect(await b.app.friends.requests(), isEmpty); final back = await a.app.deps.storage.receivedCards.byPerson(bOfA.id); expect(back.single.kind, 'personal'); }); test('자동 수락 설정이면 요청 없이 바로 친구가 됨', () async { final (a, b) = await pair(modeB: 'auto'); final added = await a.app.friends.add(pb, '나'); await AppDevice.settleAll(); expect(await b.app.friends.requests(), isEmpty); expect((await b.app.friends.people()).single.phone, pa); expect((await a.app.friends.person(added!.id))!.publicKey, b.key); }); test('거절하면 친구가 되지 않음', () async { final (a, b) = await pair(); await a.app.friends.add(pb, '나'); await AppDevice.settleAll(); await b.app.friends.decline(pa); expect(await b.app.friends.requests(), isEmpty); expect(await b.app.friends.people(), isEmpty); }); test('QR 로 추가하면 키를 바로 알고, 내 QR 은 읽으면 같은 정보', () async { final (a, b) = await pair(); final qr = (await b.app.friends.myQr())!.encode(); final p = await a.app.friends.addFromQr(qr); expect(p!.phone, pb); expect(p.publicKey, b.key); expect(await a.app.friends.addFromQr('https://x.y'), null); }); test('번호 확인: 형식 오류, 내 번호, 같은 번호·이름 변경 후보', () async { final (a, b) = await pair(); expect((await a.app.friends.check('abc', '나')).error, isNotNull); expect((await a.app.friends.check('010-1111-2222', '나')).error, '내 번호예요'); await a.app.friends.add(pb, '나'); final r = await a.app.friends.check('010 2222 3333', '나 아님'); expect(r.match!.kind, MatchKind.nameDiffers); final c = await a.app.friends.check('010-9999-8888', '나'); expect(c.match!.kind, MatchKind.phoneDiffers); final same = await a.app.friends.add(c.phone!, '나', samePersonId: c.match!.candidates.first.id); expect(same!.phone, '+821099998888'); expect(await a.app.deps.storage.people.byPhone(pb), isNotNull); // 이전 번호로도 찾아짐 expect(b.phone, pb); }); }); group('숨기기·근처 사람', () { test('모든 정보 숨기기를 켜면 모르는 사람의 요청은 쌓이지 않고, 아는 사람은 그대로', () async { final (a, b) = await pair(); await b.app.settings.setHideAll(true); expect(await b.app.settings.hideAll(), true); await a.app.friends.add(pb, '나'); await AppDevice.settleAll(); expect(await b.app.friends.requests(), isEmpty); await b.app.settings.setHideAll(false); await a.app.friends.add(pb, '나'); await AppDevice.settleAll(); expect((await b.app.friends.requests()).single.phone, pa); }); test('근처에서 번호로만 추가한 사람은 답 인사가 오면 진짜 이름으로 바뀜', () async { final (a, b) = await pair(modeB: 'auto'); final p = await a.app.friends.add(pb, pb); // 이름 대신 번호 expect(p!.name, pb); await AppDevice.settleAll(); expect((await a.app.friends.person(p.id))!.name, '나'); }); }); group('수락 전에 정한 공유', () { test('상대가 수락하기 전에 고른 회사 명함도 수락 뒤에 전달됨', () async { final (a, b) = await pair(); final added = await a.app.friends.add(pb, '나'); final company = (await a.app.cards.mine()).firstWhere((c) => c.kind == 'company'); await a.app.cards.shareWith(added!.id, [company.id]); // 아직 키를 모름 await AppDevice.settleAll(); await b.app.friends.accept(pa); await AppDevice.settleAll(); final aOfB = (await b.app.friends.people()).single; final kinds = (await b.app.network.receivedCards(aOfB.id)).map((c) => c.kind).toSet(); expect(kinds, {'personal', 'company'}); }); }); group('근처 사람', () { test('발견한 사람이 보이고, 번호로 추가하면 친구 목록에 들어가며 나 자신은 빠짐', () async { final lan = FakeLan(); final hub = MemoryHub(); final a = await AppDevice.create(hub, '010-1111-2222', pa, '가', lan: lan); await AppDevice.create(hub, '010-2222-3333', pb, '나'); lan.ctl.add(const LanPeer(phone: pa, host: '10.0.0.1', port: 1)); lan.ctl.add(const LanPeer(phone: pb, host: '10.0.0.2', port: 2)); final near = await a.app.friends.nearby(); expect(near.map((e) => e.phone).toList(), [pb]); expect(near.single.known, null); final p = await a.app.friends.addNearby(pb); expect(p!.name, pb); expect((await a.app.friends.nearby()).single.known!.id, p.id); }); }); group('명함 공유', () { test('회사 명함을 골라 공유하면 상대가 받고, 고치면 다시 보내기 전까지 상대 것은 그대로', () async { final (a, b) = await pair(); await befriend(a, b); final bOfA = (await a.app.friends.people()).single; final company = (await a.app.cards.mine()).firstWhere((c) => c.kind == 'company'); await a.app.cards.edit(company.id, fields: {'name': '가', 'company': '한빛'}); expect(await a.app.cards.shareWith(bOfA.id, [company.id]), 1); expect(await a.app.cards.shareWith(bOfA.id, [company.id]), 0); // 이미 공유 중 await AppDevice.settleAll(); final aOfB = (await b.app.friends.people()).single; var got = (await b.app.deps.storage.receivedCards.byPerson(aOfB.id)).firstWhere((c) => c.kind == 'company'); expect(decodeFields(got.fieldsJson)['company'], '한빛'); await a.app.cards.edit(company.id, fields: {'name': '가', 'company': '새빛'}); await AppDevice.settleAll(); got = (await b.app.deps.storage.receivedCards.byPerson(aOfB.id)).firstWhere((c) => c.kind == 'company'); expect(decodeFields(got.fieldsJson)['company'], '한빛'); // 아직 그대로 final need = await a.app.cards.needingResend(); expect(need.single.targetId, company.id); expect(await a.app.cards.resend(need.single.id), true); await AppDevice.settleAll(); got = (await b.app.deps.storage.receivedCards.byPerson(aOfB.id)).firstWhere((c) => c.kind == 'company'); expect(decodeFields(got.fieldsJson)['company'], '새빛'); expect(await a.app.cards.needingResend(), isEmpty); }); test('명함 만들기·순서 바꾸기·지우기', () async { final (a, _) = await pair(); final tpls = await a.app.layout.templates(); final made = (await a.app.cards.createFromTemplate(tpls[1].id))!; expect((await a.app.cards.mine()).length, 6); await a.app.cards.move(made.id, 0); expect((await a.app.cards.mine()).first.id, made.id); await a.app.cards.delete(made.id); expect((await a.app.cards.mine()).length, 5); expect(await a.app.cards.createFromTemplate('없는틀'), null); }); }); group('대화', () { test('개인 대화는 바로 되고, 주고받는 대화가 양쪽에 남음', () async { final (a, b) = await pair(); await befriend(a, b); final bOfA = (await a.app.friends.people()).single; final room = (await a.app.chats.openDirect(bOfA.id, 'personal'))!; await a.app.chats.send(room.id, '안녕하세요'); await AppDevice.settleAll(); final aRooms = await b.app.chats.summaries(); expect(aRooms.single.last!.body, '안녕하세요'); await b.app.chats.send(aRooms.single.room.id, '반가워요'); await AppDevice.settleAll(); final mine = await a.app.chats.messages(room.id); expect(mine.map((m) => m.body).toList(), ['안녕하세요', '반가워요']); expect(mine.first.senderId, ChatService.me); expect(await a.app.chats.openDirect(bOfA.id, 'personal'), isNotNull); expect((await a.app.chats.summaries()).length, 1); // 같은 방을 다시 열어도 하나 }); test('회사 대화는 회사 명함을 공유해야 열리고, 받는 쪽도 명함을 받아야 보임', () async { final (a, b) = await pair(); await befriend(a, b); final bOfA = (await a.app.friends.people()).single; expect(await a.app.chats.openDirect(bOfA.id, 'company'), null); final company = (await a.app.cards.mine()).firstWhere((c) => c.kind == 'company'); await a.app.cards.shareWith(bOfA.id, [company.id]); final room = (await a.app.chats.openDirect(bOfA.id, 'company'))!; await a.app.chats.send(room.id, '업무 이야기'); await AppDevice.settleAll(); final rooms = await b.app.chats.summaries(); expect(rooms.single.room.contextKind, 'company'); expect(rooms.single.last!.body, '업무 이야기'); }); test('빈 메시지·없는 방은 보내지 않음', () async { final (a, b) = await pair(); await befriend(a, b); final bOfA = (await a.app.friends.people()).single; final room = (await a.app.chats.openDirect(bOfA.id, 'personal'))!; expect(await a.app.chats.send(room.id, ' '), null); expect(await a.app.chats.send('없는방', '안녕'), null); }); }); group('인맥', () { test('같은 학교 명함을 주고받으면 동창으로 추측하고, 확정하면 추천에서 빠짐', () async { final (a, b) = await pair(); await befriend(a, b); final bOfA = (await a.app.friends.people()).single; final aOfB = (await b.app.friends.people()).single; for (final d in [a, b]) { final school = (await d.app.cards.mine()).firstWhere((c) => c.kind == 'school'); await d.app.cards.edit(school.id, fields: {'name': d.name, 'school': '가람초', 'year': '2007'}); } final aSchool = (await a.app.cards.mine()).firstWhere((c) => c.kind == 'school'); await a.app.cards.shareWith(bOfA.id, [aSchool.id]); await AppDevice.settleAll(); final guesses = await b.app.network.guessesFor(aOfB.id); expect(guesses.first.relation, '동창'); final sug = await b.app.network.suggestions(); expect(sug.single.person.id, aOfB.id); await b.app.network.addLink(aOfB.id, guesses.first.relation, guesses.first.detail); expect(await b.app.network.suggestions(), isEmpty); final cl = await b.app.network.clusters('school'); expect(cl.single.people.single.id, aOfB.id); expect((await b.app.network.links(aOfB.id)).single.confirmed, true); final recv = await b.app.network.receivedCards(aOfB.id); expect(recv.map((c) => c.kind).toSet(), {'personal', 'school'}); await b.app.network.deleteReceivedCard(recv.first.id); expect((await b.app.network.receivedCards(aOfB.id)).length, 1); }); test('가족 명함을 받으면 가족 목록에 나옴', () async { final (a, b) = await pair(); await befriend(a, b); final bOfA = (await a.app.friends.people()).single; final fam = (await a.app.cards.mine()).firstWhere((c) => c.kind == 'family'); await a.app.cards.edit(fam.id, fields: {'name': '가', 'relation': '형'}); await a.app.cards.shareWith(bOfA.id, [fam.id]); await AppDevice.settleAll(); final members = await b.app.network.family(); expect(members.single.relation, '형'); }); }); group('설정·배치', () { test('설정은 범위 안으로 맞춰지고, 복구 조각 수는 필요한 수보다 작아지지 않음', () async { final (a, _) = await pair(); var s = await a.app.settings.update(storageUsagePercent: 99, recoveryShares: 4, recoveryThreshold: 9); expect(s.storageUsagePercent, 95); expect(s.recoveryShares, 4); expect(s.recoveryThreshold, 4); s = await a.app.settings.update(recoveryShares: 2); expect(s.recoveryThreshold, 2); expect((await a.app.settings.load()).exchangeMode, 'request'); }); test('홈 구성·명함 틀 편집이 저장되고 변경 알림이 감', () async { final (a, _) = await pair(); var n = 0; a.app.changes.listen((_) => n++); await a.app.layout.moveHome(0, 2); await a.app.layout.removeHome('family'); expect((await a.app.layout.home()).any((e) => e.id == 'family'), false); await a.app.layout.resetHome(); expect((await a.app.layout.home()).first.id, 'chats'); expect(n >= 3, true); }); test('대화가 1MB 를 넘으면 별도 저장을 권하고 범위를 나눌 수 있음', () async { final (a, b) = await pair(); await befriend(a, b); final bOfA = (await a.app.friends.people()).single; final room = (await a.app.chats.openDirect(bOfA.id, 'personal'))!; for (var i = 0; i < 3; i++) { await a.app.chats.send(room.id, 'x' * 400000); } expect((await a.app.settings.adviseRoom(room.id)).overText, true); final span = (await a.app.settings.selectableSpan(room.id))!; expect(await a.app.settings.archiveRange(room.id, span.$1, span.$1), 1); expect((await a.app.settings.adviseRoom(room.id)).overText, false); expect((await a.app.chats.messages(room.id)).length, 2); }); }); }