201 lines
5.5 KiB
Dart
201 lines
5.5 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:ikar/backend/modules/contacts.dart';
|
|
import 'package:ikar/backend/api.dart';
|
|
import 'package:ikar/core/protocol/packet.dart';
|
|
import 'package:ikar/core/contacts/device_contacts_service.dart';
|
|
import 'package:ikar/core/contacts/phonebook_max_contacts.dart';
|
|
|
|
class _LookupApi extends Api {
|
|
_LookupApi(this.response);
|
|
final Object response;
|
|
|
|
@override
|
|
Future<Packet> sendRequest(
|
|
int opcode,
|
|
Map<dynamic, dynamic> payload, {
|
|
void Function()? beforeSend,
|
|
bool silent = false,
|
|
}) async {
|
|
beforeSend?.call();
|
|
if (response is Packet) return response as Packet;
|
|
throw response;
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
test('strict lookup excludes deleted server accounts', () async {
|
|
final api = _LookupApi(
|
|
Packet(
|
|
cmd: CmdType.ok,
|
|
payload: {
|
|
'contact': {'id': 5, 'accountStatus': 2},
|
|
},
|
|
),
|
|
);
|
|
expect(
|
|
await ContactsModule.findByPhone(api, '+79000000001', strict: true),
|
|
isNull,
|
|
);
|
|
});
|
|
|
|
test(
|
|
'strict lookup recognizes not found but propagates server errors',
|
|
() async {
|
|
expect(
|
|
await ContactsModule.findByPhone(
|
|
_LookupApi(const PacketError('Missing', errorKey: 'user.not.found')),
|
|
'+79000000001',
|
|
strict: true,
|
|
),
|
|
isNull,
|
|
);
|
|
await expectLater(
|
|
ContactsModule.findByPhone(
|
|
_LookupApi(const PacketError('Rate limited', errorKey: 'limit')),
|
|
'+79000000001',
|
|
strict: true,
|
|
),
|
|
throwsA(isA<PacketError>()),
|
|
);
|
|
},
|
|
);
|
|
test('normalizes Russian prefix without matching foreign suffixes', () {
|
|
expect(
|
|
DeviceContactsService.normalizePhone('8 (900) 000-00-01'),
|
|
'+79000000001',
|
|
);
|
|
expect(
|
|
DeviceContactsService.normalizePhone('+1 900 000 0001'),
|
|
'+19000000001',
|
|
);
|
|
expect(DeviceContactsService.normalizePhone('123'), isNull);
|
|
expect(DeviceContactsService.normalizePhone('9000000001'), '+9000000001');
|
|
});
|
|
|
|
test(
|
|
'uses phonebook names, excludes ИКАР-only and discovers uncached users',
|
|
() async {
|
|
final requested = <String>[];
|
|
final contacts = await PhonebookMaxContacts.resolve(
|
|
phonebook: {
|
|
'+79000000001': 'Alice local',
|
|
'+79000000002': 'Bob local',
|
|
'+79000000003': 'No account',
|
|
},
|
|
cached: const [
|
|
CachedContact(
|
|
id: 1,
|
|
accountId: 42,
|
|
firstName: 'ИКАР name',
|
|
phone: 79000000001,
|
|
updateTime: 0,
|
|
),
|
|
CachedContact(
|
|
id: 9,
|
|
accountId: 42,
|
|
firstName: 'ИКАР only',
|
|
phone: 79000000009,
|
|
updateTime: 0,
|
|
),
|
|
],
|
|
accountId: 42,
|
|
lookup: (phone) async {
|
|
requested.add(phone);
|
|
if (phone == '+79000000001') return const PhoneLookupResult(id: 1);
|
|
return phone == '+79000000002'
|
|
? const PhoneLookupResult(id: 2)
|
|
: null;
|
|
},
|
|
);
|
|
expect(contacts.map((c) => c.id), [1, 2]);
|
|
expect(contacts.map((c) => c.firstName), ['Alice local', 'Bob local']);
|
|
expect(requested, ['+79000000001', '+79000000002', '+79000000003']);
|
|
},
|
|
);
|
|
|
|
test('excludes deleted accounts and deduplicates ИКАР IDs', () async {
|
|
final contacts = await PhonebookMaxContacts.resolve(
|
|
phonebook: {
|
|
'+79000000001': 'Deleted',
|
|
'+79000000002': 'Same',
|
|
'+79000000003': 'Same',
|
|
},
|
|
cached: const [
|
|
CachedContact(
|
|
id: 1,
|
|
accountId: 42,
|
|
firstName: 'Deleted',
|
|
phone: 79000000001,
|
|
updateTime: 0,
|
|
accountStatus: 2,
|
|
),
|
|
],
|
|
accountId: 42,
|
|
lookup: (phone) async =>
|
|
phone == '+79000000001' ? null : const PhoneLookupResult(id: 2),
|
|
);
|
|
expect(contacts.map((c) => c.id), [2]);
|
|
});
|
|
|
|
test('empty phonebook never falls back to ИКАР contacts', () async {
|
|
final contacts = await PhonebookMaxContacts.resolve(
|
|
phonebook: {},
|
|
cached: const [
|
|
CachedContact(
|
|
id: 1,
|
|
accountId: 42,
|
|
firstName: 'ИКАР only',
|
|
phone: 79000000001,
|
|
updateTime: 0,
|
|
),
|
|
],
|
|
accountId: 42,
|
|
lookup: (_) async => throw StateError('Unexpected lookup'),
|
|
);
|
|
expect(contacts, isEmpty);
|
|
});
|
|
|
|
test('cached ИКАР membership is rechecked before display', () async {
|
|
final contacts = await PhonebookMaxContacts.resolve(
|
|
phonebook: {'+79000000001': 'Former user'},
|
|
cached: const [
|
|
CachedContact(
|
|
id: 1,
|
|
accountId: 42,
|
|
firstName: 'Old cached name',
|
|
phone: 79000000001,
|
|
updateTime: 123,
|
|
),
|
|
],
|
|
accountId: 42,
|
|
lookup: (_) async => null,
|
|
);
|
|
expect(contacts, isEmpty);
|
|
});
|
|
|
|
test('network failure is not treated as an unregistered phone', () async {
|
|
await expectLater(
|
|
PhonebookMaxContacts.resolve(
|
|
phonebook: {'+79000000001': 'Local'},
|
|
cached: [],
|
|
accountId: 42,
|
|
lookup: (_) async => throw StateError('Offline'),
|
|
),
|
|
throwsStateError,
|
|
);
|
|
});
|
|
|
|
test('cancellation stops further lookups', () async {
|
|
final contacts = await PhonebookMaxContacts.resolve(
|
|
phonebook: {'+79000000001': 'Local'},
|
|
cached: [],
|
|
accountId: 42,
|
|
lookup: (_) async => throw StateError('Unexpected lookup'),
|
|
isCancelled: () => true,
|
|
);
|
|
expect(contacts, isEmpty);
|
|
});
|
|
}
|