From 067b3dd2756f68f3f426e84d7bc3e60e485ec677 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Mon, 4 Mar 2024 13:41:44 +0100 Subject: [PATCH 01/15] WIP --- PubNub.xcodeproj/project.pbxproj | 12 + .../DependencyContainer.swift | 271 ++++++++++++++++++ Sources/PubNub/Networking/HTTPSession.swift | 52 ++-- .../Networking/Routers/PresenceRouter.swift | 12 +- .../Networking/Routers/SubscribeRouter.swift | 12 +- Sources/PubNub/PubNub.swift | 76 +---- 6 files changed, 341 insertions(+), 94 deletions(-) create mode 100644 Sources/PubNub/DependencyContainer/DependencyContainer.swift diff --git a/PubNub.xcodeproj/project.pbxproj b/PubNub.xcodeproj/project.pbxproj index 431548aa..ff2cafa4 100644 --- a/PubNub.xcodeproj/project.pbxproj +++ b/PubNub.xcodeproj/project.pbxproj @@ -428,6 +428,7 @@ 3D758DD22AB0A91C005D2B36 /* AESCBCCryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DD12AB0A91C005D2B36 /* AESCBCCryptor.swift */; }; 3D758DD52AB48A6A005D2B36 /* CryptorHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DD32AB48A6A005D2B36 /* CryptorHeader.swift */; }; 3D758DD62AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D758DD42AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift */; }; + 3D8BAC102B8C96D70059A5C3 /* DependencyContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D8BAC0F2B8C96D70059A5C3 /* DependencyContainer.swift */; }; 3D9134972A1216F7000A5124 /* PubNubPushTargetTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D9134962A1216F7000A5124 /* PubNubPushTargetTests.swift */; }; 3DACC7F72AB88F8E00210B14 /* Data+CommonCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DACC7F62AB88F8E00210B14 /* Data+CommonCrypto.swift */; }; 3DB9255C2B7A2B89001B7E90 /* SubscriptionStreamTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DB925592B7A2B89001B7E90 /* SubscriptionStreamTests.swift */; }; @@ -1021,6 +1022,7 @@ 3D758DD12AB0A91C005D2B36 /* AESCBCCryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AESCBCCryptor.swift; sourceTree = ""; }; 3D758DD32AB48A6A005D2B36 /* CryptorHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptorHeader.swift; sourceTree = ""; }; 3D758DD42AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CryptorHeaderWithinStreamFinder.swift; sourceTree = ""; }; + 3D8BAC0F2B8C96D70059A5C3 /* DependencyContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyContainer.swift; sourceTree = ""; }; 3D9134962A1216F7000A5124 /* PubNubPushTargetTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubPushTargetTests.swift; sourceTree = ""; }; 3DACC7F62AB88F8E00210B14 /* Data+CommonCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+CommonCrypto.swift"; sourceTree = ""; }; 3DB925592B7A2B89001B7E90 /* SubscriptionStreamTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionStreamTests.swift; sourceTree = ""; }; @@ -2199,6 +2201,14 @@ path = Header; sourceTree = ""; }; + 3D8BAC0E2B8C96A40059A5C3 /* DependencyContainer */ = { + isa = PBXGroup; + children = ( + 3D8BAC0F2B8C96D70059A5C3 /* DependencyContainer.swift */, + ); + path = DependencyContainer; + sourceTree = ""; + }; 3D9134952A12161A000A5124 /* Push */ = { isa = PBXGroup; children = ( @@ -2511,6 +2521,7 @@ children = ( OBJ_11 /* PubNub.swift */, 359152A022BA9AA30048842D /* PubNubConfiguration.swift */, + 3D8BAC0E2B8C96A40059A5C3 /* DependencyContainer */, 3D389FC12B35AF4A006928E7 /* EventEngine */, 35B0ACE4252BE37C00537A18 /* APIs */, 35DB0C49287475F9001E1F76 /* Core */, @@ -3554,6 +3565,7 @@ 3559978C230A02B7000BCFD1 /* PubNubLogger.swift in Sources */, 35AE6A3224FD6CEE00BBFA37 /* FileManagementRouter.swift in Sources */, 3D389FF32B35AF4A006928E7 /* WaitEffect.swift in Sources */, + 3D8BAC102B8C96D70059A5C3 /* DependencyContainer.swift in Sources */, 35089A0B22E56F1F002BCC94 /* Constants.swift in Sources */, 358C6421238C6787009CE354 /* PubNubPushMessage.swift in Sources */, 3DD1FB992B5A7804005A14E3 /* PubNubPresenceStateContainer.swift in Sources */, diff --git a/Sources/PubNub/DependencyContainer/DependencyContainer.swift b/Sources/PubNub/DependencyContainer/DependencyContainer.swift new file mode 100644 index 00000000..099586a0 --- /dev/null +++ b/Sources/PubNub/DependencyContainer/DependencyContainer.swift @@ -0,0 +1,271 @@ +// +// DependencyContainer.swift +// +// Copyright (c) PubNub Inc. +// All rights reserved. +// +// This source code is licensed under the license found in the +// LICENSE file in the root directory of this source tree. +// + +import Foundation + +// A protocol that represents a unique key for each dependency. Each type conforming to DependencyKey +// represents a distinct dependency. +protocol DependencyKey { + // A value associated with a given `DependencyKey` + associatedtype Value + // Creates a value of the type Value for a given `DependencyKey` if no existing dependency + // is found in the `DependencyContainer`. Note that `container` parameter is used in case of + // nested dependencies, i.e., when the dependency being created depends on other objects in the `DependencyContainer`. + static func value(from container: DependencyContainer) -> Value +} + +// The class that serves as a registry for dependencies. Each dependency is associated with a unique key +// conforming to the `DependencyKey` protocol. +class DependencyContainer { + private var values: [ObjectIdentifier: Any] = [:] + + init(instanceID: UUID, configuration: PubNubConfiguration) { + self[PubNubConfigurationDependencyKey.self] = configuration + self[PubNubInstanceIDDependencyKey.self] = instanceID + } + + subscript(key: K.Type) -> K.Value where K: DependencyKey { + get { + if let existingValue = values[ObjectIdentifier(key)] { + return existingValue as! K.Value + } + let value = key.value(from: self) + values[ObjectIdentifier(key)] = value + return value + } set { + values[ObjectIdentifier(key)] = newValue + } + } + + func register(value: K.Value?, forKey key: K.Type) where K: DependencyKey { + if let value { + values[ObjectIdentifier(key)] = value + } + } +} + +extension DependencyContainer { + var configuration: PubNubConfiguration { + self[PubNubConfigurationDependencyKey.self] + } + + var instanceID: UUID { + self[PubNubInstanceIDDependencyKey.self] + } + + var automaticRetry: RequestOperator? { + configuration.automaticRetry + } + + var instanceIDOperator: RequestOperator? { + configuration.useInstanceId ? InstanceIdOperator(instanceID: instanceID.uuidString) : nil + } + + var presenceStateContainer: PubNubPresenceStateContainer { + self[PresenceStateContainerDependencyKey.self] + } + + var httpSession: SessionReplaceable { + resolveSession( + session: self[HTTPSessionDependencyKey.self], + with: [instanceIDOperator].compactMap { $0 } + ) + } + + var httpSubscribeSession: SessionReplaceable { + resolveSession( + session: self[HTTPSubscribeSessionDependencyKey.self], + with: [instanceIDOperator].compactMap { $0 } + ) + } + + var httpPresenceSession: SessionReplaceable { + resolveSession( + session: self[HTTPPresenceSessionDependencyKey.self], + with: [instanceIDOperator].compactMap { $0 } + ) + } + + var httpSubscribeSessionQueue: DispatchQueue { + self[HTTPSubscribeSessionQueueDependencyKey.self] + } + + var fileURLSession: URLSessionReplaceable { + self[FileURLSessionDependencyKey.self] + } + + var subscribeEventEngine: SubscribeEngine { + self[SubscribeEventEngineDependencyKey.self] + } + + var subscribeEffectFactory: SubscribeEffectFactory { + self[SubscribeEffectFactoryDependencyKey.self] + } + + private func resolveSession(session: SessionReplaceable, with operators: [RequestOperator?]) -> SessionReplaceable { + session.defaultRequestOperator == nil ? session.usingDefault(requestOperator: MultiplexRequestOperator( + operators: operators.compactMap { $0 } + )) : session.usingDefault(requestOperator: session.defaultRequestOperator?.merge( + operators: operators.compactMap { $0 }) + ) + } +} + +// - MARK: PubNubConfiguration + +struct PubNubConfigurationDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> PubNubConfiguration { + container.configuration + } +} + +struct PubNubInstanceIDDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> UUID { + container.instanceID + } +} + +// MARK: - HTTPSession + +struct HTTPSessionDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> SessionReplaceable { + HTTPSession(configuration: .pubnub) + } +} + +struct HTTPSubscribeSessionDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> SessionReplaceable { + HTTPSession( + configuration: .subscription, + sessionQueue: container.httpSubscribeSessionQueue, + sessionStream: SessionListener(queue: container.httpSubscribeSessionQueue) + ) + } +} + +struct HTTPSubscribeSessionQueueDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> DispatchQueue { + DispatchQueue(label: "Subscribe Response Queue") + } +} + +struct HTTPPresenceSessionDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> HTTPSession { + HTTPSession( + configuration: .pubnub, + sessionQueue: container.httpSubscribeSessionQueue, + sessionStream: SessionListener(queue: container.httpSubscribeSessionQueue) + ) + } +} + +// MARK: - FileURLSession + +struct FileURLSessionDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> URLSessionReplaceable { + URLSession( + configuration: .pubnubBackground, + delegate: FileSessionManager(), + delegateQueue: .main + ) + } +} + +// MARK: - PresenceStateContainer + +struct PresenceStateContainerDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> PubNubPresenceStateContainer { + PubNubPresenceStateContainer.shared + } +} + +// MARK: SubscribeEventEngine + +struct SubscribeEventEngineDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> SubscribeEngine { + SubscribeEngine( + state: Subscribe.UnsubscribedState(), + transition: SubscribeTransition(), + dispatcher: EffectDispatcher(factory: container.subscribeEffectFactory), + dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: container.configuration)) + ) + } +} + +struct SubscribeEffectFactoryDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> SubscribeEffectFactory { + SubscribeEffectFactory( + session: container.httpSubscribeSession, + presenceStateContainer: container.presenceStateContainer + ) + } +} + +// MARK: PresenceEventEngine + +struct PresenceEffectFactoryDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> PresenceEffectFactory { + PresenceEffectFactory( + session: container.httpPresenceSession, + presenceStateContainer: container.presenceStateContainer + ) + } +} + +struct PresenceEventEngineDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> PresenceEngine { + PresenceEngine( + state: Presence.HeartbeatInactive(), + transition: PresenceTransition(configuration: container.configuration), + dispatcher: EffectDispatcher(factory: container.presenceEffectFactory), + dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: container.configuration)) + ) + } +} + +extension DependencyContainer { + var presenceEffectFactory: PresenceEffectFactory { + self[PresenceEffectFactoryDependencyKey.self] + } + var presenceEngine: PresenceEngine { + self[PresenceEventEngineDependencyKey.self] + } +} + +// MARK: - SubscriptionSession + +struct SubscriptionSessionDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> SubscriptionSession { + if container.configuration.enableEventEngine { + return SubscriptionSession( + strategy: EventEngineSubscriptionSessionStrategy( + configuration: container.configuration, + subscribeEngine: container.subscribeEventEngine, + presenceEngine: container.presenceEngine, + presenceStateContainer: container.presenceStateContainer + ) + ) + } else { + return SubscriptionSession( + strategy: LegacySubscriptionSessionStrategy( + configuration: container.configuration, + network: container.httpSubscribeSession, + presenceSession: container.httpPresenceSession + ) + ) + } + } +} + +extension DependencyContainer { + var subscriptionSession: SubscriptionSession { + self[SubscriptionSessionDependencyKey.self] + } +} diff --git a/Sources/PubNub/Networking/HTTPSession.swift b/Sources/PubNub/Networking/HTTPSession.swift index dbdc97f4..778ac201 100644 --- a/Sources/PubNub/Networking/HTTPSession.swift +++ b/Sources/PubNub/Networking/HTTPSession.swift @@ -32,7 +32,6 @@ public final class HTTPSession { public var defaultRequestOperator: RequestOperator? /// The collection of associations between `URLSessionTask` and their corresponding `Request` var taskToRequest: [URLSessionTask: RequestReplaceable] = [:] - /// Default HTTPSession configuration for PubNub REST endpoints static var pubnub = HTTPSession(configuration: .pubnub) @@ -43,13 +42,14 @@ public final class HTTPSession { requestQueue: DispatchQueue? = nil, sessionStream: SessionStream? = nil ) { - precondition(session.delegateQueue.underlyingQueue === sessionQueue, - "Session.sessionQueue must be the same DispatchQueue used as the URLSession.delegate underlyingQueue") + precondition( + session.delegateQueue.underlyingQueue === sessionQueue, + "Session.sessionQueue must be the same DispatchQueue used as the URLSession.delegate underlyingQueue" + ) self.session = session self.sessionQueue = sessionQueue self.requestQueue = requestQueue ?? DispatchQueue(label: "com.pubnub.session.requestQueue", target: sessionQueue) - self.delegate = delegate self.sessionStream = sessionStream @@ -65,18 +65,26 @@ public final class HTTPSession { requestQueue: DispatchQueue? = nil, sessionStream: SessionStream? = nil ) { - let delegateQueue = OperationQueue(maxConcurrentOperationCount: 1, - underlyingQueue: sessionQueue, - name: "org.pubnub.httpClient.URLSessionReplaceableDelegate") - - let session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: delegateQueue) + let delegateQueue = OperationQueue( + maxConcurrentOperationCount: 1, + underlyingQueue: sessionQueue, + name: "org.pubnub.httpClient.URLSessionReplaceableDelegate" + ) + + let session = URLSession( + configuration: configuration, + delegate: delegate, + delegateQueue: delegateQueue + ) session.sessionDescription = "Underlying URLSession for: com.pubnub.session" - self.init(session: session, - delegate: delegate, - sessionQueue: sessionQueue, - requestQueue: requestQueue, - sessionStream: sessionStream) + self.init( + session: session, + delegate: delegate, + sessionQueue: sessionQueue, + requestQueue: requestQueue, + sessionStream: sessionStream + ) } deinit { @@ -85,7 +93,6 @@ public final class HTTPSession { taskToRequest.values.forEach { $0.cancel(PubNubError(.sessionDeinitialized, router: $0.router)) } - invalidateAndCancel() } @@ -112,12 +119,14 @@ public final class HTTPSession { with router: HTTPRouter, requestOperator: RequestOperator? = nil ) -> RequestReplaceable { - let request = Request(with: router, - requestQueue: sessionQueue, - sessionStream: sessionStream, - requestOperator: requestOperator, - delegate: self, - createdBy: sessionID) + let request = Request( + with: router, + requestQueue: sessionQueue, + sessionStream: sessionStream, + requestOperator: requestOperator, + delegate: self, + createdBy: sessionID + ) perform(request) @@ -225,7 +234,6 @@ public final class HTTPSession { public func invalidateAndCancel() { // Ensure that we lock out task creation prior to invalidating isInvalidated = true - session.invalidateAndCancel() } } diff --git a/Sources/PubNub/Networking/Routers/PresenceRouter.swift b/Sources/PubNub/Networking/Routers/PresenceRouter.swift index 6d0abc0a..81ed9424 100644 --- a/Sources/PubNub/Networking/Routers/PresenceRouter.swift +++ b/Sources/PubNub/Networking/Routers/PresenceRouter.swift @@ -241,11 +241,13 @@ struct HereNowResponseDecoder: ResponseDecoder { ).payload.channels } - let decodedResponse = EndpointResponse(router: response.router, - request: response.request, - response: response.response, - data: response.data, - payload: hereNowPayload) + let decodedResponse = EndpointResponse( + router: response.router, + request: response.request, + response: response.response, + data: response.data, + payload: hereNowPayload + ) return .success(decodedResponse) } catch { diff --git a/Sources/PubNub/Networking/Routers/SubscribeRouter.swift b/Sources/PubNub/Networking/Routers/SubscribeRouter.swift index 41004f9d..7c8b6022 100644 --- a/Sources/PubNub/Networking/Routers/SubscribeRouter.swift +++ b/Sources/PubNub/Networking/Routers/SubscribeRouter.swift @@ -119,11 +119,13 @@ struct SubscribeDecoder: ResponseDecoder { do { let decodedPayload = try Constant.jsonDecoder.decode(Payload.self, from: response.payload) - let decodedResponse = EndpointResponse(router: response.router, - request: response.request, - response: response.response, - data: response.data, - payload: decodedPayload) + let decodedResponse = EndpointResponse( + router: response.router, + request: response.request, + response: response.response, + data: response.data, + payload: decodedPayload + ) return .success(decodedResponse) } catch { diff --git a/Sources/PubNub/PubNub.swift b/Sources/PubNub/PubNub.swift index 98a9350b..11d2932b 100644 --- a/Sources/PubNub/PubNub.swift +++ b/Sources/PubNub/PubNub.swift @@ -16,25 +16,20 @@ public class PubNub { public let instanceID: UUID /// A copy of the configuration object used for this session public private(set) var configuration: PubNubConfiguration - /// Session used for performing request/response REST calls public let networkSession: SessionReplaceable /// Session used for performing subscription calls public let subscription: SubscriptionSession - /// The URLSession used when making File upload/download requests public var fileURLSession: URLSessionReplaceable /// The URLSessionDelegate used by the `fileSession` to handle file responses - public var fileSessionManager: FileSessionManager? { - return fileURLSession.delegate as? FileSessionManager - } - + public var fileSessionManager: FileSessionManager? { fileURLSession.delegate as? FileSessionManager } /// Global log instance for the PubNub SDK public static var log = PubNubLogger(levels: [.event, .warn, .error], writers: [ConsoleLogWriter(), FileLogWriter()]) // Global log instance for Logging issues/events public static var logLog = PubNubLogger(levels: [.log], writers: [ConsoleLogWriter()]) // Container that holds current Presence states for given channels/channel groups - internal let presenceStateContainer = PubNubPresenceStateContainer.shared + let presenceStateContainer: PubNubPresenceStateContainer /// Creates a PubNub session with the specified configuration /// @@ -49,64 +44,21 @@ public class PubNub { subscribeSession: SessionReplaceable? = nil, fileSession: URLSessionReplaceable? = nil ) { - let instanceID = UUID() - - // Default operators based on config - var operators = [RequestOperator]() - if let retryOperator = configuration.automaticRetry { - operators.append(retryOperator) - } - if configuration.useInstanceId { - let instanceIdOperator = InstanceIdOperator(instanceID: instanceID.description) - operators.append(instanceIdOperator) - } + let container = DependencyContainer(instanceID: UUID(), configuration: configuration) + container.register(value: session, forKey: HTTPSessionDependencyKey.self) + container.register(value: subscribeSession, forKey: HTTPSubscribeSessionDependencyKey.self) + container.register(value: fileSession, forKey: FileURLSessionDependencyKey.self) - // Mutable session - var networkSession = session ?? HTTPSession(configuration: configuration.urlSessionConfiguration) - - // Configure the default request operators - if networkSession.defaultRequestOperator == nil { - networkSession.defaultRequestOperator = MultiplexRequestOperator(operators: operators) - } else { - networkSession.defaultRequestOperator = networkSession - .defaultRequestOperator? - .merge(requestOperator: MultiplexRequestOperator(operators: operators)) - } - - let fileSession = fileSession ?? URLSession( - configuration: .pubnubBackground, - delegate: FileSessionManager(), - delegateQueue: .main - ) - - // Set initial session also based on configuration - let subscriptionSession = SubscribeSessionFactory.shared.getSession( - from: configuration, - with: subscribeSession, - presenceSession: session - ) - - self.init( - instanceID: instanceID, - configuration: configuration, - session: networkSession, - fileSession: fileSession, - subscriptionSession: subscriptionSession - ) + self.init(container: container) } - init( - instanceID: UUID = UUID(), - configuration: PubNubConfiguration, - session: SessionReplaceable, - fileSession: URLSessionReplaceable, - subscriptionSession: SubscriptionSession - ) { - self.instanceID = instanceID - self.configuration = configuration - self.subscription = subscriptionSession - self.networkSession = session - self.fileURLSession = fileSession + init(container: DependencyContainer) { + self.instanceID = container.instanceID + self.configuration = container.configuration + self.subscription = container.subscriptionSession + self.networkSession = container.httpSession + self.fileURLSession = container.fileURLSession + self.presenceStateContainer = container.presenceStateContainer } func route( From 036ad8a68780f0dacaa1b37b671355b8d00dd2b0 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Thu, 7 Mar 2024 15:04:42 +0100 Subject: [PATCH 02/15] Getting rid of SubscribeSessionFactory --- PubNub.xcodeproj/project.pbxproj | 8 +- .../DependencyContainer.swift | 73 +- .../Presence/Effects/WaitEffect.swift | 2 +- .../Helpers/PresenceHeartbeatRequest.swift | 4 +- .../Helpers/PresenceLeaveRequest.swift | 4 +- .../EventEngine/Presence/Presence.swift | 2 +- .../Subscribe/Helpers/SubscribeRequest.swift | 4 +- .../EventEngine/Subscribe/Subscribe.swift | 4 +- Sources/PubNub/PubNub.swift | 4 +- ...entEngineSubscriptionSessionStrategy.swift | 10 +- .../LegacySubscriptionSessionStrategy.swift | 10 +- .../SubscriptionSessionStrategy.swift | 3 +- .../SubscribeSessionFactory.swift | 181 ----- .../Subscription/SubscriptionSession.swift | 54 +- .../Subscribe/subscription_invalid_json.json | 23 + .../Routers/SubscribeRouterTests.swift | 650 +++++++----------- .../SubscribeSessionFactoryTests.swift | 27 +- .../SubscriptionSessionTests.swift | 63 +- 18 files changed, 420 insertions(+), 706 deletions(-) delete mode 100644 Sources/PubNub/Subscription/SubscribeSessionFactory.swift create mode 100644 Tests/PubNubTests/Mocking/Responses/Subscribe/subscription_invalid_json.json diff --git a/PubNub.xcodeproj/project.pbxproj b/PubNub.xcodeproj/project.pbxproj index ff2cafa4..68291f41 100644 --- a/PubNub.xcodeproj/project.pbxproj +++ b/PubNub.xcodeproj/project.pbxproj @@ -209,7 +209,6 @@ 35A66A7F22F861BA00AC67A9 /* WeakBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35A66A7522F861BA00AC67A9 /* WeakBox.swift */; }; 35A66A8022F861BA00AC67A9 /* AutomaticRetry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35A66A7722F861BA00AC67A9 /* AutomaticRetry.swift */; }; 35A66A8322F861BA00AC67A9 /* PubNubMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35A66A7C22F861BA00AC67A9 /* PubNubMessage.swift */; }; - 35A66A8E22F911DB00AC67A9 /* SubscribeSessionFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35A66A8D22F911DB00AC67A9 /* SubscribeSessionFactory.swift */; }; 35A66A9022F913B200AC67A9 /* ConnectionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35A66A8F22F913B200AC67A9 /* ConnectionStatus.swift */; }; 35A66A9622F9B71200AC67A9 /* setState_missing_state.json in Resources */ = {isa = PBXBuildFile; fileRef = 35A66A9522F9B71200AC67A9 /* setState_missing_state.json */; }; 35A66A9722F9B72200AC67A9 /* setState_success.json in Resources */ = {isa = PBXBuildFile; fileRef = 35A66A8C22F9084000AC67A9 /* setState_success.json */; }; @@ -367,6 +366,7 @@ 35FE941222EFB70B0051C455 /* unrecognizedEndpointError.json in Resources */ = {isa = PBXBuildFile; fileRef = 35FE941122EFB70B0051C455 /* unrecognizedEndpointError.json */; }; 35FE941422EFB7C10051C455 /* unknownEndpointError.json in Resources */ = {isa = PBXBuildFile; fileRef = 35FE941322EFB7C10051C455 /* unknownEndpointError.json */; }; 35FE941F22F0929A0051C455 /* RequestRetrierTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FE941E22F0929A0051C455 /* RequestRetrierTests.swift */; }; + 3D34D1C42B989B440055A7FA /* subscription_invalid_json.json in Resources */ = {isa = PBXBuildFile; fileRef = 3D34D1C32B989B440055A7FA /* subscription_invalid_json.json */; }; 3D389FE12B35AF4A006928E7 /* TransitionProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FC32B35AF4A006928E7 /* TransitionProtocol.swift */; }; 3D389FE22B35AF4A006928E7 /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FC42B35AF4A006928E7 /* Dispatcher.swift */; }; 3D389FE32B35AF4A006928E7 /* EffectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FC52B35AF4A006928E7 /* EffectHandler.swift */; }; @@ -812,7 +812,6 @@ 35A66A7C22F861BA00AC67A9 /* PubNubMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PubNubMessage.swift; sourceTree = ""; }; 35A66A8B22F9080A00AC67A9 /* getState_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = getState_success.json; sourceTree = ""; }; 35A66A8C22F9084000AC67A9 /* setState_success.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = setState_success.json; sourceTree = ""; }; - 35A66A8D22F911DB00AC67A9 /* SubscribeSessionFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribeSessionFactory.swift; sourceTree = ""; }; 35A66A8F22F913B200AC67A9 /* ConnectionStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionStatus.swift; sourceTree = ""; }; 35A66A9522F9B71200AC67A9 /* setState_missing_state.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = setState_missing_state.json; sourceTree = ""; }; 35A6C77C22FB159F00E97CC5 /* PresenceRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresenceRouter.swift; sourceTree = ""; }; @@ -965,6 +964,7 @@ 35FE941122EFB70B0051C455 /* unrecognizedEndpointError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = unrecognizedEndpointError.json; sourceTree = ""; }; 35FE941322EFB7C10051C455 /* unknownEndpointError.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = unknownEndpointError.json; sourceTree = ""; }; 35FE941E22F0929A0051C455 /* RequestRetrierTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestRetrierTests.swift; sourceTree = ""; }; + 3D34D1C32B989B440055A7FA /* subscription_invalid_json.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = subscription_invalid_json.json; sourceTree = ""; }; 3D389FC32B35AF4A006928E7 /* TransitionProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionProtocol.swift; sourceTree = ""; }; 3D389FC42B35AF4A006928E7 /* Dispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatcher.swift; sourceTree = ""; }; 3D389FC52B35AF4A006928E7 /* EffectHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectHandler.swift; sourceTree = ""; }; @@ -1425,6 +1425,7 @@ 357AEB7E22E693DD00C18250 /* Subscribe */ = { isa = PBXGroup; children = ( + 3D34D1C32B989B440055A7FA /* subscription_invalid_json.json */, 3D38A02F2B35B208006928E7 /* subscription_handshake_success.json */, 359287C423185EEE0046F7A2 /* subscription_success.json */, 3DFB01932B0E30EE00146B57 /* subscription_encrypted_message_success.json */, @@ -1688,7 +1689,6 @@ 35A66A8522F8DB2E00AC67A9 /* Subscription */ = { isa = PBXGroup; children = ( - 35A66A8D22F911DB00AC67A9 /* SubscribeSessionFactory.swift */, 35A66A7422F861BA00AC67A9 /* SubscriptionSession.swift */, 35C829DB23147AC000F59D3C /* SubscriptionState.swift */, 35A66A8F22F913B200AC67A9 /* ConnectionStatus.swift */, @@ -2990,6 +2990,7 @@ 35A6C79A22FBC2AD00E97CC5 /* groups_delete_success.json in Resources */, 3DFB01942B0E30EE00146B57 /* subscription_encrypted_message_success.json in Resources */, 35293A872369F0230049A71F /* addMessageAction_error_400.json in Resources */, + 3D34D1C42B989B440055A7FA /* subscription_invalid_json.json in Resources */, 35FE93F622EF93A90051C455 /* cannotParseResponse.json in Resources */, 35A6C79C22FBC2D100E97CC5 /* groups_channels_add_success.json in Resources */, 350BC412233952FE00011262 /* objects_error_404.json in Resources */, @@ -3437,7 +3438,6 @@ 35CDA4CC2510031E00218137 /* XMLDecoder.swift in Sources */, 35D0615F2304830600FDB2F9 /* GenericServicePayloadResponse.swift in Sources */, 3D758DD62AB48A6A005D2B36 /* CryptorHeaderWithinStreamFinder.swift in Sources */, - 35A66A8E22F911DB00AC67A9 /* SubscribeSessionFactory.swift in Sources */, 3D758DBF2AAA1C49005D2B36 /* CryptoModule.swift in Sources */, 3D389FEC2B35AF4A006928E7 /* SubscribeRequest.swift in Sources */, 3D389FE72B35AF4A006928E7 /* EmitStatusEffect.swift in Sources */, diff --git a/Sources/PubNub/DependencyContainer/DependencyContainer.swift b/Sources/PubNub/DependencyContainer/DependencyContainer.swift index 099586a0..e554908e 100644 --- a/Sources/PubNub/DependencyContainer/DependencyContainer.swift +++ b/Sources/PubNub/DependencyContainer/DependencyContainer.swift @@ -16,7 +16,7 @@ protocol DependencyKey { // A value associated with a given `DependencyKey` associatedtype Value // Creates a value of the type Value for a given `DependencyKey` if no existing dependency - // is found in the `DependencyContainer`. Note that `container` parameter is used in case of + // is found in the `DependencyContainer`. The `container` parameter is used in case of // nested dependencies, i.e., when the dependency being created depends on other objects in the `DependencyContainer`. static func value(from container: DependencyContainer) -> Value } @@ -26,7 +26,7 @@ protocol DependencyKey { class DependencyContainer { private var values: [ObjectIdentifier: Any] = [:] - init(instanceID: UUID, configuration: PubNubConfiguration) { + init(instanceID: UUID = UUID(), configuration: PubNubConfiguration) { self[PubNubConfigurationDependencyKey.self] = configuration self[PubNubInstanceIDDependencyKey.self] = instanceID } @@ -44,10 +44,12 @@ class DependencyContainer { } } - func register(value: K.Value?, forKey key: K.Type) where K: DependencyKey { + @discardableResult + func register(value: K.Value?, forKey key: K.Type) -> DependencyContainer { if let value { values[ObjectIdentifier(key)] = value } + return self } } @@ -60,16 +62,12 @@ extension DependencyContainer { self[PubNubInstanceIDDependencyKey.self] } - var automaticRetry: RequestOperator? { - configuration.automaticRetry - } - - var instanceIDOperator: RequestOperator? { - configuration.useInstanceId ? InstanceIdOperator(instanceID: instanceID.uuidString) : nil + var fileURLSession: URLSessionReplaceable { + self[FileURLSessionDependencyKey.self] } - var presenceStateContainer: PubNubPresenceStateContainer { - self[PresenceStateContainerDependencyKey.self] + var subscriptionSession: SubscriptionSession { + self[SubscriptionSessionDependencyKey.self] } var httpSession: SessionReplaceable { @@ -79,37 +77,55 @@ extension DependencyContainer { ) } - var httpSubscribeSession: SessionReplaceable { + var presenceStateContainer: PubNubPresenceStateContainer { + self[PresenceStateContainerDependencyKey.self] + } + + fileprivate var automaticRetry: RequestOperator? { + configuration.automaticRetry + } + + fileprivate var instanceIDOperator: RequestOperator? { + configuration.useInstanceId ? InstanceIdOperator(instanceID: instanceID.uuidString) : nil + } + + fileprivate var httpSubscribeSession: SessionReplaceable { resolveSession( session: self[HTTPSubscribeSessionDependencyKey.self], with: [instanceIDOperator].compactMap { $0 } ) } - var httpPresenceSession: SessionReplaceable { + fileprivate var httpPresenceSession: SessionReplaceable { resolveSession( session: self[HTTPPresenceSessionDependencyKey.self], with: [instanceIDOperator].compactMap { $0 } ) } - var httpSubscribeSessionQueue: DispatchQueue { + fileprivate var httpSubscribeSessionQueue: DispatchQueue { self[HTTPSubscribeSessionQueueDependencyKey.self] } - var fileURLSession: URLSessionReplaceable { - self[FileURLSessionDependencyKey.self] - } - - var subscribeEventEngine: SubscribeEngine { + fileprivate var subscribeEventEngine: SubscribeEngine { self[SubscribeEventEngineDependencyKey.self] } - var subscribeEffectFactory: SubscribeEffectFactory { + fileprivate var subscribeEffectFactory: SubscribeEffectFactory { self[SubscribeEffectFactoryDependencyKey.self] } - private func resolveSession(session: SessionReplaceable, with operators: [RequestOperator?]) -> SessionReplaceable { + fileprivate var presenceEffectFactory: PresenceEffectFactory { + self[PresenceEffectFactoryDependencyKey.self] + } + + fileprivate var presenceEngine: PresenceEngine { + self[PresenceEventEngineDependencyKey.self] + } +} + +fileprivate extension DependencyContainer { + func resolveSession(session: SessionReplaceable, with operators: [RequestOperator?]) -> SessionReplaceable { session.defaultRequestOperator == nil ? session.usingDefault(requestOperator: MultiplexRequestOperator( operators: operators.compactMap { $0 } )) : session.usingDefault(requestOperator: session.defaultRequestOperator?.merge( @@ -230,15 +246,6 @@ struct PresenceEventEngineDependencyKey: DependencyKey { } } -extension DependencyContainer { - var presenceEffectFactory: PresenceEffectFactory { - self[PresenceEffectFactoryDependencyKey.self] - } - var presenceEngine: PresenceEngine { - self[PresenceEventEngineDependencyKey.self] - } -} - // MARK: - SubscriptionSession struct SubscriptionSessionDependencyKey: DependencyKey { @@ -263,9 +270,3 @@ struct SubscriptionSessionDependencyKey: DependencyKey { } } } - -extension DependencyContainer { - var subscriptionSession: SubscriptionSession { - self[SubscriptionSessionDependencyKey.self] - } -} diff --git a/Sources/PubNub/EventEngine/Presence/Effects/WaitEffect.swift b/Sources/PubNub/EventEngine/Presence/Effects/WaitEffect.swift index b1103395..2407e083 100644 --- a/Sources/PubNub/EventEngine/Presence/Effects/WaitEffect.swift +++ b/Sources/PubNub/EventEngine/Presence/Effects/WaitEffect.swift @@ -13,7 +13,7 @@ import Foundation class WaitEffect: EffectHandler { private let timerEffect: TimerEffect? - init(configuration: SubscriptionConfiguration) { + init(configuration: PubNubConfiguration) { if configuration.heartbeatInterval > 0 { self.timerEffect = TimerEffect(interval: TimeInterval(configuration.heartbeatInterval)) } else { diff --git a/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift b/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift index 5d1cf160..bb5c9b8a 100644 --- a/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift +++ b/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift @@ -13,7 +13,7 @@ import Foundation class PresenceHeartbeatRequest { let channels: [String] let groups: [String] - let configuration: SubscriptionConfiguration + let configuration: PubNubConfiguration private let session: SessionReplaceable private let sessionResponseQueue: DispatchQueue @@ -24,7 +24,7 @@ class PresenceHeartbeatRequest { channels: [String], groups: [String], channelStates: [String: JSONCodable], - configuration: SubscriptionConfiguration, + configuration: PubNubConfiguration, session: SessionReplaceable, sessionResponseQueue: DispatchQueue ) { diff --git a/Sources/PubNub/EventEngine/Presence/Helpers/PresenceLeaveRequest.swift b/Sources/PubNub/EventEngine/Presence/Helpers/PresenceLeaveRequest.swift index 4c08ba73..9e7f44e3 100644 --- a/Sources/PubNub/EventEngine/Presence/Helpers/PresenceLeaveRequest.swift +++ b/Sources/PubNub/EventEngine/Presence/Helpers/PresenceLeaveRequest.swift @@ -13,7 +13,7 @@ import Foundation class PresenceLeaveRequest { let channels: [String] let groups: [String] - let configuration: SubscriptionConfiguration + let configuration: PubNubConfiguration private let session: SessionReplaceable private let sessionResponseQueue: DispatchQueue @@ -22,7 +22,7 @@ class PresenceLeaveRequest { init( channels: [String], groups: [String], - configuration: SubscriptionConfiguration, + configuration: PubNubConfiguration, session: SessionReplaceable, sessionResponseQueue: DispatchQueue ) { diff --git a/Sources/PubNub/EventEngine/Presence/Presence.swift b/Sources/PubNub/EventEngine/Presence/Presence.swift index a94a053a..ad9f35fe 100644 --- a/Sources/PubNub/EventEngine/Presence/Presence.swift +++ b/Sources/PubNub/EventEngine/Presence/Presence.swift @@ -79,7 +79,7 @@ extension Presence { extension Presence { struct Dependencies { - let configuration: SubscriptionConfiguration + let configuration: PubNubConfiguration } } diff --git a/Sources/PubNub/EventEngine/Subscribe/Helpers/SubscribeRequest.swift b/Sources/PubNub/EventEngine/Subscribe/Helpers/SubscribeRequest.swift index c4a7f93e..84046296 100644 --- a/Sources/PubNub/EventEngine/Subscribe/Helpers/SubscribeRequest.swift +++ b/Sources/PubNub/EventEngine/Subscribe/Helpers/SubscribeRequest.swift @@ -16,7 +16,7 @@ class SubscribeRequest { let timetoken: Timetoken? let region: Int? - private let configuration: SubscriptionConfiguration + private let configuration: PubNubConfiguration private let session: SessionReplaceable private let sessionResponseQueue: DispatchQueue private let channelStates: [String: JSONCodable] @@ -27,7 +27,7 @@ class SubscribeRequest { var onAuthChallengeReceived: (() -> Void)? init( - configuration: SubscriptionConfiguration, + configuration: PubNubConfiguration, channels: [String], groups: [String], channelStates: [String: JSONCodable], diff --git a/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift b/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift index 62ea5499..b009106c 100644 --- a/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift +++ b/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift @@ -139,10 +139,10 @@ extension Subscribe { extension Subscribe { struct Dependencies { - let configuration: SubscriptionConfiguration + let configuration: PubNubConfiguration let listeners: [BaseSubscriptionListener] - init(configuration: SubscriptionConfiguration, listeners: [BaseSubscriptionListener] = []) { + init(configuration: PubNubConfiguration, listeners: [BaseSubscriptionListener] = []) { self.configuration = configuration self.listeners = listeners } diff --git a/Sources/PubNub/PubNub.swift b/Sources/PubNub/PubNub.swift index 11d2932b..9378ef7e 100644 --- a/Sources/PubNub/PubNub.swift +++ b/Sources/PubNub/PubNub.swift @@ -18,8 +18,6 @@ public class PubNub { public private(set) var configuration: PubNubConfiguration /// Session used for performing request/response REST calls public let networkSession: SessionReplaceable - /// Session used for performing subscription calls - public let subscription: SubscriptionSession /// The URLSession used when making File upload/download requests public var fileURLSession: URLSessionReplaceable /// The URLSessionDelegate used by the `fileSession` to handle file responses @@ -28,6 +26,8 @@ public class PubNub { public static var log = PubNubLogger(levels: [.event, .warn, .error], writers: [ConsoleLogWriter(), FileLogWriter()]) // Global log instance for Logging issues/events public static var logLog = PubNubLogger(levels: [.log], writers: [ConsoleLogWriter()]) + /// Session used for performing subscription calls + let subscription: SubscriptionSession // Container that holds current Presence states for given channels/channel groups let presenceStateContainer: PubNubPresenceStateContainer diff --git a/Sources/PubNub/Subscription/Strategy/EventEngineSubscriptionSessionStrategy.swift b/Sources/PubNub/Subscription/Strategy/EventEngineSubscriptionSessionStrategy.swift index 3ce0aa1e..d3be17da 100644 --- a/Sources/PubNub/Subscription/Strategy/EventEngineSubscriptionSessionStrategy.swift +++ b/Sources/PubNub/Subscription/Strategy/EventEngineSubscriptionSessionStrategy.swift @@ -17,7 +17,7 @@ class EventEngineSubscriptionSessionStrategy: SubscriptionSessionStrategy { let presenceStateContainer: PubNubPresenceStateContainer var listeners: WeakSet = WeakSet([]) - var configuration: SubscriptionConfiguration + var configuration: PubNubConfiguration var previousTokenResponse: SubscribeCursor? var filterExpression: String? { didSet { @@ -26,7 +26,7 @@ class EventEngineSubscriptionSessionStrategy: SubscriptionSessionStrategy { } internal init( - configuration: SubscriptionConfiguration, + configuration: PubNubConfiguration, subscribeEngine: SubscribeEngine, presenceEngine: PresenceEngine, presenceStateContainer: PubNubPresenceStateContainer @@ -57,8 +57,6 @@ class EventEngineSubscriptionSessionStrategy: SubscriptionSessionStrategy { deinit { PubNub.log.debug("SubscriptionSession Destroyed") - // Poke the session factory to clean up nil values - SubscribeSessionFactory.shared.sessionDestroyed() } private func listenForStateUpdates() { @@ -222,10 +220,6 @@ class EventEngineSubscriptionSessionStrategy: SubscriptionSessionStrategy { sendPresenceEvent(event: .leftAll) } - func onListenerAdded(_ listener: BaseSubscriptionListener) { - updateSubscribeEngineDependencies() - } - private func notify(listeners closure: (BaseSubscriptionListener) -> Void) { listeners.allObjects.forEach { closure($0) } } diff --git a/Sources/PubNub/Subscription/Strategy/LegacySubscriptionSessionStrategy.swift b/Sources/PubNub/Subscription/Strategy/LegacySubscriptionSessionStrategy.swift index 071312e8..da2c4ac7 100644 --- a/Sources/PubNub/Subscription/Strategy/LegacySubscriptionSessionStrategy.swift +++ b/Sources/PubNub/Subscription/Strategy/LegacySubscriptionSessionStrategy.swift @@ -17,7 +17,7 @@ class LegacySubscriptionSessionStrategy: SubscriptionSessionStrategy { let sessionStream: SessionListener let responseQueue: DispatchQueue - var configuration: SubscriptionConfiguration + var configuration: PubNubConfiguration var listeners: WeakSet = WeakSet([]) var filterExpression: String? var messageCache = [SubscribeMessagePayload?].init(repeating: nil, count: 100) @@ -68,7 +68,7 @@ class LegacySubscriptionSessionStrategy: SubscriptionSessionStrategy { var internalState = Atomic(SubscriptionState()) internal init( - configuration: SubscriptionConfiguration, + configuration: PubNubConfiguration, network subscribeSession: SessionReplaceable, presenceSession: SessionReplaceable ) { @@ -103,8 +103,6 @@ class LegacySubscriptionSessionStrategy: SubscriptionSessionStrategy { PubNub.log.debug("SubscriptionSession Destroyed") longPollingSession.invalidateAndCancel() nonSubscribeSession.invalidateAndCancel() - // Poke the session factory to clean up nil values - SubscribeSessionFactory.shared.sessionDestroyed() } // MARK: - Subscription Loop @@ -381,10 +379,6 @@ class LegacySubscriptionSessionStrategy: SubscriptionSessionStrategy { } } - func onListenerAdded(_ listener: BaseSubscriptionListener) { - - } - private func notify(listeners closure: (BaseSubscriptionListener) -> Void) { listeners.allObjects.forEach { closure($0) } } diff --git a/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift b/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift index 274085e1..358e7310 100644 --- a/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift +++ b/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift @@ -12,7 +12,7 @@ import Foundation protocol SubscriptionSessionStrategy: AnyObject { var uuid: UUID { get } - var configuration: SubscriptionConfiguration { get set } + var configuration: PubNubConfiguration { get set } var subscribedChannels: [String] { get } var subscribedChannelGroups: [String] { get } var subscriptionCount: Int { get } @@ -33,7 +33,6 @@ protocol SubscriptionSessionStrategy: AnyObject { presenceGroupsOnly: [PubNubChannel] ) - func onListenerAdded(_ listener: BaseSubscriptionListener) func reconnect(at cursor: SubscribeCursor?) func disconnect() func unsubscribeAll() diff --git a/Sources/PubNub/Subscription/SubscribeSessionFactory.swift b/Sources/PubNub/Subscription/SubscribeSessionFactory.swift deleted file mode 100644 index caad6a3a..00000000 --- a/Sources/PubNub/Subscription/SubscribeSessionFactory.swift +++ /dev/null @@ -1,181 +0,0 @@ -// -// SubscribeSessionFactory.swift -// -// Copyright (c) PubNub Inc. -// All rights reserved. -// -// This source code is licensed under the license found in the -// LICENSE file in the root directory of this source tree. -// - -import Foundation - -/// A factory that manages instances of `SubscriptionSession` -/// -/// This factory attempts to ensure that regardless of how many `PubNub` -/// instances you will only create a single `SubscriptionSession`. -/// -/// You should only use one instance of a `SubscriptionSession` unless you have a very specialized workflow. -/// Such as one of the following: -/// * Subscribe using multiple sets of subscribe keys -/// * Need separate network configurations on a per channel/group basis -/// -/// - Important: Having multiple `SubscriptionSession` instances will result in -/// increase network usage and battery drain. -@available(*, deprecated, message: "Use methods from a PubNub object to subscribe/unsubscribe") -public class SubscribeSessionFactory { - private typealias SessionMap = [Int: WeakBox] - - /// The singleton instance for this factory - public let subscribeQueue = DispatchQueue(label: "Subscribe Response Queue") - public static var shared = SubscribeSessionFactory() - private let sessions = Atomic([:]) - private init() {} - - /// Retrieve a session matching the hash value of the configuration or creates a new one if no match was found - /// - /// The `session` parameter will only be injected into the `SubscriptionSession` in the event - /// that a new `SubscriptionSession` is created - /// - /// - Parameters: - /// - from: A configuration that will be used to fetch an existing SubscriptionSession or create a new one - /// - with: `SessionReplaceable` that will be used as the underlying `Session` - /// - Returns: A `SubscriptionSession` that can be used to make PubNub subscribe and presence API calls with - public func getSession( - from config: SubscriptionConfiguration, - with subscribeSession: SessionReplaceable? = nil, - presenceSession: SessionReplaceable? = nil - ) -> SubscriptionSession { - // The hash value for the given configuration - let configHash = config.subscriptionHashValue - // Returns a session (if any) that matches the hash value - if let session = sessions.lockedRead({ $0[configHash]?.underlying }) { - PubNub.log.debug("Found existing session for config hash \(config.subscriptionHashValue)") - return session - } - - PubNub.log.debug("Creating new session for with hash value \(config.subscriptionHashValue)") - - return sessions.lockedWrite { dictionary in - let subscriptionSession = SubscriptionSession( - strategy: resolveStrategy( - configuration: config, - subscribeSession: subscribeSession, - presenceSession: presenceSession - ) - ) - dictionary.updateValue( - WeakBox(subscriptionSession), - forKey: configHash - ) - return subscriptionSession - } - - func resolveStrategy( - configuration: SubscriptionConfiguration, - subscribeSession: SessionReplaceable?, - presenceSession: SessionReplaceable? - ) -> any SubscriptionSessionStrategy { - // Creates default network session objects if they're not provided - let subscribeSession = subscribeSession ?? HTTPSession( - configuration: URLSessionConfiguration.subscription, - sessionQueue: subscribeQueue, - sessionStream: SessionListener(queue: subscribeQueue) - ) - let presenceSession = presenceSession ?? HTTPSession( - configuration: URLSessionConfiguration.pubnub, - sessionQueue: subscribeQueue, - sessionStream: SessionListener(queue: subscribeQueue) - ) - - if let config = config as? PubNubConfiguration, config.enableEventEngine { - let subscribeEffectFactory = SubscribeEffectFactory( - session: subscribeSession, - presenceStateContainer: .shared - ) - let subscribeEngine = EventEngineFactory().subscribeEngine( - with: config, - dispatcher: EffectDispatcher(factory: subscribeEffectFactory), - transition: SubscribeTransition() - ) - let presenceEffectFactory = PresenceEffectFactory( - session: presenceSession, - presenceStateContainer: .shared - ) - let presenceEngine = EventEngineFactory().presenceEngine( - with: config, - dispatcher: EffectDispatcher(factory: presenceEffectFactory), - transition: PresenceTransition(configuration: config) - ) - return EventEngineSubscriptionSessionStrategy( - configuration: config, - subscribeEngine: subscribeEngine, - presenceEngine: presenceEngine, - presenceStateContainer: .shared - ) - } - - return LegacySubscriptionSessionStrategy( - configuration: configuration, - network: subscribeSession, - presenceSession: presenceSession - ) - } - } - - /// Clean-up method that can be used to poke each weakbox to see if its nil - func sessionDestroyed() { - sessions.lockedWrite { sessionMap in - sessionMap.keys.forEach { if sessionMap[$0]?.underlying == nil { sessionMap.removeValue(forKey: $0) } } - } - } -} - -// MARK: - SubscriptionConfiguration - -/// The configuration used to determine the uniqueness of a `SubscriptionSession` -@available(*, deprecated, message: "Use a PubNub object with PubNubConfiguration that matches the parameters below") -public protocol SubscriptionConfiguration: RouterConfiguration { - /// Reconnection policy which will be used if/when a request fails - var automaticRetry: AutomaticRetry? { get } - /// How long (in seconds) the server will consider the client alive for presence - /// - /// - NOTE: The minimum value this field can be is 20 - var durationUntilTimeout: UInt { get } - /// How often (in seconds) the client will announce itself to server - /// - /// - NOTE: The minimum value this field can be is 0 - var heartbeatInterval: UInt { get } - /// Whether to send out the leave requests - var supressLeaveEvents: Bool { get } - /// The number of messages into the payload before emitting `RequestMessageCountExceeded` - var requestMessageCountThreshold: UInt { get } - /// PSV2 feature to subscribe with a custom filter expression. - var filterExpression: String? { get } - /// If Access Manager (PAM) is enabled, client will use `authToken` instead of `authKey` on all requests - override var authToken: String? { get set } -} - -extension SubscriptionConfiguration { - /// The hash value. - /// - /// Hash values are not guaranteed to be equal across different executions of your program. - /// Do not save hash values to use during a future execution. - var subscriptionHashValue: Int { - var hasher = Hasher() - hasher.combine(durationUntilTimeout.hashValue) - hasher.combine(heartbeatInterval.hashValue) - hasher.combine(supressLeaveEvents.hashValue) - hasher.combine(requestMessageCountThreshold.hashValue) - hasher.combine(filterExpression.hashValue) - hasher.combine(subscribeKey.hashValue) - hasher.combine(uuid.hashValue) - hasher.combine(useSecureConnections.hashValue) - hasher.combine(origin.hashValue) - hasher.combine(authKey.hashValue) - hasher.combine(cryptoModule.hashValue) - return hasher.finalize() - } -} - -extension PubNubConfiguration: SubscriptionConfiguration {} diff --git a/Sources/PubNub/Subscription/SubscriptionSession.swift b/Sources/PubNub/Subscription/SubscriptionSession.swift index cc356fd8..1f2722e2 100644 --- a/Sources/PubNub/Subscription/SubscriptionSession.swift +++ b/Sources/PubNub/Subscription/SubscriptionSession.swift @@ -10,19 +10,17 @@ import Foundation -@available(*, deprecated, message: "Subscribe and unsubscribe using methods from a PubNub object") -public class SubscriptionSession: EventEmitter, StatusEmitter { +class SubscriptionSession: EventEmitter, StatusEmitter { /// A unique identifier for subscription session - public var uuid: UUID { + var uuid: UUID { strategy.uuid } /// An underlying queue to dispatch events - public let queue: DispatchQueue + let queue: DispatchQueue /// PSV2 feature to subscribe with a custom filter expression. - @available(*, deprecated, message: "Use `subscribeFilterExpression` from a PubNub object") - public var filterExpression: String? { + var filterExpression: String? { get { strategy.filterExpression } set { @@ -31,23 +29,23 @@ public class SubscriptionSession: EventEmitter, StatusEmitter { } /// `EventEmitter` conformance - public var onEvent: ((PubNubEvent) -> Void)? - public var onEvents: (([PubNubEvent]) -> Void)? - public var onMessage: ((PubNubMessage) -> Void)? - public var onSignal: ((PubNubMessage) -> Void)? - public var onPresence: ((PubNubPresenceChange) -> Void)? - public var onMessageAction: ((PubNubMessageActionEvent) -> Void)? - public var onFileEvent: ((PubNubFileChangeEvent) -> Void)? - public var onAppContext: ((PubNubAppContextEvent) -> Void)? + var onEvent: ((PubNubEvent) -> Void)? + var onEvents: (([PubNubEvent]) -> Void)? + var onMessage: ((PubNubMessage) -> Void)? + var onSignal: ((PubNubMessage) -> Void)? + var onPresence: ((PubNubPresenceChange) -> Void)? + var onMessageAction: ((PubNubMessageActionEvent) -> Void)? + var onFileEvent: ((PubNubFileChangeEvent) -> Void)? + var onAppContext: ((PubNubAppContextEvent) -> Void)? /// `StatusEmitter` conformance - public var onConnectionStateChange: ((ConnectionStatus) -> Void)? + var onConnectionStateChange: ((ConnectionStatus) -> Void)? var previousTokenResponse: SubscribeCursor? { strategy.previousTokenResponse } - var configuration: SubscriptionConfiguration { + var configuration: PubNubConfiguration { get { strategy.configuration } set { @@ -91,22 +89,22 @@ public class SubscriptionSession: EventEmitter, StatusEmitter { /// Names of all subscribed channels /// /// This list includes both regular and presence channel names - public var subscribedChannels: [String] { + var subscribedChannels: [String] { strategy.subscribedChannels } /// List of actively subscribed groups - public var subscribedChannelGroups: [String] { + var subscribedChannelGroups: [String] { strategy.subscribedChannelGroups } /// Combined value of all subscribed channels and groups - public var subscriptionCount: Int { + var subscriptionCount: Int { strategy.subscriptionCount } /// Current connection status - public var connectionStatus: ConnectionStatus { + var connectionStatus: ConnectionStatus { strategy.connectionStatus } @@ -119,7 +117,7 @@ public class SubscriptionSession: EventEmitter, StatusEmitter { /// - and: List of channel groups to subscribe on /// - at: The timetoken to subscribe with /// - withPresence: If true it also subscribes to presence events on the specified channels. - public func subscribe( + func subscribe( to channels: [String], and groups: [String] = [], at cursor: SubscribeCursor? = nil, @@ -156,12 +154,12 @@ public class SubscriptionSession: EventEmitter, StatusEmitter { /// Reconnect a disconnected subscription stream /// - parameter timetoken: The timetoken to subscribe with - public func reconnect(at cursor: SubscribeCursor? = nil) { + func reconnect(at cursor: SubscribeCursor? = nil) { strategy.reconnect(at: cursor) } /// Disconnect the subscription stream - public func disconnect() { + func disconnect() { strategy.disconnect() } @@ -173,7 +171,7 @@ public class SubscriptionSession: EventEmitter, StatusEmitter { /// - from: List of channels to unsubscribe from /// - and: List of channel groups to unsubscribe from /// - presenceOnly: If true, it only unsubscribes from presence events on the specified channels. - public func unsubscribe( + func unsubscribe( from channels: [String], and groups: [String] = [], presenceOnly: Bool = false @@ -196,7 +194,7 @@ public class SubscriptionSession: EventEmitter, StatusEmitter { } /// Unsubscribe from all channels and channel groups - public func unsubscribeAll() { + func unsubscribeAll() { strategy.unsubscribeAll() } } @@ -429,15 +427,15 @@ extension SubscriptionSession: EventStreamEmitter { // MARK: - Hashable & CustomStringConvertible extension SubscriptionSession: Hashable, CustomStringConvertible { - public static func == (lhs: SubscriptionSession, rhs: SubscriptionSession) -> Bool { + static func == (lhs: SubscriptionSession, rhs: SubscriptionSession) -> Bool { lhs.uuid == rhs.uuid } - public func hash(into hasher: inout Hasher) { + func hash(into hasher: inout Hasher) { hasher.combine(uuid) } - public var description: String { + var description: String { uuid.uuidString } } diff --git a/Tests/PubNubTests/Mocking/Responses/Subscribe/subscription_invalid_json.json b/Tests/PubNubTests/Mocking/Responses/Subscribe/subscription_invalid_json.json new file mode 100644 index 00000000..8228f327 --- /dev/null +++ b/Tests/PubNubTests/Mocking/Responses/Subscribe/subscription_invalid_json.json @@ -0,0 +1,23 @@ +{ + "code": 200, + "body": { + "t": { + "t": "15912183441526350", + "r": 1 + }, + "m": [ + { + "a": "3", + "f": 512, + "p": { + "t": "15912183441554200", + "r": 1 + }, + "k": "demo-36", + "c": "swiftInvalidJSON.", + "d": "hello", + "b": "swiftInvalidJSON.*" + } + ] + } +} diff --git a/Tests/PubNubTests/Networking/Routers/SubscribeRouterTests.swift b/Tests/PubNubTests/Networking/Routers/SubscribeRouterTests.swift index 8c53a1e9..b045285b 100644 --- a/Tests/PubNubTests/Networking/Routers/SubscribeRouterTests.swift +++ b/Tests/PubNubTests/Networking/Routers/SubscribeRouterTests.swift @@ -32,27 +32,29 @@ final class SubscribeRouterTests: XCTestCase { ) let testChannel = "TestChannel" - // MARK: - Endpoint Tests - + func testSubscribe_Router() { let router = SubscribeRouter(.subscribe( channels: ["TestChannel"], groups: [], channelStates: [:], timetoken: 0, region: nil, heartbeat: nil, filter: nil ), configuration: config) - + XCTAssertEqual(router.endpoint.description, "Subscribe") XCTAssertEqual(router.category, "Subscribe") XCTAssertEqual(router.service, .subscribe) } - + func testSubscribe_Router_ValidationError() { let router = SubscribeRouter(.subscribe( channels: [], groups: [], channelStates: [:], timetoken: 0, region: nil, heartbeat: nil, filter: nil ), configuration: config) - - XCTAssertNotEqual(router.validationError?.pubNubError, PubNubError(.invalidEndpointType, router: router)) + + XCTAssertNotEqual( + router.validationError?.pubNubError, + PubNubError(.invalidEndpointType, router: router) + ) } } @@ -79,16 +81,15 @@ extension SubscribeRouterTests { endpoint, configuration: config ) - + // There's no guaranteed order of returned states. // Therefore, these are two possible and valid combinations: let expStateValues = [ "{\"c1\":{\"x\":1},\"c2\":{\"a\":\"someText\"}}", "{\"c2\":{\"a\":\"someText\"},\"c1\":{\"x\":1}}" ] - let queryItems = (try? router.queryItems.get()) ?? [] - + XCTAssertTrue(queryItems.count == 8) XCTAssertTrue(queryItems.contains { $0.name == "pnsdk" }) XCTAssertTrue(queryItems.contains { $0.name == "uuid" && $0.value == "someId" }) @@ -99,7 +100,7 @@ extension SubscribeRouterTests { XCTAssertTrue(queryItems.contains { $0.name == "ee" && $0.value == nil }) XCTAssertTrue(queryItems.contains { $0.name == "state" && expStateValues.contains($0.value!) }) } - + func testSubscribeRouter_QueryParamsWithEventEngineDisabled() { let config = PubNubConfiguration( publishKey: "FakeTestString", @@ -116,13 +117,10 @@ extension SubscribeRouterTests { channels: ["c1"], groups: ["group-1", "group-2"], channelStates: channelStates, timetoken: 123456, region: "42", heartbeat: 30, filter: nil ) - let router = SubscribeRouter( - endpoint, - configuration: config - ) - + + let router = SubscribeRouter(endpoint, configuration: config) let queryItems = (try? router.queryItems.get()) ?? [] - + XCTAssertTrue(queryItems.count == 6) XCTAssertTrue(queryItems.contains { $0.name == "pnsdk" }) XCTAssertTrue(queryItems.contains { $0.name == "uuid" && $0.value == "someId" }) @@ -131,7 +129,7 @@ extension SubscribeRouterTests { XCTAssertTrue(queryItems.contains { $0.name == "tt" && $0.value == "123456" }) XCTAssertTrue(queryItems.contains { $0.name == "tr" && $0.value == "42" }) } - + func testSubscribeRouter_QueryParamsWithMaintainPresenceStateDisabled() { let config = PubNubConfiguration( publishKey: "FakeTestString", @@ -148,13 +146,10 @@ extension SubscribeRouterTests { channels: ["c1"], groups: ["group-1", "group-2"], channelStates: channelStates, timetoken: 123456, region: "42", heartbeat: 30, filter: nil ) - let router = SubscribeRouter( - endpoint, - configuration: config - ) - + + let router = SubscribeRouter(endpoint, configuration: config) let queryItems = (try? router.queryItems.get()) ?? [] - + XCTAssertTrue(queryItems.count == 7) XCTAssertTrue(queryItems.contains { $0.name == "pnsdk" }) XCTAssertTrue(queryItems.contains { $0.name == "uuid" && $0.value == "someId" }) @@ -164,7 +159,7 @@ extension SubscribeRouterTests { XCTAssertTrue(queryItems.contains { $0.name == "tr" && $0.value == "42" }) XCTAssertTrue(queryItems.contains { $0.name == "ee" && $0.value == nil }) } - + func testSubscribeRouter_QueryParamsWithEmptyPresenceStates() { let config = PubNubConfiguration( publishKey: "FakeTestString", @@ -177,13 +172,9 @@ extension SubscribeRouterTests { channels: ["c1"], groups: ["group-1", "group-2"], channelStates: [:], timetoken: 123456, region: "42", heartbeat: 30, filter: nil ) - let router = SubscribeRouter( - endpoint, - configuration: config - ) - + let router = SubscribeRouter(endpoint, configuration: config) let queryItems = (try? router.queryItems.get()) ?? [] - + XCTAssertTrue(queryItems.count == 7) XCTAssertTrue(queryItems.contains { $0.name == "pnsdk" }) XCTAssertTrue(queryItems.contains { $0.name == "uuid" && $0.value == "someId" }) @@ -195,6 +186,40 @@ extension SubscribeRouterTests { } } +// MARK: - Mock HTTP session + +fileprivate extension SubscribeRouterTests { + typealias MockResult = ( + subscriptionSession: SubscriptionSession, + listener: SubscriptionListener + ) + + func mockSubscriptionSession( + with responses: [String], + raw dataResource: [Data] = [], + and configuration: PubNubConfiguration + ) -> MockResult { + // Creates a container to resolve SubscriptionSession + let container = DependencyContainer(configuration: configuration) + let listener = SubscriptionListener() + + // Registers mock URL session before retrieving SubscriptionSession + container.register( + value: try! MockURLSession.mockSession(for: responses, raw: dataResource).session!, + forKey: HTTPSubscribeSessionDependencyKey.self + ) + + // Adds a single listener and returns the output to perform further tests + let resolvedSession = container.subscriptionSession + resolvedSession.add(listener) + + return MockResult( + subscriptionSession: resolvedSession, + listener: listener + ) + } +} + // MARK: - Message Response extension SubscribeRouterTests { @@ -203,35 +228,24 @@ extension SubscribeRouterTests { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in let messageExpect = XCTestExpectation(description: "Message Event") let statusExpect = XCTestExpectation(description: "Status Event") + let mockResponses = ["subscription_handshake_success", "subscription_message_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_message_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() - - listener.didReceiveMessage = { [weak self] message in + mockResult.listener.didReceiveMessage = { [weak self, mockResult] message in XCTAssertEqual(message.channel, self?.testChannel) XCTAssertEqual(message.payload.stringOptional, "Test Message") - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() messageExpect.fulfill() } - - listener.didReceiveStatus = { status in + mockResult.listener.didReceiveStatus = { status in if let status = try? status.get(), status == .disconnected { statusExpect.fulfill() } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [messageExpect, statusExpect], timeout: 1.0) } } @@ -244,40 +258,29 @@ extension SubscribeRouterTests { func testSubscribe_Presence() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_presence_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let presenceExpect = XCTestExpectation(description: "Presence Event") let statusExpect = XCTestExpectation(description: "Status Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_presence_success", "cancelled"] - ).session - else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() - listener.didReceivePresence = { [weak self] presence in + mockResult.listener.didReceivePresence = { [weak self, mockResult] presence in XCTAssertEqual(presence.channel, self?.testChannel) XCTAssertEqual(presence.actions, [ .join(uuids: ["db9c5e39-7c95-40f5-8d71-125765b6f561", "vqwqvae39-7c95-40f5-8d71-25234165142"]), .leave(uuids: ["234vq2343-7c95-40f5-8d71-125765b6f561", "42vvsge39-7c95-40f5-8d71-25234165142"]) ]) - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() presenceExpect.fulfill() } - - listener.didReceiveStatus = { status in + mockResult.listener.didReceiveStatus = { status in if let status = try? status.get(), status == .disconnected { statusExpect.fulfill() } } - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) + + defer { mockResult.listener.cancel() } wait(for: [presenceExpect, statusExpect], timeout: 1.0) } } @@ -290,39 +293,27 @@ extension SubscribeRouterTests { func testSubscribe_Signal() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_signal_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let signalExpect = XCTestExpectation(description: "Signal Event") let statusExpect = XCTestExpectation(description: "Status Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_signal_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() - listener.didReceiveSignal = { [weak self] signal in + mockResult.listener.didReceiveSignal = { [weak self, mockResult] signal in XCTAssertEqual(signal.channel, self?.testChannel) XCTAssertEqual(signal.publisher, "TestUser") XCTAssertEqual(signal.payload.stringOptional, "Test Signal") - - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() signalExpect.fulfill() } - - listener.didReceiveStatus = { status in + mockResult.listener.didReceiveStatus = { status in if let status = try? status.get(), status == .disconnected { statusExpect.fulfill() } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [signalExpect, statusExpect], timeout: 1.0) } } @@ -336,37 +327,31 @@ extension SubscribeRouterTests { func testSubscribe_UUIDMetadata_Set() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_uuidSet_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let objectExpect = XCTestExpectation(description: "Object Event") let statusExpect = XCTestExpectation(description: "Status Event") let objectListenerExpect = XCTestExpectation(description: "Object Listener Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_uuidSet_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let baseUser = PubNubUUIDMetadataBase(metadataId: "TestUserID", name: "Not Real Name") + + let baseUser = PubNubUUIDMetadataBase( + metadataId: "TestUserID", + name: "Not Real Name" + ) let patchedObjectUser = PubNubUUIDMetadataBase( metadataId: "TestUserID", name: "Test Name", type: "Test Type", status: "Test Status", updated: DateFormatter.iso8601.date(from: "2019-10-06T01:55:50.645685Z"), eTag: "UserUpdateEtag" ) - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() - listener.didReceiveSubscription = { event in + mockResult.listener.didReceiveSubscription = { event in switch event { case let .connectionStatusChanged(status): if status == .disconnected { statusExpect.fulfill() } case let .uuidMetadataSet(changeset): - XCTAssertEqual( - try? changeset.apply(to: baseUser).transcode(), patchedObjectUser - ) + XCTAssertEqual(try? changeset.apply(to: baseUser).transcode(), patchedObjectUser) objectExpect.fulfill() case let .subscriptionChanged(change): switch change { @@ -381,29 +366,25 @@ extension SubscribeRouterTests { XCTFail("Incorrect Event Received \(event)") } } - - listener.didReceiveObjectMetadataEvent = { event in + mockResult.listener.didReceiveObjectMetadataEvent = { [mockResult] event in switch event { case let .setUUID(changeset): XCTAssertEqual(changeset.metadataId, "TestUserID") - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() objectListenerExpect.fulfill() default: XCTFail("Incorrect Event Received") } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [objectExpect, statusExpect, objectListenerExpect], timeout: 1.0) } } } - + // swiftlint:disable:next cyclomatic_complexity func testSubscribe_UUIDMetadata_Removed() { for configuration in [config, eeEnabledConfig] { @@ -411,17 +392,10 @@ extension SubscribeRouterTests { let objectExpect = XCTestExpectation(description: "Object Event") let statusExpect = XCTestExpectation(description: "Status Event") let objectListenerExpect = XCTestExpectation(description: "Object Listener Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_uuidRemove_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() + let mockResponses = ["subscription_handshake_success", "subscription_uuidRemove_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) - listener.didReceiveSubscription = { event in + mockResult.listener.didReceiveSubscription = { event in switch event { case let .connectionStatusChanged(status): if status == .disconnected { @@ -443,45 +417,40 @@ extension SubscribeRouterTests { XCTFail("Incorrect Event Received") } } - - listener.didReceiveObjectMetadataEvent = { event in + mockResult.listener.didReceiveObjectMetadataEvent = { [mockResult] event in switch event { case let .removedUUID(metadataId): XCTAssertEqual(metadataId, "TestUserID") - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() objectListenerExpect.fulfill() default: XCTFail("Incorrect Event Received") } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [objectExpect, statusExpect, objectListenerExpect], timeout: 1.0) } } } - + // swiftlint:disable:next function_body_length func testSubscribe_ChannelMetadata_Set() { for configuration in [config, eeEnabledConfig] { + let mockResponses = ["subscription_handshake_success", "subscription_channelSet_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) + XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in let objectExpect = XCTestExpectation(description: "Object Event") let statusExpect = XCTestExpectation(description: "Status Event") let objectListenerExpect = XCTestExpectation(description: "Object Listener Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_channelSet_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - + let baseChannel = PubNubChannelMetadataBase( - metadataId: "TestSpaceID", name: "Not Real Name", type: "someType" + metadataId: "TestSpaceID", + name: "Not Real Name", + type: "someType" ) let patchedChannel = PubNubChannelMetadataBase( metadataId: "TestSpaceID", @@ -490,71 +459,52 @@ extension SubscribeRouterTests { updated: DateFormatter.iso8601.date(from: "2019-10-06T01:55:50.645685Z"), eTag: "SpaceUpdateEtag" ) - - let subscription = SubscribeSessionFactory.shared.getSession(from: config, with: session) - let listener = SubscriptionListener() - listener.didReceiveSubscription = { event in + mockResult.listener.didReceiveSubscription = { event in switch event { case let .connectionStatusChanged(status): if status == .disconnected { statusExpect.fulfill() } case let .channelMetadataSet(changeset): - XCTAssertEqual( - try? changeset.apply(to: baseChannel).transcode(), patchedChannel - ) + XCTAssertEqual(try? changeset.apply(to: baseChannel).transcode(), patchedChannel) objectExpect.fulfill() case let .subscriptionChanged(change): - switch change { - default: - break - } + break default: XCTFail("Incorrect Event Received") } } - - listener.didReceiveObjectMetadataEvent = { [unowned subscription] event in + mockResult.listener.didReceiveObjectMetadataEvent = { [mockResult] event in switch event { case let .setChannel(changeset): XCTAssertEqual(changeset.metadataId, "TestSpaceID") - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() objectListenerExpect.fulfill() default: XCTFail("Incorrect Event Received") } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [objectExpect, statusExpect, objectListenerExpect], timeout: 1.0) } } } - + // swiftlint:disable:next cyclomatic_complexity func testSubscribe_ChannelMetadata_Removed() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_channelRemove_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let objectExpect = XCTestExpectation(description: "Object Event") let statusExpect = XCTestExpectation(description: "Status Event") let objectListenerExpect = XCTestExpectation(description: "Object Listener Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_channelRemove_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() - listener.didReceiveSubscription = { event in + mockResult.listener.didReceiveSubscription = { event in switch event { case let .connectionStatusChanged(status): if status == .disconnected { @@ -576,53 +526,47 @@ extension SubscribeRouterTests { XCTFail("Incorrect Event Received") } } - - listener.didReceiveObjectMetadataEvent = { event in + mockResult.listener.didReceiveObjectMetadataEvent = { [mockResult] event in switch event { case let .removedChannel(metadataId: metadataId): XCTAssertEqual(metadataId, "TestSpaceID") - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() objectListenerExpect.fulfill() default: XCTFail("Incorrect Event Received") } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [objectExpect, statusExpect, objectListenerExpect], timeout: 1.0) } } } - + // swiftlint:disable:next function_body_length cyclomatic_complexity func testSubscribe_Membership_Set() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_membershipSet_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let objectExpect = XCTestExpectation(description: "Object Event") let statusExpect = XCTestExpectation(description: "Status Event") let objectListenerExpect = XCTestExpectation(description: "Object Listener Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_membershipSet_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) + let channel = PubNubChannelMetadataBase(metadataId: "TestSpaceID") let uuid = PubNubUUIDMetadataBase(metadataId: "TestUserID") + let testMembership = PubNubMembershipMetadataBase( - uuidMetadataId: "TestUserID", channelMetadataId: "TestSpaceID", uuid: uuid, channel: channel, custom: ["something": true], + uuidMetadataId: "TestUserID", + channelMetadataId: "TestSpaceID", + uuid: uuid, channel: channel, + custom: ["something": true], updated: DateFormatter.iso8601.date(from: "2019-10-05T23:35:38.457823306Z"), eTag: "TestETag" ) - let listener = SubscriptionListener() - listener.didReceiveSubscription = { [unowned self] event in + mockResult.listener.didReceiveSubscription = { [unowned self] event in switch event { case let .connectionStatusChanged(status): if status == .disconnected { @@ -644,53 +588,44 @@ extension SubscribeRouterTests { XCTFail("Incorrect Event Received \(event)") } } - - listener.didReceiveObjectMetadataEvent = { event in + mockResult.listener.didReceiveObjectMetadataEvent = { [mockResult] event in switch event { case let .setMembership(membership): XCTAssertEqual(try? membership.transcode(), testMembership) - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() objectListenerExpect.fulfill() default: XCTFail("Incorrect Event Received \(event)") } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [objectExpect, statusExpect, objectListenerExpect], timeout: 1.0) } } } - + // swiftlint:disable:next function_body_length cyclomatic_complexity func testSubscribe_Membership_Removed() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_membershipRemove_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let objectExpect = XCTestExpectation(description: "Object Event") let statusExpect = XCTestExpectation(description: "Status Event") let objectListenerExpect = XCTestExpectation(description: "Object Listener Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_membershipRemove_success", "leave_success"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: config, with: session) let channel = PubNubChannelMetadataBase(metadataId: "TestSpaceID") let uuid = PubNubUUIDMetadataBase(metadataId: "TestUserID") + let testMembership = PubNubMembershipMetadataBase( - uuidMetadataId: "TestUserID", channelMetadataId: "TestSpaceID", uuid: uuid, channel: channel, + uuidMetadataId: "TestUserID", channelMetadataId: "TestSpaceID", + uuid: uuid, channel: channel, updated: DateFormatter.iso8601.date(from: "2019-10-05T23:35:38.457823306Z"), eTag: "TestETag" ) - - let listener = SubscriptionListener() - listener.didReceiveSubscription = { [weak self] event in + + mockResult.listener.didReceiveSubscription = { [weak self] event in switch event { case let .connectionStatusChanged(status): if status == .disconnected { @@ -712,24 +647,20 @@ extension SubscribeRouterTests { XCTFail("Incorrect Event Received \(event)") } } - - listener.didReceiveObjectMetadataEvent = { [unowned subscription] event in + mockResult.listener.didReceiveObjectMetadataEvent = { [mockResult] event in switch event { case let .removedMembership(membership): XCTAssertEqual(try? membership.transcode(), testMembership) - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() objectListenerExpect.fulfill() default: XCTFail("Incorrect Event Received \(event)") } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [objectExpect, statusExpect, objectListenerExpect], timeout: 1.0) } } @@ -746,17 +677,10 @@ extension SubscribeRouterTests { let actionExpect = XCTestExpectation(description: "Message Action Event") let statusExpect = XCTestExpectation(description: "Status Event") let actionListenerExpect = XCTestExpectation(description: "Action Listener Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_addMessageAction_success", "leave_success"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() + let mockResponses = ["subscription_handshake_success", "subscription_addMessageAction_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) - listener.didReceiveSubscription = { [weak self] event in + mockResult.listener.didReceiveSubscription = { [weak self] event in switch event { case let .connectionStatusChanged(status): if status == .disconnected { @@ -778,47 +702,36 @@ extension SubscribeRouterTests { XCTFail("Incorrect Event Received \(event)") } } - - listener.didReceiveMessageAction = { [weak self] event in + mockResult.listener.didReceiveMessageAction = { [weak self, mockResult] event in switch event { case let .added(action): XCTAssertEqual(try? action.transcode(), self?.testAction) - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() actionListenerExpect.fulfill() default: XCTFail("Incorrect Event Received") } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [actionExpect, statusExpect, actionListenerExpect], timeout: 1.0) } } } - + // swiftlint:disable:next cyclomatic_complexity function_body_length func testSubscribe_MessageAction_Removed() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_removeMessageAction_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let actionExpect = XCTestExpectation(description: "Message Action Event") let statusExpect = XCTestExpectation(description: "Status Event") let actionListenerExpect = XCTestExpectation(description: "Action Listener Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_removeMessageAction_success", "leave_success"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() - listener.didReceiveSubscription = { [weak self] event in + mockResult.listener.didReceiveSubscription = { [weak self] event in switch event { case let .connectionStatusChanged(status): if status == .disconnected { @@ -840,24 +753,20 @@ extension SubscribeRouterTests { XCTFail("Incorrect Event Received") } } - - listener.didReceiveMessageAction = { [weak self] event in + mockResult.listener.didReceiveMessageAction = { [weak self, mockResult] event in switch event { case let .removed(action): XCTAssertEqual(try? action.transcode(), self?.testAction) - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() actionListenerExpect.fulfill() default: XCTFail("Incorrect Event Received") } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [actionExpect, statusExpect, actionListenerExpect], timeout: 1.0) } } @@ -870,54 +779,44 @@ extension SubscribeRouterTests { func testSubscribe_Mixed() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_mixed_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let messageExpect = XCTestExpectation(description: "Message Event") let presenceExpect = XCTestExpectation(description: "Presence Event") let signalExpect = XCTestExpectation(description: "Signal Event") let statusExpect = XCTestExpectation(description: "Status Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_mixed_success", "leave_success"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - - let listener = SubscriptionListener() var payloadCount = 0 - listener.didReceiveSubscription = { _ in + + mockResult.listener.didReceiveSubscription = { [mockResult] _ in payloadCount += 1 if payloadCount == 7 { - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() } } - - listener.didReceiveMessage = { [weak self] message in + mockResult.listener.didReceiveMessage = { [weak self] message in XCTAssertEqual(message.channel, self?.testChannel) XCTAssertEqual(message.payload.stringOptional, "Test Message") messageExpect.fulfill() } - listener.didReceivePresence = { [weak self] presence in + mockResult.listener.didReceivePresence = { [weak self] presence in XCTAssertEqual(presence.channel, self?.testChannel) XCTAssertEqual(presence.actions, [.join(uuids: ["db9c5e39-7c95-40f5-8d71-125765b6f561"])]) presenceExpect.fulfill() } - listener.didReceiveSignal = { [weak self] signal in + mockResult.listener.didReceiveSignal = { [weak self] signal in XCTAssertEqual(signal.channel, self?.testChannel) XCTAssertEqual(signal.payload.stringOptional, "Test Signal") signalExpect.fulfill() } - listener.didReceiveStatus = { status in + mockResult.listener.didReceiveStatus = { status in if let status = try? status.get(), status == .disconnected { statusExpect.fulfill() } } - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) + + defer { mockResult.listener.cancel() } wait(for: [signalExpect, statusExpect], timeout: 1.0) } } @@ -932,25 +831,17 @@ extension SubscribeRouterTests { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in // swiftlint:disable:next line_length let corruptBase64Response = "eyJ0Ijp7InQiOiIxNTkxMjE4MzQ0MTUyNjM1MCIsInIiOjF9LCJtIjpbeyJhIjoiMyIsImYiOjUxMiwicCI6eyJ0IjoiMTU5MTIxODM0NDE1NTQyMDAiLCJyIjoxfSwiayI6ImRlbW8tMzYiLCJjIjoic3dpZnRJbnZhbGlkSlNPTi7/IiwiZCI6ImhlbGxvIiwiYiI6InN3aWZ0SW52YWxpZEpTT04uKiJ9XX0=" - + guard let corruptedData = Data(base64Encoded: corruptBase64Response) else { return XCTFail("Could not create Data from String") } - + + let mockResponses = ["subscription_handshake_success", "subscription_invalid_json", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, raw: [corruptedData], and: configuration) let errorExpect = XCTestExpectation(description: "Error Event") let statusExpect = XCTestExpectation(description: "Status Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "cancelled"], - raw: [corruptedData] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() - listener.didReceiveSubscription = { event in + mockResult.listener.didReceiveSubscription = { [mockResult] event in switch event { case .subscriptionChanged: break @@ -960,19 +851,16 @@ extension SubscribeRouterTests { } case let .subscribeError(error): XCTAssertEqual(error.reason, .jsonDataDecodingFailure) - subscription.unsubscribeAll() + mockResult.subscriptionSession.unsubscribeAll() errorExpect.fulfill() default: XCTFail("Unexpected event received \(event)") } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [errorExpect, statusExpect], timeout: 1.0, enforceOrder: true) } } @@ -985,19 +873,13 @@ extension SubscribeRouterTests { func testUnsubscribe() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_mixed_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let statusExpect = XCTestExpectation(description: "Status Event") statusExpect.expectedFulfillmentCount = 2 - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_mixed_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) - let listener = SubscriptionListener() + statusExpect.assertForOverFulfill = true - listener.didReceiveSubscription = { [unowned self] event in + mockResult.listener.didReceiveSubscription = { [unowned self, mockResult] event in switch event { case let .subscriptionChanged(change): switch change { @@ -1011,8 +893,8 @@ extension SubscribeRouterTests { case let .connectionStatusChanged(status): switch status { case .connected: - subscription.unsubscribe(from: [self.testChannel]) - XCTAssertEqual(subscription.subscribedChannels, []) + mockResult.subscriptionSession.unsubscribe(from: [self.testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, []) statusExpect.fulfill() case .disconnected: statusExpect.fulfill() @@ -1023,34 +905,24 @@ extension SubscribeRouterTests { break } } + mockResult.subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, [testChannel]) - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [statusExpect], timeout: 1.0) } } } - + func testUnsubscribeAll() { for configuration in [config, eeEnabledConfig] { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in + let mockResponses = ["subscription_handshake_success", "subscription_mixed_success", "cancelled"] + let mockResult = mockSubscriptionSession(with: mockResponses, and: configuration) let statusExpect = XCTestExpectation(description: "Status Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_mixed_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) let otherChannel = "OtherChannel" - let listener = SubscriptionListener() - listener.didReceiveSubscription = { [weak self] event in + mockResult.listener.didReceiveSubscription = { [weak self, mockResult] event in switch event { case let .subscriptionChanged(change): switch change { @@ -1066,8 +938,8 @@ extension SubscribeRouterTests { case let .connectionStatusChanged(status): switch status { case .connected: - subscription.unsubscribeAll() - XCTAssertEqual(subscription.subscribedChannels, []) + mockResult.subscriptionSession.unsubscribeAll() + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, []) statusExpect.fulfill() case .disconnected: statusExpect.fulfill() @@ -1079,17 +951,13 @@ extension SubscribeRouterTests { } } - subscription.add(listener) - subscription.subscribe(to: [testChannel, otherChannel]) - - XCTAssertTrue(subscription.subscribedChannels.contains(testChannel)) - XCTAssertTrue(subscription.subscribedChannels.contains(otherChannel)) - - subscription.unsubscribeAll() + mockResult.subscriptionSession.subscribe(to: [testChannel, otherChannel]) + XCTAssertTrue(mockResult.subscriptionSession.subscribedChannels.contains(testChannel)) + XCTAssertTrue(mockResult.subscriptionSession.subscribedChannels.contains(otherChannel)) + mockResult.subscriptionSession.unsubscribeAll() + XCTAssertEqual(mockResult.subscriptionSession.subscribedChannels, []) - XCTAssertEqual(subscription.subscribedChannels, []) - - defer { listener.cancel() } + defer { mockResult.listener.cancel() } wait(for: [statusExpect], timeout: 1.0) } } @@ -1101,12 +969,8 @@ extension SubscribeRouterTests { extension SubscribeRouterTests { func testSubscribe_DecryptNonEncryptedMessage() { let messageExpect = XCTestExpectation(description: "Message Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_message_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } + messageExpect.assertForOverFulfill = true + messageExpect.expectedFulfillmentCount = 1 let config = PubNubConfiguration( publishKey: "pubKey", @@ -1114,22 +978,29 @@ extension SubscribeRouterTests { userId: "userId", cryptoModule: CryptoModule.aesCbcCryptoModule(with: "pubnubenigma") ) - let pubNubWithMockedSession = PubNub( - configuration: config, - subscribeSession: session + let mockResponses = [ + "subscription_handshake_success", + "subscription_message_success", + "cancelled" + ] + let container = DependencyContainer(configuration: config).register( + value: try! MockURLSession.mockSession(for: mockResponses).session, + forKey: HTTPSubscribeSessionDependencyKey.self ) + + let pubnub = PubNub(container: container) let listener = SubscriptionListener() - listener.didReceiveMessage = { [weak self, unowned pubNubWithMockedSession] message in + listener.didReceiveMessage = { [weak self, unowned pubnub] message in XCTAssertEqual(message.channel, self?.testChannel) XCTAssertEqual(message.payload.stringOptional, "Test Message") XCTAssertTrue(message.error?.reason == .decryptionFailure) - pubNubWithMockedSession.unsubscribeAll() + pubnub.unsubscribeAll() messageExpect.fulfill() } - pubNubWithMockedSession.add(listener) - pubNubWithMockedSession.subscribe(to: [testChannel]) + pubnub.add(listener) + pubnub.subscribe(to: [testChannel]) defer { listener.cancel() } wait(for: [messageExpect], timeout: 1.0) @@ -1137,12 +1008,8 @@ extension SubscribeRouterTests { func testSubscribe_DecryptEncryptedMessage() { let messageExpect = XCTestExpectation(description: "Message Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_encrypted_message_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } + messageExpect.assertForOverFulfill = true + messageExpect.expectedFulfillmentCount = 1 let config = PubNubConfiguration( publishKey: "pubKey", @@ -1150,22 +1017,29 @@ extension SubscribeRouterTests { userId: "userId", cryptoModule: CryptoModule.aesCbcCryptoModule(with: "pubnubenigma") ) - let pubNubWithMockedSession = PubNub( - configuration: config, - subscribeSession: session + let mockResponses = [ + "subscription_handshake_success", + "subscription_encrypted_message_success", + "cancelled" + ] + let container = DependencyContainer(configuration: config).register( + value: try! MockURLSession.mockSession(for: mockResponses).session, + forKey: HTTPSubscribeSessionDependencyKey.self ) + + let pubnub = PubNub(container: container) let listener = SubscriptionListener() - listener.didReceiveMessage = { [weak self, unowned pubNubWithMockedSession] message in + listener.didReceiveMessage = { [weak self, unowned pubnub] message in XCTAssertEqual(message.channel, self?.testChannel) XCTAssertEqual(message.payload.stringOptional, "Test Message") XCTAssertNil(message.error) - pubNubWithMockedSession.unsubscribeAll() + pubnub.unsubscribeAll() messageExpect.fulfill() } - pubNubWithMockedSession.add(listener) - pubNubWithMockedSession.subscribe(to: [testChannel]) + pubnub.add(listener) + pubnub.subscribe(to: [testChannel]) defer { listener.cancel() } wait(for: [messageExpect], timeout: 1.0) @@ -1173,12 +1047,8 @@ extension SubscribeRouterTests { func testSubscribe_DecryptEncryptedMessageWithMismatchedKey() { let messageExpect = XCTestExpectation(description: "Message Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_encrypted_message_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } + messageExpect.assertForOverFulfill = true + messageExpect.expectedFulfillmentCount = 1 let config = PubNubConfiguration( publishKey: "pubKey", @@ -1186,23 +1056,29 @@ extension SubscribeRouterTests { userId: "userId", cryptoModule: CryptoModule.aesCbcCryptoModule(with: "lorem-ipsum-dolor-sit-amet") ) - let pubNubWithMockedSession = PubNub( - configuration: config, - subscribeSession: session + let mockResponses = [ + "subscription_handshake_success", + "subscription_encrypted_message_success", + "cancelled" + ] + let container = DependencyContainer(configuration: config).register( + value: try! MockURLSession.mockSession(for: mockResponses).session, + forKey: HTTPSubscribeSessionDependencyKey.self ) + let pubnub = PubNub(container: container) let listener = SubscriptionListener() - listener.didReceiveMessage = { [weak self, unowned pubNubWithMockedSession] message in + listener.didReceiveMessage = { [weak self, unowned pubnub] message in XCTAssertEqual(message.channel, self?.testChannel) XCTAssertEqual(message.payload.stringOptional, "UE5FRAFBQ1JIEGOmGQMIMXD+91V+5hTxm7p7uEUhEEYohYLQz5fEGITC") XCTAssertTrue(message.error?.reason == .decryptionFailure) - pubNubWithMockedSession.unsubscribeAll() + pubnub.unsubscribeAll() messageExpect.fulfill() } - pubNubWithMockedSession.add(listener) - pubNubWithMockedSession.subscribe(to: [testChannel]) + pubnub.add(listener) + pubnub.subscribe(to: [testChannel]) defer { listener.cancel() } wait(for: [messageExpect], timeout: 1.0) diff --git a/Tests/PubNubTests/Subscription/SubscribeSessionFactoryTests.swift b/Tests/PubNubTests/Subscription/SubscribeSessionFactoryTests.swift index be09221a..6d9cb92d 100644 --- a/Tests/PubNubTests/Subscription/SubscribeSessionFactoryTests.swift +++ b/Tests/PubNubTests/Subscription/SubscribeSessionFactoryTests.swift @@ -14,19 +14,30 @@ import XCTest class SubscribeSessionFactoryTests: XCTestCase { func testLoggingSameInstance() { let config = PubNubConfiguration(publishKey: nil, subscribeKey: "FakeKey", userId: UUID().uuidString) - let first = SubscribeSessionFactory.shared.getSession(from: config) - let second = SubscribeSessionFactory.shared.getSession(from: config) + let dependencyContainer = DependencyContainer(configuration: config) + let first = dependencyContainer.subscriptionSession + let second = dependencyContainer.subscriptionSession XCTAssertEqual(first.uuid, second.uuid) } func testMutlipleInstances() { - let config = PubNubConfiguration(publishKey: nil, subscribeKey: "FakeKey", userId: UUID().uuidString) - var newConfig = PubNubConfiguration(publishKey: nil, subscribeKey: "OtherKey", userId: UUID().uuidString) - newConfig.authKey = "SomeNewKey" - - let first = SubscribeSessionFactory.shared.getSession(from: config) - let third = SubscribeSessionFactory.shared.getSession(from: newConfig) + let config = PubNubConfiguration( + publishKey: nil, + subscribeKey: "FakeKey", + userId: UUID().uuidString + ) + let newConfig = PubNubConfiguration( + publishKey: nil, + subscribeKey: "OtherKey", + userId: UUID().uuidString, + authKey: "SomeNewKey" + ) + + let dependencyContainer = DependencyContainer(configuration: config) + let nextDependencyContainer = DependencyContainer(configuration: config) + let first = dependencyContainer.subscriptionSession + let third = nextDependencyContainer.subscriptionSession XCTAssertNotEqual(first.uuid, third.uuid) } diff --git a/Tests/PubNubTests/Subscription/SubscriptionSessionTests.swift b/Tests/PubNubTests/Subscription/SubscriptionSessionTests.swift index 6ff2a66f..70c00a63 100644 --- a/Tests/PubNubTests/Subscription/SubscriptionSessionTests.swift +++ b/Tests/PubNubTests/Subscription/SubscriptionSessionTests.swift @@ -48,42 +48,33 @@ class SubscriptionSessionTests: XCTestCase { XCTContext.runActivity(named: "Testing with enableEventEngine=\(configuration.enableEventEngine)") { _ in let messageExpect = XCTestExpectation(description: "Message Event") let statusExpect = XCTestExpectation(description: "Status Event") - - guard let session = try? MockURLSession.mockSession( - for: ["subscription_handshake_success", "subscription_message_success", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: configuration, with: session) + let mockResponses = ["subscription_handshake_success", "subscription_message_success", "cancelled"] + let subscriptionSession = mockSubscriptionSession(with: mockResponses, and: configuration) let listener = SubscriptionListener() listener.didReceiveMessage = { message in XCTAssertEqual( - subscription.previousTokenResponse, + subscriptionSession.previousTokenResponse, SubscribeCursor(timetoken: 15614817397807903, region: 2) ) - subscription.unsubscribeAll() + subscriptionSession.unsubscribeAll() messageExpect.fulfill() } listener.didReceiveStatus = { status in if let status = try? status.get(), status == .connected { XCTAssertEqual( - subscription.previousTokenResponse, + subscriptionSession.previousTokenResponse, SubscribeCursor(timetoken: 16873352451141050, region: 42) ) statusExpect.fulfill() } } - - subscription.add(listener) - subscription.subscribe(to: [testChannel]) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) + subscriptionSession.add(listener) + subscriptionSession.subscribe(to: [testChannel]) + XCTAssertEqual(subscriptionSession.subscribedChannels, [testChannel]) defer { listener.cancel() } wait(for: [messageExpect, statusExpect], timeout: 1.0) - } } } @@ -95,32 +86,25 @@ class SubscriptionSessionTests: XCTestCase { statusExpect.assertForOverFulfill = true statusExpect.expectedFulfillmentCount = configuration.enableEventEngine ? 2 : 1 - guard let session = try? MockURLSession.mockSession( - for: ["badURL", "cancelled"] - ).session else { - return XCTFail("Could not create mock url session") - } - - let subscription = SubscribeSessionFactory.shared.getSession(from: config, with: session) + let mockResponses = ["badURL", "cancelled"] + let subscriptionSession = mockSubscriptionSession(with: mockResponses, and: configuration) let listener = SubscriptionListener() - listener.didReceiveStatus = { [unowned subscription] status in + listener.didReceiveStatus = { [unowned subscriptionSession] status in if case .failure(_) = status { - XCTAssertNil(subscription.previousTokenResponse) + XCTAssertNil(subscriptionSession.previousTokenResponse) statusExpect.fulfill() } if case .success(let newStatus) = status { if newStatus == .connectionError(PubNubError(.invalidURL)) { - XCTAssertNil(subscription.previousTokenResponse) + XCTAssertNil(subscriptionSession.previousTokenResponse) statusExpect.fulfill() } } } - - subscription.add(listener) - subscription.subscribe(to: [testChannel], at: SubscribeCursor(timetoken: 123456, region: 1)) - - XCTAssertEqual(subscription.subscribedChannels, [testChannel]) + subscriptionSession.add(listener) + subscriptionSession.subscribe(to: [testChannel], at: SubscribeCursor(timetoken: 123456, region: 1)) + XCTAssertEqual(subscriptionSession.subscribedChannels, [testChannel]) defer { listener.cancel() } wait(for: [statusExpect], timeout: 1.0) @@ -128,3 +112,18 @@ class SubscriptionSessionTests: XCTestCase { } } } + +fileprivate extension SubscriptionSessionTests { + func mockSubscriptionSession( + with responses: [String], + and configuration: PubNubConfiguration + ) -> SubscriptionSession { + let dependencyContainer = DependencyContainer(configuration: configuration) + let mockURLSession = try! MockURLSession.mockSession(for: responses).session + + return dependencyContainer.register( + value: mockURLSession, + forKey: HTTPSubscribeSessionDependencyKey.self + ).subscriptionSession + } +} From e5877d26833330b9ddd2283b899d26ed5a026296 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Fri, 8 Mar 2024 12:25:27 +0100 Subject: [PATCH 03/15] Fixed some tests --- PubNub.xcodeproj/project.pbxproj | 4 - .../DependencyContainer.swift | 159 ++++++++++++++---- .../EventEngine/Core/EventEngineFactory.swift | 47 ------ Sources/PubNub/PubNub.swift | 4 +- .../PubNubEventEngineTestsHelpers.swift | 5 +- ...ubNubPresenceEngineContractTestSteps.swift | 72 +++----- ...NubSubscribeEngineContractTestsSteps.swift | 70 +++----- 7 files changed, 170 insertions(+), 191 deletions(-) delete mode 100644 Sources/PubNub/EventEngine/Core/EventEngineFactory.swift diff --git a/PubNub.xcodeproj/project.pbxproj b/PubNub.xcodeproj/project.pbxproj index 68291f41..dfff811f 100644 --- a/PubNub.xcodeproj/project.pbxproj +++ b/PubNub.xcodeproj/project.pbxproj @@ -371,7 +371,6 @@ 3D389FE22B35AF4A006928E7 /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FC42B35AF4A006928E7 /* Dispatcher.swift */; }; 3D389FE32B35AF4A006928E7 /* EffectHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FC52B35AF4A006928E7 /* EffectHandler.swift */; }; 3D389FE42B35AF4A006928E7 /* EventEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FC62B35AF4A006928E7 /* EventEngine.swift */; }; - 3D389FE52B35AF4A006928E7 /* EventEngineFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FC72B35AF4A006928E7 /* EventEngineFactory.swift */; }; 3D389FE62B35AF4A006928E7 /* EmitMessagesEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FCA2B35AF4A006928E7 /* EmitMessagesEffect.swift */; }; 3D389FE72B35AF4A006928E7 /* EmitStatusEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FCB2B35AF4A006928E7 /* EmitStatusEffect.swift */; }; 3D389FE82B35AF4A006928E7 /* SubscribeEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3D389FCC2B35AF4A006928E7 /* SubscribeEffects.swift */; }; @@ -969,7 +968,6 @@ 3D389FC42B35AF4A006928E7 /* Dispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatcher.swift; sourceTree = ""; }; 3D389FC52B35AF4A006928E7 /* EffectHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectHandler.swift; sourceTree = ""; }; 3D389FC62B35AF4A006928E7 /* EventEngine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventEngine.swift; sourceTree = ""; }; - 3D389FC72B35AF4A006928E7 /* EventEngineFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventEngineFactory.swift; sourceTree = ""; }; 3D389FCA2B35AF4A006928E7 /* EmitMessagesEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmitMessagesEffect.swift; sourceTree = ""; }; 3D389FCB2B35AF4A006928E7 /* EmitStatusEffect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmitStatusEffect.swift; sourceTree = ""; }; 3D389FCC2B35AF4A006928E7 /* SubscribeEffects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscribeEffects.swift; sourceTree = ""; }; @@ -2025,7 +2023,6 @@ 3D389FC42B35AF4A006928E7 /* Dispatcher.swift */, 3D389FC52B35AF4A006928E7 /* EffectHandler.swift */, 3D389FC62B35AF4A006928E7 /* EventEngine.swift */, - 3D389FC72B35AF4A006928E7 /* EventEngineFactory.swift */, ); path = Core; sourceTree = ""; @@ -3571,7 +3568,6 @@ 3DD1FB992B5A7804005A14E3 /* PubNubPresenceStateContainer.swift in Sources */, 3D758DC82AB06A12005D2B36 /* CryptoInputStream.swift in Sources */, 35E4604F234B8B9D005D04AE /* ErrorDescription.swift in Sources */, - 3D389FE52B35AF4A006928E7 /* EventEngineFactory.swift in Sources */, 35089A0922E3C08D002BCC94 /* Error+PubNub.swift in Sources */, 3534D4E422C57659008E89FA /* PublishRouter.swift in Sources */, 35EE358C22E26A4D00E3F081 /* HTTPURLResponse+PubNub.swift in Sources */, diff --git a/Sources/PubNub/DependencyContainer/DependencyContainer.swift b/Sources/PubNub/DependencyContainer/DependencyContainer.swift index e554908e..ff696cfd 100644 --- a/Sources/PubNub/DependencyContainer/DependencyContainer.swift +++ b/Sources/PubNub/DependencyContainer/DependencyContainer.swift @@ -10,7 +10,7 @@ import Foundation -// A protocol that represents a unique key for each dependency. Each type conforming to DependencyKey +// A protocol that represents a unique key for each dependency. Each type conforming to `DependencyKey` // represents a distinct dependency. protocol DependencyKey { // A value associated with a given `DependencyKey` @@ -53,6 +53,9 @@ class DependencyContainer { } } +typealias SubscribeEngine = EventEngine<(any SubscribeState), Subscribe.Event, Subscribe.Invocation, Subscribe.Dependencies> +typealias PresenceEngine = EventEngine<(any PresenceState), Presence.Event, Presence.Invocation, Presence.Dependencies> + extension DependencyContainer { var configuration: PubNubConfiguration { self[PubNubConfigurationDependencyKey.self] @@ -70,23 +73,15 @@ extension DependencyContainer { self[SubscriptionSessionDependencyKey.self] } - var httpSession: SessionReplaceable { - resolveSession( - session: self[HTTPSessionDependencyKey.self], - with: [instanceIDOperator].compactMap { $0 } - ) - } - var presenceStateContainer: PubNubPresenceStateContainer { self[PresenceStateContainerDependencyKey.self] } - fileprivate var automaticRetry: RequestOperator? { - configuration.automaticRetry - } - - fileprivate var instanceIDOperator: RequestOperator? { - configuration.useInstanceId ? InstanceIdOperator(instanceID: instanceID.uuidString) : nil + var defaultHTTPSession: SessionReplaceable { + resolveSession( + session: self[DefaultHTTPSessionDependencyKey.self], + with: [automaticRetry].compactMap { $0 } + ) } fileprivate var httpSubscribeSession: SessionReplaceable { @@ -103,25 +98,73 @@ extension DependencyContainer { ) } + fileprivate var automaticRetry: RequestOperator? { + configuration.automaticRetry + } + + fileprivate var instanceIDOperator: RequestOperator? { + configuration.useInstanceId ? InstanceIdOperator(instanceID: instanceID.uuidString) : nil + } + fileprivate var httpSubscribeSessionQueue: DispatchQueue { self[HTTPSubscribeSessionQueueDependencyKey.self] } - fileprivate var subscribeEventEngine: SubscribeEngine { + fileprivate var subscribeEngine: SubscribeEngine { self[SubscribeEventEngineDependencyKey.self] } - fileprivate var subscribeEffectFactory: SubscribeEffectFactory { - self[SubscribeEffectFactoryDependencyKey.self] + fileprivate var subscribeEngineTransition: some TransitionProtocol< + SubscribeState, + Subscribe.Event, + Subscribe.Invocation + > { + self[SubscribeTransitionDependencyKey.self] } - fileprivate var presenceEffectFactory: PresenceEffectFactory { - self[PresenceEffectFactoryDependencyKey.self] + fileprivate var subscribeEngineEffectDispatcher: some Dispatcher< + Subscribe.Invocation, + Subscribe.Event, + Subscribe.Dependencies + > { + self[SubscribeEffectDispatcherDependencyKey.self] + } + + fileprivate var subscribeEngineEffectFactory: some EffectHandlerFactory< + Subscribe.Invocation, + Subscribe.Event, + Subscribe.Dependencies + > { + self[SubscribeEffectFactoryDependencyKey.self] } fileprivate var presenceEngine: PresenceEngine { self[PresenceEventEngineDependencyKey.self] } + + fileprivate var presenceEngineTransition: some TransitionProtocol< + PresenceState, + Presence.Event, + Presence.Invocation + > { + self[PresenceTransitionDependencyKey.self] + } + + fileprivate var presenceEngineEffectDispatcher: some Dispatcher< + Presence.Invocation, + Presence.Event, + Presence.Dependencies + > { + self[PresenceEffectDispatcherDependencyKey.self] + } + + fileprivate var presenceEngineEffectFactory: some EffectHandlerFactory< + Presence.Invocation, + Presence.Event, + Presence.Dependencies + > { + self[PresenceEffectFactoryDependencyKey.self] + } } fileprivate extension DependencyContainer { @@ -148,9 +191,9 @@ struct PubNubInstanceIDDependencyKey: DependencyKey { } } -// MARK: - HTTPSession +// MARK: - HTTPSessions -struct HTTPSessionDependencyKey: DependencyKey { +struct DefaultHTTPSessionDependencyKey: DependencyKey { static func value(from container: DependencyContainer) -> SessionReplaceable { HTTPSession(configuration: .pubnub) } @@ -208,15 +251,19 @@ struct SubscribeEventEngineDependencyKey: DependencyKey { static func value(from container: DependencyContainer) -> SubscribeEngine { SubscribeEngine( state: Subscribe.UnsubscribedState(), - transition: SubscribeTransition(), - dispatcher: EffectDispatcher(factory: container.subscribeEffectFactory), + transition: container.subscribeEngineTransition, + dispatcher: container.subscribeEngineEffectDispatcher, dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: container.configuration)) ) } } struct SubscribeEffectFactoryDependencyKey: DependencyKey { - static func value(from container: DependencyContainer) -> SubscribeEffectFactory { + static func value(from container: DependencyContainer) -> some EffectHandlerFactory< + Subscribe.Invocation, + Subscribe.Event, + Subscribe.Dependencies + > { SubscribeEffectFactory( session: container.httpSubscribeSession, presenceStateContainer: container.presenceStateContainer @@ -224,28 +271,72 @@ struct SubscribeEffectFactoryDependencyKey: DependencyKey { } } -// MARK: PresenceEventEngine +struct SubscribeEffectDispatcherDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> some Dispatcher< + Subscribe.Invocation, + Subscribe.Event, + Subscribe.Dependencies + > { + EffectDispatcher(factory: container.subscribeEngineEffectFactory) + } +} -struct PresenceEffectFactoryDependencyKey: DependencyKey { - static func value(from container: DependencyContainer) -> PresenceEffectFactory { - PresenceEffectFactory( - session: container.httpPresenceSession, - presenceStateContainer: container.presenceStateContainer - ) +struct SubscribeTransitionDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> some TransitionProtocol< + SubscribeState, + Subscribe.Event, + Subscribe.Invocation + > { + SubscribeTransition() } } +// MARK: PresenceEventEngine + struct PresenceEventEngineDependencyKey: DependencyKey { static func value(from container: DependencyContainer) -> PresenceEngine { PresenceEngine( state: Presence.HeartbeatInactive(), - transition: PresenceTransition(configuration: container.configuration), - dispatcher: EffectDispatcher(factory: container.presenceEffectFactory), + transition: container.presenceEngineTransition, + dispatcher: container.presenceEngineEffectDispatcher, dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: container.configuration)) ) } } +struct PresenceEffectFactoryDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> some EffectHandlerFactory< + Presence.Invocation, + Presence.Event, + Presence.Dependencies + > { + PresenceEffectFactory( + session: container.httpPresenceSession, + presenceStateContainer: container.presenceStateContainer + ) + } +} + +struct PresenceEffectDispatcherDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> some Dispatcher< + Presence.Invocation, + Presence.Event, + Presence.Dependencies + > { + EffectDispatcher(factory: container.presenceEngineEffectFactory) + } +} + +struct PresenceTransitionDependencyKey: DependencyKey { + static func value(from container: DependencyContainer) -> some TransitionProtocol< + PresenceState, + Presence.Event, + Presence.Invocation + > { + PresenceTransition(configuration: container.configuration) + } +} + // MARK: - SubscriptionSession struct SubscriptionSessionDependencyKey: DependencyKey { @@ -254,7 +345,7 @@ struct SubscriptionSessionDependencyKey: DependencyKey { return SubscriptionSession( strategy: EventEngineSubscriptionSessionStrategy( configuration: container.configuration, - subscribeEngine: container.subscribeEventEngine, + subscribeEngine: container.subscribeEngine, presenceEngine: container.presenceEngine, presenceStateContainer: container.presenceStateContainer ) diff --git a/Sources/PubNub/EventEngine/Core/EventEngineFactory.swift b/Sources/PubNub/EventEngine/Core/EventEngineFactory.swift deleted file mode 100644 index 0efb70ec..00000000 --- a/Sources/PubNub/EventEngine/Core/EventEngineFactory.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// EventEngineFactory.swift -// -// Copyright (c) PubNub Inc. -// All rights reserved. -// -// This source code is licensed under the license found in the -// LICENSE file in the root directory of this source tree. -// - -import Foundation - -typealias SubscribeEngine = EventEngine<(any SubscribeState), Subscribe.Event, Subscribe.Invocation, Subscribe.Dependencies> -typealias PresenceEngine = EventEngine<(any PresenceState), Presence.Event, Presence.Invocation, Presence.Dependencies> - -typealias SubscribeTransitions = TransitionProtocol<(any SubscribeState), Subscribe.Event, Subscribe.Invocation> -typealias PresenceTransitions = TransitionProtocol<(any PresenceState), Presence.Event, Presence.Invocation> -typealias SubscribeDispatcher = Dispatcher -typealias PresenceDispatcher = Dispatcher - -class EventEngineFactory { - func subscribeEngine( - with configuration: PubNubConfiguration, - dispatcher: some SubscribeDispatcher, - transition: some SubscribeTransitions - ) -> SubscribeEngine { - EventEngine( - state: Subscribe.UnsubscribedState(), - transition: transition, - dispatcher: dispatcher, - dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: configuration)) - ) - } - - func presenceEngine( - with configuration: PubNubConfiguration, - dispatcher: some PresenceDispatcher, - transition: some PresenceTransitions - ) -> PresenceEngine { - EventEngine( - state: Presence.HeartbeatInactive(), - transition: transition, - dispatcher: dispatcher, - dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: configuration)) - ) - } -} diff --git a/Sources/PubNub/PubNub.swift b/Sources/PubNub/PubNub.swift index 9378ef7e..21c50a58 100644 --- a/Sources/PubNub/PubNub.swift +++ b/Sources/PubNub/PubNub.swift @@ -45,7 +45,7 @@ public class PubNub { fileSession: URLSessionReplaceable? = nil ) { let container = DependencyContainer(instanceID: UUID(), configuration: configuration) - container.register(value: session, forKey: HTTPSessionDependencyKey.self) + container.register(value: session, forKey: DefaultHTTPSessionDependencyKey.self) container.register(value: subscribeSession, forKey: HTTPSubscribeSessionDependencyKey.self) container.register(value: fileSession, forKey: FileURLSessionDependencyKey.self) @@ -56,7 +56,7 @@ public class PubNub { self.instanceID = container.instanceID self.configuration = container.configuration self.subscription = container.subscriptionSession - self.networkSession = container.httpSession + self.networkSession = container.defaultHTTPSession self.fileURLSession = container.fileURLSession self.presenceStateContainer = container.presenceStateContainer } diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubEventEngineTestsHelpers.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubEventEngineTestsHelpers.swift index 04259833..b3ba0c2c 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubEventEngineTestsHelpers.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubEventEngineTestsHelpers.swift @@ -16,7 +16,10 @@ protocol ContractTestIdentifiable { var contractTestIdentifier: String { get } } -extension EffectInvocation: ContractTestIdentifiable where Invocation: ContractTestIdentifiable, Invocation.Cancellable: ContractTestIdentifiable { +extension EffectInvocation: ContractTestIdentifiable where + Invocation: ContractTestIdentifiable, + Invocation.Cancellable: ContractTestIdentifiable +{ var contractTestIdentifier: String { switch self { case .managed(let invocation): diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift index c876c444..1f61f97e 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift @@ -66,9 +66,17 @@ extension Presence.Event: ContractTestIdentifiable { class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsSteps { // A decorator that records Invocations and forwards all calls to the original instance - private var dispatcherDecorator: DispatcherDecorator! + private var dispatcherDecorator: DispatcherDecorator< + Presence.Invocation, + Presence.Event, + Presence.Dependencies + >! // A decorator that records Events and forwards all calls to the original instance - private var transitionDecorator: TransitionDecorator! + private var transitionDecorator: TransitionDecorator< + any PresenceState, + Presence.Event, + Presence.Invocation + >! override func handleAfterHook() { dispatcherDecorator = nil @@ -77,57 +85,17 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep } override func createPubNubClient() -> PubNub { - let configuration = self.configuration - let factory = EventEngineFactory() - - /// Wraps original EffectDispatcher with Decorator that allows recording incoming Invocations - dispatcherDecorator = DispatcherDecorator( - wrappedInstance: EffectDispatcher( - factory: PresenceEffectFactory( - session: HTTPSession( - configuration: .pubnub, - sessionQueue: .global(qos: .default), - sessionStream: SessionListener(queue: .global(qos: .default)) - ), presenceStateContainer: .shared - ) - ) - ) - /// Wraps original Transition with Decorator that allows recording incoming Events - transitionDecorator = TransitionDecorator( - wrappedInstance: PresenceTransition(configuration: configuration) - ) - - let subscribeEffectFactory = SubscribeEffectFactory( - session: HTTPSession( - configuration: URLSessionConfiguration.subscription, - sessionQueue: .global(qos: .default), - sessionStream: SessionListener(queue: .global(qos: .default)) - ), presenceStateContainer: .shared - ) - let subscribeEngine = EventEngineFactory().subscribeEngine( - with: configuration, - dispatcher: EffectDispatcher(factory: subscribeEffectFactory), - transition: SubscribeTransition() - ) - let presenceEngine = factory.presenceEngine( - with: configuration, - dispatcher: dispatcherDecorator, - transition: transitionDecorator - ) - let subscriptionSession = SubscriptionSession( - strategy: EventEngineSubscriptionSessionStrategy( - configuration: configuration, - subscribeEngine: subscribeEngine, - presenceEngine: presenceEngine, - presenceStateContainer: .shared - ) - ) - return PubNub( - configuration: configuration, - session: HTTPSession(configuration: configuration.urlSessionConfiguration), - fileSession: URLSession(configuration: .pubnubBackground), - subscriptionSession: subscriptionSession + let container = DependencyContainer(configuration: self.configuration) + let key = PresenceEventEngineDependencyKey.self + + container[key] = PresenceEngine( + state: Presence.HeartbeatInactive(), + transition: TransitionDecorator(wrappedInstance: container[PresenceTransitionDependencyKey.self]), + dispatcher: DispatcherDecorator(wrappedInstance: container[PresenceEffectDispatcherDependencyKey.self]), + dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: configuration)) ) + + return PubNub(container: container) } override public func setup() { diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift index 64263bde..02f60d9e 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift @@ -86,9 +86,17 @@ extension Subscribe.Event: ContractTestIdentifiable { class PubNubSubscribeEngineContractTestsSteps: PubNubEventEngineContractTestsSteps { // A decorator that records Invocations and forwards all calls to the original instance - private var dispatcherDecorator: DispatcherDecorator! + private var dispatcherDecorator: DispatcherDecorator< + Subscribe.Invocation, + Subscribe.Event, + Subscribe.Dependencies + >! // A decorator that records Events and forwards all calls to the original instance - private var transitionDecorator: TransitionDecorator! + private var transitionDecorator: TransitionDecorator< + any SubscribeState, + Subscribe.Event, + Subscribe.Invocation + >! override func handleAfterHook() { dispatcherDecorator = nil @@ -106,57 +114,17 @@ class PubNubSubscribeEngineContractTestsSteps: PubNubEventEngineContractTestsSte } override func createPubNubClient() -> PubNub { - /// Wraps original EffectDispatcher with Decorator that allows recording incoming Invocations - dispatcherDecorator = DispatcherDecorator( - wrappedInstance: EffectDispatcher( - factory: SubscribeEffectFactory( - session: HTTPSession( - configuration: URLSessionConfiguration.subscription, - sessionQueue: .global(qos: .default), - sessionStream: SessionListener(queue: .global(qos: .default)) - ), presenceStateContainer: .shared - ) - ) - ) - /// Wraps original Transition with Decorator that allows recording incoming Events - transitionDecorator = TransitionDecorator( - wrappedInstance: SubscribeTransition() - ) + let container = DependencyContainer(configuration: self.configuration) + let key = SubscribeEventEngineDependencyKey.self - let factory = EventEngineFactory() - let configuration = self.configuration - - let subscribeEngine = factory.subscribeEngine( - with: configuration, - dispatcher: self.dispatcherDecorator, - transition: self.transitionDecorator - ) - let presenceEffectFactory = PresenceEffectFactory( - session: HTTPSession( - configuration: .pubnub, - sessionQueue: .global(qos: .default), - sessionStream: SessionListener(queue: .global(qos: .default)) - ), presenceStateContainer: .shared - ) - let presenceEngine = factory.presenceEngine( - with: configuration, - dispatcher: EffectDispatcher(factory: presenceEffectFactory), - transition: PresenceTransition(configuration: configuration) - ) - let subscriptionSession = SubscriptionSession( - strategy: EventEngineSubscriptionSessionStrategy( - configuration: configuration, - subscribeEngine: subscribeEngine, - presenceEngine: presenceEngine, - presenceStateContainer: .shared - ) - ) - return PubNub( - configuration: configuration, - session: HTTPSession(configuration: configuration.urlSessionConfiguration), - fileSession: URLSession(configuration: .pubnubBackground), - subscriptionSession: subscriptionSession + container[key] = SubscribeEngine( + state: Subscribe.UnsubscribedState(), + transition: TransitionDecorator(wrappedInstance: container[SubscribeTransitionDependencyKey.self]), + dispatcher: DispatcherDecorator(wrappedInstance: container[SubscribeEffectDispatcherDependencyKey.self]), + dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: configuration)) ) + + return PubNub(container: container) } override public func setup() { From 2b710fa1d3a838a162f33d4f5c100b8f44d02fcc Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Fri, 8 Mar 2024 12:43:09 +0100 Subject: [PATCH 04/15] InstanceIdOperatorTests.swift --- PubNub.xcodeproj/project.pbxproj | 4 -- .../Operators/InstanceIdOperatorTests.swift | 57 ------------------- 2 files changed, 61 deletions(-) delete mode 100644 Tests/PubNubTests/Networking/Operators/InstanceIdOperatorTests.swift diff --git a/PubNub.xcodeproj/project.pbxproj b/PubNub.xcodeproj/project.pbxproj index dfff811f..c75d8879 100644 --- a/PubNub.xcodeproj/project.pbxproj +++ b/PubNub.xcodeproj/project.pbxproj @@ -128,7 +128,6 @@ 3557CE0723886434004BBACC /* PubNubAPNSPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3557CE0623886434004BBACC /* PubNubAPNSPayload.swift */; }; 35580682230F3A34005CDD92 /* RequestIdOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35580681230F3A34005CDD92 /* RequestIdOperator.swift */; }; 35580686230F47EA005CDD92 /* RequestIdOperatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35580684230F4771005CDD92 /* RequestIdOperatorTests.swift */; }; - 3558068A230F4C99005CDD92 /* InstanceIdOperatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35580687230F4B75005CDD92 /* InstanceIdOperatorTests.swift */; }; 3558069C231303D9005CDD92 /* AutomaticRetryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558069B231303D9005CDD92 /* AutomaticRetryTests.swift */; }; 355806DB23145749005CDD92 /* PubNub.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = "PubNub::PubNub::Product" /* PubNub.framework */; }; 3559977B23073D53000BCFD1 /* WeakBoxTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3559977A23073D53000BCFD1 /* WeakBoxTests.swift */; }; @@ -711,7 +710,6 @@ 3557CE0623886434004BBACC /* PubNubAPNSPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PubNubAPNSPayload.swift; sourceTree = ""; }; 35580681230F3A34005CDD92 /* RequestIdOperator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestIdOperator.swift; sourceTree = ""; }; 35580684230F4771005CDD92 /* RequestIdOperatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestIdOperatorTests.swift; sourceTree = ""; }; - 35580687230F4B75005CDD92 /* InstanceIdOperatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstanceIdOperatorTests.swift; sourceTree = ""; }; 3558069B231303D9005CDD92 /* AutomaticRetryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomaticRetryTests.swift; sourceTree = ""; }; 3558073723145749005CDD92 /* PubNubIntTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PubNubIntTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3559977A23073D53000BCFD1 /* WeakBoxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakBoxTests.swift; sourceTree = ""; }; @@ -1315,7 +1313,6 @@ 35FE941E22F0929A0051C455 /* RequestRetrierTests.swift */, 3580A59322F0C74100B12E5E /* RequestMutatorTests.swift */, 35580684230F4771005CDD92 /* RequestIdOperatorTests.swift */, - 35580687230F4B75005CDD92 /* InstanceIdOperatorTests.swift */, 3558069B231303D9005CDD92 /* AutomaticRetryTests.swift */, ); path = Operators; @@ -3639,7 +3636,6 @@ 3D38A00E2B35AF6A006928E7 /* SubscribeRequestTests.swift in Sources */, 3D38A0142B35AF6B006928E7 /* PresenceTransitionTests.swift in Sources */, OBJ_49 /* PubNubTests.swift in Sources */, - 3558068A230F4C99005CDD92 /* InstanceIdOperatorTests.swift in Sources */, 35CF549E248D913A0099FE81 /* ObjectsUUIDRouterTests.swift in Sources */, 3DB925642B7A2BF5001B7E90 /* SubscriptionSetTests.swift in Sources */, 35458BA3230CB3570085B502 /* SubscribeSessionFactoryTests.swift in Sources */, diff --git a/Tests/PubNubTests/Networking/Operators/InstanceIdOperatorTests.swift b/Tests/PubNubTests/Networking/Operators/InstanceIdOperatorTests.swift deleted file mode 100644 index fed9e972..00000000 --- a/Tests/PubNubTests/Networking/Operators/InstanceIdOperatorTests.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// InstanceIdOperatorTests.swift -// -// Copyright (c) PubNub Inc. -// All rights reserved. -// -// This source code is licensed under the license found in the -// LICENSE file in the root directory of this source tree. -// - -@testable import PubNub -import XCTest - -class InstanceIdOperatorTests: XCTestCase { - var pubnub: PubNub! - var config = PubNubConfiguration(publishKey: "FakeTestString", subscribeKey: "FakeTestString", userId: UUID().uuidString) - - func testUseInstanceID_Success() { - var expectations = [XCTestExpectation]() - - let sessionListener = SessionListener(queue: DispatchQueue(label: "Session Listener", - qos: .userInitiated, - attributes: .concurrent)) - - guard let sessions = try? MockURLSession.mockSession(for: ["time_success"], - with: sessionListener) - else { - return XCTFail("Could not create mock url session") - } - - let sessionExpector = SessionExpector(session: sessionListener) - sessionExpector.expectDidMutateRequest { _, initialURLRequest, mutatedURLRequest in - guard let mutatedURL = mutatedURLRequest.url, let initialURL = initialURLRequest.url else { - return XCTFail("Could not create URL during request mutation") - } - - XCTAssertFalse(initialURL.absoluteString.contains(InstanceIdOperator.instanceIDKey)) - XCTAssertTrue(mutatedURL.absoluteString.contains(InstanceIdOperator.instanceIDKey)) - } - - let totalExpectation = expectation(description: "Time Response Received") - config.useInstanceId = true - pubnub = PubNub(configuration: config, session: sessions.session) - - XCTAssertTrue(pubnub.configuration.useInstanceId) - - pubnub.time { _ in - totalExpectation.fulfill() - } - expectations.append(totalExpectation) - - XCTAssertEqual(sessionExpector.expectations.count, 1) - expectations.append(contentsOf: sessionExpector.expectations) - - wait(for: expectations, timeout: 1.0) - } -} From c4d63c909af09c4afe867c46f91286eecbdc6a82 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Fri, 8 Mar 2024 19:37:25 +0100 Subject: [PATCH 05/15] Removing error related with some keyword --- .../DependencyContainer.swift | 136 +++--------------- ...ubNubPresenceEngineContractTestSteps.swift | 14 +- ...NubSubscribeEngineContractTestsSteps.swift | 14 +- 3 files changed, 40 insertions(+), 124 deletions(-) diff --git a/Sources/PubNub/DependencyContainer/DependencyContainer.swift b/Sources/PubNub/DependencyContainer/DependencyContainer.swift index ff696cfd..fed77603 100644 --- a/Sources/PubNub/DependencyContainer/DependencyContainer.swift +++ b/Sources/PubNub/DependencyContainer/DependencyContainer.swift @@ -114,57 +114,9 @@ extension DependencyContainer { self[SubscribeEventEngineDependencyKey.self] } - fileprivate var subscribeEngineTransition: some TransitionProtocol< - SubscribeState, - Subscribe.Event, - Subscribe.Invocation - > { - self[SubscribeTransitionDependencyKey.self] - } - - fileprivate var subscribeEngineEffectDispatcher: some Dispatcher< - Subscribe.Invocation, - Subscribe.Event, - Subscribe.Dependencies - > { - self[SubscribeEffectDispatcherDependencyKey.self] - } - - fileprivate var subscribeEngineEffectFactory: some EffectHandlerFactory< - Subscribe.Invocation, - Subscribe.Event, - Subscribe.Dependencies - > { - self[SubscribeEffectFactoryDependencyKey.self] - } - fileprivate var presenceEngine: PresenceEngine { self[PresenceEventEngineDependencyKey.self] } - - fileprivate var presenceEngineTransition: some TransitionProtocol< - PresenceState, - Presence.Event, - Presence.Invocation - > { - self[PresenceTransitionDependencyKey.self] - } - - fileprivate var presenceEngineEffectDispatcher: some Dispatcher< - Presence.Invocation, - Presence.Event, - Presence.Dependencies - > { - self[PresenceEffectDispatcherDependencyKey.self] - } - - fileprivate var presenceEngineEffectFactory: some EffectHandlerFactory< - Presence.Invocation, - Presence.Event, - Presence.Dependencies - > { - self[PresenceEffectFactoryDependencyKey.self] - } } fileprivate extension DependencyContainer { @@ -249,45 +201,17 @@ struct PresenceStateContainerDependencyKey: DependencyKey { struct SubscribeEventEngineDependencyKey: DependencyKey { static func value(from container: DependencyContainer) -> SubscribeEngine { - SubscribeEngine( - state: Subscribe.UnsubscribedState(), - transition: container.subscribeEngineTransition, - dispatcher: container.subscribeEngineEffectDispatcher, - dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: container.configuration)) - ) - } -} - -struct SubscribeEffectFactoryDependencyKey: DependencyKey { - static func value(from container: DependencyContainer) -> some EffectHandlerFactory< - Subscribe.Invocation, - Subscribe.Event, - Subscribe.Dependencies - > { - SubscribeEffectFactory( + let effectHandlerFactory = SubscribeEffectFactory( session: container.httpSubscribeSession, presenceStateContainer: container.presenceStateContainer ) - } -} - -struct SubscribeEffectDispatcherDependencyKey: DependencyKey { - static func value(from container: DependencyContainer) -> some Dispatcher< - Subscribe.Invocation, - Subscribe.Event, - Subscribe.Dependencies - > { - EffectDispatcher(factory: container.subscribeEngineEffectFactory) - } -} - -struct SubscribeTransitionDependencyKey: DependencyKey { - static func value(from container: DependencyContainer) -> some TransitionProtocol< - SubscribeState, - Subscribe.Event, - Subscribe.Invocation - > { - SubscribeTransition() + let subscribeEngine = SubscribeEngine( + state: Subscribe.UnsubscribedState(), + transition: SubscribeTransition(), + dispatcher: EffectDispatcher(factory: effectHandlerFactory), + dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: container.configuration)) + ) + return subscribeEngine } } @@ -295,45 +219,17 @@ struct SubscribeTransitionDependencyKey: DependencyKey { struct PresenceEventEngineDependencyKey: DependencyKey { static func value(from container: DependencyContainer) -> PresenceEngine { - PresenceEngine( - state: Presence.HeartbeatInactive(), - transition: container.presenceEngineTransition, - dispatcher: container.presenceEngineEffectDispatcher, - dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: container.configuration)) - ) - } -} - -struct PresenceEffectFactoryDependencyKey: DependencyKey { - static func value(from container: DependencyContainer) -> some EffectHandlerFactory< - Presence.Invocation, - Presence.Event, - Presence.Dependencies - > { - PresenceEffectFactory( + let effectHandlerFactory = PresenceEffectFactory( session: container.httpPresenceSession, presenceStateContainer: container.presenceStateContainer ) - } -} - -struct PresenceEffectDispatcherDependencyKey: DependencyKey { - static func value(from container: DependencyContainer) -> some Dispatcher< - Presence.Invocation, - Presence.Event, - Presence.Dependencies - > { - EffectDispatcher(factory: container.presenceEngineEffectFactory) - } -} - -struct PresenceTransitionDependencyKey: DependencyKey { - static func value(from container: DependencyContainer) -> some TransitionProtocol< - PresenceState, - Presence.Event, - Presence.Invocation - > { - PresenceTransition(configuration: container.configuration) + let presenceEngine = PresenceEngine( + state: Presence.HeartbeatInactive(), + transition: PresenceTransition(configuration: container.configuration), + dispatcher: EffectDispatcher(factory: effectHandlerFactory), + dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: container.configuration)) + ) + return presenceEngine } } diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift index 1f61f97e..3a1fe891 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift @@ -88,10 +88,20 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep let container = DependencyContainer(configuration: self.configuration) let key = PresenceEventEngineDependencyKey.self + let dispatcher = DispatcherDecorator(wrappedInstance: EffectDispatcher( + factory: PresenceEffectFactory( + session: container[HTTPPresenceSessionDependencyKey.self], + presenceStateContainer: container[PresenceStateContainerDependencyKey.self] + ) + )) + let transition = TransitionDecorator( + wrappedInstance: PresenceTransition(configuration: configuration) + ) + container[key] = PresenceEngine( state: Presence.HeartbeatInactive(), - transition: TransitionDecorator(wrappedInstance: container[PresenceTransitionDependencyKey.self]), - dispatcher: DispatcherDecorator(wrappedInstance: container[PresenceEffectDispatcherDependencyKey.self]), + transition: transition, + dispatcher: dispatcher, dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: configuration)) ) diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift index 02f60d9e..637b955f 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift @@ -117,10 +117,20 @@ class PubNubSubscribeEngineContractTestsSteps: PubNubEventEngineContractTestsSte let container = DependencyContainer(configuration: self.configuration) let key = SubscribeEventEngineDependencyKey.self + let dispatcher = DispatcherDecorator(wrappedInstance: EffectDispatcher( + factory: SubscribeEffectFactory( + session: container[DefaultHTTPSessionDependencyKey.self], + presenceStateContainer: container[PresenceStateContainerDependencyKey.self] + ) + )) + let transition = TransitionDecorator( + wrappedInstance: SubscribeTransition() + ) + container[key] = SubscribeEngine( state: Subscribe.UnsubscribedState(), - transition: TransitionDecorator(wrappedInstance: container[SubscribeTransitionDependencyKey.self]), - dispatcher: DispatcherDecorator(wrappedInstance: container[SubscribeEffectDispatcherDependencyKey.self]), + transition: transition, + dispatcher: dispatcher, dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: configuration)) ) From ca7c31e60bb6c3e8c64e3ef33c2e540808211bf7 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Mon, 11 Mar 2024 09:24:41 +0100 Subject: [PATCH 06/15] Fixes for EE contract tests --- .../PubNubPresenceEngineContractTestSteps.swift | 8 ++++---- .../PubNubSubscribeEngineContractTestsSteps.swift | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift index 3a1fe891..afb85242 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift @@ -88,20 +88,20 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep let container = DependencyContainer(configuration: self.configuration) let key = PresenceEventEngineDependencyKey.self - let dispatcher = DispatcherDecorator(wrappedInstance: EffectDispatcher( + self.dispatcherDecorator = DispatcherDecorator(wrappedInstance: EffectDispatcher( factory: PresenceEffectFactory( session: container[HTTPPresenceSessionDependencyKey.self], presenceStateContainer: container[PresenceStateContainerDependencyKey.self] ) )) - let transition = TransitionDecorator( + self.transitionDecorator = TransitionDecorator( wrappedInstance: PresenceTransition(configuration: configuration) ) container[key] = PresenceEngine( state: Presence.HeartbeatInactive(), - transition: transition, - dispatcher: dispatcher, + transition: self.transitionDecorator, + dispatcher: self.dispatcherDecorator, dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: configuration)) ) diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift index 637b955f..bf48eb0e 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift @@ -117,20 +117,20 @@ class PubNubSubscribeEngineContractTestsSteps: PubNubEventEngineContractTestsSte let container = DependencyContainer(configuration: self.configuration) let key = SubscribeEventEngineDependencyKey.self - let dispatcher = DispatcherDecorator(wrappedInstance: EffectDispatcher( + self.dispatcherDecorator = DispatcherDecorator(wrappedInstance: EffectDispatcher( factory: SubscribeEffectFactory( - session: container[DefaultHTTPSessionDependencyKey.self], + session: container[HTTPSubscribeSessionDependencyKey.self], presenceStateContainer: container[PresenceStateContainerDependencyKey.self] ) )) - let transition = TransitionDecorator( + self.transitionDecorator = TransitionDecorator( wrappedInstance: SubscribeTransition() ) container[key] = SubscribeEngine( state: Subscribe.UnsubscribedState(), - transition: transition, - dispatcher: dispatcher, + transition: self.transitionDecorator, + dispatcher: self.dispatcherDecorator, dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: configuration)) ) From 53d09e211d0709993ce1192bda91b4d3e514cdcd Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Mon, 11 Mar 2024 09:26:17 +0100 Subject: [PATCH 07/15] Improved readability for Subscribe Effect and DelayedHeartbeatEffect tests --- .../DelayedHeartbeatEffectTests.swift | 16 +- .../Subscribe/SubscribeEffectsTests.swift | 545 ++++++++++-------- 2 files changed, 313 insertions(+), 248 deletions(-) diff --git a/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift b/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift index d25fedeb..78bd36b6 100644 --- a/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift +++ b/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift @@ -28,9 +28,10 @@ class DelayedHeartbeatEffectTests: XCTestCase { } override func tearDown() { - mockUrlSession = nil delegate = nil + mockUrlSession = nil httpSession = nil + factory = nil super.tearDown() } @@ -43,7 +44,7 @@ class DelayedHeartbeatEffectTests: XCTestCase { let delayRange = 2.0...3.0 let automaticRetry = AutomaticRetry(retryLimit: 3, policy: .linear(delay: delayRange.lowerBound), excluded: []) - let effect = configureEffect(attempt: 0, automaticRetry: automaticRetry, error: PubNubError(.unknown)) + let effect = configureEffectToTest(retryAttempt: 0, automaticRetry: automaticRetry, dueTo: PubNubError(.unknown)) let startDate = Date() effect.performTask { returnedEvents in @@ -65,7 +66,7 @@ class DelayedHeartbeatEffectTests: XCTestCase { let delayRange = 2.0...3.0 let automaticRetry = AutomaticRetry(retryLimit: 3, policy: .linear(delay: delayRange.lowerBound), excluded: []) let error = PubNubError(.unknown) - let effect = configureEffect(attempt: 0, automaticRetry: automaticRetry, error: error) + let effect = configureEffectToTest(retryAttempt: 0, automaticRetry: automaticRetry, dueTo: error) effect.performTask { returnedEvents in let expectedError = PubNubError(.internalServiceError) @@ -84,7 +85,7 @@ class DelayedHeartbeatEffectTests: XCTestCase { let automaticRetry = AutomaticRetry(retryLimit: 3, policy: .linear(delay: 2.0), excluded: []) let error = PubNubError(.unknown) - let effect = configureEffect(attempt: 3, automaticRetry: automaticRetry, error: error) + let effect = configureEffectToTest(retryAttempt: 3, automaticRetry: automaticRetry, dueTo: error) mockResponse(GenericServicePayloadResponse(status: 200)) @@ -108,9 +109,10 @@ fileprivate extension DelayedHeartbeatEffectTests { } } - func configureEffect( - attempt: Int, automaticRetry: AutomaticRetry?, - error: PubNubError + func configureEffectToTest( + retryAttempt attempt: Int, + automaticRetry: AutomaticRetry?, + dueTo error: PubNubError ) -> any EffectHandler { factory.effect( for: .delayedHeartbeat( diff --git a/Tests/PubNubTests/EventEngine/Subscribe/SubscribeEffectsTests.swift b/Tests/PubNubTests/EventEngine/Subscribe/SubscribeEffectsTests.swift index b66624a2..6a847a51 100644 --- a/Tests/PubNubTests/EventEngine/Subscribe/SubscribeEffectsTests.swift +++ b/Tests/PubNubTests/EventEngine/Subscribe/SubscribeEffectsTests.swift @@ -23,10 +23,7 @@ class SubscribeEffectsTests: XCTestCase { publishKey: "pubKey", subscribeKey: "subKey", userId: "userId", - automaticRetry: AutomaticRetry( - retryLimit: 3, - policy: .linear(delay: 2.0) - ) + automaticRetry: AutomaticRetry(retryLimit: 3, policy: .linear(delay: 2.0)) ) private func configWithLinearPolicy(_ delay: Double = 2.0) -> PubNubConfiguration { @@ -47,9 +44,10 @@ class SubscribeEffectsTests: XCTestCase { } override func tearDown() { - mockUrlSession = nil delegate = nil + mockUrlSession = nil httpSession = nil + factory = nil super.tearDown() } } @@ -62,21 +60,28 @@ extension SubscribeEffectsTests { cursor: SubscribeCursor(timetoken: 12345, region: 1), messages: [] )) - runEffect( - configuration: config, - invocation: .handshakeRequest( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"] - ), - expectedOutput: [ - .handshakeSuccess( - cursor: SubscribeCursor( - timetoken: 12345, - region: 1 - ) - ) - ] + + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true + + let testedInvocation: Subscribe.Invocation = .handshakeRequest( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"] + ) + let effect = factory.effect( + for: testedInvocation, + with: EventEngineDependencies(value: Subscribe.Dependencies(configuration: config)) + ) + let expectedOutput: Subscribe.Event = .handshakeSuccess( + cursor: SubscribeCursor(timetoken: 12345, region: 1) ) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) } func test_HandshakingEffectWithFailedResponse() { @@ -84,17 +89,28 @@ extension SubscribeEffectsTests { errorIfAny: URLError(.cannotFindHost), httpResponse: HTTPURLResponse(statusCode: 404)! ) - runEffect( - configuration: config, - invocation: .handshakeRequest( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"] - ), expectedOutput: [ - .handshakeFailure( - error: PubNubError(.nameResolutionFailure, underlying: URLError(.cannotFindHost)) - ) - ] + + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true + + let testedInvocation: Subscribe.Invocation = .handshakeRequest( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"] + ) + let expectedOutput: Subscribe.Event = .handshakeFailure( + error: PubNubError(.nameResolutionFailure, underlying: URLError(.cannotFindHost)) + ) + let effect = factory.effect( + for: testedInvocation, + with: EventEngineDependencies(value: Subscribe.Dependencies(configuration: config)) ) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) } } @@ -106,19 +122,30 @@ extension SubscribeEffectsTests { cursor: SubscribeCursor(timetoken: 12345, region: 1), messages: [firstMessage, secondMessage] )) - runEffect( - configuration: config, - invocation: .receiveMessages( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - cursor: SubscribeCursor(timetoken: 111, region: 1) - ), expectedOutput: [ - .receiveSuccess( - cursor: SubscribeCursor(timetoken: 12345, region: 1), - messages: [firstMessage, secondMessage] - ) - ] + + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true + + let testedInvocation: Subscribe.Invocation = .receiveMessages( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + cursor: SubscribeCursor(timetoken: 111, region: 1) + ) + let expectedOutput: Subscribe.Event = .receiveSuccess( + cursor: SubscribeCursor(timetoken: 12345, region: 1), + messages: [firstMessage, secondMessage] ) + let effect = factory.effect( + for: testedInvocation, + with: EventEngineDependencies(value: Subscribe.Dependencies(configuration: config)) + ) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) } func test_ReceivingEffectWithFailedResponse() { @@ -126,17 +153,29 @@ extension SubscribeEffectsTests { errorIfAny: URLError(.cannotFindHost), httpResponse: HTTPURLResponse(statusCode: 404)! ) - runEffect( - configuration: config, - invocation: .receiveMessages( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - cursor: SubscribeCursor(timetoken: 111, region: 1) - ), expectedOutput: [ - .receiveFailure( - error: PubNubError(.nameResolutionFailure, underlying: URLError(.cannotFindHost)) - ) - ]) + + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true + + let testedInvocation: Subscribe.Invocation = .receiveMessages( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + cursor: SubscribeCursor(timetoken: 111, region: 1) + ) + let expectedOutput: Subscribe.Event = .receiveFailure( + error: PubNubError(.nameResolutionFailure, underlying: URLError(.cannotFindHost)) + ) + let effect = factory.effect( + for: testedInvocation, + with: EventEngineDependencies(value: Subscribe.Dependencies(configuration: config)) + ) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) } } @@ -144,115 +183,141 @@ extension SubscribeEffectsTests { extension SubscribeEffectsTests { func test_HandshakeReconnectingSuccess() { - let delayRange = 2.0...3.0 - let urlError = URLError(.badServerResponse) - let httpUrlResponse = HTTPURLResponse(statusCode: 500)! - let pubNubError = PubNubError(urlError.pubnubReason!, underlying: urlError, affected: [.response(httpUrlResponse)]) - mockResponse(subscribeResponse: SubscribeResponse( cursor: SubscribeCursor(timetoken: 12345, region: 1), messages: [] )) - runEffect( - configuration: configWithLinearPolicy(delayRange.lowerBound), - invocation: .handshakeReconnect( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - retryAttempt: 1, - reason: pubNubError - ), - timeout: 2 * delayRange.upperBound, - expectedOutput: [ - .handshakeReconnectSuccess(cursor: SubscribeCursor( - timetoken: 12345, - region: 1 - )) - ] + + let delayRange = 2.0...3.0 + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true + + let testedInvocation: Subscribe.Invocation = .handshakeReconnect( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + retryAttempt: 1, + reason: PubNubError( + URLError(.badServerResponse).pubnubReason!, + underlying: URLError(.badServerResponse), + affected: [.response(HTTPURLResponse(statusCode: 500)!)] + ) ) + let expectedOutput: Subscribe.Event = .handshakeReconnectSuccess( + cursor: SubscribeCursor(timetoken: 12345, region: 1) + ) + let effect = factory.effect( + for: testedInvocation, + with: EventEngineDependencies(value: Subscribe.Dependencies( + configuration: configWithLinearPolicy(delayRange.lowerBound) + )) + ) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 2 * delayRange.upperBound) } func test_HandshakeReconnectingFailed() { - let delayRange = 2.0...3.0 - let urlError = URLError(.badServerResponse) - let httpUrlResponse = HTTPURLResponse(statusCode: 500)! - let pubNubError = PubNubError(urlError.pubnubReason!, underlying: urlError, affected: [.response(httpUrlResponse)]) - mockResponse( errorIfAny: URLError(.cannotFindHost), httpResponse: HTTPURLResponse(statusCode: 404)! ) - runEffect( - configuration: configWithLinearPolicy(delayRange.lowerBound), - invocation: .handshakeReconnect( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - retryAttempt: 1, - reason: pubNubError - ), - timeout: 2 * delayRange.upperBound, - expectedOutput: [ - .handshakeReconnectFailure( - error: PubNubError( - .nameResolutionFailure, - underlying: URLError(.cannotFindHost) - ) - ) - ] + + let delayRange = 2.0...3.0 + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true + + let testedInvocation: Subscribe.Invocation = .handshakeReconnect( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + retryAttempt: 1, + reason: PubNubError( + URLError(.badServerResponse).pubnubReason!, + underlying: URLError(.badServerResponse), + affected: [.response(HTTPURLResponse(statusCode: 500)!)] + ) + ) + let expectedOutput: Subscribe.Event = .handshakeReconnectFailure( + error: PubNubError(.nameResolutionFailure, underlying: URLError(.cannotFindHost)) + ) + let effect = factory.effect( + for: testedInvocation, + with: EventEngineDependencies(value: Subscribe.Dependencies( + configuration: configWithLinearPolicy(delayRange.lowerBound) + )) ) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 2 * delayRange.upperBound) } func test_HandshakeReconnectGiveUp() { let delayRange = 2.0...3.0 - let urlError = URLError(.badServerResponse) - - runEffect( - configuration: configWithLinearPolicy(delayRange.lowerBound), - invocation: .handshakeReconnect( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - retryAttempt: 3, - reason: PubNubError(urlError.pubnubReason!, underlying: urlError) - ), - expectedOutput: [ - .handshakeReconnectGiveUp( - error: PubNubError(.badServerResponse) - ) - ] + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true + + let testedInvocation: Subscribe.Invocation = .handshakeReconnect( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + retryAttempt: 3, + reason: PubNubError(URLError(.badServerResponse).pubnubReason!, underlying: URLError(.badServerResponse)) + ) + let expectedOutput: Subscribe.Event = .handshakeReconnectGiveUp( + error: PubNubError(.badServerResponse) + ) + let effect = factory.effect( + for: testedInvocation, + with: EventEngineDependencies(value: Subscribe.Dependencies( + configuration: configWithLinearPolicy(delayRange.lowerBound) + )) ) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) } func test_HandshakeReconnectIsDelayed() { - let urlError = URLError(.badServerResponse) - let httpUrlResponse = HTTPURLResponse(statusCode: 500)! - let pubNubError = PubNubError(urlError.pubnubReason!, underlying: urlError, affected: [.response(httpUrlResponse)]) - - let delayRange = 2.0...3.0 - let startDate = Date() - mockResponse(subscribeResponse: SubscribeResponse( cursor: SubscribeCursor(timetoken: 12345, region: 1), messages: [] )) - runEffect( - configuration: configWithLinearPolicy(delayRange.lowerBound), - invocation: .handshakeReconnect( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - retryAttempt: 1, - reason: pubNubError - ), - timeout: 2.5 * delayRange.upperBound, - expectedOutput: [ - .handshakeReconnectSuccess( - cursor: SubscribeCursor(timetoken: 12345, region: 1) - ) - ], - additionalValidations: { - XCTAssertTrue( - Int(Date().timeIntervalSince(startDate)) <= Int(delayRange.upperBound) - ) - } + + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true + + let testedInvocation: Subscribe.Invocation = .handshakeReconnect( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + retryAttempt: 3, + reason: PubNubError( + URLError(.badServerResponse).pubnubReason!, + underlying: URLError(.badServerResponse), + affected: [.response(HTTPURLResponse(statusCode: 500)!)] + ) ) + + let delayRange = 2.0...3.0 + let startDate = Date() + let depValue = Subscribe.Dependencies(configuration: configWithLinearPolicy(delayRange.lowerBound)) + let effect = factory.effect(for: testedInvocation, with: EventEngineDependencies(value: depValue)) + + effect.performTask { _ in + XCTAssertTrue(Int(Date().timeIntervalSince(startDate)) <= Int(delayRange.upperBound)) + expectation.fulfill() + } + wait(for: [expectation], timeout: 2 * delayRange.upperBound) } } @@ -260,121 +325,142 @@ extension SubscribeEffectsTests { extension SubscribeEffectsTests { func test_ReceiveReconnectingSuccess() { - let urlError = URLError(.badServerResponse) - let httpUrlResponse = HTTPURLResponse(statusCode: 500)! - let pubNubError = PubNubError(urlError.pubnubReason!, underlying: urlError, affected: [.response(httpUrlResponse)]) - let delayRange = 2.0...3.0 - mockResponse(subscribeResponse: SubscribeResponse( cursor: SubscribeCursor(timetoken: 12345, region: 1), messages: [firstMessage, secondMessage] )) - runEffect( - configuration: configWithLinearPolicy(delayRange.lowerBound), - invocation: .receiveReconnect( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - cursor: SubscribeCursor(timetoken: 1111, region: 1), - retryAttempt: 1, - reason: pubNubError - ), - timeout: 2 * delayRange.upperBound, - expectedOutput: [ - .receiveReconnectSuccess( - cursor: SubscribeCursor(timetoken: 12345, region: 1), - messages: [firstMessage, secondMessage] - ) - ] + + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true + + let testedInvocation: Subscribe.Invocation = .receiveReconnect( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + cursor: SubscribeCursor(timetoken: 1111, region: 1), + retryAttempt: 1, + reason: PubNubError( + URLError(.badServerResponse).pubnubReason!, + underlying: URLError(.badServerResponse), + affected: [.response(HTTPURLResponse(statusCode: 500)!)] + ) + ) + let expectedOutput: Subscribe.Event = .receiveReconnectSuccess( + cursor: SubscribeCursor(timetoken: 12345, region: 1), + messages: [firstMessage, secondMessage] ) + + let delayRange = 2.0...3.0 + let depValue = Subscribe.Dependencies(configuration: configWithLinearPolicy(delayRange.lowerBound)) + let effect = factory.effect(for: testedInvocation, with: EventEngineDependencies(value: depValue)) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 2 * delayRange.upperBound) } func test_ReceiveReconnectingFailure() { - let delayRange = 2.0...3.0 - let urlError = URLError(.badServerResponse) - let httpUrlResponse = HTTPURLResponse(statusCode: 500)! - let pubNubError = PubNubError(urlError.pubnubReason!, underlying: urlError, affected: [.response(httpUrlResponse)]) + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true mockResponse( errorIfAny: URLError(.cannotFindHost), httpResponse: HTTPURLResponse(statusCode: 404)! ) - runEffect( - configuration: configWithLinearPolicy(delayRange.lowerBound), - invocation: .receiveReconnect( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - cursor: SubscribeCursor(timetoken: 1111, region: 1), - retryAttempt: 1, - reason: pubNubError - ), - timeout: 2 * delayRange.upperBound, - expectedOutput: [ - .receiveReconnectFailure( - error: PubNubError(.nameResolutionFailure) - ) - ] + let testedInvocation: Subscribe.Invocation = .receiveReconnect( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + cursor: SubscribeCursor(timetoken: 1111, region: 1), + retryAttempt: 1, + reason: PubNubError( + URLError(.badServerResponse).pubnubReason!, + underlying: URLError(.badServerResponse), + affected: [.response(HTTPURLResponse(statusCode: 500)!)] + ) ) + + let expectedOutput: Subscribe.Event = .receiveReconnectFailure(error: PubNubError(.nameResolutionFailure)) + let delayRange = 2.0...3.0 + let depValue = Subscribe.Dependencies(configuration: configWithLinearPolicy(delayRange.lowerBound)) + let effect = factory.effect(for: testedInvocation, with: EventEngineDependencies(value: depValue)) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 2 * delayRange.upperBound) } func test_ReceiveReconnectGiveUp() { - let urlError = URLError(.badServerResponse) - let delayRange = 2.0...3.0 + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true mockResponse( errorIfAny: URLError(.cannotFindHost), httpResponse: HTTPURLResponse(statusCode: 404)! ) - runEffect( - configuration: configWithLinearPolicy(delayRange.lowerBound), - invocation: .receiveReconnect( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - cursor: SubscribeCursor(timetoken: 1111, region: 1), - retryAttempt: 3, - reason: PubNubError(urlError.pubnubReason!, underlying: urlError) - ), - expectedOutput: [ - .receiveReconnectGiveUp( - error: PubNubError(.badServerResponse) - ) - ] + + let testedInvocation: Subscribe.Invocation = .receiveReconnect( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + cursor: SubscribeCursor(timetoken: 1111, region: 1), + retryAttempt: 3, + reason: PubNubError( + URLError(.badServerResponse).pubnubReason!, + underlying: URLError(.badServerResponse) + ) ) + let expectedOutput: Subscribe.Event = .receiveReconnectGiveUp( + error: PubNubError(.badServerResponse) + ) + + let delayRange = 2.0...3.0 + let depValue = Subscribe.Dependencies(configuration: configWithLinearPolicy(delayRange.lowerBound)) + let effect = factory.effect(for: testedInvocation, with: EventEngineDependencies(value: depValue)) + + effect.performTask { + XCTAssertEqual([expectedOutput], $0) + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) } func test_ReceiveReconnectingIsDelayed() { - let urlError = URLError(.badServerResponse) - let httpUrlResponse = HTTPURLResponse(statusCode: 500)! - let pubNubError = PubNubError(urlError.pubnubReason!, underlying: urlError, affected: [.response(httpUrlResponse)]) - - let delayRange = 2.0...3.0 - let startDate = Date() + let expectation = XCTestExpectation(description: "Effect Completion") + expectation.expectedFulfillmentCount = 1 + expectation.assertForOverFulfill = true mockResponse(subscribeResponse: SubscribeResponse( cursor: SubscribeCursor(timetoken: 12345, region: 1), messages: [firstMessage, secondMessage] )) - runEffect( - configuration: configWithLinearPolicy(delayRange.lowerBound), - invocation: .receiveReconnect( - channels: ["channel1", "channel1-pnpres", "channel2"], - groups: ["g1", "g2", "g2-pnpres"], - cursor: SubscribeCursor(timetoken: 1111, region: 1), - retryAttempt: 1, - reason: pubNubError - ), - timeout: 2 * delayRange.upperBound, - expectedOutput: [ - .receiveReconnectSuccess( - cursor: SubscribeCursor(timetoken: 12345, region: 1), - messages: [firstMessage, secondMessage] - ) - ], - additionalValidations: { - XCTAssertTrue( - Int(Date().timeIntervalSince(startDate)) <= Int(delayRange.upperBound) - ) - } + + let testedInvocation: Subscribe.Invocation = .receiveReconnect( + channels: ["channel1", "channel1-pnpres", "channel2"], + groups: ["g1", "g2", "g2-pnpres"], + cursor: SubscribeCursor(timetoken: 1111, region: 1), + retryAttempt: 1, + reason: PubNubError( + URLError(.badServerResponse).pubnubReason!, + underlying: URLError(.badServerResponse), + affected: [.response(HTTPURLResponse(statusCode: 500)!)] + ) ) + + let delayRange = 2.0...3.0 + let startDate = Date() + let depValue = Subscribe.Dependencies(configuration: configWithLinearPolicy(delayRange.lowerBound)) + let effect = factory.effect(for: testedInvocation, with: EventEngineDependencies(value: depValue)) + + effect.performTask { _ in + XCTAssertTrue(Int(Date().timeIntervalSince(startDate)) <= Int(delayRange.upperBound)) + expectation.fulfill() + } + wait(for: [expectation], timeout: 2 * delayRange.upperBound) } } @@ -393,29 +479,6 @@ fileprivate extension SubscribeEffectsTests { return task } } - - private func runEffect( - configuration: PubNubConfiguration, - invocation: Subscribe.Invocation, - timeout: TimeInterval = 0.5, - expectedOutput results: [Subscribe.Event] = [], - additionalValidations validations: @escaping () -> Void = {} - ) { - let expectation = XCTestExpectation(description: "Effect Completion") - expectation.expectedFulfillmentCount = 1 - expectation.assertForOverFulfill = true - - let effect = factory.effect( - for: invocation, - with: EventEngineDependencies(value: Subscribe.Dependencies(configuration: configuration)) - ) - effect.performTask { - XCTAssertEqual(results, $0) - validations() - expectation.fulfill() - } - wait(for: [expectation], timeout: timeout) - } } fileprivate let firstMessage = SubscribeMessagePayload( From 20ffb46f2429dd5b1e5c3b92fd1ac11b50064a59 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Tue, 12 Mar 2024 09:52:36 +0100 Subject: [PATCH 08/15] Minor fixes for DependencyContainer.swift and PubNub.swift --- .../DependencyContainer.swift | 42 ++++++++++--------- Sources/PubNub/PubNub.swift | 2 + 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/Sources/PubNub/DependencyContainer/DependencyContainer.swift b/Sources/PubNub/DependencyContainer/DependencyContainer.swift index fed77603..e86f66d3 100644 --- a/Sources/PubNub/DependencyContainer/DependencyContainer.swift +++ b/Sources/PubNub/DependencyContainer/DependencyContainer.swift @@ -25,16 +25,20 @@ protocol DependencyKey { // conforming to the `DependencyKey` protocol. class DependencyContainer { private var values: [ObjectIdentifier: Any] = [:] - + init(instanceID: UUID = UUID(), configuration: PubNubConfiguration) { self[PubNubConfigurationDependencyKey.self] = configuration self[PubNubInstanceIDDependencyKey.self] = instanceID } - + subscript(key: K.Type) -> K.Value where K: DependencyKey { get { if let existingValue = values[ObjectIdentifier(key)] { - return existingValue as! K.Value + if let existingValue = existingValue as? K.Value { + return existingValue + } else { + preconditionFailure("Cannot resolve value for \(key)") + } } let value = key.value(from: self) values[ObjectIdentifier(key)] = value @@ -43,7 +47,7 @@ class DependencyContainer { values[ObjectIdentifier(key)] = newValue } } - + @discardableResult func register(value: K.Value?, forKey key: K.Type) -> DependencyContainer { if let value { @@ -53,73 +57,73 @@ class DependencyContainer { } } -typealias SubscribeEngine = EventEngine<(any SubscribeState), Subscribe.Event, Subscribe.Invocation, Subscribe.Dependencies> -typealias PresenceEngine = EventEngine<(any PresenceState), Presence.Event, Presence.Invocation, Presence.Dependencies> +typealias SubscribeEngine = EventEngine +typealias PresenceEngine = EventEngine extension DependencyContainer { var configuration: PubNubConfiguration { self[PubNubConfigurationDependencyKey.self] } - + var instanceID: UUID { self[PubNubInstanceIDDependencyKey.self] } - + var fileURLSession: URLSessionReplaceable { self[FileURLSessionDependencyKey.self] } - + var subscriptionSession: SubscriptionSession { self[SubscriptionSessionDependencyKey.self] } - + var presenceStateContainer: PubNubPresenceStateContainer { self[PresenceStateContainerDependencyKey.self] } - + var defaultHTTPSession: SessionReplaceable { resolveSession( session: self[DefaultHTTPSessionDependencyKey.self], with: [automaticRetry].compactMap { $0 } ) } - + fileprivate var httpSubscribeSession: SessionReplaceable { resolveSession( session: self[HTTPSubscribeSessionDependencyKey.self], with: [instanceIDOperator].compactMap { $0 } ) } - + fileprivate var httpPresenceSession: SessionReplaceable { resolveSession( session: self[HTTPPresenceSessionDependencyKey.self], with: [instanceIDOperator].compactMap { $0 } ) } - + fileprivate var automaticRetry: RequestOperator? { configuration.automaticRetry } - + fileprivate var instanceIDOperator: RequestOperator? { configuration.useInstanceId ? InstanceIdOperator(instanceID: instanceID.uuidString) : nil } - + fileprivate var httpSubscribeSessionQueue: DispatchQueue { self[HTTPSubscribeSessionQueueDependencyKey.self] } - + fileprivate var subscribeEngine: SubscribeEngine { self[SubscribeEventEngineDependencyKey.self] } - + fileprivate var presenceEngine: PresenceEngine { self[PresenceEventEngineDependencyKey.self] } } -fileprivate extension DependencyContainer { +private extension DependencyContainer { func resolveSession(session: SessionReplaceable, with operators: [RequestOperator?]) -> SessionReplaceable { session.defaultRequestOperator == nil ? session.usingDefault(requestOperator: MultiplexRequestOperator( operators: operators.compactMap { $0 } diff --git a/Sources/PubNub/PubNub.swift b/Sources/PubNub/PubNub.swift index 21c50a58..13b05ccc 100644 --- a/Sources/PubNub/PubNub.swift +++ b/Sources/PubNub/PubNub.swift @@ -1471,3 +1471,5 @@ extension PubNub: StatusEmitter { set { subscription.onConnectionStateChange = newValue } } } + +// swiftlint:disable:this file_length From e2f680dc19e090ce7e7b7d7054e9255dcf25a471 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Mon, 18 Mar 2024 14:22:04 +0100 Subject: [PATCH 09/15] Fixes after rebasing --- .../Subscription/SubscriptionSession.swift | 72 +++++++------------ 1 file changed, 25 insertions(+), 47 deletions(-) diff --git a/Sources/PubNub/Subscription/SubscriptionSession.swift b/Sources/PubNub/Subscription/SubscriptionSession.swift index 1f2722e2..d3f7ec2e 100644 --- a/Sources/PubNub/Subscription/SubscriptionSession.swift +++ b/Sources/PubNub/Subscription/SubscriptionSession.swift @@ -11,15 +11,14 @@ import Foundation class SubscriptionSession: EventEmitter, StatusEmitter { - /// A unique identifier for subscription session - var uuid: UUID { - strategy.uuid - } - - /// An underlying queue to dispatch events + // An underlying queue to dispatch events let queue: DispatchQueue + // A unique identifier for subscription session + var uuid: UUID { strategy.uuid } + // The `Timetoken` used for the last successful subscription request + var previousTokenResponse: SubscribeCursor? { strategy.previousTokenResponse } - /// PSV2 feature to subscribe with a custom filter expression. + // PSV2 feature to subscribe with a custom filter expression. var filterExpression: String? { get { strategy.filterExpression @@ -27,8 +26,14 @@ class SubscriptionSession: EventEmitter, StatusEmitter { strategy.filterExpression = newValue } } + var configuration: PubNubConfiguration { + get { + strategy.configuration + } set { + strategy.configuration = newValue + } + } - /// `EventEmitter` conformance var onEvent: ((PubNubEvent) -> Void)? var onEvents: (([PubNubEvent]) -> Void)? var onMessage: ((PubNubMessage) -> Void)? @@ -37,21 +42,7 @@ class SubscriptionSession: EventEmitter, StatusEmitter { var onMessageAction: ((PubNubMessageActionEvent) -> Void)? var onFileEvent: ((PubNubFileChangeEvent) -> Void)? var onAppContext: ((PubNubAppContextEvent) -> Void)? - - /// `StatusEmitter` conformance var onConnectionStateChange: ((ConnectionStatus) -> Void)? - - var previousTokenResponse: SubscribeCursor? { - strategy.previousTokenResponse - } - - var configuration: PubNubConfiguration { - get { - strategy.configuration - } set { - strategy.configuration = newValue - } - } private lazy var globalEventsListener: BaseSubscriptionListenerAdapter = .init( receiver: self, @@ -86,37 +77,30 @@ class SubscriptionSession: EventEmitter, StatusEmitter { add(globalStatusListener) } - /// Names of all subscribed channels - /// - /// This list includes both regular and presence channel names + // Names of all subscribed channels + // + // This list includes both regular and presence channel names var subscribedChannels: [String] { strategy.subscribedChannels } - /// List of actively subscribed groups + // List of actively subscribed groups var subscribedChannelGroups: [String] { strategy.subscribedChannelGroups } - /// Combined value of all subscribed channels and groups + // Combined value of all subscribed channels and groups var subscriptionCount: Int { strategy.subscriptionCount } - /// Current connection status + // Current connection status var connectionStatus: ConnectionStatus { strategy.connectionStatus } // MARK: - Subscription Loop - /// Subscribe to channels and/or channel groups - /// - /// - Parameters: - /// - to: List of channels to subscribe on - /// - and: List of channel groups to subscribe on - /// - at: The timetoken to subscribe with - /// - withPresence: If true it also subscribes to presence events on the specified channels. func subscribe( to channels: [String], and groups: [String] = [], @@ -141,36 +125,31 @@ class SubscriptionSession: EventEmitter, StatusEmitter { at: cursor?.timetoken ) for subscription in channelSubscriptions { - subscription.subscriptionNames.flatMap { $0 }.forEach { + subscription.subscriptionNames.compactMap { $0 }.forEach { globalChannelSubscriptions[$0] = subscription } } for subscription in channelGroupSubscriptions { - subscription.subscriptionNames.flatMap { $0 }.forEach { + subscription.subscriptionNames.compactMap { $0 }.forEach { globalGroupSubscriptions[$0] = subscription } } } - /// Reconnect a disconnected subscription stream - /// - parameter timetoken: The timetoken to subscribe with + // MARK: - Reconnect + func reconnect(at cursor: SubscribeCursor? = nil) { strategy.reconnect(at: cursor) } - /// Disconnect the subscription stream + // MARK: - Disconnect + func disconnect() { strategy.disconnect() } // MARK: - Unsubscribe - /// Unsubscribe from channels and/or channel groups - /// - /// - Parameters: - /// - from: List of channels to unsubscribe from - /// - and: List of channel groups to unsubscribe from - /// - presenceOnly: If true, it only unsubscribes from presence events on the specified channels. func unsubscribe( from channels: [String], and groups: [String] = [], @@ -193,7 +172,6 @@ class SubscriptionSession: EventEmitter, StatusEmitter { } } - /// Unsubscribe from all channels and channel groups func unsubscribeAll() { strategy.unsubscribeAll() } From 6e2d99c5b56568eda1dcf3779124deb3de1f1162 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Mon, 18 Mar 2024 15:22:35 +0100 Subject: [PATCH 10/15] TARGETED_DEVICE_FAMILY + inline comments --- PubNub.xcodeproj/project.pbxproj | 36 +++++++++---------- .../Helpers/Crypto/Cryptors/Cryptor.swift | 16 ++++++--- Sources/PubNub/PubNub.swift | 19 +++++----- 3 files changed, 38 insertions(+), 33 deletions(-) diff --git a/PubNub.xcodeproj/project.pbxproj b/PubNub.xcodeproj/project.pbxproj index c75d8879..18a5af73 100644 --- a/PubNub.xcodeproj/project.pbxproj +++ b/PubNub.xcodeproj/project.pbxproj @@ -3855,7 +3855,7 @@ SKIP_INSTALL = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -3903,7 +3903,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -3932,7 +3932,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = NO; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; WATCHOS_DEPLOYMENT_TARGET = 4.0; }; @@ -3958,7 +3958,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.pubnub.swift.PubNubUserTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; WATCHOS_DEPLOYMENT_TARGET = 4.0; @@ -4011,7 +4011,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4061,7 +4061,7 @@ SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -4097,7 +4097,7 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; WATCHOS_DEPLOYMENT_TARGET = 4.0; }; @@ -4129,7 +4129,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; WATCHOS_DEPLOYMENT_TARGET = 4.0; @@ -4181,7 +4181,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -4229,7 +4229,7 @@ PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -4343,7 +4343,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = NO; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; WATCHOS_DEPLOYMENT_TARGET = 4.0; }; @@ -4369,7 +4369,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.pubnub.swift.PubNubUserTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; WATCHOS_DEPLOYMENT_TARGET = 4.0; @@ -4403,7 +4403,7 @@ SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; WATCHOS_DEPLOYMENT_TARGET = 4.0; }; @@ -4435,7 +4435,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; WATCHOS_DEPLOYMENT_TARGET = 4.0; @@ -4547,7 +4547,7 @@ SWIFT_OBJC_INTERFACE_HEADER_NAME = "PubNubContractTests-Swift.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TARGET_NAME = PubNubContractTests; WATCHOS_DEPLOYMENT_TARGET = 4.0; }; @@ -4588,7 +4588,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "Tests/PubNubContractTest/PubNubContractTests-Bridging-Header.h"; SWIFT_OBJC_INTERFACE_HEADER_NAME = "PubNubContractTests-Swift.h"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TARGET_NAME = PubNubContractTests; WATCHOS_DEPLOYMENT_TARGET = 4.0; }; @@ -4631,7 +4631,7 @@ SWIFT_OBJC_INTERFACE_HEADER_NAME = "PubNubContractTests-Swift.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TARGET_NAME = PubNubContractTestsBeta; WATCHOS_DEPLOYMENT_TARGET = 4.0; }; @@ -4673,7 +4673,7 @@ SWIFT_OBJC_BRIDGING_HEADER = "Tests/PubNubContractTest/PubNubContractTests-Bridging-Header.h"; SWIFT_OBJC_INTERFACE_HEADER_NAME = "PubNubContractTests-Swift.h"; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; + TARGETED_DEVICE_FAMILY = "1,2,3,4"; TARGET_NAME = PubNubContractTestsBeta; WATCHOS_DEPLOYMENT_TARGET = 4.0; }; diff --git a/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift b/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift index e8c1d983..cc631108 100644 --- a/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift +++ b/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift @@ -43,14 +43,18 @@ public protocol Cryptor: Hashable { /// /// - Parameters: /// - data: Data to encrypt - /// - Returns: A success, storing an ``EncryptedData`` value if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + /// - Returns: + /// - **Success**: ``EncryptedData`` representing encrypted content + /// - **Failure**: `Error` describing the reason of failure func encrypt(data: Data) -> Result /// Decrypts the given `Data` object /// /// - Parameters: /// - data: Data to encrypt - /// - Returns: A success, storing decrypted `Data` if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + /// - Returns: + /// - **Success**: ``Data`` representing decrypted content + /// - **Failure**: `Error` describing the reason of failure func decrypt(data: EncryptedData) -> Result /// Encrypts the given `InputStream` object @@ -58,7 +62,9 @@ public protocol Cryptor: Hashable { /// - Parameters: /// - stream: Stream to encrypt /// - contentLength: Content length of encoded stream - /// - Returns: A success, storing an ``EncryptedStreamData`` value if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + /// - Returns: + /// - **Success**: ``EncryptedStreamData`` representing encrypted content + /// - **Failure**: `Error` describing the reason of failure func encrypt(stream: InputStream, contentLength: Int) -> Result /// Decrypts the given `InputStream` object @@ -66,6 +72,8 @@ public protocol Cryptor: Hashable { /// - Parameters: /// - data: A value describing encrypted stream /// - outputPath: URL where the stream should be decrypted to - /// - Returns: A success, storing a decrypted `InputStream` value at the given path if operation succeeds. Otherwise, a failure storing `PubNubError` is returned + /// - Returns: + /// - **Success**: ``InputStream`` representing decrypted content + /// - **Failure**: `Error` describing the reason of failure func decrypt(data: EncryptedStreamData, outputPath: URL) -> Result } diff --git a/Sources/PubNub/PubNub.swift b/Sources/PubNub/PubNub.swift index 13b05ccc..ae4987a4 100644 --- a/Sources/PubNub/PubNub.swift +++ b/Sources/PubNub/PubNub.swift @@ -111,15 +111,14 @@ public extension PubNub { public var customSession: SessionReplaceable? /// The endpoint configuration used by the request public var customConfiguration: RouterConfiguration? - /// The response queue that will + /// The queue that will be used for dispatching a response public var responseQueue: DispatchQueue /// Default init for all fields /// - Parameters: /// - customSession: The custom Network session that that will be used to make the request /// - customConfiguration: The endpoint configuration used by the request - /// - responseQueue: The response queue that will - /// + /// - responseQueue: The queue that will be used for dispatching a response public init( customSession: SessionReplaceable? = nil, customConfiguration: RouterConfiguration? = nil, @@ -141,6 +140,7 @@ public extension PubNub { /// - Parameters: /// - start: The value of the start of a next page /// - end: The value of the end of a slice of paged data + /// - totalCount: Number of items to fetch public init(start: String? = nil, end: String? = nil, totalCount: Int? = nil) { self.start = start self.end = end @@ -292,7 +292,6 @@ public extension PubNub { /// - Parameters: /// - channel: The destination of the message /// - message: The message to publish - /// - shouldCompress: Whether the message needs to be compressed before transmission /// - custom: Custom configuration overrides for this request /// - completion: The async `Result` of the method call /// - **Success**: The `Timetoken` of the published Message @@ -327,7 +326,6 @@ public extension PubNub { /// - and: List of channel groups to subscribe on /// - at: The initial timetoken to subscribe with /// - withPresence: If true it also subscribes to presence events on the specified channels. - /// - region: The region code from a previous `SubscribeCursor` func subscribe( to channels: [String], and channelGroups: [String] = [], @@ -358,14 +356,12 @@ public extension PubNub { } /// Stops the subscriptions in progress - /// - Important: This subscription might be shared with multiple `PubNub` instances. func disconnect() { subscription.disconnect() } /// Reconnets to a stopped subscription with the previous subscribed channels and channel groups /// - Parameter at: The timetoken value used to reconnect or nil to use the previous stored value - /// - Important: This subscription might be shared with multiple `PubNub` instances. func reconnect(at timetoken: Timetoken? = nil) { subscription.reconnect(at: SubscribeCursor(timetoken: timetoken)) } @@ -475,6 +471,7 @@ public extension PubNub { /// - state: The UUID for which to query the subscribed channels of /// - on: Additional network configuration to use on the request /// - and: The queue the completion handler should be returned on + /// - custom: Custom configuration overrides for this request /// - completion: The async `Result` of the method call /// - **Success**: The presence State set as a `JSONCodable` /// - **Failure**: An `Error` describing the failure @@ -511,6 +508,7 @@ public extension PubNub { /// - for: The UUID for which to query the subscribed channels of /// - on: Additional network configuration to use on the request /// - and: The queue the completion handler should be returned on + /// - custom: Custom configuration overrides for this request /// - completion: The async `Result` of the method call /// - **Success**: A `Tuple` containing the UUID that set the State and a `Dictionary` of channels mapped to their respective State /// - **Failure**: An `Error` describing the failure @@ -637,7 +635,6 @@ public extension PubNub { /// - completion: The async `Result` of the method call /// - **Success**: The channel-group that was removed /// - **Failure**: An `Error` describing the failure - /// - result: A `Result` containing either the removed channel-group **or** an `Error` func remove( channelGroup: String, custom requestConfig: RequestConfiguration = RequestConfiguration(), @@ -1051,7 +1048,7 @@ public extension PubNub { /// - page: The paging object used for pagination /// - custom: Custom configuration overrides for this request /// - completion: The async `Result` of the method call - /// - **Success**: A `Tuple` of a `Dictionary` of channels mapped to an `Array` their respective `PubNubMessages`, and the next request `PubNubBoundedPage` (if one exists) + /// - **Success**: A `Tuple` containing a `Dictionary` mapping channels to `PubNubMessage` arrays, and an optional next `PubNubBoundedPage`. /// - **Failure**: An `Error` describing the failure func fetchMessageHistory( for channels: [String], @@ -1353,8 +1350,8 @@ extension PubNub { } /// Decrypts the given `Data` object using `CryptoModule` provided in `configuration` - /// - Parameter message: The encrypted `Data` to decrypt - /// - Returns: A `Result` containing either the decrypted plain text message or the `CryptoError` + /// - Parameter data: The encrypted `Data` to decrypt + /// - Returns: A `Result` containing either the decrypted plain text message or the `CryptoError` public func decrypt(data: Data) -> Result { guard let cryptoModule = configuration.cryptoModule else { PubNub.log.error(ErrorDescription.missingCryptoKey) From acd77083929377ecbe3657d9c674bebf4fd6ab96 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Tue, 19 Mar 2024 10:58:19 +0100 Subject: [PATCH 11/15] swiftlint + swiftformat --- .swiftformat | 19 +++++-- .swiftlint.yml | 2 + .../Presence/Effects/WaitEffect.swift | 6 +-- .../Helpers/PresenceHeartbeatRequest.swift | 2 +- .../EventEngine/Presence/Presence.swift | 21 ++++---- .../EventEngine/Subscribe/Subscribe.swift | 40 +++++++------- .../Helpers/Crypto/Cryptors/Cryptor.swift | 12 ++--- Sources/PubNub/Networking/HTTPSession.swift | 4 +- .../Networking/Routers/PresenceRouter.swift | 4 +- Sources/PubNub/PubNub.swift | 53 +++++++++---------- .../SubscriptionSessionStrategy.swift | 2 +- .../Subscription/SubscriptionSession.swift | 1 + .../PubNubEventEngineTestsHelpers.swift | 8 +-- ...ubNubPresenceEngineContractTestSteps.swift | 28 +++++----- ...NubSubscribeEngineContractTestsSteps.swift | 44 +++++++-------- .../DelayedHeartbeatEffectTests.swift | 4 +- .../Subscribe/SubscribeEffectsTests.swift | 8 +-- 17 files changed, 137 insertions(+), 121 deletions(-) diff --git a/.swiftformat b/.swiftformat index d180dd25..55ae0e87 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,11 +1,24 @@ -# format options +# Format options --indent 2 --commas inline --disable wrapMultilineStatementBraces +--wraparguments before-first +--wrapparameters before-first +--maxwidth 130 +--disable wrapSingleLineComments +--voidtype void +--redundanttype inferred +--self remove -# file options +--enable spaceAroundBrackets,spaceAroundComments,spaceAroundGenerics,spaceAroundParens,spaceInsideBraces +--enable spaceInsideBrackets,spaceInsideComments,spaceInsideGenerics,spaceInsideParens,trailingclosures,trailingCommas +--enable wrapConditionalBodies, wrapEnumCases, wrapLoopBodies +--enable redundantLetError,redundantParens,redundantSelf,redundantReturn,redundantBreak +--enable duplicateImports + +# File options --exclude .build # Swift Version ---swiftversion 5.0 +--swiftversion 5.8 diff --git a/.swiftlint.yml b/.swiftlint.yml index d04a6c2d..04650714 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,4 +1,5 @@ line_length: + warning: 130 ignores_comments: true disabled_rules: - identifier_name @@ -11,6 +12,7 @@ excluded: - .bundle - fastlane - Tests + - Pods opt_in_rules: - force_unwrapping - overridden_super_call diff --git a/Sources/PubNub/EventEngine/Presence/Effects/WaitEffect.swift b/Sources/PubNub/EventEngine/Presence/Effects/WaitEffect.swift index 2407e083..c63ca28c 100644 --- a/Sources/PubNub/EventEngine/Presence/Effects/WaitEffect.swift +++ b/Sources/PubNub/EventEngine/Presence/Effects/WaitEffect.swift @@ -12,7 +12,7 @@ import Foundation class WaitEffect: EffectHandler { private let timerEffect: TimerEffect? - + init(configuration: PubNubConfiguration) { if configuration.heartbeatInterval > 0 { self.timerEffect = TimerEffect(interval: TimeInterval(configuration.heartbeatInterval)) @@ -20,7 +20,7 @@ class WaitEffect: EffectHandler { self.timerEffect = nil } } - + func performTask(completionBlock: @escaping ([Presence.Event]) -> Void) { guard let timerEffect = timerEffect else { completionBlock([]); return @@ -29,7 +29,7 @@ class WaitEffect: EffectHandler { completionBlock([.timesUp]) }) } - + func cancelTask() { timerEffect?.cancelTask() } diff --git a/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift b/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift index bb5c9b8a..d4d14865 100644 --- a/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift +++ b/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift @@ -49,7 +49,7 @@ class PresenceHeartbeatRequest { ) request?.validate().response(on: sessionResponseQueue, decoder: GenericServiceResponseDecoder()) { result in switch result { - case .success(_): + case .success: completionBlock(.success(())) case .failure(let error): completionBlock(.failure(error as? PubNubError ?? PubNubError(.unknown, underlying: error))) diff --git a/Sources/PubNub/EventEngine/Presence/Presence.swift b/Sources/PubNub/EventEngine/Presence/Presence.swift index ad9f35fe..553f7a15 100644 --- a/Sources/PubNub/EventEngine/Presence/Presence.swift +++ b/Sources/PubNub/EventEngine/Presence/Presence.swift @@ -20,6 +20,7 @@ extension PresenceState { var channels: [String] { input.channels } + var groups: [String] { input.groups } @@ -36,7 +37,7 @@ extension Presence { struct Heartbeating: PresenceState { let input: PresenceInput } - + struct HeartbeatCooldown: PresenceState { let input: PresenceInput } @@ -46,18 +47,18 @@ extension Presence { let retryAttempt: Int let error: PubNubError } - + struct HeartbeatFailed: PresenceState { let input: PresenceInput let error: PubNubError } - + struct HeartbeatStopped: PresenceState { let input: PresenceInput } - + struct HeartbeatInactive: PresenceState { - let input: PresenceInput = PresenceInput() + let input: PresenceInput = .init() } } @@ -91,11 +92,11 @@ extension Presence { case leave(channels: [String], groups: [String]) case delayedHeartbeat(channels: [String], groups: [String], retryAttempt: Int, error: PubNubError) case wait - + enum Cancellable: AnyCancellableInvocation { case wait case delayedHeartbeat - + var id: String { switch self { case .wait: @@ -105,16 +106,16 @@ extension Presence { } } } - + var id: String { switch self { - case .heartbeat(_,_): + case .heartbeat: return "Presence.Heartbeat" case .wait: return Cancellable.wait.id case .delayedHeartbeat: return Cancellable.delayedHeartbeat.id - case .leave(_,_): + case .leave: return "Presence.Leave" } } diff --git a/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift b/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift index b009106c..43d1065e 100644 --- a/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift +++ b/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift @@ -37,13 +37,13 @@ extension Subscribe { let cursor: SubscribeCursor let connectionStatus = ConnectionStatus.connecting } - + struct HandshakeStoppedState: SubscribeState { let input: SubscribeInput let cursor: SubscribeCursor let connectionStatus = ConnectionStatus.disconnected } - + struct HandshakeReconnectingState: SubscribeState { let input: SubscribeInput let cursor: SubscribeCursor @@ -51,13 +51,13 @@ extension Subscribe { let reason: PubNubError let connectionStatus = ConnectionStatus.connecting } - + struct HandshakeFailedState: SubscribeState { let input: SubscribeInput let cursor: SubscribeCursor let error: PubNubError let connectionStatus: ConnectionStatus - + init(input: SubscribeInput, cursor: SubscribeCursor, error: PubNubError) { self.input = input self.cursor = cursor @@ -65,13 +65,13 @@ extension Subscribe { self.connectionStatus = .connectionError(error) } } - + struct ReceivingState: SubscribeState { let input: SubscribeInput let cursor: SubscribeCursor let connectionStatus = ConnectionStatus.connected } - + struct ReceiveReconnectingState: SubscribeState { let input: SubscribeInput let cursor: SubscribeCursor @@ -79,19 +79,19 @@ extension Subscribe { let reason: PubNubError let connectionStatus = ConnectionStatus.connected } - + struct ReceiveStoppedState: SubscribeState { let input: SubscribeInput let cursor: SubscribeCursor let connectionStatus = ConnectionStatus.disconnected } - + struct ReceiveFailedState: SubscribeState { let input: SubscribeInput let cursor: SubscribeCursor let error: PubNubError let connectionStatus: ConnectionStatus - + init(input: SubscribeInput, cursor: SubscribeCursor, error: PubNubError) { self.input = input self.cursor = cursor @@ -99,10 +99,10 @@ extension Subscribe { self.connectionStatus = .disconnectedUnexpectedly(error) } } - + struct UnsubscribedState: SubscribeState { - let cursor: SubscribeCursor = SubscribeCursor(timetoken: 0)! - let input: SubscribeInput = SubscribeInput() + let cursor: SubscribeCursor = .init(timetoken: 0)! + let input: SubscribeInput = .init() let connectionStatus = ConnectionStatus.disconnected } } @@ -141,7 +141,7 @@ extension Subscribe { struct Dependencies { let configuration: PubNubConfiguration let listeners: [BaseSubscriptionListener] - + init(configuration: PubNubConfiguration, listeners: [BaseSubscriptionListener] = []) { self.configuration = configuration self.listeners = listeners @@ -159,7 +159,7 @@ extension Subscribe { case receiveReconnect(channels: [String], groups: [String], cursor: SubscribeCursor, retryAttempt: Int, reason: PubNubError) case emitStatus(change: Subscribe.ConnectionStatusChange) case emitMessages(events: [SubscribeMessagePayload], forCursor: SubscribeCursor) - + enum Cancellable: AnyCancellableInvocation { case handshakeRequest case handshakeReconnect @@ -182,17 +182,17 @@ extension Subscribe { var id: String { switch self { - case .handshakeRequest(_, _): + case .handshakeRequest: return Cancellable.handshakeRequest.id - case .handshakeReconnect(_, _, _, _): + case .handshakeReconnect: return Cancellable.handshakeReconnect.id - case .receiveMessages(_, _, _): + case .receiveMessages: return Cancellable.receiveMessages.id - case .receiveReconnect(_, _, _, _, _): + case .receiveReconnect: return Cancellable.receiveReconnect.id - case .emitMessages(_,_): + case .emitMessages: return "Subscribe.EmitMessages" - case .emitStatus(_): + case .emitStatus: return "Subscribe.EmitStatus" } } diff --git a/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift b/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift index cc631108..edd95574 100644 --- a/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift +++ b/Sources/PubNub/Helpers/Crypto/Cryptors/Cryptor.swift @@ -8,8 +8,8 @@ // LICENSE file in the root directory of this source tree. // -import Foundation import CommonCrypto +import Foundation /// Represents the result of encrypted `Data` public struct EncryptedData { @@ -38,7 +38,7 @@ public protocol Cryptor: Hashable { /// /// - Important: `[0x41, 0x43, 0x52, 0x48]` and `[0x00, 0x00, 0x00, 0x00]` values are reserved var id: CryptorId { get } - + /// Encrypts the given `Data` object /// /// - Parameters: @@ -47,7 +47,7 @@ public protocol Cryptor: Hashable { /// - **Success**: ``EncryptedData`` representing encrypted content /// - **Failure**: `Error` describing the reason of failure func encrypt(data: Data) -> Result - + /// Decrypts the given `Data` object /// /// - Parameters: @@ -56,7 +56,7 @@ public protocol Cryptor: Hashable { /// - **Success**: ``Data`` representing decrypted content /// - **Failure**: `Error` describing the reason of failure func decrypt(data: EncryptedData) -> Result - + /// Encrypts the given `InputStream` object /// /// - Parameters: @@ -66,9 +66,9 @@ public protocol Cryptor: Hashable { /// - **Success**: ``EncryptedStreamData`` representing encrypted content /// - **Failure**: `Error` describing the reason of failure func encrypt(stream: InputStream, contentLength: Int) -> Result - + /// Decrypts the given `InputStream` object - /// + /// /// - Parameters: /// - data: A value describing encrypted stream /// - outputPath: URL where the stream should be decrypted to diff --git a/Sources/PubNub/Networking/HTTPSession.swift b/Sources/PubNub/Networking/HTTPSession.swift index 778ac201..d57f08f7 100644 --- a/Sources/PubNub/Networking/HTTPSession.swift +++ b/Sources/PubNub/Networking/HTTPSession.swift @@ -90,8 +90,8 @@ public final class HTTPSession { deinit { PubNub.log.debug("Session Destroyed \(sessionID) with active requests \(taskToRequest.values.map { $0.requestID })") - taskToRequest.values.forEach { - $0.cancel(PubNubError(.sessionDeinitialized, router: $0.router)) + for value in taskToRequest.values { + value.cancel(PubNubError(.sessionDeinitialized, router: value.router)) } invalidateAndCancel() } diff --git a/Sources/PubNub/Networking/Routers/PresenceRouter.swift b/Sources/PubNub/Networking/Routers/PresenceRouter.swift index 81ed9424..594f7748 100644 --- a/Sources/PubNub/Networking/Routers/PresenceRouter.swift +++ b/Sources/PubNub/Networking/Routers/PresenceRouter.swift @@ -85,7 +85,7 @@ struct PresenceRouter: HTTPRouter { var endpoint: Endpoint var configuration: RouterConfiguration - + // Protocol Properties var service: PubNubService { return .presence @@ -233,7 +233,7 @@ struct HereNowResponseDecoder: ResponseDecoder { // Single Channel w/o Groups if channels.count == 1, groups.isEmpty, let channel = channels.first { - hereNowPayload = [channel: try Constant.jsonDecoder.decode(HereNowChannelsPayload.self, from: response.payload)] + hereNowPayload = try [channel: Constant.jsonDecoder.decode(HereNowChannelsPayload.self, from: response.payload)] } else { // Multi-Channel HereNow hereNowPayload = try Constant.jsonDecoder.decode( diff --git a/Sources/PubNub/PubNub.swift b/Sources/PubNub/PubNub.swift index ae4987a4..e9426e66 100644 --- a/Sources/PubNub/PubNub.swift +++ b/Sources/PubNub/PubNub.swift @@ -30,7 +30,7 @@ public class PubNub { let subscription: SubscriptionSession // Container that holds current Presence states for given channels/channel groups let presenceStateContainer: PubNubPresenceStateContainer - + /// Creates a PubNub session with the specified configuration /// /// - Parameters: @@ -51,7 +51,7 @@ public class PubNub { self.init(container: container) } - + init(container: DependencyContainer) { self.instanceID = container.instanceID self.configuration = container.configuration @@ -396,7 +396,7 @@ public extension PubNub { var connectionStatus: ConnectionStatus { return subscription.connectionStatus } - + /// An override for the default filter expression set during initialization var subscribeFilterExpression: String? { get { @@ -413,11 +413,11 @@ extension PubNub: SubscribeReceiver { func registerAdapter(_ adapter: BaseSubscriptionListenerAdapter) { subscription.registerAdapter(adapter) } - + func hasRegisteredAdapter(with uuid: UUID) -> Bool { subscription.hasRegisteredAdapter(with: uuid) } - + func internalSubscribe( with channels: [Subscription], and groups: [Subscription], @@ -429,7 +429,7 @@ extension PubNub: SubscribeReceiver { at: timetoken ) } - + func internalUnsubscribe( from channels: [Subscription], and groups: [Subscription], @@ -449,15 +449,15 @@ extension PubNub: EntityCreator { public func channel(_ name: String) -> ChannelRepresentation { subscription.channel(name) } - + public func channelGroup(_ name: String) -> ChannelGroupRepresentation { subscription.channelGroup(name) } - + public func userMetadata(_ name: String) -> UserMetadataRepresentation { subscription.userMetadata(name) } - + public func channelMetadata(_ name: String) -> ChannelMetadataRepresentation { subscription.channelMetadata(name) } @@ -487,14 +487,14 @@ public extension PubNub { configuration: requestConfig.customConfiguration ?? configuration ) let shouldMaintainPresenceState = configuration.enableEventEngine && configuration.maintainPresenceState - + route( router, requestOperator: configuration.automaticRetry?.retryOperator(for: .presence), responseDecoder: PresenceResponseDecoder>(), custom: requestConfig ) { [weak self] result in - if case .success(_) = result { + if case .success = result { if shouldMaintainPresenceState { self?.presenceStateContainer.registerState(AnyJSON(state), forChannels: channels) } @@ -1224,9 +1224,7 @@ public extension PubNub { case let .success(response): completion?(.success(( actions: response.payload.actions.map { PubNubMessageActionBase(from: $0, on: channel) }, - next: PubNubBoundedPageBase( - start: response.payload.start, end: response.payload.end, limit: response.payload.limit - ) + next: PubNubBoundedPageBase(start: response.payload.start, end: response.payload.end, limit: response.payload.limit) ))) case let .failure(error): completion?(.failure(error)) @@ -1329,11 +1327,11 @@ public extension PubNub { // MARK: - Crypto -extension PubNub { +public extension PubNub { /// Encrypts the `Data` object using `CryptoModule` provided in configuration /// - Parameter message: The plain text message to be encrypted /// - Returns: A `Result` containing either the encryped `Data` (mapped to Base64-encoded data) or the `CryptoError` - public func encrypt(message: String) -> Result { + func encrypt(message: String) -> Result { guard let cryptoModule = configuration.cryptoModule else { PubNub.log.error(ErrorDescription.missingCryptoKey) return .failure(CryptoError.invalidKey) @@ -1341,7 +1339,7 @@ extension PubNub { guard let dataMessage = message.data(using: .utf8) else { return .failure(CryptoError.decodeError) } - + return cryptoModule.encrypt(data: dataMessage).map { $0.base64EncodedData() }.mapError { @@ -1352,7 +1350,7 @@ extension PubNub { /// Decrypts the given `Data` object using `CryptoModule` provided in `configuration` /// - Parameter data: The encrypted `Data` to decrypt /// - Returns: A `Result` containing either the decrypted plain text message or the `CryptoError` - public func decrypt(data: Data) -> Result { + func decrypt(data: Data) -> Result { guard let cryptoModule = configuration.cryptoModule else { PubNub.log.error(ErrorDescription.missingCryptoKey) return .failure(CryptoError.invalidKey) @@ -1361,7 +1359,7 @@ extension PubNub { PubNub.log.error("Cannot create Base64-encoded data") return .failure(CryptoError.decodeError) } - + return cryptoModule.decrypt(data: base64EncodedData) .flatMap { guard let string = String(data: $0, encoding: .utf8) else { @@ -1415,45 +1413,46 @@ extension PubNub: EventEmitter { public var queue: DispatchQueue { subscription.queue } + public var uuid: UUID { subscription.uuid } - + public var onEvent: ((PubNubEvent) -> Void)? { get { subscription.onEvent } set { subscription.onEvent = newValue } } - + public var onEvents: (([PubNubEvent]) -> Void)? { get { subscription.onEvents } set { subscription.onEvents = newValue } } - + public var onMessage: ((PubNubMessage) -> Void)? { get { subscription.onMessage } set { subscription.onMessage = newValue } } - + public var onSignal: ((PubNubMessage) -> Void)? { get { subscription.onSignal } set { subscription.onSignal = newValue } } - + public var onPresence: ((PubNubPresenceChange) -> Void)? { get { subscription.onPresence } set { subscription.onPresence = newValue } } - + public var onMessageAction: ((PubNubMessageActionEvent) -> Void)? { get { subscription.onMessageAction } set { subscription.onMessageAction = newValue } } - + public var onFileEvent: ((PubNubFileChangeEvent) -> Void)? { get { subscription.onFileEvent } set { subscription.onFileEvent = newValue } } - + public var onAppContext: ((PubNubAppContextEvent) -> Void)? { get { subscription.onAppContext } set { subscription.onAppContext = newValue } diff --git a/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift b/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift index 358e7310..7cc8b888 100644 --- a/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift +++ b/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift @@ -32,7 +32,7 @@ protocol SubscriptionSessionStrategy: AnyObject { mainGroups: [PubNubChannel], presenceGroupsOnly: [PubNubChannel] ) - + func reconnect(at cursor: SubscribeCursor?) func disconnect() func unsubscribeAll() diff --git a/Sources/PubNub/Subscription/SubscriptionSession.swift b/Sources/PubNub/Subscription/SubscriptionSession.swift index d3f7ec2e..5e910a85 100644 --- a/Sources/PubNub/Subscription/SubscriptionSession.swift +++ b/Sources/PubNub/Subscription/SubscriptionSession.swift @@ -26,6 +26,7 @@ class SubscriptionSession: EventEmitter, StatusEmitter { strategy.filterExpression = newValue } } + var configuration: PubNubConfiguration { get { strategy.configuration diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubEventEngineTestsHelpers.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubEventEngineTestsHelpers.swift index b3ba0c2c..321266aa 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubEventEngineTestsHelpers.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubEventEngineTestsHelpers.swift @@ -40,7 +40,7 @@ class DispatcherDecorator: Dispat self.wrappedInstance = wrappedInstance self.recordedInvocations = [] } - + func dispatch( invocations: [EffectInvocation], with dependencies: EventEngineDependencies, @@ -54,16 +54,16 @@ class DispatcherDecorator: Dispat class TransitionDecorator: TransitionProtocol { private let wrappedInstance: any TransitionProtocol private(set) var recordedEvents: [Event] - + init(wrappedInstance: some TransitionProtocol) { self.wrappedInstance = wrappedInstance self.recordedEvents = [] } - + func canTransition(from state: State, dueTo event: Event) -> Bool { wrappedInstance.canTransition(from: state, dueTo: event) } - + func transition(from state: State, event: Event) -> TransitionResult { recordedEvents.append(event) return wrappedInstance.transition(from: state, event: event) diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift index afb85242..5c3cca46 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift @@ -8,19 +8,19 @@ // LICENSE file in the root directory of this source tree. // -import Foundation import Cucumberish +import Foundation @testable import PubNub extension Presence.Invocation: ContractTestIdentifiable { var contractTestIdentifier: String { switch self { - case .heartbeat(_, _): + case .heartbeat: return "HEARTBEAT" - case .leave(_, _): + case .leave: return "LEAVE" - case .delayedHeartbeat(_, _, _, _): + case .delayedHeartbeat: return "DELAYED_HEARTBEAT" case .wait: return "WAIT" @@ -42,9 +42,9 @@ extension Presence.Invocation.Cancellable: ContractTestIdentifiable { extension Presence.Event: ContractTestIdentifiable { var contractTestIdentifier: String { switch self { - case .joined(_, _): + case .joined: return "JOINED" - case .left(_, _): + case .left: return "LEFT" case .leftAll: return "LEFT_ALL" @@ -56,9 +56,9 @@ extension Presence.Event: ContractTestIdentifiable { return "TIMES_UP" case .heartbeatSuccess: return "HEARTBEAT_SUCCESS" - case .heartbeatFailed(_): + case .heartbeatFailed: return "HEARTBEAT_FAILURE" - case .heartbeatGiveUp(_): + case .heartbeatGiveUp: return "HEARTBEAT_GIVEUP" } } @@ -111,7 +111,7 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep override public func setup() { startCucumberHookEventsListening() - Given("^the demo keyset with Presence Event Engine enabled$") { args, _ in + Given("^the demo keyset with Presence Event Engine enabled$") { _, _ in self.replacePubNubConfiguration(with: PubNubConfiguration( publishKey: self.configuration.publishKey, subscribeKey: self.configuration.subscribeKey, @@ -122,7 +122,7 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep )) } - Given("a linear reconnection policy with 3 retries") { args, _ in + Given("a linear reconnection policy with 3 retries") { _, _ in self.replacePubNubConfiguration(with: PubNubConfiguration( publishKey: self.configuration.publishKey, subscribeKey: self.configuration.subscribeKey, @@ -166,7 +166,7 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep self.subscribeSynchronously(self.client, to: [firstChannel, secondChannel, thirdChannel], with: true) } - Then("^I wait for getting Presence joined events$") { args, _ in + Then("^I wait for getting Presence joined events$") { _, _ in XCTAssertNotNil(self.waitForPresenceChanges(self.client, count: 3)) } @@ -174,7 +174,7 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep self.waitFor(delay: TimeInterval(args!.first!)!) } - Then("^I wait for getting Presence left events$") { args, _ in + Then("^I wait for getting Presence left events$") { _, _ in XCTAssertNotNil(self.waitForPresenceChanges(self.client, count: 2)) } @@ -189,7 +189,7 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep self.waitFor(delay: 9.5) } - Match(["And", "Then"], "^I observe the following Events and Invocations of the Presence EE:$") { args, value in + Match(["And", "Then"], "^I observe the following Events and Invocations of the Presence EE:$") { _, value in let recordedEvents = self.transitionDecorator.recordedEvents.map { $0.contractTestIdentifier } let recordedInvocations = self.dispatcherDecorator.recordedInvocations.map { $0.contractTestIdentifier } @@ -197,7 +197,7 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep XCTAssertTrue(recordedInvocations.elementsEqual(self.extractExpectedResults(from: value).invocations)) } - Then("^I don't observe any Events and Invocations of the Presence EE") { args, value in + Then("^I don't observe any Events and Invocations of the Presence EE") { _, _ in XCTAssertTrue(self.transitionDecorator.recordedEvents.isEmpty) XCTAssertTrue(self.dispatcherDecorator.recordedInvocations.isEmpty) } diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift index bf48eb0e..c827b6bd 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift @@ -8,25 +8,25 @@ // LICENSE file in the root directory of this source tree. // -import Foundation import Cucumberish +import Foundation @testable import PubNub extension Subscribe.Invocation: ContractTestIdentifiable { var contractTestIdentifier: String { switch self { - case .handshakeRequest(_, _): + case .handshakeRequest: return "HANDSHAKE" - case .handshakeReconnect(_, _, _, _): + case .handshakeReconnect: return "HANDSHAKE_RECONNECT" - case .receiveMessages(_, _, _): + case .receiveMessages: return "RECEIVE_MESSAGES" - case .receiveReconnect(_, _, _, _, _): + case .receiveReconnect: return "RECEIVE_RECONNECT" - case .emitMessages(_,_): + case .emitMessages: return "EMIT_MESSAGES" - case .emitStatus(_): + case .emitStatus: return "EMIT_STATUS" } } @@ -50,29 +50,29 @@ extension Subscribe.Invocation.Cancellable: ContractTestIdentifiable { extension Subscribe.Event: ContractTestIdentifiable { var contractTestIdentifier: String { switch self { - case .handshakeSuccess(_): + case .handshakeSuccess: return "HANDSHAKE_SUCCESS" - case .handshakeFailure(_): + case .handshakeFailure: return "HANDSHAKE_FAILURE" - case .handshakeReconnectSuccess(_): + case .handshakeReconnectSuccess: return "HANDSHAKE_RECONNECT_SUCCESS" - case .handshakeReconnectFailure(_): + case .handshakeReconnectFailure: return "HANDSHAKE_RECONNECT_FAILURE" - case .handshakeReconnectGiveUp(_): + case .handshakeReconnectGiveUp: return "HANDSHAKE_RECONNECT_GIVEUP" - case .receiveSuccess(_,_): + case .receiveSuccess: return "RECEIVE_SUCCESS" - case .receiveFailure(_): + case .receiveFailure: return "RECEIVE_FAILURE" - case .receiveReconnectSuccess(_,_): + case .receiveReconnectSuccess: return "RECEIVE_RECONNECT_SUCCESS" - case .receiveReconnectFailure(_): + case .receiveReconnectFailure: return "RECEIVE_RECONNECT_FAILURE" - case .receiveReconnectGiveUp(_): + case .receiveReconnectGiveUp: return "RECEIVE_RECONNECT_GIVEUP" - case .subscriptionChanged(_, _): + case .subscriptionChanged: return "SUBSCRIPTION_CHANGED" - case .subscriptionRestored(_, _, _): + case .subscriptionRestored: return "SUBSCRIPTION_RESTORED" case .unsubscribeAll: return "UNSUBSCRIBE_ALL" @@ -140,7 +140,7 @@ class PubNubSubscribeEngineContractTestsSteps: PubNubEventEngineContractTestsSte override public func setup() { startCucumberHookEventsListening() - Given("a linear reconnection policy with 3 retries") { args, _ in + Given("a linear reconnection policy with 3 retries") { _, _ in self.replacePubNubConfiguration(with: PubNubConfiguration( publishKey: self.configuration.publishKey, subscribeKey: self.configuration.subscribeKey, @@ -179,12 +179,12 @@ class PubNubSubscribeEngineContractTestsSteps: PubNubEventEngineContractTestsSte XCTAssertNotNil(self.receivedErrorStatuses.first) } - Then("I receive the message in my subscribe response") { _, userInfo in + Then("I receive the message in my subscribe response") { _, _ in let messages = self.waitForMessages(self.client, count: 1) ?? [] XCTAssertNotNil(messages.first) } - Match(["And"], "I observe the following:") { args, value in + Match(["And"], "I observe the following:") { _, value in let recordedEvents = self.transitionDecorator.recordedEvents.map { $0.contractTestIdentifier } let recordedInvocations = self.dispatcherDecorator.recordedInvocations.map { $0.contractTestIdentifier } diff --git a/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift b/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift index 78bd36b6..6ddabf03 100644 --- a/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift +++ b/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift @@ -99,9 +99,9 @@ class DelayedHeartbeatEffectTests: XCTestCase { } } -fileprivate extension DelayedHeartbeatEffectTests { +private extension DelayedHeartbeatEffectTests { func mockResponse(_ response: GenericServicePayloadResponse) { - mockUrlSession.responseForDataTask = { task, id in + mockUrlSession.responseForDataTask = { task, _ in task.mockError = nil task.mockData = try? Constant.jsonEncoder.encode(response) task.mockResponse = HTTPURLResponse(statusCode: response.status) diff --git a/Tests/PubNubTests/EventEngine/Subscribe/SubscribeEffectsTests.swift b/Tests/PubNubTests/EventEngine/Subscribe/SubscribeEffectsTests.swift index 6a847a51..461cf21a 100644 --- a/Tests/PubNubTests/EventEngine/Subscribe/SubscribeEffectsTests.swift +++ b/Tests/PubNubTests/EventEngine/Subscribe/SubscribeEffectsTests.swift @@ -466,13 +466,13 @@ extension SubscribeEffectsTests { // MARK: - Helpers -fileprivate extension SubscribeEffectsTests { +private extension SubscribeEffectsTests { func mockResponse( subscribeResponse: SubscribeResponse? = nil, errorIfAny: Error? = nil, httpResponse: HTTPURLResponse = HTTPURLResponse(statusCode: 200)! ) { - mockUrlSession.responseForDataTask = { task, id in + mockUrlSession.responseForDataTask = { task, _ in task.mockError = errorIfAny task.mockData = try? Constant.jsonEncoder.encode(subscribeResponse) task.mockResponse = httpResponse @@ -481,7 +481,7 @@ fileprivate extension SubscribeEffectsTests { } } -fileprivate let firstMessage = SubscribeMessagePayload( +private let firstMessage = SubscribeMessagePayload( shard: "", subscription: nil, channel: "test-channel", @@ -496,7 +496,7 @@ fileprivate let firstMessage = SubscribeMessagePayload( error: nil ) -fileprivate let secondMessage = SubscribeMessagePayload( +private let secondMessage = SubscribeMessagePayload( shard: "", subscription: nil, channel: "test-channel", From 695b0dd69bc5af6ee6c1c01ba969bfc6e4028e26 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Thu, 21 Mar 2024 16:35:13 +0100 Subject: [PATCH 12/15] Removing redundant compactMap --- .../PubNub/DependencyContainer/DependencyContainer.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/PubNub/DependencyContainer/DependencyContainer.swift b/Sources/PubNub/DependencyContainer/DependencyContainer.swift index e86f66d3..bc130562 100644 --- a/Sources/PubNub/DependencyContainer/DependencyContainer.swift +++ b/Sources/PubNub/DependencyContainer/DependencyContainer.swift @@ -124,12 +124,12 @@ extension DependencyContainer { } private extension DependencyContainer { - func resolveSession(session: SessionReplaceable, with operators: [RequestOperator?]) -> SessionReplaceable { + func resolveSession(session: SessionReplaceable, with operators: [RequestOperator]) -> SessionReplaceable { session.defaultRequestOperator == nil ? session.usingDefault(requestOperator: MultiplexRequestOperator( - operators: operators.compactMap { $0 } + operators: operators )) : session.usingDefault(requestOperator: session.defaultRequestOperator?.merge( - operators: operators.compactMap { $0 }) - ) + operators: operators + )) } } From 2ba40840c9ba4b3a3f5a07db2c1bbf6915a7eece Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Mon, 25 Mar 2024 09:24:04 +0100 Subject: [PATCH 13/15] Fixes for DependencyContainer --- .../DependencyContainer.swift | 109 +++++++++++++++--- ...ubNubPresenceEngineContractTestSteps.swift | 13 ++- ...NubSubscribeEngineContractTestsSteps.swift | 15 ++- 3 files changed, 110 insertions(+), 27 deletions(-) diff --git a/Sources/PubNub/DependencyContainer/DependencyContainer.swift b/Sources/PubNub/DependencyContainer/DependencyContainer.swift index bc130562..bff0c2d8 100644 --- a/Sources/PubNub/DependencyContainer/DependencyContainer.swift +++ b/Sources/PubNub/DependencyContainer/DependencyContainer.swift @@ -24,35 +24,82 @@ protocol DependencyKey { // The class that serves as a registry for dependencies. Each dependency is associated with a unique key // conforming to the `DependencyKey` protocol. class DependencyContainer { - private var values: [ObjectIdentifier: Any] = [:] - + private var resolvedValues: [ObjectIdentifier: any Wrappable] = [:] + private var registeredKeys: [ObjectIdentifier: (key: any DependencyKey.Type, scope: Scope)] = [:] + + // Defines the lifecycle of the given dependency + enum Scope { + // The dependency is owned by the container. It lives as long as the container itself lives. + // The dependency is strongly referenced by the container. + case container + // The container does not own the dependency. The dependency could be deallocated even if the container + // is still alive, if there are no more strong references to it. + case weak + // Indicates that the DependencyContainer doesn't keep any reference (neither strong nor weak) to the dependency. + // Each time the dependency is requested, a new instance is created and returned + case transient + } + init(instanceID: UUID = UUID(), configuration: PubNubConfiguration) { - self[PubNubConfigurationDependencyKey.self] = configuration - self[PubNubInstanceIDDependencyKey.self] = instanceID + register(value: configuration, forKey: PubNubConfigurationDependencyKey.self) + register(value: instanceID, forKey: PubNubInstanceIDDependencyKey.self) + register(key: FileURLSessionDependencyKey.self, scope: .weak) + register(key: DefaultHTTPSessionDependencyKey.self, scope: .weak) + register(key: HTTPSubscribeSessionDependencyKey.self, scope: .weak) + register(key: HTTPPresenceSessionDependencyKey.self, scope: .weak) + register(key: HTTPSubscribeSessionQueueDependencyKey.self, scope: .weak) + register(key: PresenceStateContainerDependencyKey.self, scope: .weak) + register(key: SubscribeEventEngineDependencyKey.self, scope: .weak) + register(key: PresenceEventEngineDependencyKey.self, scope: .weak) + register(key: SubscriptionSessionDependencyKey.self, scope: .weak) } subscript(key: K.Type) -> K.Value where K: DependencyKey { get { - if let existingValue = values[ObjectIdentifier(key)] { - if let existingValue = existingValue as? K.Value { - return existingValue + guard let underlyingKey = registeredKeys[ObjectIdentifier(key)] else { + preconditionFailure("Cannot find \(key). Ensure this key was registered before") + } + if underlyingKey.scope == .transient { + if let value = underlyingKey.key.value(from: self) as? K.Value { + return value } else { - preconditionFailure("Cannot resolve value for \(key)") + preconditionFailure("Cannot create value for key \(key)") } } - let value = key.value(from: self) - values[ObjectIdentifier(key)] = value - return value - } set { - values[ObjectIdentifier(key)] = newValue + if let valueWrapper = resolvedValues[ObjectIdentifier(key)] { + if let underlyingValue = valueWrapper.value as? K.Value { + return underlyingValue + } + } + if let value = underlyingKey.key.value(from: self) as? K.Value { + if Mirror(reflecting: value).displayStyle == .class && underlyingKey.scope == .weak { + resolvedValues[ObjectIdentifier(key)] = WeakWrapper(value as AnyObject) + } else { + resolvedValues[ObjectIdentifier(key)] = ValueWrapper(value) + } + return value + } + preconditionFailure("Cannot create value for key \(key)") } } + + func register(key: K.Type, scope: Scope = .container) { + registeredKeys[ObjectIdentifier(key)] = (key: key, scope: scope) + } @discardableResult - func register(value: K.Value?, forKey key: K.Type) -> DependencyContainer { - if let value { - values[ObjectIdentifier(key)] = value + func register(value: K.Value?, forKey key: K.Type, in scope: Scope = .container) -> DependencyContainer { + guard let value = value else { + return self } + registeredKeys[ObjectIdentifier(key)] = (key: key, scope: scope) + + if Mirror(reflecting: value).displayStyle == .class && scope == .weak { + resolvedValues[ObjectIdentifier(key)] = WeakWrapper(value as AnyObject) + } else { + resolvedValues[ObjectIdentifier(key)] = ValueWrapper(value) + } + return self } } @@ -261,3 +308,33 @@ struct SubscriptionSessionDependencyKey: DependencyKey { } } } + +// Provides a standard interface for objects that wrap or encapsulate other objects in a dependency container context. +protocol Wrappable { + associatedtype T + var value: T? { get } +} + +// A concrete implementation of the `Wrappable` protocol, designed to hold a weak reference to the object it wraps. +// It only accepts classes (reference types) as its generic parameter, because weak references +// can only be made to reference types. +private class WeakWrapper: Wrappable { + private weak var optionalValue: T? + + var value: T? { + optionalValue + } + + init(_ value: T) { + self.optionalValue = value + } +} + +// Holds a strong reference to the object it wraps +private class ValueWrapper: Wrappable { + let value: T? + + init(_ value: T) { + self.value = value + } +} diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift index 5c3cca46..5bc4c671 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift @@ -98,11 +98,14 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep wrappedInstance: PresenceTransition(configuration: configuration) ) - container[key] = PresenceEngine( - state: Presence.HeartbeatInactive(), - transition: self.transitionDecorator, - dispatcher: self.dispatcherDecorator, - dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: configuration)) + container.register( + value: PresenceEngine( + state: Presence.HeartbeatInactive(), + transition: self.transitionDecorator, + dispatcher: self.dispatcherDecorator, + dependencies: EventEngineDependencies(value: Presence.Dependencies(configuration: configuration)) + ), + forKey: PresenceEventEngineDependencyKey.self ) return PubNub(container: container) diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift index c827b6bd..75dfb278 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift @@ -126,12 +126,15 @@ class PubNubSubscribeEngineContractTestsSteps: PubNubEventEngineContractTestsSte self.transitionDecorator = TransitionDecorator( wrappedInstance: SubscribeTransition() ) - - container[key] = SubscribeEngine( - state: Subscribe.UnsubscribedState(), - transition: self.transitionDecorator, - dispatcher: self.dispatcherDecorator, - dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: configuration)) + + container.register( + value: SubscribeEngine( + state: Subscribe.UnsubscribedState(), + transition: self.transitionDecorator, + dispatcher: self.dispatcherDecorator, + dependencies: EventEngineDependencies(value: Subscribe.Dependencies(configuration: configuration)) + ), + forKey: SubscribeEventEngineDependencyKey.self ) return PubNub(container: container) From f054c8f2a01596a430bff338c550f5db9c815c58 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Mon, 25 Mar 2024 12:26:45 +0100 Subject: [PATCH 14/15] Quality gates (Codacy) --- .../DependencyContainer.swift | 44 +++++++++---------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/Sources/PubNub/DependencyContainer/DependencyContainer.swift b/Sources/PubNub/DependencyContainer/DependencyContainer.swift index bff0c2d8..82ca1b22 100644 --- a/Sources/PubNub/DependencyContainer/DependencyContainer.swift +++ b/Sources/PubNub/DependencyContainer/DependencyContainer.swift @@ -39,7 +39,7 @@ class DependencyContainer { // Each time the dependency is requested, a new instance is created and returned case transient } - + init(instanceID: UUID = UUID(), configuration: PubNubConfiguration) { register(value: configuration, forKey: PubNubConfigurationDependencyKey.self) register(value: instanceID, forKey: PubNubInstanceIDDependencyKey.self) @@ -55,32 +55,30 @@ class DependencyContainer { } subscript(key: K.Type) -> K.Value where K: DependencyKey { - get { - guard let underlyingKey = registeredKeys[ObjectIdentifier(key)] else { - preconditionFailure("Cannot find \(key). Ensure this key was registered before") - } - if underlyingKey.scope == .transient { - if let value = underlyingKey.key.value(from: self) as? K.Value { - return value - } else { - preconditionFailure("Cannot create value for key \(key)") - } - } - if let valueWrapper = resolvedValues[ObjectIdentifier(key)] { - if let underlyingValue = valueWrapper.value as? K.Value { - return underlyingValue - } - } + guard let underlyingKey = registeredKeys[ObjectIdentifier(key)] else { + preconditionFailure("Cannot find \(key). Ensure this key was registered before") + } + if underlyingKey.scope == .transient { if let value = underlyingKey.key.value(from: self) as? K.Value { - if Mirror(reflecting: value).displayStyle == .class && underlyingKey.scope == .weak { - resolvedValues[ObjectIdentifier(key)] = WeakWrapper(value as AnyObject) - } else { - resolvedValues[ObjectIdentifier(key)] = ValueWrapper(value) - } return value + } else { + preconditionFailure("Cannot create value for key \(key)") + } + } + if let valueWrapper = resolvedValues[ObjectIdentifier(key)] { + if let underlyingValue = valueWrapper.value as? K.Value { + return underlyingValue + } + } + if let value = underlyingKey.key.value(from: self) as? K.Value { + if Mirror(reflecting: value).displayStyle == .class && underlyingKey.scope == .weak { + resolvedValues[ObjectIdentifier(key)] = WeakWrapper(value as AnyObject) + } else { + resolvedValues[ObjectIdentifier(key)] = ValueWrapper(value) } - preconditionFailure("Cannot create value for key \(key)") + return value } + preconditionFailure("Cannot create value for key \(key)") } func register(key: K.Type, scope: Scope = .container) { From bd6be5f75177a3403a59d34bc7ebc0b0ef4874b3 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Mon, 25 Mar 2024 13:07:27 +0100 Subject: [PATCH 15/15] Codacy --- .../DependencyContainer/DependencyContainer.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/PubNub/DependencyContainer/DependencyContainer.swift b/Sources/PubNub/DependencyContainer/DependencyContainer.swift index 82ca1b22..53566bf1 100644 --- a/Sources/PubNub/DependencyContainer/DependencyContainer.swift +++ b/Sources/PubNub/DependencyContainer/DependencyContainer.swift @@ -80,7 +80,7 @@ class DependencyContainer { } preconditionFailure("Cannot create value for key \(key)") } - + func register(key: K.Type, scope: Scope = .container) { registeredKeys[ObjectIdentifier(key)] = (key: key, scope: scope) } @@ -97,7 +97,7 @@ class DependencyContainer { } else { resolvedValues[ObjectIdentifier(key)] = ValueWrapper(value) } - + return self } } @@ -318,11 +318,11 @@ protocol Wrappable { // can only be made to reference types. private class WeakWrapper: Wrappable { private weak var optionalValue: T? - + var value: T? { optionalValue } - + init(_ value: T) { self.optionalValue = value } @@ -331,7 +331,7 @@ private class WeakWrapper: Wrappable { // Holds a strong reference to the object it wraps private class ValueWrapper: Wrappable { let value: T? - + init(_ value: T) { self.value = value }