diff --git a/WireAPI/Sources/WireAPI/Models/UpdateEvent/UserEvent/UserPropertiesSetEvent.swift b/WireAPI/Sources/WireAPI/Models/UpdateEvent/UserEvent/UserPropertiesSetEvent.swift index 8d4912875d7..1184300833c 100644 --- a/WireAPI/Sources/WireAPI/Models/UpdateEvent/UserEvent/UserPropertiesSetEvent.swift +++ b/WireAPI/Sources/WireAPI/Models/UpdateEvent/UserEvent/UserPropertiesSetEvent.swift @@ -27,4 +27,8 @@ public struct UserPropertiesSetEvent: Equatable, Codable { public let property: UserProperty + public init(property: UserProperty) { + self.property = property + } + } diff --git a/WireDomain/Project/WireDomain Project.xcodeproj/project.pbxproj b/WireDomain/Project/WireDomain Project.xcodeproj/project.pbxproj index 4557096dfc4..3652f00c820 100644 --- a/WireDomain/Project/WireDomain Project.xcodeproj/project.pbxproj +++ b/WireDomain/Project/WireDomain Project.xcodeproj/project.pbxproj @@ -29,6 +29,11 @@ C97C014B2CB00F92000683C5 /* OneOnOneResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C014A2CB00F92000683C5 /* OneOnOneResolver.swift */; }; C97C01502CB01BDF000683C5 /* OneOnOneResolverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C014F2CB01BDF000683C5 /* OneOnOneResolverTests.swift */; }; C97C01522CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01512CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift */; }; + C97C01592CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01562CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift */; }; + C97C015A2CB40010000683C5 /* FederationDeleteEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C01572CB40010000683C5 /* FederationDeleteEventProcessorTests.swift */; }; + C97C015C2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C015B2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift */; }; + C97C015F2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C97C015E2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift */; }; + C9841A1F2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9841A1E2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift */; }; C99322D22C986E3A0065E10F /* TeamRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322B22C986E3A0065E10F /* TeamRepository.swift */; }; C99322D32C986E3A0065E10F /* TeamRepositoryError.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322B32C986E3A0065E10F /* TeamRepositoryError.swift */; }; C99322D42C986E3A0065E10F /* SelfUserProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C99322B52C986E3A0065E10F /* SelfUserProvider.swift */; }; @@ -63,7 +68,6 @@ C9E8A3B72C749F2A0093DD5C /* ConversationLabelsRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E8A3B62C749F2A0093DD5C /* ConversationLabelsRepositoryTests.swift */; }; C9E8A3C02C761EDD0093DD5C /* FeatureConfigRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9E8A3BF2C761EDD0093DD5C /* FeatureConfigRepositoryTests.swift */; }; C9EA769F2C92DD0F00A7D35C /* PushSupportedProtocolsUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EA769E2C92DD0F00A7D35C /* PushSupportedProtocolsUseCase.swift */; }; - C9EA76A12C93104C00A7D35C /* PushSupportedProtocolsUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EA76A02C93104C00A7D35C /* PushSupportedProtocolsUseCaseTests.swift */; }; C9F691292C9B164A008CC41F /* UserPushRemoveEventProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9F691282C9B164A008CC41F /* UserPushRemoveEventProcessorTests.swift */; }; C9FDF3EC2CAA988900D78098 /* ConnectionsModelMappings.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9FDF3EA2CAA988100D78098 /* ConnectionsModelMappings.swift */; }; CB7979132C738508006FBA58 /* WireTransportSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CB7979122C738508006FBA58 /* WireTransportSupport.framework */; }; @@ -149,12 +153,17 @@ 01D0DCE92C1C8EA10076CB1C /* WireDomain.docc */ = {isa = PBXFileReference; lastKnownFileType = folder.documentationcatalog; path = WireDomain.docc; sourceTree = ""; }; 01D0DCFC2C1C8F9B0076CB1C /* WireDomain.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = WireDomain.xctestplan; sourceTree = ""; }; 1623564F2C2B223100C6666C /* UserRepositoryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserRepositoryTests.swift; sourceTree = ""; }; + C91F111A2C9C4B2B00BA5BE2 /* ConnectionsRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionsRepository.swift; sourceTree = ""; }; + C91F111B2C9C4B2B00BA5BE2 /* ConnectionsLocalStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionsLocalStore.swift; sourceTree = ""; }; C97C013C2CAD7D69000683C5 /* UserPropertiesDeleteEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPropertiesDeleteEventProcessorTests.swift; sourceTree = ""; }; C97C014A2CB00F92000683C5 /* OneOnOneResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = OneOnOneResolver.swift; path = ../../OneOnOneResolver.swift; sourceTree = ""; }; C97C014F2CB01BDF000683C5 /* OneOnOneResolverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneOnOneResolverTests.swift; sourceTree = ""; }; C97C01512CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConnectionEventProcessorTests.swift; sourceTree = ""; }; - C91F111A2C9C4B2B00BA5BE2 /* ConnectionsRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionsRepository.swift; sourceTree = ""; }; - C91F111B2C9C4B2B00BA5BE2 /* ConnectionsLocalStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionsLocalStore.swift; sourceTree = ""; }; + C97C01562CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederationConnectionRemovedEventProcessorTests.swift; sourceTree = ""; }; + C97C01572CB40010000683C5 /* FederationDeleteEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FederationDeleteEventProcessorTests.swift; sourceTree = ""; }; + C97C015B2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPropertiesSetEventProcessorTests.swift; sourceTree = ""; }; + C97C015E2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSupportedProtocolsUseCaseTests.swift; sourceTree = ""; }; + C9841A1E2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockFeatureConfigRepositoryProtocol.swift; sourceTree = ""; }; C99322B22C986E3A0065E10F /* TeamRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamRepository.swift; sourceTree = ""; }; C99322B32C986E3A0065E10F /* TeamRepositoryError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TeamRepositoryError.swift; sourceTree = ""; }; C99322B52C986E3A0065E10F /* SelfUserProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SelfUserProvider.swift; sourceTree = ""; }; @@ -189,7 +198,6 @@ C9E8A3BF2C761EDD0093DD5C /* FeatureConfigRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureConfigRepositoryTests.swift; sourceTree = ""; }; C9E8A3E72C7F6EA40093DD5C /* ConversationRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationRepositoryTests.swift; sourceTree = ""; }; C9EA769E2C92DD0F00A7D35C /* PushSupportedProtocolsUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSupportedProtocolsUseCase.swift; sourceTree = ""; }; - C9EA76A02C93104C00A7D35C /* PushSupportedProtocolsUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushSupportedProtocolsUseCaseTests.swift; sourceTree = ""; }; C9F691282C9B164A008CC41F /* UserPushRemoveEventProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPushRemoveEventProcessorTests.swift; sourceTree = ""; }; C9FDF3EA2CAA988100D78098 /* ConnectionsModelMappings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionsModelMappings.swift; sourceTree = ""; }; CB7979122C738508006FBA58 /* WireTransportSupport.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = WireTransportSupport.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -285,6 +293,7 @@ 017F67992C20801800B6E02D /* Repositories */ = { isa = PBXGroup; children = ( + C9841A1D2CA309090062A834 /* Mock */, C9E8A3B62C749F2A0093DD5C /* ConversationLabelsRepositoryTests.swift */, C9E8A3AD2C73878B0093DD5C /* ConnectionsRepositoryTests.swift */, 017F67982C20801800B6E02D /* TeamRepositoryTests.swift */, @@ -299,6 +308,7 @@ 017F679A2C20801800B6E02D /* WireDomain */ = { isa = PBXGroup; children = ( + C97C015D2CB40EBE000683C5 /* UseCases */, C9C8FDCD2C9DBE0E00702B91 /* Event Processing */, EEC410252C60D48900E89394 /* Synchronization */, EE0E117C2C2C4076004BBD29 /* Helpers */, @@ -413,6 +423,31 @@ path = ../Tests; sourceTree = ""; }; + C97C01582CB40010000683C5 /* FederationEventProcessor */ = { + isa = PBXGroup; + children = ( + C97C01562CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift */, + C97C01572CB40010000683C5 /* FederationDeleteEventProcessorTests.swift */, + ); + path = FederationEventProcessor; + sourceTree = ""; + }; + C97C015D2CB40EBE000683C5 /* UseCases */ = { + isa = PBXGroup; + children = ( + C97C015E2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift */, + ); + path = UseCases; + sourceTree = ""; + }; + C9841A1D2CA309090062A834 /* Mock */ = { + isa = PBXGroup; + children = ( + C9841A1E2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift */, + ); + path = Mock; + sourceTree = ""; + }; C99322B42C986E3A0065E10F /* Team */ = { isa = PBXGroup; children = ( @@ -508,8 +543,9 @@ C9C8FDCC2C9DBE0E00702B91 /* UserEventProcessor */ = { isa = PBXGroup; children = ( - C97C01512CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift */, C97C014F2CB01BDF000683C5 /* OneOnOneResolverTests.swift */, + C97C01512CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift */, + C97C015B2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift */, C97C013C2CAD7D69000683C5 /* UserPropertiesDeleteEventProcessorTests.swift */, C9F691282C9B164A008CC41F /* UserPushRemoveEventProcessorTests.swift */, C9C8FDC92C9DBE0E00702B91 /* UserClientAddEventProcessorTests.swift */, @@ -522,6 +558,7 @@ C9C8FDCD2C9DBE0E00702B91 /* Event Processing */ = { isa = PBXGroup; children = ( + C97C01582CB40010000683C5 /* FederationEventProcessor */, C9C8FDC42C9DBE0E00702B91 /* FeatureConfigEventProcessor */, C9C8FDC82C9DBE0E00702B91 /* TeamEventProcessor */, C9C8FDCC2C9DBE0E00702B91 /* UserEventProcessor */, @@ -656,7 +693,6 @@ isa = PBXGroup; children = ( EEC410242C60D48900E89394 /* SyncManagerTests.swift */, - C9EA76A02C93104C00A7D35C /* PushSupportedProtocolsUseCaseTests.swift */, ); path = Synchronization; sourceTree = ""; @@ -947,18 +983,22 @@ C97C01522CB01BEF000683C5 /* UserConnectionEventProcessorTests.swift in Sources */, C9C8FDD02C9DBE0E00702B91 /* TeamMemberLeaveEventProcessorTests.swift in Sources */, C9E8A3C02C761EDD0093DD5C /* FeatureConfigRepositoryTests.swift in Sources */, + C9841A1F2CA309310062A834 /* MockFeatureConfigRepositoryProtocol.swift in Sources */, + C97C015C2CB40038000683C5 /* UserPropertiesSetEventProcessorTests.swift in Sources */, CB7979162C738547006FBA58 /* TestSetup.swift in Sources */, 162356502C2B223100C6666C /* UserRepositoryTests.swift in Sources */, EE3F97542C2ADC4C00668DF1 /* ProteusMessageDecryptorTests.swift in Sources */, EE57A70B2C2A8BAA0096F242 /* UpdateEventDecryptorTests.swift in Sources */, C9E8A3AE2C73878B0093DD5C /* ConnectionsRepositoryTests.swift in Sources */, + C97C015F2CB40EF3000683C5 /* PushSupportedProtocolsUseCaseTests.swift in Sources */, C97C013D2CAD7D69000683C5 /* UserPropertiesDeleteEventProcessorTests.swift in Sources */, C97C01502CB01BDF000683C5 /* OneOnOneResolverTests.swift in Sources */, C9C8FDD22C9DBE0E00702B91 /* UserClientAddEventProcessorTests.swift in Sources */, C9C8FDD12C9DBE0E00702B91 /* TeamMemberUpdateEventProcessorTests.swift in Sources */, - C9EA76A12C93104C00A7D35C /* PushSupportedProtocolsUseCaseTests.swift in Sources */, C9C8FDCE2C9DBE0E00702B91 /* FeatureConfigUpdateEventProcessorTests.swift in Sources */, C9C8FDCF2C9DBE0E00702B91 /* TeamDeleteEventProcessorTests.swift in Sources */, + C97C01592CB40010000683C5 /* FederationConnectionRemovedEventProcessorTests.swift in Sources */, + C97C015A2CB40010000683C5 /* FederationDeleteEventProcessorTests.swift in Sources */, C9C8FDD42C9DBE0E00702B91 /* UserLegalholdRequestEventProcessorTests.swift in Sources */, 017F679C2C20801800B6E02D /* TeamRepositoryTests.swift in Sources */, C9E8A3B72C749F2A0093DD5C /* ConversationLabelsRepositoryTests.swift in Sources */, diff --git a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserPropertiesSetEventProcessor.swift b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserPropertiesSetEventProcessor.swift index cab591a68c5..387a61efc6d 100644 --- a/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserPropertiesSetEventProcessor.swift +++ b/WireDomain/Sources/WireDomain/Event Processing/UserEventProcessor/UserPropertiesSetEventProcessor.swift @@ -32,9 +32,10 @@ protocol UserPropertiesSetEventProcessorProtocol { struct UserPropertiesSetEventProcessor: UserPropertiesSetEventProcessorProtocol { - func processEvent(_: UserPropertiesSetEvent) async throws { - // TODO: [WPB-10197] - assertionFailure("not implemented yet") + let repository: any UserRepositoryProtocol + + func processEvent(_ event: UserPropertiesSetEvent) async throws { + try await repository.updateUserProperty(event.property) } } diff --git a/WireDomain/Sources/WireDomain/OneOnOneResolver.swift b/WireDomain/Sources/WireDomain/OneOnOneResolver.swift index 4ee5a8c86ac..881f22ef127 100644 --- a/WireDomain/Sources/WireDomain/OneOnOneResolver.swift +++ b/WireDomain/Sources/WireDomain/OneOnOneResolver.swift @@ -84,11 +84,10 @@ struct OneOnOneResolver: OneOnOneResolverProtocol { private func resolveOneOnOneConversation( with userID: WireDataModel.QualifiedID ) async throws { - guard let user = userRepository.fetchUser( - with: userID.uuid, domain: userID.domain - ) else { - throw Error.failedToActivateConversation - } + let user = try await userRepository.fetchUser( + with: userID.uuid, + domain: userID.domain + ) let selfUser = userRepository.fetchSelfUser() let commonProtocol = getCommonProtocol(between: selfUser, and: user) diff --git a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepository.swift b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepository.swift index ee625d28af3..5f9898e7904 100644 --- a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepository.swift @@ -20,19 +20,32 @@ import CoreData import WireAPI import WireDataModel -/// Facilitate access to conversation labels related domain objects. +// sourcery: AutoMockable +/// Facilitates access to conversation labels related domain objects. +public protocol ConversationLabelsRepositoryProtocol { -protocol ConversationLabelsRepositoryProtocol { - - /// Pull conversation labels from the server and store locally + /// Pulls conversation labels from the server and stores locally func pullConversationLabels() async throws + + /// Updates conversation labels locally + /// - parameters: + /// - conversationLabels: The conversation labels to update locally. + + func updateConversationLabels( + _ conversationLabels: [ConversationLabel] + ) async throws } -final class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol { +public class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol { + + // MARK: - Properties private let userPropertiesAPI: any UserPropertiesAPI private let context: NSManagedObjectContext + private let logger = WireLogger(tag: "conversation-labels") + + // MARK: - Object lifecycle init( userPropertiesAPI: any UserPropertiesAPI, @@ -42,26 +55,57 @@ final class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol { self.context = context } + // MARK: - Public + /// Retrieve from backend and store conversation labels locally - func pullConversationLabels() async throws { + public func pullConversationLabels() async throws { let conversationLabels = try await userPropertiesAPI.getLabels() + try await updateConversationLabels(conversationLabels) + } + + public func updateConversationLabels( + _ conversationLabels: [ConversationLabel] + ) async throws { + await storeLabelsLocally(conversationLabels) + try await deleteOldLabelsLocally(excludedLabels: conversationLabels) + } + + // MARK: - Private + private func storeLabelsLocally( + _ conversationLabels: [ConversationLabel] + ) async { await withThrowingTaskGroup(of: Void.self) { taskGroup in for conversationLabel in conversationLabels { taskGroup.addTask { [self] in try await storeLabelLocally(conversationLabel) } } - } - try await deleteOldLabelsLocally(excludedLabels: conversationLabels) + /// Iterates through the group child tasks results and logs the error if any. + while let result = await taskGroup.nextResult() { + switch result { + case .success: + continue + case .failure(let error): + let repoError = error as? ConversationLabelsRepositoryError + if case .failedToStoreLabelLocally(let label) = repoError { + logger.error("Failed to store conversation label with id \(label.id): \(error)") + } else { + logger.error("Failed to store conversation with error: \(error)") + } + } + } + } } /// Save label and related conversations objects to local storage. /// - Parameter conversationLabel: conversation label from WireAPI - private func storeLabelLocally(_ conversationLabel: ConversationLabel) async throws { + private func storeLabelLocally( + _ conversationLabel: ConversationLabel + ) async throws { try await context.perform { [context] in var created = false let label: Label? = if conversationLabel.type == Label.Kind.favorite.rawValue { @@ -85,7 +129,11 @@ final class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol { label.conversations = conversations label.modifiedKeys = nil - try context.save() + do { + try context.save() + } catch { + throw ConversationLabelsRepositoryError.failedToStoreLabelLocally(conversationLabel) + } } } @@ -93,8 +141,10 @@ final class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol { /// - Parameter excludedLabels: remote labels that should be excluded from deletion. /// - Only old labels of type `folder` are deleted, `favorite` labels always remain in the local storage. - private func deleteOldLabelsLocally(excludedLabels remoteLabels: [ConversationLabel]) async throws { - try await context.perform { [context] in + private func deleteOldLabelsLocally( + excludedLabels remoteLabels: [ConversationLabel] + ) async throws { + try await context.perform { [self] in let uuids = remoteLabels.map { $0.id.uuidData as NSData } let predicateFormat = "type == \(Label.Kind.folder.rawValue) AND NOT remoteIdentifier_data IN %@" @@ -119,27 +169,29 @@ final class ConversationLabelsRepository: ConversationLabelsRepositoryProtocol { deleteRequest.resultType = .resultTypeObjectIDs - let batchDelete = try context.execute(deleteRequest) as? NSBatchDeleteResult - - guard let deleteResult = batchDelete?.result as? [NSManagedObjectID] else { - throw ConversationLabelsRepositoryError.failedToDeleteStoredLabels - } + do { + let batchDelete = try context.execute(deleteRequest) as? NSBatchDeleteResult - let deletedObjects: [AnyHashable: Any] = [ - NSDeletedObjectsKey: deleteResult - ] + guard let deleteResult = batchDelete?.result as? [NSManagedObjectID] else { + throw ConversationLabelsRepositoryError.failedToDeleteStoredLabels + } - /// Since `NSBatchDeleteRequest` only operates at the SQL level (in the persistent store itself), - /// we need to manually update our in-memory objects after execution. + let deletedObjects: [AnyHashable: Any] = [ + NSDeletedObjectsKey: deleteResult + ] - NSManagedObjectContext.mergeChanges( - fromRemoteContextSave: deletedObjects, - into: [context] - ) + /// Since `NSBatchDeleteRequest` only operates at the SQL level (in the persistent store itself), + /// we need to manually update our in-memory objects after execution. - /// Ensures the context and the persistent store are in sync + NSManagedObjectContext.mergeChanges( + fromRemoteContextSave: deletedObjects, + into: [context] + ) - context.saveOrRollback() + } catch { + logger.error("Failed to delete old labels: \(error)") + throw error + } } } diff --git a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepositoryError.swift b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepositoryError.swift index e0bf3f111d0..d5c17089b81 100644 --- a/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepositoryError.swift +++ b/WireDomain/Sources/WireDomain/Repositories/ConversationsLabels/ConversationLabelsRepositoryError.swift @@ -20,7 +20,7 @@ import WireAPI /// Errors originating from `ConversationLabelsRepository`. -enum ConversationLabelsRepositoryError: Error { +enum ConversationLabelsRepositoryError: Error, Equatable { /// Unable to store label locally diff --git a/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepository.swift b/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepository.swift index e84f3bd149b..a1909395096 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Team/TeamRepository.swift @@ -62,14 +62,18 @@ public protocol TeamRepositoryProtocol { } -final class TeamRepository: TeamRepositoryProtocol { +public class TeamRepository: TeamRepositoryProtocol { + + // MARK: - Properties private let selfTeamID: UUID private let userRepository: any UserRepositoryProtocol private let teamsAPI: any TeamsAPI private let context: NSManagedObjectContext - init( + // MARK: - Object lifecycle + + public init( selfTeamID: UUID, userRepository: any UserRepositoryProtocol, teamsAPI: any TeamsAPI, @@ -81,19 +85,43 @@ final class TeamRepository: TeamRepositoryProtocol { self.context = context } - // MARK: - Pull self team + // MARK: - Public - func pullSelfTeam() async throws { + public func pullSelfTeam() async throws { let team = try await fetchSelfTeamRemotely() await storeTeamLocally(team) } - func deleteMembership( + public func pullSelfTeamRoles() async throws { + let teamRoles = try await fetchSelfTeamRolesRemotely() + try await storeTeamRolesLocally(teamRoles) + } + + public func pullSelfTeamMembers() async throws { + let teamMembers = try await fetchSelfTeamMembersRemotely() + try await storeTeamMembersLocally(teamMembers) + } + + public func fetchSelfLegalholdStatus() async throws -> LegalholdStatus { + let selfUserID: UUID = await context.perform { [userRepository] in + userRepository.fetchSelfUser().remoteIdentifier + } + + return try await teamsAPI.getLegalholdStatus( + for: selfTeamID, + userID: selfUserID + ) + } + + public func deleteMembership( forUser userID: UUID, fromTeam teamID: UUID, at time: Date ) async throws { - let user = try await userRepository.fetchUser(with: userID) + let user = try await userRepository.fetchUser( + with: userID, + domain: nil + ) let member = try await context.perform { guard let member = user.membership else { @@ -113,7 +141,7 @@ final class TeamRepository: TeamRepositoryProtocol { } } - func storeTeamMemberNeedsBackendUpdate(membershipID: UUID) async throws { + public func storeTeamMemberNeedsBackendUpdate(membershipID: UUID) async throws { try await context.perform { [context] in guard let member = Member.fetch( @@ -129,6 +157,8 @@ final class TeamRepository: TeamRepositoryProtocol { } } + // MARK: - Private + private func fetchSelfTeamRemotely() async throws -> WireAPI.Team { do { return try await teamsAPI.getTeam(for: selfTeamID) @@ -164,13 +194,6 @@ final class TeamRepository: TeamRepositoryProtocol { } } - // MARK: - Pull self team roles - - func pullSelfTeamRoles() async throws { - let teamRoles = try await fetchSelfTeamRolesRemotely() - try await storeTeamRolesLocally(teamRoles) - } - private func fetchSelfTeamRolesRemotely() async throws -> [WireAPI.ConversationRole] { do { return try await teamsAPI.getTeamRoles(for: selfTeamID) @@ -220,13 +243,6 @@ final class TeamRepository: TeamRepositoryProtocol { } } - // MARK: - Pull self team members - - func pullSelfTeamMembers() async throws { - let teamMembers = try await fetchSelfTeamMembersRemotely() - try await storeTeamMembersLocally(teamMembers) - } - private func fetchSelfTeamMembersRemotely() async throws -> [WireAPI.TeamMember] { do { return try await teamsAPI.getTeamMembers( @@ -278,19 +294,6 @@ final class TeamRepository: TeamRepositoryProtocol { } } - // MARK: - Fetch self legalhold status - - func fetchSelfLegalholdStatus() async throws -> LegalholdStatus { - let selfUserID: UUID = await context.perform { [userRepository] in - userRepository.fetchSelfUser().remoteIdentifier - } - - return try await teamsAPI.getLegalholdStatus( - for: selfTeamID, - userID: selfUserID - ) - } - } private extension ConversationAction { diff --git a/WireDomain/Sources/WireDomain/Repositories/User/UserRepository.swift b/WireDomain/Sources/WireDomain/Repositories/User/UserRepository.swift index 61d8e3deed5..377d733cdb6 100644 --- a/WireDomain/Sources/WireDomain/Repositories/User/UserRepository.swift +++ b/WireDomain/Sources/WireDomain/Repositories/User/UserRepository.swift @@ -37,8 +37,12 @@ public protocol UserRepositoryProtocol { /// - parameters /// - id: The ID of the user. /// - domain: The domain of the user. + /// - returns : A local`ZMUser`. - func fetchUser(with id: UUID, domain: String?) -> ZMUser? + func fetchUser( + with id: UUID, + domain: String? + ) async throws -> ZMUser /// Push self user supported protocols /// - Parameter supportedProtocols: A list of supported protocols. @@ -61,11 +65,6 @@ public protocol UserRepositoryProtocol { /// Removes user push token from storage. func removePushToken() - /// Fetches a user with a specific id. - /// - Parameter id: The ID of the user. - /// - Returns: A `ZMUser` object. - - func fetchUser(with id: UUID) async throws -> ZMUser /// Fetches or creates a user client locally. /// @@ -111,6 +110,15 @@ public protocol UserRepositoryProtocol { func disableUserLegalHold() async throws + /// Updates a user property + /// + /// - parameters: + /// - userProperty: The user property to update. + + func updateUserProperty( + _ userProperty: WireAPI.UserProperty + ) async throws + /// Deletes a user property. /// /// - parameters: @@ -141,6 +149,7 @@ public final class UserRepository: UserRepositoryProtocol { private let context: NSManagedObjectContext private let usersAPI: any UsersAPI private let selfUserAPI: any SelfUserAPI + private let conversationLabelsRepository: any ConversationLabelsRepositoryProtocol private let conversationRepository: any ConversationRepositoryProtocol private let storage: UserDefaults @@ -150,12 +159,14 @@ public final class UserRepository: UserRepositoryProtocol { context: NSManagedObjectContext, usersAPI: any UsersAPI, selfUserAPI: any SelfUserAPI, + conversationLabelsRepository: any ConversationLabelsRepositoryProtocol, conversationRepository: ConversationRepositoryProtocol, sharedUserDefaults: UserDefaults = .standard ) { self.context = context self.usersAPI = usersAPI self.selfUserAPI = selfUserAPI + self.conversationLabelsRepository = conversationLabelsRepository self.conversationRepository = conversationRepository storage = sharedUserDefaults } @@ -166,8 +177,17 @@ public final class UserRepository: UserRepositoryProtocol { ZMUser.selfUser(in: context) } - public func fetchUser(with id: UUID, domain: String?) -> ZMUser? { - ZMUser.fetch(with: id, domain: domain, in: context) + public func fetchUser( + with id: UUID, + domain: String? + ) async throws -> ZMUser { + try await context.perform { [context] in + guard let user = ZMUser.fetch(with: id, in: context) else { + throw UserRepositoryError.failedToFetchUser(id) + } + + return user + } } public func pushSelfSupportedProtocols( @@ -336,6 +356,26 @@ public final class UserRepository: UserRepositoryProtocol { } } + public func updateUserProperty(_ userProperty: UserProperty) async throws { + switch userProperty { + case .areReadReceiptsEnabled(let isEnabled): + let selfUser = fetchSelfUser() + + await context.perform { + selfUser.readReceiptsEnabled = isEnabled + selfUser.readReceiptsEnabledChangedRemotely = true + } + + case .conversationLabels(let conversationLabels): + try await conversationLabelsRepository.updateConversationLabels(conversationLabels) + + default: + WireLogger.updateEvent.warn( + "\(String(describing: userProperty)) property not handled." + ) + } + } + public func deleteUserProperty( withKey key: UserProperty.Key ) async { diff --git a/WireDomain/Sources/WireDomainSupport/Sourcery/generated/AutoMockable.generated.swift b/WireDomain/Sources/WireDomainSupport/Sourcery/generated/AutoMockable.generated.swift index 0d03b462491..63d8e9d903b 100644 --- a/WireDomain/Sources/WireDomainSupport/Sourcery/generated/AutoMockable.generated.swift +++ b/WireDomain/Sources/WireDomainSupport/Sourcery/generated/AutoMockable.generated.swift @@ -105,6 +105,55 @@ public class MockConnectionsRepositoryProtocol: ConnectionsRepositoryProtocol { } +public class MockConversationLabelsRepositoryProtocol: ConversationLabelsRepositoryProtocol { + + // MARK: - Life cycle + + public init() {} + + + // MARK: - pullConversationLabels + + public var pullConversationLabels_Invocations: [Void] = [] + public var pullConversationLabels_MockError: Error? + public var pullConversationLabels_MockMethod: (() async throws -> Void)? + + public func pullConversationLabels() async throws { + pullConversationLabels_Invocations.append(()) + + if let error = pullConversationLabels_MockError { + throw error + } + + guard let mock = pullConversationLabels_MockMethod else { + fatalError("no mock for `pullConversationLabels`") + } + + try await mock() + } + + // MARK: - updateConversationLabels + + public var updateConversationLabels_Invocations: [[ConversationLabel]] = [] + public var updateConversationLabels_MockError: Error? + public var updateConversationLabels_MockMethod: (([ConversationLabel]) async throws -> Void)? + + public func updateConversationLabels(_ conversationLabels: [ConversationLabel]) async throws { + updateConversationLabels_Invocations.append(conversationLabels) + + if let error = updateConversationLabels_MockError { + throw error + } + + guard let mock = updateConversationLabels_MockMethod else { + fatalError("no mock for `updateConversationLabels`") + } + + try await mock(conversationLabels) + } + +} + public class MockConversationLocalStoreProtocol: ConversationLocalStoreProtocol { // MARK: - Life cycle @@ -727,14 +776,19 @@ public class MockUserRepositoryProtocol: UserRepositoryProtocol { // MARK: - fetchUser public var fetchUserWithDomain_Invocations: [(id: UUID, domain: String?)] = [] - public var fetchUserWithDomain_MockMethod: ((UUID, String?) -> ZMUser?)? - public var fetchUserWithDomain_MockValue: ZMUser?? + public var fetchUserWithDomain_MockError: Error? + public var fetchUserWithDomain_MockMethod: ((UUID, String?) async throws -> ZMUser)? + public var fetchUserWithDomain_MockValue: ZMUser? - public func fetchUser(with id: UUID, domain: String?) -> ZMUser? { + public func fetchUser(with id: UUID, domain: String?) async throws -> ZMUser { fetchUserWithDomain_Invocations.append((id: id, domain: domain)) + if let error = fetchUserWithDomain_MockError { + throw error + } + if let mock = fetchUserWithDomain_MockMethod { - return mock(id, domain) + return try await mock(id, domain) } else if let mock = fetchUserWithDomain_MockValue { return mock } else { @@ -817,29 +871,6 @@ public class MockUserRepositoryProtocol: UserRepositoryProtocol { mock() } - // MARK: - fetchUser - - public var fetchUserWith_Invocations: [UUID] = [] - public var fetchUserWith_MockError: Error? - public var fetchUserWith_MockMethod: ((UUID) async throws -> ZMUser)? - public var fetchUserWith_MockValue: ZMUser? - - public func fetchUser(with id: UUID) async throws -> ZMUser { - fetchUserWith_Invocations.append(id) - - if let error = fetchUserWith_MockError { - throw error - } - - if let mock = fetchUserWith_MockMethod { - return try await mock(id) - } else if let mock = fetchUserWith_MockValue { - return mock - } else { - fatalError("no mock for `fetchUserWith`") - } - } - // MARK: - fetchOrCreateUserClient public var fetchOrCreateUserClientWith_Invocations: [String] = [] @@ -918,6 +949,26 @@ public class MockUserRepositoryProtocol: UserRepositoryProtocol { try await mock() } + // MARK: - updateUserProperty + + public var updateUserProperty_Invocations: [WireAPI.UserProperty] = [] + public var updateUserProperty_MockError: Error? + public var updateUserProperty_MockMethod: ((WireAPI.UserProperty) async throws -> Void)? + + public func updateUserProperty(_ userProperty: WireAPI.UserProperty) async throws { + updateUserProperty_Invocations.append(userProperty) + + if let error = updateUserProperty_MockError { + throw error + } + + guard let mock = updateUserProperty_MockMethod else { + fatalError("no mock for `updateUserProperty`") + } + + try await mock(userProperty) + } + // MARK: - deleteUserProperty public var deleteUserPropertyWithKey_Invocations: [UserProperty.Key] = [] diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/FeatureConfigEventProcessor/FeatureConfigUpdateEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/FeatureConfigEventProcessor/FeatureConfigUpdateEventProcessorTests.swift index 46416feca36..0aa07229037 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/FeatureConfigEventProcessor/FeatureConfigUpdateEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/FeatureConfigEventProcessor/FeatureConfigUpdateEventProcessorTests.swift @@ -16,95 +16,60 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation import WireAPI -import WireAPISupport -import WireDataModel -import WireDataModelSupport @testable import WireDomain +import WireDomainSupport import XCTest final class FeatureConfigUpdateEventProcessorTests: XCTestCase { - private var sut: FeatureConfigUpdateEventProcessor! - private var coreDataStack: CoreDataStack! - private var coreDataStackHelper: CoreDataStackHelper! - private var modelHelper: ModelHelper! - - private var context: NSManagedObjectContext { - coreDataStack.syncContext - } + var sut: FeatureConfigUpdateEventProcessor! + var featureConfigRepository: MockFeatureConfigRepositoryProtocol! override func setUp() async throws { try await super.setUp() - coreDataStackHelper = CoreDataStackHelper() - modelHelper = ModelHelper() - coreDataStack = try await coreDataStackHelper.createStack() - sut = FeatureConfigUpdateEventProcessor( - repository: FeatureConfigRepository( - featureConfigsAPI: MockFeatureConfigsAPI(), - context: context - ) - ) + featureConfigRepository = MockFeatureConfigRepositoryProtocol() + sut = FeatureConfigUpdateEventProcessor(repository: featureConfigRepository) } override func tearDown() async throws { try await super.tearDown() - modelHelper = nil sut = nil - try coreDataStackHelper.cleanupDirectory() - coreDataStackHelper = nil - coreDataStack = nil + featureConfigRepository = nil } // MARK: - Tests - func testProcessEvent_It_Updates_Feature_Config_Locally() async throws { + func testProcessEvent_It_Invokes_Update_Feature_Config_Repo_Method() async throws { // Given - try await context.perform { [context] in - let config = try JSONEncoder().encode(Scaffolding.config) - Feature.updateOrCreate(havingName: .mls, in: context) { feature in - feature.status = .enabled - feature.config = config - } - } + let event = FeatureConfigUpdateEvent( + featureConfig: Scaffolding.config + ) + + // Mock + + featureConfigRepository.updateFeatureConfig_MockMethod = { _ in } // When - try await sut.processEvent(Scaffolding.event) + try await sut.processEvent(event) // Then - try await context.perform { [context] in - let feature = try XCTUnwrap(Feature.fetch(name: .mls, context: context)) - let data = try XCTUnwrap(feature.config) - let config = try JSONDecoder().decode(Feature.MLS.Config.self, from: data) - XCTAssertEqual(feature.status, .disabled) - XCTAssertEqual(config.supportedProtocols, [.mls]) - } + XCTAssertEqual(featureConfigRepository.updateFeatureConfig_Invocations, [Scaffolding.config]) } private enum Scaffolding { - static let event = FeatureConfigUpdateEvent( - featureConfig: .mls(updatedConfig) - ) - - static let updatedConfig = MLSFeatureConfig( - status: .disabled, /// updated property - protocolToggleUsers: [UUID()], - defaultProtocol: .proteus, - allowedCipherSuites: [.MLS_128_DHKEMP256_AES128GCM_SHA256_P256], - defaultCipherSuite: .MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519, - supportedProtocols: [.mls] /// updated property - ) - - nonisolated(unsafe) static let config = Feature.MLS.Config( - protocolToggleUsers: [UUID()], - defaultProtocol: .proteus, - allowedCipherSuites: [.MLS_128_DHKEMP256_AES128GCM_SHA256_P256], - defaultCipherSuite: .MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519, - supportedProtocols: [.proteus] + static let config = FeatureConfig.mls( + MLSFeatureConfig( + status: .disabled, + protocolToggleUsers: [UUID()], + defaultProtocol: .proteus, + allowedCipherSuites: [.MLS_128_DHKEMP256_AES128GCM_SHA256_P256], + defaultCipherSuite: .MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519, + supportedProtocols: [.mls] + ) ) } } diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/FederationEventProcessor/FederationConnectionRemovedEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/FederationEventProcessor/FederationConnectionRemovedEventProcessorTests.swift index f1b38e9b4c6..ff54ec855ca 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/FederationEventProcessor/FederationConnectionRemovedEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/FederationEventProcessor/FederationConnectionRemovedEventProcessorTests.swift @@ -16,31 +16,24 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation import WireAPI -import WireAPISupport import WireDataModel import WireDataModelSupport -import XCTest - @testable import WireDomain +import XCTest final class FederationConnectionRemovedEventProcessorTests: XCTestCase { - var sut: FederationConnectionRemovedEventProcessor! - - var coreDataStack: CoreDataStack! - var coreDataStackHelper: CoreDataStackHelper! - var modelHelper: ModelHelper! - - var context: NSManagedObjectContext { + private var sut: FederationConnectionRemovedEventProcessor! + private var coreDataStack: CoreDataStack! + private var coreDataStackHelper: CoreDataStackHelper! + private var context: NSManagedObjectContext { coreDataStack.syncContext } override func setUp() async throws { try await super.setUp() coreDataStackHelper = CoreDataStackHelper() - modelHelper = ModelHelper() coreDataStack = try await coreDataStackHelper.createStack() sut = FederationConnectionRemovedEventProcessor( context: context @@ -53,7 +46,6 @@ final class FederationConnectionRemovedEventProcessorTests: XCTestCase { sut = nil try coreDataStackHelper.cleanupDirectory() coreDataStackHelper = nil - modelHelper = nil } // MARK: - Tests @@ -213,26 +205,22 @@ final class FederationConnectionRemovedEventProcessorTests: XCTestCase { return conversation } -} + private enum Scaffolding { -// MARK: - Scaffolding + /// UUIDs + static let userID = UUID() + static let otherUserID = UUID() + static let groupConversationID = UUID() -private enum Scaffolding { + /// Domains + static let firstDomain = "domain.com" + static let secondDomain = "domain2.com" + static let thirdDomain = "domain3.com" - /// UUIDs - - static let userID = UUID() - static let otherUserID = UUID() - static let groupConversationID = UUID() - - /// Domains - - static let firstDomain = "domain.com" - static let secondDomain = "domain2.com" - static let thirdDomain = "domain3.com" + static let event = FederationConnectionRemovedEvent( + domains: Set([secondDomain, thirdDomain]) + ) - static let event = FederationConnectionRemovedEvent( - domains: Set([secondDomain, thirdDomain]) - ) + } } diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/FederationEventProcessor/FederationDeleteEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/FederationEventProcessor/FederationDeleteEventProcessorTests.swift index 912162f210a..598e6c9a89a 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/FederationEventProcessor/FederationDeleteEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/FederationEventProcessor/FederationDeleteEventProcessorTests.swift @@ -16,31 +16,25 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation import WireAPI -import WireAPISupport import WireDataModel import WireDataModelSupport -import XCTest - @testable import WireDomain +import XCTest final class FederationDeleteEventProcessorTests: XCTestCase { - var sut: FederationDeleteEventProcessor! + private var sut: FederationDeleteEventProcessor! + private var coreDataStack: CoreDataStack! + private var coreDataStackHelper: CoreDataStackHelper! - var coreDataStack: CoreDataStack! - var coreDataStackHelper: CoreDataStackHelper! - var modelHelper: ModelHelper! - - var context: NSManagedObjectContext { + private var context: NSManagedObjectContext { coreDataStack.syncContext } override func setUp() async throws { try await super.setUp() coreDataStackHelper = CoreDataStackHelper() - modelHelper = ModelHelper() coreDataStack = try await coreDataStackHelper.createStack() sut = FederationDeleteEventProcessor( context: context @@ -53,7 +47,6 @@ final class FederationDeleteEventProcessorTests: XCTestCase { sut = nil try coreDataStackHelper.cleanupDirectory() coreDataStackHelper = nil - modelHelper = nil } // MARK: - Tests @@ -481,28 +474,24 @@ final class FederationDeleteEventProcessorTests: XCTestCase { return groupConversation } -} - -// MARK: - Scaffolding - -private enum Scaffolding { - - /// UUIDs + private enum Scaffolding { - static let selfUserID = UUID() - static let userID = UUID() - static let otherUserID = UUID() - static let groupConversationID = UUID() - static let oneOnOneConversationID = UUID() + /// UUIDs + static let selfUserID = UUID() + static let userID = UUID() + static let otherUserID = UUID() + static let groupConversationID = UUID() + static let oneOnOneConversationID = UUID() - /// Domains + /// Domains + static let firstDomain = "domain.com" + static let secondDomain = "domain2.com" + static let defederatedDomain = "domain3.com" - static let firstDomain = "domain.com" - static let secondDomain = "domain2.com" - static let defederatedDomain = "domain3.com" + static let event = FederationDeleteEvent( + domain: defederatedDomain + ) - static let event = FederationDeleteEvent( - domain: defederatedDomain - ) + } } diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamDeleteEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamDeleteEventProcessorTests.swift index 0f34a2ee78e..65def35945b 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamDeleteEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamDeleteEventProcessorTests.swift @@ -16,7 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation import WireAPI import WireDataModel import WireDataModelSupport @@ -28,7 +27,6 @@ final class TeamDeleteEventProcessorTests: XCTestCase { private var sut: TeamDeleteEventProcessor! private var coreDataStack: CoreDataStack! private var coreDataStackHelper: CoreDataStackHelper! - private var modelHelper: ModelHelper! private var context: NSManagedObjectContext { coreDataStack.syncContext @@ -37,7 +35,6 @@ final class TeamDeleteEventProcessorTests: XCTestCase { override func setUp() async throws { try await super.setUp() coreDataStackHelper = CoreDataStackHelper() - modelHelper = ModelHelper() coreDataStack = try await coreDataStackHelper.createStack() sut = TeamDeleteEventProcessor( context: context @@ -46,7 +43,6 @@ final class TeamDeleteEventProcessorTests: XCTestCase { override func tearDown() async throws { try await super.tearDown() - modelHelper = nil coreDataStack = nil sut = nil try coreDataStackHelper.cleanupDirectory() @@ -75,6 +71,8 @@ final class TeamDeleteEventProcessorTests: XCTestCase { // When try await sut.processEvent() + + await fulfillment(of: [expectation], timeout: 5.0) } } diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamMemberUpdateEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamMemberUpdateEventProcessorTests.swift index d9792ade522..91d884f090c 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamMemberUpdateEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/TeamEventProcessor/TeamMemberUpdateEventProcessorTests.swift @@ -41,23 +41,38 @@ final class TeamMemberUpdateEventProcessorTests: XCTestCase { // MARK: - Tests func testProcessEvent_It_Invokes_Repo_Method() async throws { + // Given + + let event = TeamMemberUpdateEvent( + teamID: Scaffolding.teamID, + membershipID: Scaffolding.membershipID + ) + // Mock teamRepository.storeTeamMemberNeedsBackendUpdateMembershipID_MockMethod = { _ in } // When - try await sut.processEvent(Scaffolding.event) + try await sut.processEvent(event) // Then + XCTAssertEqual(teamRepository.storeTeamMemberNeedsBackendUpdateMembershipID_Invocations, [Scaffolding.membershipID]) XCTAssertEqual(teamRepository.storeTeamMemberNeedsBackendUpdateMembershipID_Invocations.count, 1) } private enum Scaffolding { + + static let domain = "example.com" + static let teamID = UUID() + static let membershipID = UUID() + static let event = TeamMemberUpdateEvent( teamID: UUID(), membershipID: UUID() ) + } + } diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserClientAddEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserClientAddEventProcessorTests.swift index 7889e127767..bea66da797f 100644 --- a/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserClientAddEventProcessorTests.swift +++ b/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserClientAddEventProcessorTests.swift @@ -95,4 +95,5 @@ final class UserClientAddEventProcessorTests: XCTestCase { ) ) } + } diff --git a/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserPropertiesSetEventProcessorTests.swift b/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserPropertiesSetEventProcessorTests.swift new file mode 100644 index 00000000000..340cc31b2fb --- /dev/null +++ b/WireDomain/Tests/WireDomainTests/Event Processing/UserEventProcessor/UserPropertiesSetEventProcessorTests.swift @@ -0,0 +1,85 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +@testable import WireAPI +@testable import WireDomain +import WireDomainSupport +import XCTest + +final class UserPropertiesSetEventProcessorTests: XCTestCase { + + var sut: UserPropertiesSetEventProcessor! + var userRepository: MockUserRepositoryProtocol! + + override func setUp() async throws { + try await super.setUp() + userRepository = MockUserRepositoryProtocol() + sut = UserPropertiesSetEventProcessor(repository: userRepository) + } + + override func tearDown() async throws { + try await super.tearDown() + sut = nil + userRepository = nil + } + + // MARK: - Tests + + func testProcessEvent_It_Invokes_Update_User_Property_Repo_Method() async throws { + // Given + let labels = [Scaffolding.conversationLabel1, Scaffolding.conversationLabel2] + let event = UserPropertiesSetEvent( + property: .conversationLabels(labels) + ) + + // Mock + + userRepository.updateUserProperty_MockMethod = { _ in } + + // When + + try await sut.processEvent(event) + + // Then + + XCTAssertEqual(userRepository.updateUserProperty_Invocations, [.conversationLabels(labels)]) + XCTAssertEqual(userRepository.updateUserProperty_Invocations.count, 1) + } + + private enum Scaffolding { + static let conversationLabel1 = ConversationLabel( + id: UUID(uuidString: "f3d302fb-3fd5-43b2-927b-6336f9e787b0")!, + name: "ConversationLabel1", + type: 0, + conversationIDs: [ + UUID(uuidString: "ffd0a9af-c0d0-4748-be9b-ab309c640dde")!, + UUID(uuidString: "03fe0d05-f0d5-4ee4-a8ff-8d4b4dcf89d8")! + ] + ) + static let conversationLabel2 = ConversationLabel( + id: UUID(uuidString: "2AA27182-AA54-4D79-973E-8974A3BBE375")!, + name: "ConversationLabel2", + type: 0, + conversationIDs: [ + UUID(uuidString: "ceb3f577-3b22-4fe9-8ffd-757f29c47ffc")!, + UUID(uuidString: "eca55fdb-8f81-4112-9175-4ffca7691bf8")! + ] + ) + } + +} diff --git a/WireDomain/Tests/WireDomainTests/Repositories/ConnectionsRepositoryTests.swift b/WireDomain/Tests/WireDomainTests/Repositories/ConnectionsRepositoryTests.swift index 8e703fec05e..22e24f4e935 100644 --- a/WireDomain/Tests/WireDomainTests/Repositories/ConnectionsRepositoryTests.swift +++ b/WireDomain/Tests/WireDomainTests/Repositories/ConnectionsRepositoryTests.swift @@ -21,15 +21,12 @@ import WireAPISupport import WireDataModel import WireDataModelSupport @testable import WireDomain -import WireDomainSupport -import WireTransport import XCTest final class ConnectionsRepositoryTests: XCTestCase { private var sut: ConnectionsRepository! private var connectionsAPI: MockConnectionsAPI! - private var stack: CoreDataStack! private var coreDataStackHelper: CoreDataStackHelper! private var modelHelper: ModelHelper! diff --git a/WireDomain/Tests/WireDomainTests/Repositories/ConversationLabelsRepositoryTests.swift b/WireDomain/Tests/WireDomainTests/Repositories/ConversationLabelsRepositoryTests.swift index bd87d0c9451..182841a50ec 100644 --- a/WireDomain/Tests/WireDomainTests/Repositories/ConversationLabelsRepositoryTests.swift +++ b/WireDomain/Tests/WireDomainTests/Repositories/ConversationLabelsRepositoryTests.swift @@ -83,8 +83,8 @@ final class ConversationLabelsRepositoryTests: XCTestCase { try await context.perform { [context] in let fetchRequest = NSFetchRequest