import 'package:flutter_test/flutter_test.dart'; import 'package:friendly_map/modules/core/core.dart'; import 'package:friendly_map/modules/groups/groups.dart'; void main() { group('그룹 목록', () { test('처음에는 가족·학교·회사·그룹·여행 네 개', () { final b = GroupBook(); expect(b.groups.map((g) => g.name), ['가족', '학교', '회사', '그룹·여행']); expect(b.groups.map((g) => g.kind), [CardKinds.family, CardKinds.school, CardKinds.company, CardKinds.group]); }); test('더하기·이름 바꾸기·지우기, 빈 이름과 겹치는 이름은 거절', () { final b = GroupBook(); final g = b.add(' 동호회 ', CardKinds.group)!; expect(g.name, '동호회'); expect(b.add('', CardKinds.group), isNull); expect(b.add('가족', CardKinds.family), isNull); expect(b.rename(g.id, '등산 모임'), isTrue); expect(b.rename(g.id, '학교'), isFalse); expect(b.rename(g.id, '등산 모임'), isTrue, reason: '자기 이름 그대로는 괜찮다'); expect(b.byId(g.id)!.name, '등산 모임'); expect(b.remove(g.id), isTrue); expect(b.byId(g.id), isNull); expect(b.remove('없음'), isFalse); }); test('이름은 20자까지 자른다', () { final b = GroupBook(); expect(b.add('가' * 30, CardKinds.group)!.name.length, 20); }); test('순서 바꾸기', () { final b = GroupBook(); b.move(CardKinds.group, 0); expect(b.groups.first.id, CardKinds.group); b.move(CardKinds.group, 99); expect(b.groups.last.id, CardKinds.group); }); test('구성원 더하기·빼기, 사람이 든 그룹, 사람 지우기', () { final b = GroupBook(); expect(b.addMembers(CardKinds.family, ['a', 'b']), 2); expect(b.addMembers(CardKinds.family, ['b', 'c']), 1, reason: '이미 든 사람은 다시 세지 않는다'); b.addMembers(CardKinds.school, ['a']); expect(b.groupsOf('a').map((g) => g.id), [CardKinds.family, CardKinds.school]); expect(b.removeMember(CardKinds.family, 'c'), isTrue); expect(b.removeMember(CardKinds.family, 'c'), isFalse); b.forgetPerson('a'); expect(b.groupsOf('a'), isEmpty); expect(b.byId(CardKinds.family)!.members, {'b'}); }); test('잠긴 그룹이 닫혀 있으면 구성원을 숨긴다 (다른 그룹에도 있어도)', () async { final b = GroupBook(); b.addMembers(CardKinds.family, ['a', 'b']); b.addMembers(CardKinds.school, ['a', 'c']); b.setLock(CardKinds.family, await Pin.make('1234', iterations: 10)); expect(b.hiddenPeople((g) => true), {'a', 'b'}); expect(b.hiddenPeople((g) => false), isEmpty); }); test('글자로 저장했다 되살리면 같고, 깨진 글자는 처음 그룹', () async { final b = GroupBook(relockMinutes: 10); final g = b.add('동호회', CardKinds.group)!; b.addMembers(g.id, ['x']); b.setLock(g.id, await Pin.make('abcd', iterations: 10)); final c = GroupBook.fromJson(b.toJson()); expect(c.groups.map((x) => x.name), b.groups.map((x) => x.name)); expect(c.byId(g.id)!.members, {'x'}); expect(c.byId(g.id)!.locked, isTrue); expect(c.relockMinutes, 10); expect(c.add('새 그룹', CardKinds.group)!.id, isNot(g.id), reason: '새 번호표는 겹치지 않는다'); expect(GroupBook.fromJson('{깨짐').groups.length, 4); expect(GroupBook.fromJson(null).groups.length, 4); }); }); group('비밀번호', () { test('맞는 비밀번호만 통과, 원문은 남지 않는다', () async { final h = await Pin.make('2580', iterations: 100); expect(await Pin.verify('2580', h), isTrue); expect(await Pin.verify('2581', h), isFalse); expect(h.toJson().toString(), isNot(contains('2580'))); final again = await Pin.make('2580', iterations: 100); expect(again.salt, isNot(h.salt), reason: '같은 비밀번호도 소금이 달라 다른 값'); }); test('짧거나 길거나 앞뒤 빈칸이면 안내', () { expect(Pin.problem('123'), isNotNull); expect(Pin.problem('1' * 17), isNotNull); expect(Pin.problem(' 1234'), isNotNull); expect(Pin.problem('1234'), isNull); }); test('깨진 저장값은 통과하지 않는다', () async { expect(await Pin.verify('1234', const PinHash('!!', '??', 10)), isFalse); expect(PinHash.fromJson({'salt': 1}), isNull); }); }); group('여러 번 틀리면 기다리기', () { test('5번까지는 바로, 그 뒤로 30초·60초…', () { final g = UnlockGuard(); for (var i = 0; i < 4; i++) { g.failure(0); expect(g.waitLeft(0), 0); } expect(g.triesLeft(), 1); g.failure(0); expect(g.waitLeft(0), 3000); expect(g.waitLeft(3000), 0); g.failure(3000); expect(g.waitLeft(3000), 6000); g.success(); expect(g.waitLeft(3000), 0); expect(g.triesLeft(), 5); }); test('기다림은 1시간을 넘지 않는다', () { final g = UnlockGuard(); for (var i = 0; i < 40; i++) { g.failure(0); } expect(g.waitLeft(0), 360000); }); }); }