From 76c87b1cc404d77f8210497bf91852e9128a1adc Mon Sep 17 00:00:00 2001 From: Igor Kravchenko Date: Wed, 11 Dec 2024 13:55:25 +0200 Subject: [PATCH] Create PendingInteraction model after configuration Make sure PendingInteraction model is created after configuration to prevent subscription for unread message count and pending SC status from failure, because 'pubsub' is created only when Core SDK is configured. MOB-3871 --- .../Public/Glia/Glia+EngagementSetup.swift | 2 +- .../Public/Glia/Glia+EntryWidget.swift | 2 +- GliaWidgets/Public/Glia/Glia.swift | 19 +++++-- GliaWidgets/Public/GliaError.swift | 3 ++ ...cureConversations.PendingInteraction.swift | 48 ++++++++++++++---- ...ersations.PendingInteraction.Failing.swift | 4 +- ...onversations.PendingInteractionTests.swift | 8 +-- GliaWidgetsTests/Sources/Glia/GliaTests.swift | 50 ++++++++++++++++--- 8 files changed, 106 insertions(+), 30 deletions(-) diff --git a/GliaWidgets/Public/Glia/Glia+EngagementSetup.swift b/GliaWidgets/Public/Glia/Glia+EngagementSetup.swift index fae09f96b..94a403dfd 100644 --- a/GliaWidgets/Public/Glia/Glia+EngagementSetup.swift +++ b/GliaWidgets/Public/Glia/Glia+EngagementSetup.swift @@ -142,7 +142,7 @@ extension Glia { ) { let engagementLaunching: EngagementCoordinator.EngagementLaunching - switch (pendingInteraction.hasPendingInteraction, engagementKind) { + switch (pendingInteraction?.hasPendingInteraction ?? false, engagementKind) { case (false, _): // if there is no pending Secure Conversation, open regular flow. engagementLaunching = .direct(kind: engagementKind) diff --git a/GliaWidgets/Public/Glia/Glia+EntryWidget.swift b/GliaWidgets/Public/Glia/Glia+EntryWidget.swift index 99ab32470..0d59c9f70 100644 --- a/GliaWidgets/Public/Glia/Glia+EntryWidget.swift +++ b/GliaWidgets/Public/Glia/Glia+EntryWidget.swift @@ -26,7 +26,7 @@ extension Glia { isAuthenticated: environment.isAuthenticated, hasPendingInteraction: { [weak self] in guard let self else { return false } - return pendingInteraction.hasPendingInteraction + return pendingInteraction?.hasPendingInteraction ?? false } ) ) diff --git a/GliaWidgets/Public/Glia/Glia.swift b/GliaWidgets/Public/Glia/Glia.swift index 9229d4267..c5326e9dc 100644 --- a/GliaWidgets/Public/Glia/Glia.swift +++ b/GliaWidgets/Public/Glia/Glia.swift @@ -135,7 +135,7 @@ public class Glia { // // Currently it's used to know if we have to force a visitor to SecureMessaging screen, // once they try to start an engagement with media type other than `messaging`. - let pendingInteraction: SecureConversations.PendingInteraction + var pendingInteraction: SecureConversations.PendingInteraction? init(environment: Environment) { self.environment = environment @@ -179,7 +179,6 @@ public class Glia { viewFactory: viewFactory ) ) - pendingInteraction = .init(environment: .init(with: environment.coreSdk)) } /// Setup SDK using specific engagement configuration without starting the engagement. @@ -258,7 +257,21 @@ public class Glia { // Configuration completion handler has to be called in any case, // at the end of the scope, whether there's ongoing engagement or not. - defer { completion(.success(())) } + defer { + // PendingInteraction is essential part of SC flow, so it's not + // valid to consider SDK configured if PI is not created. + do { + pendingInteraction = try .init(environment: .init(with: environment.coreSdk)) + completion(.success(())) + } catch let error as SecureConversations.PendingInteraction.Error { + switch error { + case .subscriptionFailure: + completion(.failure(GliaError.internalEventSubscriptionFailure)) + } + } catch { + completion(.failure(GliaError.internalError)) + } + } guard let currentEngagement = self.environment.coreSdk.getCurrentEngagement() else { return } diff --git a/GliaWidgets/Public/GliaError.swift b/GliaWidgets/Public/GliaError.swift index 51af1d6aa..443c06e14 100644 --- a/GliaWidgets/Public/GliaError.swift +++ b/GliaWidgets/Public/GliaError.swift @@ -30,4 +30,7 @@ public enum GliaError: Error { /// Internal error. case internalError + + /// Internal event subscription failure. + case internalEventSubscriptionFailure } diff --git a/GliaWidgets/SecureConversations/SecureConversations.PendingInteraction.swift b/GliaWidgets/SecureConversations/SecureConversations.PendingInteraction.swift index de9051133..25acc2ae3 100644 --- a/GliaWidgets/SecureConversations/SecureConversations.PendingInteraction.swift +++ b/GliaWidgets/SecureConversations/SecureConversations.PendingInteraction.swift @@ -9,21 +9,34 @@ extension SecureConversations { private(set) var pendingStatusCancellationToken: String? private(set) var unreadMessageCountCancellationToken: String? - init(environment: Environment) { + init(environment: Environment) throws { self.environment = environment - self.pendingStatusCancellationToken = environment.observePendingSecureConversationsStatus { [weak self] result in + let pendingStatusCancellationToken = environment.observePendingSecureConversationsStatus { [weak self] result in guard let self else { return } // At this point it is enough to know if there is a pending conversation, // so no need to handle error. pendingStatus = (try? result.get()) ?? false } - self.unreadMessageCountCancellationToken = environment.observeSecureConversationsUnreadMessageCount { [weak self] result in + + guard pendingStatusCancellationToken != nil else { + throw Error.subscriptionFailure(.pendingStatus) + } + + self.pendingStatusCancellationToken = pendingStatusCancellationToken + + let unreadMessageCountCancellationToken = environment.observeSecureConversationsUnreadMessageCount { [weak self] result in guard let self else { return } // At this point it is enough to know if there is an unread message count, // so no need to handle error. unreadMessageCount = (try? result.get()) ?? 0 } + guard unreadMessageCountCancellationToken != nil else { + throw Error.subscriptionFailure(.unreadMessageCount) + } + + self.unreadMessageCountCancellationToken = unreadMessageCountCancellationToken + $pendingStatus.combineLatest($unreadMessageCount) .map { hasPending, unreadCount in hasPending || unreadCount > 0 @@ -52,6 +65,16 @@ extension SecureConversations.PendingInteraction { } } +extension SecureConversations.PendingInteraction { + enum Error: Swift.Error { + enum Subscription { + case unreadMessageCount + case pendingStatus + } + case subscriptionFailure(Subscription) + } +} + extension SecureConversations.PendingInteraction.Environment { init(with client: CoreSdkClient) { self.observePendingSecureConversationsStatus = client.observePendingSecureConversationStatus @@ -63,17 +86,20 @@ extension SecureConversations.PendingInteraction.Environment { #if DEBUG extension SecureConversations.PendingInteraction.Environment { - static let mock = Self( - observePendingSecureConversationsStatus: { _ in nil }, - observeSecureConversationsUnreadMessageCount: { _ in nil }, - unsubscribeFromUnreadCount: { _ in }, - unsubscribeFromPendingStatus: { _ in } - ) + static let mock: Self = { + let uuidGen = UUID.incrementing + return Self( + observePendingSecureConversationsStatus: { _ in uuidGen().uuidString }, + observeSecureConversationsUnreadMessageCount: { _ in uuidGen().uuidString }, + unsubscribeFromUnreadCount: { _ in }, + unsubscribeFromPendingStatus: { _ in } + ) + }() } extension SecureConversations.PendingInteraction { - static func mock(environment: Environment = .mock) -> Self { - .init(environment: environment) + static func mock(environment: Environment = .mock) throws -> Self { + try .init(environment: environment) } } #endif diff --git a/GliaWidgetsTests/SecureConversations/SecureConversations.PendingInteraction.Failing.swift b/GliaWidgetsTests/SecureConversations/SecureConversations.PendingInteraction.Failing.swift index b125e8798..59bbbdfb0 100644 --- a/GliaWidgetsTests/SecureConversations/SecureConversations.PendingInteraction.Failing.swift +++ b/GliaWidgetsTests/SecureConversations/SecureConversations.PendingInteraction.Failing.swift @@ -20,7 +20,7 @@ extension SecureConversations.PendingInteraction.Environment { } extension SecureConversations.PendingInteraction { - static func failing() -> Self { - .init(environment: .failing) + static func failing() throws -> Self { + try .init(environment: .failing) } } diff --git a/GliaWidgetsTests/SecureConversations/SecureConversations.PendingInteractionTests.swift b/GliaWidgetsTests/SecureConversations/SecureConversations.PendingInteractionTests.swift index ceedbab84..895418124 100644 --- a/GliaWidgetsTests/SecureConversations/SecureConversations.PendingInteractionTests.swift +++ b/GliaWidgetsTests/SecureConversations/SecureConversations.PendingInteractionTests.swift @@ -18,7 +18,7 @@ final class SecureConversationsPendingInteractionTests: XCTestCase { environment.unsubscribeFromPendingStatus = { _ in } environment.unsubscribeFromUnreadCount = { _ in } - let pendingInteraction = SecureConversations.PendingInteraction(environment: environment) + let pendingInteraction = try SecureConversations.PendingInteraction(environment: environment) // Assert initial pending interaction is false. XCTAssertFalse(pendingInteraction.hasPendingInteraction) // Affect pending secure conversations value by setting it to `true` and assert `hasPendingInteraction`, @@ -36,7 +36,7 @@ final class SecureConversationsPendingInteractionTests: XCTestCase { XCTAssertFalse(pendingInteraction.hasPendingInteraction) } - func test_unsubscribeIsCalledOnDeinit() { + func test_unsubscribeIsCalledOnDeinit() throws { enum Call { case unsubscribeFromPendingStatus case unsubscribeFromUnreadCount @@ -52,8 +52,8 @@ final class SecureConversationsPendingInteractionTests: XCTestCase { environment.unsubscribeFromUnreadCount = { _ in calls.append(.unsubscribeFromUnreadCount) } - var pendingInteraction = SecureConversations.PendingInteraction(environment: environment) - pendingInteraction = .mock() + var pendingInteraction = try SecureConversations.PendingInteraction(environment: environment) + pendingInteraction = try .mock() _ = pendingInteraction XCTAssertEqual(calls, [.unsubscribeFromUnreadCount, .unsubscribeFromPendingStatus]) } diff --git a/GliaWidgetsTests/Sources/Glia/GliaTests.swift b/GliaWidgetsTests/Sources/Glia/GliaTests.swift index 5b3b3d55b..86b868828 100644 --- a/GliaWidgetsTests/Sources/Glia/GliaTests.swift +++ b/GliaWidgetsTests/Sources/Glia/GliaTests.swift @@ -747,10 +747,12 @@ final class GliaTests: XCTestCase { XCTAssertEqual(messages, ["Initialize Glia Widgets SDK", "Setting Unified UI Config"]) } - func test_hasPendingInteractionIfPendingSecureConversationExists() { + func test_hasPendingInteractionIfPendingSecureConversationExists() throws { var gliaEnv = Glia.Environment.failing var logger = CoreSdkClient.Logger.failing let uuidGen = UUID.incrementing + logger.infoClosure = { _, _, _, _ in } + logger.prefixedClosure = { _ in logger } logger.configureLocalLogLevelClosure = { _ in } logger.configureRemoteLogLevelClosure = { _ in } gliaEnv.coreSdk.createLogger = { _ in logger } @@ -765,18 +767,28 @@ final class GliaTests: XCTestCase { } gliaEnv.coreSdk.unsubscribeFromPendingSecureConversationStatus = { _ in } gliaEnv.coreSdk.unsubscribeFromUnreadCount = { _ in } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion(.success(())) + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } let sdk = Glia(environment: gliaEnv) + try sdk.configure( + with: .mock(), + theme: .mock() + ) { _ in } - XCTAssertTrue(sdk.pendingInteraction.hasPendingInteraction) + XCTAssertTrue(try XCTUnwrap(sdk.pendingInteraction).hasPendingInteraction) } - func test_hasPendingInteractionIfUnreadMessagesExist() { + func test_hasPendingInteractionIfUnreadMessagesExist() throws { var gliaEnv = Glia.Environment.failing var logger = CoreSdkClient.Logger.failing let uuidGen = UUID.incrementing logger.configureLocalLogLevelClosure = { _ in } logger.configureRemoteLogLevelClosure = { _ in } + logger.infoClosure = { _, _, _, _ in } + logger.prefixedClosure = { _ in logger } gliaEnv.coreSdk.createLogger = { _ in logger } gliaEnv.conditionalCompilation.isDebug = { true } gliaEnv.coreSdk.subscribeForUnreadSCMessageCount = { callback in @@ -789,28 +801,50 @@ final class GliaTests: XCTestCase { } gliaEnv.coreSdk.unsubscribeFromPendingSecureConversationStatus = { _ in } gliaEnv.coreSdk.unsubscribeFromUnreadCount = { _ in } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion(.success(())) + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } let sdk = Glia(environment: gliaEnv) - XCTAssertTrue(sdk.pendingInteraction.hasPendingInteraction) + try sdk.configure( + with: .mock(), + theme: .mock() + ) { _ in } + + XCTAssertTrue(try XCTUnwrap(sdk.pendingInteraction).hasPendingInteraction) } - func test_hasPendingInteractionIfNoUnreadMessageAndPendingSecureConversationExist() { + func test_hasPendingInteractionIfNoUnreadMessageAndPendingSecureConversationExist() throws { + var uuidGen = UUID.incrementing var gliaEnv = Glia.Environment.failing var logger = CoreSdkClient.Logger.failing logger.configureLocalLogLevelClosure = { _ in } logger.configureRemoteLogLevelClosure = { _ in } + logger.configureRemoteLogLevelClosure = { _ in } + logger.infoClosure = { _, _, _, _ in } + logger.prefixedClosure = { _ in logger } gliaEnv.coreSdk.createLogger = { _ in logger } gliaEnv.coreSdk.pendingSecureConversationStatus = { $0(.success(false)) } gliaEnv.coreSdk.getSecureUnreadMessageCount = { $0(.success(0)) } gliaEnv.conditionalCompilation.isDebug = { true } - gliaEnv.coreSdk.subscribeForUnreadSCMessageCount = { _ in nil } - gliaEnv.coreSdk.observePendingSecureConversationStatus = { _ in nil } + gliaEnv.coreSdk.subscribeForUnreadSCMessageCount = { _ in uuidGen().uuidString } + gliaEnv.coreSdk.observePendingSecureConversationStatus = { _ in uuidGen().uuidString } gliaEnv.coreSdk.unsubscribeFromPendingSecureConversationStatus = { _ in } gliaEnv.coreSdk.unsubscribeFromUnreadCount = { _ in } + gliaEnv.coreSDKConfigurator.configureWithConfiguration = { _, completion in + completion(.success(())) + } + gliaEnv.coreSDKConfigurator.configureWithInteractor = { _ in } let sdk = Glia(environment: gliaEnv) - XCTAssertFalse(sdk.pendingInteraction.hasPendingInteraction) + try sdk.configure( + with: .mock(), + theme: .mock() + ) { _ in } + + XCTAssertFalse(try XCTUnwrap(sdk.pendingInteraction).hasPendingInteraction) } }