diff --git a/GliaWidgets.xcodeproj/project.pbxproj b/GliaWidgets.xcodeproj/project.pbxproj index 29ddbaf51..fd84eb5f7 100644 --- a/GliaWidgets.xcodeproj/project.pbxproj +++ b/GliaWidgets.xcodeproj/project.pbxproj @@ -165,12 +165,13 @@ 1AFB1E6825F7AE3C00CA460D /* ChatAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AFB1E6725F7AE3C00CA460D /* ChatAttachment.swift */; }; 1AFB1E7425F8B00B00CA460D /* ChatTextContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AFB1E7325F8B00B00CA460D /* ChatTextContentView.swift */; }; 1AFB1E7825F8B26800CA460D /* ChatTextContentStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AFB1E7725F8B26800CA460D /* ChatTextContentStyle.swift */; }; - 2100B4802CB6B5A400AC7527 /* LockIsolated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B47F2CB6B5A400AC7527 /* LockIsolated.swift */; }; 2100B47C2CB66B6500AC7527 /* EntryWidget.Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B47B2CB66B6500AC7527 /* EntryWidget.Environment.swift */; }; - 2100B47E2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B47D2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift */; }; 2100B4802CB6B5A400AC7527 /* LockIsolated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B47F2CB6B5A400AC7527 /* LockIsolated.swift */; }; 2100B4842CB8143400AC7527 /* QueuesMonitor.Failing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B4832CB8143400AC7527 /* QueuesMonitor.Failing.swift */; }; 2100B4872CB91E7B00AC7527 /* CancelBag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B4862CB91E7B00AC7527 /* CancelBag.swift */; }; + 210150742CC13E8900294BA4 /* QueuesMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 210150732CC13E8900294BA4 /* QueuesMonitorTests.swift */; }; + 210150782CC14E8200294BA4 /* QueuesMonitor.Environment.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 210150752CC140A600294BA4 /* QueuesMonitor.Environment.Mock.swift */; }; + 210150792CC14E8200294BA4 /* QueuesMonitor.Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2100B47D2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift */; }; 215A25902CA44D8A0013023E /* Glia+EngagementLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215A258F2CA44D8A0013023E /* Glia+EngagementLauncher.swift */; }; 215A25932CA44D900013023E /* EngagementLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215A25912CA44D900013023E /* EngagementLauncher.swift */; }; 215A25982CABC7DF0013023E /* EngagementLauncherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 215A25972CABC7DF0013023E /* EngagementLauncherTests.swift */; }; @@ -1214,12 +1215,13 @@ 1AFB1E6725F7AE3C00CA460D /* ChatAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatAttachment.swift; sourceTree = ""; }; 1AFB1E7325F8B00B00CA460D /* ChatTextContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextContentView.swift; sourceTree = ""; }; 1AFB1E7725F8B26800CA460D /* ChatTextContentStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTextContentStyle.swift; sourceTree = ""; }; - 2100B47F2CB6B5A400AC7527 /* LockIsolated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockIsolated.swift; sourceTree = ""; }; 2100B47B2CB66B6500AC7527 /* EntryWidget.Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryWidget.Environment.swift; sourceTree = ""; }; 2100B47D2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuesMonitor.Mock.swift; sourceTree = ""; }; 2100B47F2CB6B5A400AC7527 /* LockIsolated.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockIsolated.swift; sourceTree = ""; }; 2100B4832CB8143400AC7527 /* QueuesMonitor.Failing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuesMonitor.Failing.swift; sourceTree = ""; }; 2100B4862CB91E7B00AC7527 /* CancelBag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelBag.swift; sourceTree = ""; }; + 210150732CC13E8900294BA4 /* QueuesMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuesMonitorTests.swift; sourceTree = ""; }; + 210150752CC140A600294BA4 /* QueuesMonitor.Environment.Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueuesMonitor.Environment.Mock.swift; sourceTree = ""; }; 215A258F2CA44D8A0013023E /* Glia+EngagementLauncher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Glia+EngagementLauncher.swift"; sourceTree = ""; }; 215A25912CA44D900013023E /* EngagementLauncher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EngagementLauncher.swift; sourceTree = ""; }; 215A25972CABC7DF0013023E /* EngagementLauncherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EngagementLauncherTests.swift; sourceTree = ""; }; @@ -3242,6 +3244,14 @@ path = Utilities; sourceTree = ""; }; + 210150722CC13E7300294BA4 /* QueuesMonitor */ = { + isa = PBXGroup; + children = ( + 210150732CC13E8900294BA4 /* QueuesMonitorTests.swift */, + ); + path = QueuesMonitor; + sourceTree = ""; + }; 215A25922CA44D900013023E /* EngagementLauncher */ = { isa = PBXGroup; children = ( @@ -3263,6 +3273,7 @@ children = ( 2198B7AB2CAEB14D002C442B /* QueuesMonitor.Live.swift */, 2100B47D2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift */, + 210150752CC140A600294BA4 /* QueuesMonitor.Environment.Mock.swift */, 2198B7AD2CB035A6002C442B /* QueuesMonitor.Environment.swift */, ); path = QueuesMonitor; @@ -3538,6 +3549,7 @@ 7512A57827BF9FB800319DF1 /* Sources */ = { isa = PBXGroup; children = ( + 210150722CC13E7300294BA4 /* QueuesMonitor */, 215A25962CABC7C50013023E /* EngagementLauncher */, C0D6C9FE2C106A0D00D4709B /* AlertManager */, 8418AD792BFB68C9007DE207 /* OnHoldOverlayVisualEffectView */, @@ -5781,10 +5793,10 @@ C0F3DE3B2C6E0DD900DE6D7B /* EntryWidgetView.swift in Sources */, C090474C2B7E210C003C437C /* FileUploadStyle.Equatable.swift in Sources */, C09047402B7E1FBC003C437C /* MessageCenterFileUploadStyle.swift in Sources */, - 2100B47E2CB6A37A00AC7527 /* QueuesMonitor.Mock.swift in Sources */, 9A8130BB27D7A41000220BBD /* FileUpload.Environment.Interface.swift in Sources */, C09046AB2B7D0967003C437C /* WelcomeStyle.SendButton.DisabledStyle.Accessibility.swift in Sources */, 84520BE72B1769AB00F97617 /* ChatViewController.Environment.swift in Sources */, + 210150792CC14E8200294BA4 /* QueuesMonitor.Mock.swift in Sources */, 3100D92B296E946600DEC9CE /* SecureConversations.ConfirmationStyle.swift in Sources */, C090477C2B7E27F1003C437C /* EngagementStyle.Equatable.swift in Sources */, 8491AF132A7ACC5400CC3E72 /* Theme.OperatorChatMessageStyle.swift in Sources */, @@ -5849,6 +5861,7 @@ 7594093F298D376B008B173A /* RemoteConfiguration+Chat.swift in Sources */, C09047232B7E1B7F003C437C /* GvaGalleryCardStyle.TextStyle.RemoteConfig.swift in Sources */, 9A3E1D9427B6C29C005634EB /* ChatEngagementFile.Mock.swift in Sources */, + 210150782CC14E8200294BA4 /* QueuesMonitor.Environment.Mock.swift in Sources */, C09046D22B7E0843003C437C /* BadgeStyle.RemoteConfig.swift in Sources */, 1A2DA73B25EFC00500032611 /* FileUploadListStyle.swift in Sources */, C0D6CA632C19C59100D4709B /* Glia.OpaqueAuthentication.Environment.swift in Sources */, @@ -6551,6 +6564,7 @@ 846A5C4529F6BEFA0049B29F /* GliaTests+StartEngagement.swift in Sources */, 7512A57A27BF9FCD00319DF1 /* ChatViewModelTests.swift in Sources */, 84602A792AEAB7CA0031E606 /* Survey.Mock.swift in Sources */, + 210150742CC13E8900294BA4 /* QueuesMonitorTests.swift in Sources */, 31CCE3E42BCE8F3A00F92535 /* CallVisualizerCoordinatorTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Environment.Mock.swift b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Environment.Mock.swift new file mode 100644 index 000000000..d12dc4edf --- /dev/null +++ b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Environment.Mock.swift @@ -0,0 +1,9 @@ +#if DEBUG +extension QueuesMonitor.Environment { + static let mock: Self = .init( + listQueues: { _ in }, + subscribeForQueuesUpdates: { _, _ in UUID().uuidString }, + unsubscribeFromUpdates: { _, _ in } + ) +} +#endif diff --git a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Live.swift b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Live.swift index 863d52a9d..23f2ec7d2 100644 --- a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Live.swift +++ b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Live.swift @@ -2,7 +2,6 @@ import Foundation import GliaCoreSDK import Combine -// TODO: Add unit tests in context of MOB-3669 final class QueuesMonitor { enum State { case idle @@ -12,7 +11,8 @@ final class QueuesMonitor { @Published private(set) var state: State = .idle - private let environment: Environment + /// Declared as internal var for unit tests purposes + var environment: Environment private var subscriptionId: String? { _subscriptionId.value } diff --git a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Mock.swift b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Mock.swift index 447ce766b..9f7b937dd 100644 --- a/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Mock.swift +++ b/GliaWidgets/Sources/QueuesMonitor/QueuesMonitor.Mock.swift @@ -1,13 +1,5 @@ #if DEBUG -import Foundation - extension QueuesMonitor { - static let mock = QueuesMonitor( - environment: .init( - listQueues: { _ in }, - subscribeForQueuesUpdates: { _, _ in UUID().uuidString }, - unsubscribeFromUpdates: { _, _ in } - ) - ) + static let mock = QueuesMonitor(environment: .mock) } #endif diff --git a/GliaWidgetsTests/Sources/QueuesMonitor/QueuesMonitorTests.swift b/GliaWidgetsTests/Sources/QueuesMonitor/QueuesMonitorTests.swift new file mode 100644 index 000000000..c08b63379 --- /dev/null +++ b/GliaWidgetsTests/Sources/QueuesMonitor/QueuesMonitorTests.swift @@ -0,0 +1,255 @@ +import XCTest +import Combine +import GliaCoreSDK + +@testable import GliaWidgets + +class QueuesMonitorTests: XCTestCase { + private enum Call { + case listQueues + case subscribeForQueuesUpdates + case unsubscribeFromUpdates + } + + private var monitor: QueuesMonitor! + private var cancellables: CancelBag! + + override func setUp() { + super.setUp() + cancellables = .init() + + monitor = QueuesMonitor(environment: .mock) + } + + override func tearDown() { + monitor = nil + super.tearDown() + } + + // MARK: Start monitoring + func test_startMonitoringWithQueuesUpdatesWithQueuesList() { + var envCalls: [Call] = [] + + let mockQueueId = UUID().uuidString + let expectedObservedQueues = [Queue.mock(id: mockQueueId)] + let mockQueues = [expectedObservedQueues[0], Queue.mock(id: UUID().uuidString)] + + monitor.environment.listQueues = { completion in + envCalls.append(.listQueues) + completion(mockQueues, nil) + } + monitor.environment.subscribeForQueuesUpdates = { _, completion in + envCalls.append(.subscribeForQueuesUpdates) + completion(.success(expectedObservedQueues[0])) + return UUID().uuidString + } + + var receivedQueues: [Queue]? + monitor.$state + .sink { state in + if case let .updated(queues) = state { + receivedQueues = queues + } + } + .store(in: &cancellables) + + monitor.startMonitoring(queuesIds: [mockQueueId]) + + XCTAssertEqual(receivedQueues, expectedObservedQueues) + XCTAssertEqual(envCalls, [.listQueues, .subscribeForQueuesUpdates]) + } + + func test_startMonitoringWithWrongQueueIdsReturnsUpdatesWithDefaultQueue() { + var envCalls: [Call] = [] + + let expectedObservedQueues = [Queue.mock(isDefault: true)] + let mockQueues = [expectedObservedQueues[0], Queue.mock()] + + monitor.environment.listQueues = { completion in + envCalls.append(.listQueues) + completion(mockQueues, nil) + } + monitor.environment.subscribeForQueuesUpdates = { _, completion in + envCalls.append(.subscribeForQueuesUpdates) + return UUID().uuidString + } + + var receivedQueues: [Queue]? + monitor.$state + .sink { state in + if case let .updated(queues) = state { + receivedQueues = queues + } + } + .store(in: &cancellables) + + monitor.startMonitoring(queuesIds: ["1"]) + + XCTAssertEqual(receivedQueues, expectedObservedQueues) + XCTAssertEqual(envCalls, [.listQueues, .subscribeForQueuesUpdates]) + } + + func test_startMonitoringListQueuesReturnsError() { + var envCalls: [Call] = [] + + let expectedError = CoreSdkClient.SalemoveError.mock() + + monitor.environment.listQueues = { completion in + envCalls.append(.listQueues) + completion(nil, expectedError) + } + monitor.environment.subscribeForQueuesUpdates = { _, completion in + envCalls.append(.subscribeForQueuesUpdates) + return UUID().uuidString + } + + var receivedError: CoreSdkClient.SalemoveError? + monitor.$state + .sink { state in + if case let .failed(error as CoreSdkClient.SalemoveError) = state { + receivedError = error + } + } + .store(in: &cancellables) + + monitor.startMonitoring(queuesIds: ["1"]) + + XCTAssertEqual(receivedError, expectedError) + XCTAssertEqual(envCalls, [.listQueues]) + } + + func test_startMonitoringWithQueuesAndReceiveUpdatedQueueSuccess() { + var envCalls: [Call] = [] + + let mockQueueId = UUID().uuidString + let expectedObservedQueue = Queue.mock(id: mockQueueId, status: .closed) + let expectedUpdatedQueue = Queue.mock(id: mockQueueId, status: .open) + let mockQueues = [expectedObservedQueue, Queue.mock(id: UUID().uuidString)] + + monitor.environment.listQueues = { completion in + envCalls.append(.listQueues) + completion(mockQueues, nil) + } + monitor.environment.subscribeForQueuesUpdates = { _, completion in + envCalls.append(.subscribeForQueuesUpdates) + completion(.success(expectedUpdatedQueue)) + return UUID().uuidString + } + + var receivedQueues: [Queue]? + var receivedUpdatedQueue: Queue? + monitor.$state + // Drop initial .idle and .updated with listed queues state update + .dropFirst(2) + .sink { state in + print(state) + if case let .updated(queues) = state { + receivedQueues = queues + receivedUpdatedQueue = queues[0] + } + } + .store(in: &cancellables) + + monitor.startMonitoring(queuesIds: [mockQueueId]) + + XCTAssertEqual(receivedQueues, [expectedUpdatedQueue]) + XCTAssertEqual(receivedUpdatedQueue?.state.status, .open) + XCTAssertEqual(envCalls, [.listQueues, .subscribeForQueuesUpdates]) + } + + func test_startMonitoringWithQueuesAndReceiveUpdatedQueueError() { + var envCalls: [Call] = [] + + let expectedError = CoreSdkClient.SalemoveError.mock() + let mockQueues = [Queue.mock()] + + monitor.environment.listQueues = { completion in + envCalls.append(.listQueues) + completion(mockQueues, nil) + } + monitor.environment.subscribeForQueuesUpdates = { _, completion in + envCalls.append(.subscribeForQueuesUpdates) + completion(.failure(expectedError)) + return nil + } + + var receivedError: CoreSdkClient.SalemoveError? + monitor.$state + // Drop initial .idle and .updated with listed queues state update + .dropFirst(2) + .sink { state in + print(state) + if case let .failed(error as CoreSdkClient.SalemoveError) = state { + receivedError = error + } + } + .store(in: &cancellables) + + monitor.startMonitoring(queuesIds: [UUID().uuidString]) + + XCTAssertEqual(receivedError, expectedError) + XCTAssertEqual(envCalls, [.listQueues, .subscribeForQueuesUpdates]) + } + + // MARK: Stop monitoring + func test_stopMonitoringReturnError() { + var envCalls: [Call] = [] + + let expectedError = CoreSdkClient.SalemoveError.mock() + + monitor.environment.listQueues = { completion in + envCalls.append(.listQueues) + completion([], nil) + } + monitor.environment.subscribeForQueuesUpdates = { _, completion in + envCalls.append(.subscribeForQueuesUpdates) + completion(.success(.mock())) + return UUID().uuidString + } + monitor.environment.unsubscribeFromUpdates = { subscriptionId, completion in + envCalls.append(.unsubscribeFromUpdates) + completion(expectedError) + } + + monitor.startMonitoring(queuesIds: ["1"]) + + var receivedError: CoreSdkClient.SalemoveError? + monitor.$state + .dropFirst() + .sink { state in + if case .failed(let error as CoreSdkClient.SalemoveError) = state { + receivedError = error + } + } + .store(in: &cancellables) + + monitor.stopMonitoring() + + XCTAssertEqual(receivedError, expectedError) + XCTAssertEqual(envCalls, [.listQueues, .subscribeForQueuesUpdates, .unsubscribeFromUpdates]) + } + + func test_stopMonitoringSuccess() { + var envCalls: [Call] = [] + + monitor.environment.listQueues = { completion in + envCalls.append(.listQueues) + completion([], nil) + } + monitor.environment.subscribeForQueuesUpdates = { _, completion in + envCalls.append(.subscribeForQueuesUpdates) + completion(.success(.mock())) + return UUID().uuidString + } + monitor.environment.unsubscribeFromUpdates = { subscriptionId, completion in + envCalls.append(.unsubscribeFromUpdates) + completion(.mock()) + } + + monitor.startMonitoring(queuesIds: ["1"]) + + monitor.stopMonitoring() + + XCTAssertEqual(envCalls, [.listQueues, .subscribeForQueuesUpdates, .unsubscribeFromUpdates]) + } +}