From 09d572a931ef0466377e7e8a49429ad4827ea9a3 Mon Sep 17 00:00:00 2001 From: Dat PHAM HOANG Date: Mon, 3 Mar 2025 23:09:52 +0700 Subject: [PATCH] [SYNC-CONTACT-V2] Unit tests for FederationIdentityLookUpInteractor --- .../contacts/federation_look_up_argument.dart | 10 +- ..._look_up_phonebook_contact_interactor.dart | 11 +- ...federation_identity_lookup_exceptions.dart | 10 + .../federation_third_party_contact.dart | 79 +--- .../federation_identity_lookup_state.dart | 7 + ...federation_identity_lookup_interactor.dart | 63 ++-- test/modules/federation_contact_fixtures.dart | 74 ++++ ...ation_identity_lookup_interactor_test.dart | 336 ++++++++++++++++++ .../federation_third_party_contact_test.dart | 112 ------ 9 files changed, 482 insertions(+), 220 deletions(-) create mode 100644 lib/modules/federation_identity_lookup/domain/exceptions/federation_identity_lookup_exceptions.dart create mode 100644 test/modules/federation_contact_fixtures.dart create mode 100644 test/modules/federation_identity_lookup/domain/usecase/federation_identity_lookup_interactor_test.dart delete mode 100644 test/modules/federation_identity_lookup/federation_third_party_contact_test.dart diff --git a/lib/domain/usecase/contacts/federation_look_up_argument.dart b/lib/domain/usecase/contacts/federation_look_up_argument.dart index 41dba8496..6cb64ccf2 100644 --- a/lib/domain/usecase/contacts/federation_look_up_argument.dart +++ b/lib/domain/usecase/contacts/federation_look_up_argument.dart @@ -1,16 +1,14 @@ -import 'package:equatable/equatable.dart'; +import 'package:fluffychat/domain/usecase/contacts/twake_look_up_argument.dart'; -class FederationLookUpArgument with EquatableMixin { - final String homeServerUrl; +class FederationLookUpArgument extends TwakeLookUpArgument { final String federationUrl; final String withMxId; - final String withAccessToken; FederationLookUpArgument({ - required this.homeServerUrl, + required super.homeServerUrl, required this.federationUrl, required this.withMxId, - required this.withAccessToken, + required super.withAccessToken, }); @override diff --git a/lib/domain/usecase/contacts/federation_look_up_phonebook_contact_interactor.dart b/lib/domain/usecase/contacts/federation_look_up_phonebook_contact_interactor.dart index 527e87885..5143ca2f4 100644 --- a/lib/domain/usecase/contacts/federation_look_up_phonebook_contact_interactor.dart +++ b/lib/domain/usecase/contacts/federation_look_up_phonebook_contact_interactor.dart @@ -27,6 +27,8 @@ import 'package:matrix/matrix.dart'; class FederationLookUpPhonebookContactInteractor { final PhonebookContactRepository _phonebookContactRepository = getIt.get(); + final IdentityLookupManager _identityLookupManager = + getIt.get(); Stream> execute({ int lookupChunkSize = 10, @@ -40,9 +42,6 @@ class FederationLookUpPhonebookContactInteractor { FederationRegisterResponse? federationRegisterToken; - final IdentityLookupManager identityLookupManager = - IdentityLookupManager(); - final FederationIdentityRequestTokenManager federationIdentityRequestTokenManager = FederationIdentityRequestTokenManager(); @@ -78,7 +77,7 @@ class FederationLookUpPhonebookContactInteractor { } try { - final res = await identityLookupManager.register( + final res = await _identityLookupManager.register( federationUrl: argument.federationUrl, tokenInformation: federationIdentityRequestTokenRes!, ); @@ -124,7 +123,7 @@ class FederationLookUpPhonebookContactInteractor { FederationHashDetailsResponse? hashDetails; try { - final res = await identityLookupManager.getHashDetails( + final res = await _identityLookupManager.getHashDetails( federationUrl: argument.federationUrl, registeredToken: federationRegisterToken.token!, ); @@ -214,7 +213,7 @@ class FederationLookUpPhonebookContactInteractor { ); FederationLookupMxidResponse? response; try { - response = await identityLookupManager.lookupMxid( + response = await _identityLookupManager.lookupMxid( federationUrl: argument.federationUrl, request: request, registeredToken: federationRegisterToken.token!, diff --git a/lib/modules/federation_identity_lookup/domain/exceptions/federation_identity_lookup_exceptions.dart b/lib/modules/federation_identity_lookup/domain/exceptions/federation_identity_lookup_exceptions.dart new file mode 100644 index 000000000..eafd621af --- /dev/null +++ b/lib/modules/federation_identity_lookup/domain/exceptions/federation_identity_lookup_exceptions.dart @@ -0,0 +1,10 @@ +class LookUpFederationIdentityNotFoundException implements Exception { + final dynamic error; + + LookUpFederationIdentityNotFoundException(this.error); + + @override + String toString() { + return 'LookUpFederationIdentityNotFoundException $error'; + } +} \ No newline at end of file diff --git a/lib/modules/federation_identity_lookup/domain/models/federation_third_party_contact.dart b/lib/modules/federation_identity_lookup/domain/models/federation_third_party_contact.dart index 2f86fae42..845364e62 100644 --- a/lib/modules/federation_identity_lookup/domain/models/federation_third_party_contact.dart +++ b/lib/modules/federation_identity_lookup/domain/models/federation_third_party_contact.dart @@ -1,26 +1,12 @@ -import 'dart:convert'; - -import 'package:crypto/crypto.dart'; -import 'package:equatable/equatable.dart'; -import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_hash_details_response.dart'; +import 'package:fluffychat/domain/model/contact/contact.dart'; import 'package:fluffychat/utils/string_extension.dart'; -import 'package:matrix/encryption/utils/base64_unpadded.dart'; - -abstract class FederationThirdPartyContact with EquatableMixin { - final String? matrixId; - - final String thirdPartyIdType; - - final String thirdPartyId; - - /// Support for multiple peppers - final Map? thirdPartyIdToHashMap; +abstract class FederationThirdPartyContact extends ThirdPartyContact { FederationThirdPartyContact({ - this.matrixId, - required this.thirdPartyIdType, - required this.thirdPartyId, - this.thirdPartyIdToHashMap, + super.matrixId, + required super.thirdPartyIdType, + required super.thirdPartyId, + super.thirdPartyIdToHashMap, }); @override @@ -30,51 +16,6 @@ abstract class FederationThirdPartyContact with EquatableMixin { thirdPartyId, thirdPartyIdToHashMap, ]; - - String calculateHashWithAlgorithmSha256({ - required String pepper, - }) { - final input = [thirdPartyId, thirdPartyIdType, pepper].join(' '); - final bytes = utf8.encode(input); - final lookupHash = - encodeBase64Unpadded(sha256.convert(bytes).bytes).urlSafeBase64; - return lookupHash; - } - - String calculateHashWithoutAlgorithm() { - return [thirdPartyId, thirdPartyIdType].join(' '); - } - - Set calculateHashUsingAllPeppers({ - required FederationHashDetailsResponse hashDetails, - }) { - final Set hashes = {}; - - if (hashDetails.algorithms == null || hashDetails.algorithms!.isEmpty) { - final hash = calculateHashWithoutAlgorithm(); - hashes.add(hash); - return hashes; - } - - for (final algorithm in hashDetails.algorithms!) { - final peppers = { - hashDetails.lookupPepper, - }; - - for (final pepper in peppers) { - if (algorithm == 'sha256') { - final hash = calculateHashWithAlgorithmSha256( - pepper: pepper ?? '', - ); - hashes.add(hash); - } else { - final hash = calculateHashWithoutAlgorithm(); - hashes.add(hash); - } - } - } - return hashes; - } } class FederationPhone extends FederationThirdPartyContact { @@ -85,7 +26,7 @@ class FederationPhone extends FederationThirdPartyContact { super.matrixId, super.thirdPartyIdToHashMap, }) : super( - thirdPartyIdType: 'msisdn', + thirdPartyIdType: ThirdPartyIdType.msisdn, thirdPartyId: number.msisdnSanitizer(), ); @@ -99,7 +40,7 @@ class FederationPhone extends FederationThirdPartyContact { FederationPhone copyWith({ String? matrixId, - Map? thirdPartyIdToHashMap, + Map>? thirdPartyIdToHashMap, }) { return FederationPhone( matrixId: matrixId ?? this.matrixId, @@ -118,7 +59,7 @@ class FederationEmail extends FederationThirdPartyContact { required this.address, super.thirdPartyIdToHashMap, }) : super( - thirdPartyIdType: 'email', + thirdPartyIdType: ThirdPartyIdType.email, thirdPartyId: address, ); @@ -132,7 +73,7 @@ class FederationEmail extends FederationThirdPartyContact { FederationEmail copyWith({ String? matrixId, - Map? thirdPartyIdToHashMap, + Map>? thirdPartyIdToHashMap, }) { return FederationEmail( matrixId: matrixId ?? this.matrixId, diff --git a/lib/modules/federation_identity_lookup/domain/state/federation_identity_lookup_state.dart b/lib/modules/federation_identity_lookup/domain/state/federation_identity_lookup_state.dart index 069eef7c6..2fe7f2dc5 100644 --- a/lib/modules/federation_identity_lookup/domain/state/federation_identity_lookup_state.dart +++ b/lib/modules/federation_identity_lookup/domain/state/federation_identity_lookup_state.dart @@ -29,6 +29,13 @@ class FederationIdentityCalculationHashesEmpty extends Failure { List get props => []; } +class NoFederationIdentityURL extends Failure { + const NoFederationIdentityURL(); + + @override + List get props => []; +} + class FederationIdentityRegisterAccountFailure extends Failure { const FederationIdentityRegisterAccountFailure({ required this.identityServer, diff --git a/lib/modules/federation_identity_lookup/domain/usecase/federation_identity_lookup_interactor.dart b/lib/modules/federation_identity_lookup/domain/usecase/federation_identity_lookup_interactor.dart index ee78c3a70..994ae3b96 100644 --- a/lib/modules/federation_identity_lookup/domain/usecase/federation_identity_lookup_interactor.dart +++ b/lib/modules/federation_identity_lookup/domain/usecase/federation_identity_lookup_interactor.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart'; import 'package:dartz/dartz.dart'; import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/exceptions/federation_identity_lookup_exceptions.dart'; import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_arguments.dart'; import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_contact.dart'; import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_lookup_mxid_request.dart'; @@ -20,11 +21,14 @@ class FederationIdentityLookupInteractor { required FederationArguments arguments, }) async { try { + if (arguments.federationUrl.isEmpty) { + return const Left(NoFederationIdentityURL()); + } + final registerResponse = await federationIdentityLookupRepository.register( tokenInformation: arguments.tokenInformation, ); - if (registerResponse.token == null && registerResponse.token!.isEmpty) { return Left( FederationIdentityRegisterAccountFailure( @@ -37,7 +41,6 @@ class FederationIdentityLookupInteractor { await federationIdentityLookupRepository.getHashDetails( registeredToken: registerResponse.token!, ); - if (hashDetails.lookupPepper == null || hashDetails.algorithms == null) { return const Left(FederationIdentityGetHashDetailsFailure()); } @@ -47,9 +50,9 @@ class FederationIdentityLookupInteractor { final Map> contactIdToHashesMap = {}; for (final contact in arguments.contactMaps.values) { - final phoneToHashMap = {}; + final phoneToHashMap = >{}; - final emailToHashMap = {}; + final emailToHashMap = >{}; if (hashDetails.algorithms!.isEmpty) { return const Left(FederationIdentityGetHashDetailsFailure()); } @@ -57,30 +60,32 @@ class FederationIdentityLookupInteractor { if (contact.phoneNumbers != null) { for (final phone in contact.phoneNumbers!) { final hashes = phone.calculateHashUsingAllPeppers( - hashDetails: hashDetails, + lookupPepper: hashDetails.lookupPepper, + altLookupPeppers: hashDetails.altLookupPeppers, + algorithms: hashDetails.algorithms, ); - for (final hash in hashes) { - phoneToHashMap.putIfAbsent(phone.number, () => hash); - } - contactIdToHashesMap.putIfAbsent(contact.id, () => []).addAll( - phoneToHashMap.values, - ); + phoneToHashMap.putIfAbsent(phone.number, () => []).addAll(hashes); + + contactIdToHashesMap + .putIfAbsent(contact.id, () => []) + .addAll(phoneToHashMap.values.expand((e) => e)); } } if (contact.emails != null) { for (final email in contact.emails!) { final hashes = email.calculateHashUsingAllPeppers( - hashDetails: hashDetails, + lookupPepper: hashDetails.lookupPepper, + altLookupPeppers: hashDetails.altLookupPeppers, + algorithms: hashDetails.algorithms, ); - for (final hash in hashes) { - emailToHashMap.putIfAbsent(email.address, () => hash); - } - contactIdToHashesMap.putIfAbsent(contact.id, () => []).addAll( - emailToHashMap.values, - ); + emailToHashMap.putIfAbsent(email.address, () => []).addAll(hashes); + + contactIdToHashesMap + .putIfAbsent(contact.id, () => []) + .addAll(emailToHashMap.values.expand((e) => e)); } } @@ -110,9 +115,11 @@ class FederationIdentityLookupInteractor { registeredToken: registerResponse.token!, ); - if (lookupMxidResponse.mappings == null) { - return const Left( - FederationIdentityLookupFailure(exception: 'No mappings found'), + if (lookupMxidResponse.mappings == null || lookupMxidResponse.mappings!.isEmpty) { + return Left( + FederationIdentityLookupFailure( + exception: LookUpFederationIdentityNotFoundException('No mappings found'), + ), ); } @@ -158,8 +165,8 @@ class FederationIdentityLookupInteractor { FederationContact updateContactWithHashes( FederationContact contact, - Map phoneToHashMap, - Map emailToHashMap, + Map> phoneToHashMap, + Map> emailToHashMap, ) { final updatedPhoneNumbers = {}; final updatedEmails = {}; @@ -207,8 +214,9 @@ class FederationIdentityLookupInteractor { final thirdPartyIdToHashMap = phoneNumber.thirdPartyIdToHashMap ?? {}; for (final mappingHash in thirdPartyIdToHashMap.values) { - final foundHash = mappings.keys.firstWhereOrNull( - (hash) => mappingHash == hash, + final foundHash = mappingHash.firstWhereOrNull( + (hash) => + mappings.keys.firstWhereOrNull((key) => key == hash) != null, ); if (foundHash != null) { final updatedPhoneNumber = phoneNumber.copyWith( @@ -229,8 +237,9 @@ class FederationIdentityLookupInteractor { for (final email in contact.emails!) { final thirdPartyIdToHashMap = email.thirdPartyIdToHashMap ?? {}; for (final mappingHash in thirdPartyIdToHashMap.values) { - final foundHash = mappings.keys.firstWhereOrNull( - (hash) => mappingHash == hash, + final foundHash = mappingHash.firstWhereOrNull( + (hash) => + mappings.keys.firstWhereOrNull((key) => key == hash) != null, ); if (foundHash != null) { final updatedEmail = email.copyWith( diff --git a/test/modules/federation_contact_fixtures.dart b/test/modules/federation_contact_fixtures.dart new file mode 100644 index 000000000..729b2364c --- /dev/null +++ b/test/modules/federation_contact_fixtures.dart @@ -0,0 +1,74 @@ +import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_contact.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_third_party_contact.dart'; + +class FederationContactFixtures { + static FederationContact contact1 = FederationContact( + id: 'id_1', + name: 'Alice', + phoneNumbers: { + FederationPhone( + number: '(212)555-6789', + ), + }, + emails: { + FederationEmail( + address: 'alice@gmail.com', + ), + }, + ); + + static FederationContact contact2 = FederationContact( + id: 'id_2', + name: 'Bob', + phoneNumbers: { + FederationPhone( + number: '(212)555-1234', + ), + }, + emails: { + FederationEmail( + address: 'bob@gmail.com', + ), + }, + ); + + static FederationContact contact3 = FederationContact( + id: 'id_3', + name: 'Charlie', + phoneNumbers: { + FederationPhone( + number: '(212)555-2345', + ), + }, + ); + + static FederationContact contact4 = FederationContact( + id: 'id_4', + name: 'Diana', + emails: { + FederationEmail( + address: 'diana@gmail.com', + ), + }, + ); + + static FederationContact contact5 = FederationContact( + id: 'id_5', + name: 'Eve', + phoneNumbers: { + FederationPhone( + number: '(212)555-4567', + ), + }, + ); + + static FederationContact contact6 = FederationContact( + id: 'id_6', + name: 'Frank', + emails: { + FederationEmail( + address: 'frank@gmail.com', + ), + }, + ); +} \ No newline at end of file diff --git a/test/modules/federation_identity_lookup/domain/usecase/federation_identity_lookup_interactor_test.dart b/test/modules/federation_identity_lookup/domain/usecase/federation_identity_lookup_interactor_test.dart new file mode 100644 index 000000000..65ff92d05 --- /dev/null +++ b/test/modules/federation_identity_lookup/domain/usecase/federation_identity_lookup_interactor_test.dart @@ -0,0 +1,336 @@ +import 'package:fluffychat/modules/federation_identity_lookup/domain/exceptions/federation_identity_lookup_exceptions.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_contact.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_hash_details_response.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_lookup_mxid_response.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_register_response.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_arguments.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/repository/federation_identity_lookup_repository.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/usecase/federation_identity_lookup_interactor.dart'; +import 'package:fluffychat/modules/federation_identity_lookup/domain/state/federation_identity_lookup_state.dart'; +import 'package:fluffychat/modules/federation_identity_request_token/domain/models/federation_token_information.dart'; +import 'package:mockito/mockito.dart'; + +import '../../../federation_contact_fixtures.dart'; +import 'federation_identity_lookup_interactor_test.mocks.dart'; + +@GenerateMocks([FederationIdentityLookupRepository]) +void main() { + late FederationIdentityLookupInteractor interactor; + late MockFederationIdentityLookupRepository mockRepository; + + const testToken = + FederationTokenInformation(accessToken: 'test_token', tokenType: 'test'); + + setUp(() { + mockRepository = MockFederationIdentityLookupRepository(); + interactor = FederationIdentityLookupInteractor( + federationIdentityLookupRepository: mockRepository, + ); + }); + + group('Registration Failures', () { + test('should handle registration exceptions', () async { + when(mockRepository.register( + tokenInformation: testToken,),) + .thenThrow(Exception('Network error')); + + final result = await interactor.execute( + arguments: FederationArguments( + federationUrl: 'test.server', + tokenInformation: testToken, + contactMaps: {}, + ), + ); + + expect(result.isLeft(), true); + final failure = result.swap().getOrElse(() => throw Exception('Expect a failure')); + expect(failure, isA()); + }); + }); + + group('Hash Calculation', () { + test('should handle empty contact list', () async { + when(mockRepository.register( + tokenInformation: testToken,),) + .thenAnswer((_) async => + const FederationRegisterResponse(token: 'valid_token')); + when(mockRepository.getHashDetails(registeredToken: 'valid_token')) + .thenAnswer((_) async => const FederationHashDetailsResponse( + algorithms: {'sha256'}, + lookupPepper: 'pepper', + )); + + final result = await interactor.execute( + arguments: FederationArguments( + federationUrl: 'test.server', + tokenInformation: testToken, + contactMaps: {}, + ), + ); + + expect(result.isLeft(), true); + final failure = result.swap().getOrElse(() => throw Exception('Expect a failure')); + expect( + failure, isA()); + }); + + test('should handle mixed phone/email contacts', () async { + when( + mockRepository.register( + tokenInformation: testToken, + ), + ).thenAnswer( + (_) async => const FederationRegisterResponse(token: 'valid_token'), + ); + when(mockRepository.getHashDetails(registeredToken: 'valid_token')) + .thenAnswer( + (_) async => const FederationHashDetailsResponse( + algorithms: {'sha256'}, + lookupPepper: 'pepper', + ), + ); + when( + mockRepository.lookupMxid( + request: anyNamed('request'), + registeredToken: anyNamed('registeredToken'), + ), + ).thenAnswer( + (_) async => const FederationLookupMxidResponse( + mappings: { + '6mWe5lBps9Rqabkqc_QIh0-jsdFogvcBi9EWs523fok': '@alice:matrix.com', + '0OWxtHmcUFS0KCHxRc2E8SrcU28Q-5EuRT5MJxnDdkg': '@alice_mail:matrix.com' + }, + ), + ); + + final contacts = { + FederationContactFixtures.contact1.id: FederationContactFixtures.contact1, + }; + + final result = await interactor.execute( + arguments: FederationArguments( + federationUrl: 'test.server', + tokenInformation: testToken, + contactMaps: contacts, + ), + ); + + expect(result.isRight(), true); + final success = result + .getOrElse(() => throw Exception('Expected Success')); + final contact = (success as FederationIdentityLookupSuccess) + .newContacts[FederationContactFixtures.contact1.id]!; + expect(contact.phoneNumbers?.first.matrixId, '@alice:matrix.com'); + expect(contact.emails?.first.matrixId, '@alice_mail:matrix.com'); + }); + }); + + group('MXID Lookup', () { + test('should handle partial mapping matches', () async { + when(mockRepository.register( + tokenInformation: testToken)) + .thenAnswer((_) async => const FederationRegisterResponse(token: 'valid_token')); + when(mockRepository.getHashDetails(registeredToken: 'valid_token')) + .thenAnswer((_) async => const FederationHashDetailsResponse( + algorithms: {'sha256'}, + lookupPepper: 'pepper', + altLookupPeppers: {'pepper1', 'pepper2'}, + )); + when(mockRepository.lookupMxid( + request: anyNamed('request'), + registeredToken: anyNamed('registeredToken'), + )).thenAnswer((_) async => const FederationLookupMxidResponse( + mappings: { + 'lWcTz7CJ9a9OqxlYWsl2MibzKep0abdGl6g3I3t7BPM': '@alice_pepper1:matrix.com', + 'rJnCyQMaiAcZNw_qB5D5iCCjUdKUKF7Mzl18HMY6DjQ': '@alice_pepper2:matrix.com', + })); + + final contacts = { + FederationContactFixtures.contact1.id: FederationContactFixtures.contact1, + }; + + final result = await interactor.execute( + arguments: FederationArguments( + federationUrl: 'test.server', + tokenInformation: testToken, + contactMaps: contacts, + ), + ); + + expect(result.isRight(), true); + final success = + result.getOrElse(() => throw Exception('Expected Success')); + expect( + (success as FederationIdentityLookupSuccess) + .newContacts[FederationContactFixtures.contact1.id] + ?.phoneNumbers + ?.first + .matrixId, + '@alice_pepper1:matrix.com',); + expect( + (success) + .newContacts[FederationContactFixtures.contact1.id] + ?.emails + ?.first + .matrixId, + '@alice_pepper2:matrix.com'); + }); + + test('should handle empty lookup response', () async { + when(mockRepository.register( + tokenInformation: testToken)) + .thenAnswer((_) async => const FederationRegisterResponse(token: 'valid_token')); + when(mockRepository.getHashDetails(registeredToken: 'valid_token')) + .thenAnswer((_) async => const FederationHashDetailsResponse( + algorithms: {'sha256'}, + lookupPepper: 'pepper', + altLookupPeppers: {'pepper1', 'pepper2'}, + )); + when(mockRepository.lookupMxid( + request: anyNamed('request'), + registeredToken: anyNamed('registeredToken'), + )).thenAnswer((_) async => const FederationLookupMxidResponse(mappings: {})); + + final contacts = { + FederationContactFixtures.contact1.id: FederationContactFixtures.contact1, + }; + + final result = await interactor.execute( + arguments: FederationArguments( + federationUrl: 'test.server', + tokenInformation: testToken, + contactMaps: contacts, + ), + ); + + expect(result.isLeft(), true); + final failure = result.swap() + .getOrElse(() => throw Exception("Expected failure")); + expect( + (failure as FederationIdentityLookupFailure).exception, + isA(), + ); + }); + }); + + group('Input Validation', () { + test('should handle invalid federation URL', () async { + final result = await interactor.execute( + arguments: FederationArguments( + federationUrl: '', + tokenInformation: testToken, + contactMaps: {}, + ), + ); + + expect(result.isLeft(), true); + final failure = result.swap().getOrElse(() => throw Exception('Expect a failure')); + expect(failure, isA()); + }); + + test('should handle invalid token information', () async { + when(mockRepository.register(tokenInformation: anyNamed('tokenInformation'))) + .thenThrow(Exception("Can not register")); + + final result = await interactor.execute( + arguments: FederationArguments( + federationUrl: 'test.server', + tokenInformation: const FederationTokenInformation(accessToken: 'test', tokenType: ''), + contactMaps: {}, + ), + ); + + expect(result.isLeft(), true); + final failure = result.swap() + .getOrElse(() => throw Exception('Expect an exception')); + expect(failure, isA()); + }); + + test('should handle invalid contact data', () async { + when(mockRepository.register( + tokenInformation: testToken)) + .thenAnswer((_) async => const FederationRegisterResponse(token: 'valid_token')); + when(mockRepository.getHashDetails(registeredToken: 'valid_token')) + .thenAnswer((_) async => const FederationHashDetailsResponse( + algorithms: {'sha256'}, + lookupPepper: 'pepper', + altLookupPeppers: {'pepper1', 'pepper2'}, + )); + when(mockRepository.lookupMxid( + request: anyNamed('request'), + registeredToken: anyNamed('registeredToken'), + )).thenAnswer((_) async => const FederationLookupMxidResponse(mappings: {})); + + final Map contacts = {}; + final result = await interactor.execute( + arguments: FederationArguments( + federationUrl: 'test.server', + tokenInformation: testToken, + contactMaps: contacts, + ), + ); + + expect(result.isLeft(), true); + final failure = result.swap() + .getOrElse(() => throw Exception("Expected failure")); + expect( + failure, + isA(), + ); + }); + }); + + group('Hash Details', () { + test('should handle unsupported hash algorithms', () async { + when(mockRepository.register( + tokenInformation: testToken)) + .thenAnswer((_) async => const FederationRegisterResponse(token: 'valid_token')); + when(mockRepository.getHashDetails(registeredToken: 'valid_token')) + .thenAnswer((_) async => const FederationHashDetailsResponse( + algorithms: {'unsupported algorithm'}, + lookupPepper: 'pepper', + )); + + when(mockRepository.lookupMxid( + request: anyNamed('request'), + registeredToken: anyNamed('registeredToken'), + )).thenAnswer((_) async => const FederationLookupMxidResponse( + mappings: { + '2125556789 msisdn': '@alice_pepper1:matrix.com', + 'alice@gmail.com email': '@alice_pepper2:matrix.com', + })); + + final contacts = { + FederationContactFixtures.contact1.id: FederationContactFixtures.contact1, + }; + + final result = await interactor.execute( + arguments: FederationArguments( + federationUrl: 'test.server', + tokenInformation: testToken, + contactMaps: contacts, + ), + ); + + expect(result.isRight(), true); + final success = + result.getOrElse(() => throw Exception('Expected Success')); + expect( + (success as FederationIdentityLookupSuccess) + .newContacts[FederationContactFixtures.contact1.id] + ?.phoneNumbers + ?.first + .matrixId, + '@alice_pepper1:matrix.com',); + expect( + (success) + .newContacts[FederationContactFixtures.contact1.id] + ?.emails + ?.first + .matrixId, + '@alice_pepper2:matrix.com'); + }); + }); +} diff --git a/test/modules/federation_identity_lookup/federation_third_party_contact_test.dart b/test/modules/federation_identity_lookup/federation_third_party_contact_test.dart deleted file mode 100644 index 1b03d3eef..000000000 --- a/test/modules/federation_identity_lookup/federation_third_party_contact_test.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_hash_details_response.dart'; -import 'package:fluffychat/modules/federation_identity_lookup/domain/models/federation_third_party_contact.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - group('Calculate hash using all Peppers test', () { - test( - 'Given a hash details response with no algorithms' - 'When calculateHashUsingAllPeppers is called' - 'Then an empty set is returned', - () { - const hashDetails = FederationHashDetailsResponse( - algorithms: null, - lookupPepper: 'pepper', - ); - - final result = FederationPhone( - number: '123456789', - ).calculateHashUsingAllPeppers(hashDetails: hashDetails); - - expect(result.isNotEmpty, true); - - expect(result, {'123456789 msisdn'}); - }, - ); - - test( - 'Given a hash details response with 1 algorithm and 1 pepper' - 'When calculateHashUsingAllPeppers is called' - 'Then a set with 1 hash is returned', - () { - const hashDetails = FederationHashDetailsResponse( - algorithms: {'sha256'}, - lookupPepper: 'pepper', - ); - - final result = FederationPhone( - number: '123456789', - ).calculateHashUsingAllPeppers(hashDetails: hashDetails); - - expect(result.isNotEmpty, true); - - expect(result, {'3ajyeUS818BntAyy_3tUdVll0Zn37PQvsYWJaPsCpyY'}); - }, - ); - - test( - 'Given a hash details response with 2 algorithm and 1 pepper' - 'When calculateHashUsingAllPeppers is called' - 'Then a set with 2 hashes is returned', - () { - const hashDetails = FederationHashDetailsResponse( - algorithms: { - 'sha256', - 'none', - }, - lookupPepper: 'pepper', - ); - - final result = FederationPhone( - number: '123456789', - ).calculateHashUsingAllPeppers(hashDetails: hashDetails); - - expect(result.length, 2); - - expect( - result, - { - '3ajyeUS818BntAyy_3tUdVll0Zn37PQvsYWJaPsCpyY', - '123456789 msisdn', - }, - ); - }, - ); - }); - - group('Calculate hash with algorithm SHA256', () { - test( - 'Given a phone number and a pepper is empty' - 'When calculateHashWithAlgorithmSha256 is called' - 'Then a hash is returned', - () { - const pepper = ''; - - final result = FederationPhone( - number: '123456789', - ).calculateHashWithAlgorithmSha256(pepper: pepper); - - expect(result.isNotEmpty, true); - - expect(result, 'dxslPU6M0Q2C_asaKTLP8a0ZA9_oXyQ0LY1XXvwEBjs'); - }, - ); - - test( - 'Given a phone number and a pepper' - 'When calculateHashWithAlgorithmSha256 is called' - 'Then a hash is returned', - () { - const pepper = 'pepper'; - - final result = FederationPhone( - number: '123456789', - ).calculateHashWithAlgorithmSha256(pepper: pepper); - - expect(result.isNotEmpty, true); - - expect(result, '3ajyeUS818BntAyy_3tUdVll0Zn37PQvsYWJaPsCpyY'); - }, - ); - }); -}