From b4f324efb0f50c1c512cab9745f5dc00fd2aaa14 Mon Sep 17 00:00:00 2001 From: jguz-pubnub Date: Fri, 24 Nov 2023 17:14:00 +0100 Subject: [PATCH] Presence & Subscribe Event Engine * PubNubConfiguration for both Subscribe & Presence EE * Fixes for Presence's contract tests --- .../EventEngine/Core/EventEngineFactory.swift | 4 +-- .../Presence/Effects/HeartbeatEffect.swift | 3 ++ .../Presence/Effects/LeaveEffect.swift | 3 ++ .../Helpers/PresenceHeartbeatRequest.swift | 4 +-- .../Helpers/PresenceLeaveRequest.swift | 4 +-- .../EventEngine/Presence/Presence.swift | 2 +- .../Presence/PresenceTransition.swift | 30 +++++++++---------- .../EventEngine/Subscribe/Subscribe.swift | 4 +-- ...entEngineSubscriptionSessionStrategy.swift | 4 +-- .../LegacySubscriptionSessionStrategy.swift | 4 +-- .../SubscriptionSessionStrategy.swift | 2 +- .../Subscription/SubscriptionSession.swift | 2 +- ...ubNubPresenceEngineContractTestSteps.swift | 9 ++++-- ...NubSubscribeEngineContractTestsSteps.swift | 23 +++++++------- .../DelayedHeartbeatEffectTests.swift | 4 +-- .../Presence/HeartbeatEffectTests.swift | 3 +- .../Presence/LeaveEffectTests.swift | 24 +++++++++++++++ 17 files changed, 83 insertions(+), 46 deletions(-) diff --git a/Sources/PubNub/EventEngine/Core/EventEngineFactory.swift b/Sources/PubNub/EventEngine/Core/EventEngineFactory.swift index d4af5377..210f14b9 100644 --- a/Sources/PubNub/EventEngine/Core/EventEngineFactory.swift +++ b/Sources/PubNub/EventEngine/Core/EventEngineFactory.swift @@ -32,7 +32,7 @@ typealias PresenceEngine = EventEngine<(any PresenceState), Presence.Event, Pres class EventEngineFactory { func subscribeEngine( - with configuration: SubscriptionConfiguration, + with configuration: PubNubConfiguration, dispatcher: some Dispatcher, transition: some TransitionProtocol ) -> SubscribeEngine { @@ -45,7 +45,7 @@ class EventEngineFactory { } func presenceEngine( - with configuration: SubscriptionConfiguration, + with configuration: PubNubConfiguration, dispatcher: some Dispatcher, transition: some TransitionProtocol ) -> PresenceEngine { diff --git a/Sources/PubNub/EventEngine/Presence/Effects/HeartbeatEffect.swift b/Sources/PubNub/EventEngine/Presence/Effects/HeartbeatEffect.swift index ebee82eb..be06a4a4 100644 --- a/Sources/PubNub/EventEngine/Presence/Effects/HeartbeatEffect.swift +++ b/Sources/PubNub/EventEngine/Presence/Effects/HeartbeatEffect.swift @@ -35,6 +35,9 @@ class HeartbeatEffect: EffectHandler { } func performTask(completionBlock: @escaping ([Presence.Event]) -> Void) { + guard request.configuration.heartbeatInterval > 0 else { + completionBlock([]); return + } request.execute() { result in switch result { case .success(_): diff --git a/Sources/PubNub/EventEngine/Presence/Effects/LeaveEffect.swift b/Sources/PubNub/EventEngine/Presence/Effects/LeaveEffect.swift index 6c5c336c..5b842714 100644 --- a/Sources/PubNub/EventEngine/Presence/Effects/LeaveEffect.swift +++ b/Sources/PubNub/EventEngine/Presence/Effects/LeaveEffect.swift @@ -35,6 +35,9 @@ class LeaveEffect: EffectHandler { } func performTask(completionBlock: @escaping ([Presence.Event]) -> Void) { + guard !request.configuration.supressLeaveEvents else { + completionBlock([]); return + } request.execute() { result in switch result { case .success(_): diff --git a/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift b/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift index d40ad5e8..ea8d86f2 100644 --- a/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift +++ b/Sources/PubNub/EventEngine/Presence/Helpers/PresenceHeartbeatRequest.swift @@ -30,7 +30,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 @@ -39,7 +39,7 @@ class PresenceHeartbeatRequest { init( channels: [String], groups: [String], - 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 81f8ca5c..356d3bc1 100644 --- a/Sources/PubNub/EventEngine/Presence/Helpers/PresenceLeaveRequest.swift +++ b/Sources/PubNub/EventEngine/Presence/Helpers/PresenceLeaveRequest.swift @@ -30,8 +30,8 @@ import Foundation class PresenceLeaveRequest { let channels: [String] let groups: [String] + let configuration: PubNubConfiguration - private let configuration: SubscriptionConfiguration private let session: SessionReplaceable private let sessionResponseQueue: DispatchQueue private var request: RequestReplaceable? @@ -39,7 +39,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 ce9083e4..4ba07fbe 100644 --- a/Sources/PubNub/EventEngine/Presence/Presence.swift +++ b/Sources/PubNub/EventEngine/Presence/Presence.swift @@ -96,7 +96,7 @@ extension Presence { extension Presence { struct EngineInput { - let configuration: SubscriptionConfiguration + let configuration: PubNubConfiguration } } diff --git a/Sources/PubNub/EventEngine/Presence/PresenceTransition.swift b/Sources/PubNub/EventEngine/Presence/PresenceTransition.swift index 11d071a9..b5f54328 100644 --- a/Sources/PubNub/EventEngine/Presence/PresenceTransition.swift +++ b/Sources/PubNub/EventEngine/Presence/PresenceTransition.swift @@ -41,9 +41,9 @@ class PresenceTransition: TransitionProtocol { func canTransition(from state: State, dueTo event: Event) -> Bool { switch event { case .joined(_,_): - return configuration.heartbeatInterval > 0 + return true case .left(_,_): - return !(state is Presence.HeartbeatInactive) && !configuration.supressLeaveEvents + return !(state is Presence.HeartbeatInactive) case .heartbeatSuccess: return state is Presence.Heartbeating || state is Presence.HeartbeatReconnecting case .heartbeatFailed(_): @@ -65,13 +65,13 @@ class PresenceTransition: TransitionProtocol { switch state { case is Presence.Heartbeating: return [.regular(.heartbeat(channels: state.channels, groups: state.input.groups))] - case is Presence.HeartbeatCooldown: - return [.managed(.wait)] case let state as Presence.HeartbeatReconnecting: return [.managed(.delayedHeartbeat( channels: state.channels, groups: state.groups, retryAttempt: state.retryAttempt, error: state.error ))] + case is Presence.HeartbeatCooldown: + return [.managed(.wait)] default: return [] } @@ -93,9 +93,9 @@ class PresenceTransition: TransitionProtocol { switch event { case .joined(let channels, let groups): - results = heartbeatingTransition(from: state, joined: channels, and: groups) + results = heartbeatingTransition(from: state, joining: (channels: channels, groups: groups)) case .left(let channels, let groups): - results = heartbeatingTransition(from: state, left: channels, and: groups) + results = heartbeatingTransition(from: state, leaving: (channels: channels, groups: groups)) case .heartbeatSuccess: results = heartbeatSuccessTransition(from: state) case .heartbeatFailed(let error): @@ -122,12 +122,11 @@ class PresenceTransition: TransitionProtocol { fileprivate extension PresenceTransition { func heartbeatingTransition( from state: State, - joined channels: [String], - and groups: [String] + joining: (channels: [String], groups: [String]) ) -> TransitionResult { let newInput = state.input + PresenceInput( - channels: channels, - groups: groups + channels: joining.channels, + groups: joining.groups ) if state is Presence.HeartbeatStopped { return TransitionResult(state: Presence.HeartbeatStopped(input: newInput)) @@ -140,12 +139,11 @@ fileprivate extension PresenceTransition { fileprivate extension PresenceTransition { func heartbeatingTransition( from state: State, - left channels: [String], - and groups: [String] + leaving: (channels: [String], groups: [String]) ) -> TransitionResult { let newInput = state.input - PresenceInput( - channels: channels, - groups: groups + channels: leaving.channels, + groups: leaving.groups ) if state is Presence.HeartbeatStopped { return TransitionResult( @@ -155,12 +153,12 @@ fileprivate extension PresenceTransition { } else if newInput.isEmpty { return TransitionResult( state: Presence.HeartbeatInactive(), - invocations: [.regular(.leave(channels: channels, groups: groups))] + invocations: [.regular(.leave(channels: leaving.channels, groups: leaving.groups))] ) } else { return TransitionResult( state: Presence.Heartbeating(input: newInput), - invocations: [.regular(.leave(channels: channels, groups: groups))] + invocations: [.regular(.leave(channels: leaving.channels, groups: leaving.groups))] ) } } diff --git a/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift b/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift index 904eb436..63c3805c 100644 --- a/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift +++ b/Sources/PubNub/EventEngine/Subscribe/Subscribe.swift @@ -142,10 +142,10 @@ extension Subscribe { extension Subscribe { struct EngineInput { - 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/Subscription/Strategy/EventEngineSubscriptionSessionStrategy.swift b/Sources/PubNub/Subscription/Strategy/EventEngineSubscriptionSessionStrategy.swift index 1b2c4ca2..119b6d59 100644 --- a/Sources/PubNub/Subscription/Strategy/EventEngineSubscriptionSessionStrategy.swift +++ b/Sources/PubNub/Subscription/Strategy/EventEngineSubscriptionSessionStrategy.swift @@ -31,13 +31,13 @@ class EventEngineSubscriptionSessionStrategy: SubscriptionSessionStrategy { let uuid = UUID() var privateListeners: WeakSet = WeakSet([]) - var configuration: SubscriptionConfiguration + var configuration: PubNubConfiguration var subscribeEngine: SubscribeEngine var presenceEngine: PresenceEngine var previousTokenResponse: SubscribeCursor? internal init( - configuration: SubscriptionConfiguration, + configuration: PubNubConfiguration, subscribeEngine: SubscribeEngine, presenceEngine: PresenceEngine ) { diff --git a/Sources/PubNub/Subscription/Strategy/LegacySubscriptionSessionStrategy.swift b/Sources/PubNub/Subscription/Strategy/LegacySubscriptionSessionStrategy.swift index 91f1fd4a..0a57e5d2 100644 --- a/Sources/PubNub/Subscription/Strategy/LegacySubscriptionSessionStrategy.swift +++ b/Sources/PubNub/Subscription/Strategy/LegacySubscriptionSessionStrategy.swift @@ -34,7 +34,7 @@ class LegacySubscriptionSessionStrategy: SubscriptionSessionStrategy { let sessionStream: SessionListener let responseQueue: DispatchQueue - var configuration: SubscriptionConfiguration + var configuration: PubNubConfiguration var privateListeners: WeakSet = WeakSet([]) var filterExpression: String? var messageCache = [SubscribeMessagePayload?].init(repeating: nil, count: 100) @@ -85,7 +85,7 @@ class LegacySubscriptionSessionStrategy: SubscriptionSessionStrategy { var internalState = Atomic(SubscriptionState()) internal init( - configuration: SubscriptionConfiguration, + configuration: PubNubConfiguration, network subscribeSession: SessionReplaceable, presenceSession: SessionReplaceable ) { diff --git a/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift b/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift index 63b642eb..501a4f96 100644 --- a/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift +++ b/Sources/PubNub/Subscription/Strategy/SubscriptionSessionStrategy.swift @@ -29,7 +29,7 @@ import Foundation protocol SubscriptionSessionStrategy: EventStreamEmitter where ListenerType == BaseSubscriptionListener { 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 } diff --git a/Sources/PubNub/Subscription/SubscriptionSession.swift b/Sources/PubNub/Subscription/SubscriptionSession.swift index 1adb7315..c90cb216 100644 --- a/Sources/PubNub/Subscription/SubscriptionSession.swift +++ b/Sources/PubNub/Subscription/SubscriptionSession.swift @@ -40,7 +40,7 @@ public class SubscriptionSession { strategy.previousTokenResponse } - var configuration: SubscriptionConfiguration { + var configuration: PubNubConfiguration { get { strategy.configuration } set { diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift index 6f94fc9b..18f9860c 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubPresenceEngineContractTestSteps.swift @@ -107,12 +107,13 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep wrappedInstance: PresenceTransition(configuration: configuration) ) + let configuration = self.configuration let factory = EventEngineFactory() let subscriptionSession = SubscriptionSession( strategy: EventEngineSubscriptionSessionStrategy( - configuration: self.configuration, + configuration: configuration, subscribeEngine: factory.subscribeEngine( - with: self.configuration, + with: configuration, dispatcher: EffectDispatcher(factory: SubscribeEffectFactory(session: HTTPSession( configuration: URLSessionConfiguration.subscription, sessionQueue: DispatchQueue(label: "Subscribe Response Queue"), @@ -173,6 +174,10 @@ class PubNubPresenceEngineContractTestsSteps: PubNubEventEngineContractTestsStep XCTAssertNotNil(self.waitForPresenceChanges(self.client, count: 3)) } + Then("^I wait '([0-9]+)' seconds$") { args, _ in + self.waitFor(delay: TimeInterval(args!.first!)!) + } + Then("^I wait for getting Presence left events$") { args, _ in XCTAssertNotNil(self.waitForPresenceChanges(self.client, count: 2)) } diff --git a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift index 1be4552c..e6659bb8 100644 --- a/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift +++ b/Tests/PubNubContractTest/Steps/EventEngine/PubNubSubscribeEngineContractTestsSteps.swift @@ -164,12 +164,13 @@ class PubNubSubscribeEngineContractTestsSteps: PubNubEventEngineContractTestsSte Given("a linear reconnection policy with 3 retries") { args, _ in self.replacePubNubConfiguration(with: PubNubConfiguration( - publishKey: defaultPublishKey, - subscribeKey: defaultSubscribeKey, - userId: UUID().uuidString, - useSecureConnections: false, - origin: mockServerAddress, + publishKey: self.configuration.publishKey, + subscribeKey: self.configuration.subscribeKey, + userId: self.configuration.userId, + useSecureConnections: self.configuration.useSecureConnections, + origin: self.configuration.origin, automaticRetry: AutomaticRetry(retryLimit: 3, policy: .linear(delay: 0.5)), + heartbeatInterval: 0, supressLeaveEvents: true, enableEventEngine: true )) @@ -177,11 +178,13 @@ class PubNubSubscribeEngineContractTestsSteps: PubNubEventEngineContractTestsSte Given("the demo keyset with event engine enabled") { _, _ in self.replacePubNubConfiguration(with: PubNubConfiguration( - publishKey: defaultPublishKey, - subscribeKey: defaultSubscribeKey, - userId: UUID().uuidString, - useSecureConnections: false, - origin: mockServerAddress, + publishKey: self.configuration.publishKey, + subscribeKey: self.configuration.subscribeKey, + userId: self.configuration.userId, + useSecureConnections: self.configuration.useSecureConnections, + origin: self.configuration.origin, + heartbeatInterval: 0, + supressLeaveEvents: true, enableEventEngine: true )) } diff --git a/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift b/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift index d3ff0d38..1c5a5c1c 100644 --- a/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift +++ b/Tests/PubNubTests/EventEngine/Presence/DelayedHeartbeatEffectTests.swift @@ -52,7 +52,7 @@ class DelayedHeartbeatEffectTests: XCTestCase { super.tearDown() } - func test_DelayedHeartbeatEffectFiresImmediatelyForFirstAttempt() { + func test_DelayedHeartbeatEffectForFirstAttempt() { let expectation = XCTestExpectation() expectation.expectationDescription = "Effect Completion Expectation" expectation.assertForOverFulfill = true @@ -160,7 +160,7 @@ fileprivate extension DelayedHeartbeatEffectTests { factory.effect( for: .delayedHeartbeat( channels: ["channel-1", "channel-2"], groups: ["group-1", "group-2"], - retryAttempt: attempt, error: PubNubError(.unknown) + retryAttempt: attempt, error: error ), with: EventEngineCustomInput(value: Presence.EngineInput( configuration: PubNubConfiguration( diff --git a/Tests/PubNubTests/EventEngine/Presence/HeartbeatEffectTests.swift b/Tests/PubNubTests/EventEngine/Presence/HeartbeatEffectTests.swift index e689b536..3db7f84b 100644 --- a/Tests/PubNubTests/EventEngine/Presence/HeartbeatEffectTests.swift +++ b/Tests/PubNubTests/EventEngine/Presence/HeartbeatEffectTests.swift @@ -39,7 +39,8 @@ class HeartbeatEffectTests: XCTestCase { private let config = PubNubConfiguration( publishKey: "pubKey", subscribeKey: "subKey", - userId: "userId" + userId: "userId", + heartbeatInterval: 30 ) override func setUp() { diff --git a/Tests/PubNubTests/EventEngine/Presence/LeaveEffectTests.swift b/Tests/PubNubTests/EventEngine/Presence/LeaveEffectTests.swift index 1a2138a4..a914467f 100644 --- a/Tests/PubNubTests/EventEngine/Presence/LeaveEffectTests.swift +++ b/Tests/PubNubTests/EventEngine/Presence/LeaveEffectTests.swift @@ -75,6 +75,30 @@ class LeaveEffectTests: XCTestCase { } wait(for: [expectation], timeout: 0.5) } + + func test_LeaveEffectForFailedRequest() { + let expectation = XCTestExpectation() + expectation.expectationDescription = "Effect Completion Expectation" + expectation.assertForOverFulfill = true + + mockResponse(GenericServicePayloadResponse(status: 500)) + + let config = PubNubConfiguration( + publishKey: "pubKey", + subscribeKey: "subKey", + userId: "userId", + heartbeatInterval: 2 + ) + let effect = factory.effect( + for: .leave(channels: ["c1", "c2"], groups: ["g1", "g2"]), + with: EventEngineCustomInput(value: Presence.EngineInput(configuration: config)) + ) + effect.performTask { returnedEvents in + XCTAssertTrue(returnedEvents.isEmpty) + expectation.fulfill() + } + wait(for: [expectation], timeout: 0.5) + } } fileprivate extension LeaveEffectTests {