Skip to content

Commit

Permalink
fix: calculate supported protocols - WPB-15297 (#2384)
Browse files Browse the repository at this point in the history
  • Loading branch information
netbe authored Jan 14, 2025
1 parent 76cbca6 commit 480e9f2
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public struct FeatureConfigsAPIBuilder {

/// Create a new builder.
///
/// - Parameter httpClient: A http client.
/// - Parameter APIService: An api service.

public init(apiService: any APIServiceProtocol) {
self.apiService = apiService
Expand Down
84 changes: 84 additions & 0 deletions WireDomain/Sources/WireDomain/UseCases/PullSelfUserClients.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//
// Wire
// Copyright (C) 2025 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/.
//

import CoreData
import WireAPI

/// Pull self clients from backend and update local state
public struct PullSelfUserClients: PullSelfUserClientsProtocol {
private let userClientsAPI: any UserClientsAPI
private let userClientsLocalStore: any UserClientsLocalStoreProtocol

init(userClientsAPI: any UserClientsAPI, userClientsLocalStore: any UserClientsLocalStoreProtocol) {
self.userClientsAPI = userClientsAPI
self.userClientsLocalStore = userClientsLocalStore
}

public func pullSelfClients() async throws {
let remoteSelfClients = try await userClientsAPI.getSelfClients()

for remoteSelfClient in remoteSelfClients {
let localUserClient = await userClientsLocalStore.fetchOrCreateClient(
id: remoteSelfClient.id
)

try await updateClient(
id: remoteSelfClient.id,
from: remoteSelfClient,
isNewClient: localUserClient.isNew
)
}

let deletedSelfClientsIDs = await userClientsLocalStore.deletedSelfClients(
newClients: remoteSelfClients.map(\.id)
)

for deletedSelfClientID in deletedSelfClientsIDs {
await userClientsLocalStore.deleteClient(id: deletedSelfClientID)
}
}

func updateClient(
id: String,
from remoteClient: WireAPI.SelfUserClient,
isNewClient: Bool
) async throws {
await userClientsLocalStore.updateClient(
id: id,
isNewClient: isNewClient,
userClientInfo: remoteClient.toDomainModel()
)
}

}

public extension PullSelfUserClients {

static func make(
apiService: any APIServiceProtocol,
apiVersion: WireAPI.APIVersion,
context: NSManagedObjectContext
) -> PullSelfUserClientsProtocol {
let userClientsAPI = UserClientsAPIBuilder(apiService: apiService).makeAPI(for: apiVersion)

let userLocalStore = UserLocalStore(context: context)
let userClientsLocalStore = UserClientsLocalStore(context: context, userLocalStore: userLocalStore)

return PullSelfUserClients(userClientsAPI: userClientsAPI, userClientsLocalStore: userClientsLocalStore)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Wire
// Copyright (C) 2025 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/.
//

// sourcery: AutoMockable
public protocol PullSelfUserClientsProtocol {

func pullSelfClients() async throws
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// Wire
// Copyright (C) 2025 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/.
//

import WireAPISupport
import WireDataModel
import WireDataModelSupport
import WireDomainSupport
import WireTestingPackage
import XCTest
@testable import WireAPI
@testable import WireDomain

final class PullSelfUserClientsTests: XCTestCase {

private var sut: PullSelfUserClients!
private var userClientsAPI: MockUserClientsAPI!
private var userClientsLocalStore: MockUserClientsLocalStoreProtocol!
private var stack: CoreDataStack!
private var coreDataStackHelper: CoreDataStackHelper!
private var modelHelper: ModelHelper!

private var context: NSManagedObjectContext {
stack.syncContext
}

override func setUp() async throws {
coreDataStackHelper = CoreDataStackHelper()
modelHelper = ModelHelper()
stack = try await coreDataStackHelper.createStack()
userClientsAPI = MockUserClientsAPI()
userClientsLocalStore = MockUserClientsLocalStoreProtocol()

sut = PullSelfUserClients(
userClientsAPI: userClientsAPI,
userClientsLocalStore: userClientsLocalStore
)
}

override func tearDown() async throws {
stack = nil
userClientsAPI = nil
sut = nil
try coreDataStackHelper.cleanupDirectory()
coreDataStackHelper = nil
modelHelper = nil
}

// MARK: - Tests

func testPullSelfClients_It_Invokes_Local_Store_And_User_Repo_Methods() async throws {
// Mock

let selfUserClient = await context.perform { [self] in
return modelHelper.createSelfClient(
id: Scaffolding.otherUserClientID,
in: context
)
}

userClientsAPI.getSelfClients_MockValue = [
Scaffolding.selfUserClient
]

userClientsLocalStore.fetchOrCreateClientId_MockValue = (selfUserClient, false)
userClientsLocalStore.updateClientIdIsNewClientUserClientInfo_MockMethod = { _, _, _ in }
userClientsLocalStore.deletedSelfClientsNewClients_MockValue = [Scaffolding.userClientID]
userClientsLocalStore.deleteClientId_MockMethod = { _ in }

// When

try await sut.pullSelfClients()

// Then

XCTAssertEqual(userClientsLocalStore.fetchOrCreateClientId_Invocations.count, 1)
XCTAssertEqual(userClientsLocalStore.updateClientIdIsNewClientUserClientInfo_Invocations.count, 1)
XCTAssertEqual(userClientsLocalStore.deletedSelfClientsNewClients_Invocations.count, 1)
XCTAssertEqual(userClientsLocalStore.deleteClientId_Invocations.count, 1)
}

private enum Scaffolding {
static let userClientID = UUID.mockID1.uuidString
static let otherUserClientID = UUID.mockID2.uuidString

static let selfUserClient = WireAPI.SelfUserClient(
id: userClientID,
type: .permanent,
activationDate: .now,
label: "test",
model: "test",
deviceClass: .phone,
capabilities: []
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol {
proteusProvider: ProteusProviding,
mlsService: MLSServiceInterface,
coreCryptoProvider: CoreCryptoProviderProtocol,
pullSelfUserClientsFactory: @escaping PullSelfUserClientsFactory,
searchUsersCache: SearchUsersCache?
) {

Expand All @@ -71,6 +72,7 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol {
proteusProvider: proteusProvider,
mlsService: mlsService,
coreCryptoProvider: coreCryptoProvider,
pullSelfUserClientsFactory: pullSelfUserClientsFactory,
searchUsersCache: searchUsersCache
)

Expand Down Expand Up @@ -110,6 +112,7 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol {
proteusProvider: ProteusProviding,
mlsService: MLSServiceInterface,
coreCryptoProvider: CoreCryptoProviderProtocol,
pullSelfUserClientsFactory: @escaping PullSelfUserClientsFactory,
searchUsersCache: SearchUsersCache?
) -> [Any] {
let syncMOC = contextProvider.syncContext
Expand Down Expand Up @@ -382,7 +385,8 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol {
clientUpdateStatus: applicationStatusDirectory.clientUpdateStatus,
resolveOneOnOneConversations: makeResolveOneOnOneConversationsUseCase(
context: syncMOC,
resolver: oneOnOneResolver
resolver: oneOnOneResolver,
pullSelfUserClientsFactory: pullSelfUserClientsFactory
)
),
ResetSessionRequestStrategy(
Expand Down Expand Up @@ -417,12 +421,15 @@ public class StrategyDirectory: NSObject, StrategyDirectoryProtocol {

private static func makeResolveOneOnOneConversationsUseCase(
context: NSManagedObjectContext,
resolver: any OneOnOneResolverInterface
resolver: any OneOnOneResolverInterface,
pullSelfUserClientsFactory: @escaping PullSelfUserClientsFactory
) -> any ResolveOneOnOneConversationsUseCaseProtocol {

ResolveOneOnOneConversationsUseCase(
context: context,
supportedProtocolService: SupportedProtocolsService(context: context),
resolver: resolver
resolver: resolver,
pullSelfUserClientsFactory: pullSelfUserClientsFactory
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
//

import Foundation
import WireDomain
import WireLogging

// sourcery: AutoMockable
public protocol ResolveOneOnOneConversationsUseCaseProtocol {
Expand All @@ -25,20 +27,22 @@ public protocol ResolveOneOnOneConversationsUseCaseProtocol {

}

typealias PullSelfUserClientsFactory = (NSManagedObjectContext) -> PullSelfUserClientsProtocol

struct ResolveOneOnOneConversationsUseCase: ResolveOneOnOneConversationsUseCaseProtocol {

let context: NSManagedObjectContext
let supportedProtocolService: any SupportedProtocolsServiceInterface
let resolver: any OneOnOneResolverInterface
let pullSelfUserClientsFactory: PullSelfUserClientsFactory

func invoke() async throws {
let (oldProtocols, newProtocols) = await context.perform {
let oldProtocols = await context.perform {
let selfUser = ZMUser.selfUser(in: context)
let oldProtocols = selfUser.supportedProtocols
let newProtocols = supportedProtocolService.calculateSupportedProtocols()
return (oldProtocols, newProtocols)
return selfUser.supportedProtocols
}

let newProtocols = await calculateSupportedProtocols()
if oldProtocols != newProtocols {
var action = PushSupportedProtocolsAction(supportedProtocols: newProtocols)
try await action.perform(in: context.notificationContext)
Expand All @@ -53,4 +57,15 @@ struct ResolveOneOnOneConversationsUseCase: ResolveOneOnOneConversationsUseCaseP
try await resolver.resolveAllOneOnOneConversations(in: context)
}
}

private func calculateSupportedProtocols() async -> Set<WireDataModel.MessageProtocol> {
// we need the self clients to be up to date before calculating supported protocols
let pullSelfUserClients = pullSelfUserClientsFactory(context)
do {
try await pullSelfUserClients.pullSelfClients()
} catch {
WireLogger.userClient.error("error syncing selfclients: \(error.localizedDescription)")
}
return await context.perform { supportedProtocolService.calculateSupportedProtocols() }
}
}
Loading

0 comments on commit 480e9f2

Please sign in to comment.