Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: calculate supported protocols - WPB-15297 #2384

Merged
merged 11 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
90 changes: 90 additions & 0 deletions WireDomain/Sources/WireDomain/UseCases/PullSelfUserClients.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// 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

// sourcery: AutoMockable
public protocol PullSelfUserClientsProtocol {

func pullSelfClients() async throws
}
netbe marked this conversation as resolved.
Show resolved Hide resolved

/// 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)
}
}

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
netbe marked this conversation as resolved.
Show resolved Hide resolved

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
Loading