diff --git a/Split/Api/DefaultSplitClient.swift b/Split/Api/DefaultSplitClient.swift index 1d3beb8be..6ce325d21 100644 --- a/Split/Api/DefaultSplitClient.swift +++ b/Split/Api/DefaultSplitClient.swift @@ -94,7 +94,7 @@ extension DefaultSplitClient { Logger.w("A handler was added for \(event.toString()) on the SDK, which has already fired and won’t be emitted again. The callback won’t be executed.") return } - eventsManager.register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) + eventsManager.register(event: event, task: task) } // MARK: Listeners with Metadata diff --git a/Split/Events/EventsManagerCoordinator.swift b/Split/Events/EventsManagerCoordinator.swift index ac65fd2a9..c25213105 100644 --- a/Split/Events/EventsManagerCoordinator.swift +++ b/Split/Events/EventsManagerCoordinator.swift @@ -21,15 +21,16 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { private let queue = DispatchQueue(label: "split-event-manager-coordinator") private let eventsToHandle: Set = Set( [.splitsLoadedFromCache, - .splitsUpdated, - .splitKilledNotification] + .splitsUpdated, + .splitKilledNotification, + .sdkError] ) func notifyInternalEvent(_ event: SplitInternalEvent) { - notifyInternalEventWithMetadata(SplitInternalEventWithMetadata(event, metadata: nil)) + notifyInternalEvent(SplitInternalEventWithMetadata(event, metadata: nil)) } - func notifyInternalEventWithMetadata(_ event: SplitInternalEventWithMetadata) { + func notifyInternalEvent(_ event: SplitInternalEventWithMetadata) { if !eventsToHandle.contains(event.type) { return } @@ -38,7 +39,7 @@ class MainSplitEventsManager: SplitEventsManagerCoordinator { self.triggered.insert(event.type) self.managers.forEach { _, manager in - manager.notifyInternalEvent(event.type) + manager.notifyInternalEvent(event) } } } diff --git a/Split/Events/SplitEvent.swift b/Split/Events/SplitEvent.swift index ab624200b..0b2a7872d 100644 --- a/Split/Events/SplitEvent.swift +++ b/Split/Events/SplitEvent.swift @@ -11,7 +11,7 @@ import Foundation let type: SplitEvent let metadata: EventMetadata? - @objc public init(type: SplitEvent, metadata: EventMetadata? = nil) { + @objc public init(_ type: SplitEvent, metadata: EventMetadata? = nil) { self.type = type self.metadata = metadata } @@ -22,17 +22,20 @@ import Foundation case sdkReadyTimedOut case sdkReadyFromCache case sdkUpdated + case sdkError public func toString() -> String { switch self { - case .sdkReady: - return "SDK_READY" - case .sdkUpdated: - return "SDK_UPDATE" - case .sdkReadyTimedOut: - return "SDK_READY_TIMED_OUT" - case .sdkReadyFromCache: - return "SDK_READY_FROM_CACHE" + case .sdkReady: + return "SDK_READY" + case .sdkUpdated: + return "SDK_UPDATE" + case .sdkReadyTimedOut: + return "SDK_READY_TIMED_OUT" + case .sdkReadyFromCache: + return "SDK_READY_FROM_CACHE" + case .sdkError: + return "SDK_ERROR" } } } diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 6dbfd94f8..3bca15aaa 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -10,14 +10,15 @@ import Foundation protocol SplitEventsManager: AnyObject { func register(event: SplitEvent, task: SplitEventTask) - func register(event: SplitEventWithMetadata, task: SplitEventTask) func notifyInternalEvent(_ event: SplitInternalEvent) + func notifyInternalEvent(_ event: SplitInternalEventWithMetadata) func start() func stop() func eventAlreadyTriggered(event: SplitEvent) -> Bool } class DefaultSplitEventsManager: SplitEventsManager { + private let readingRefreshTime: Int private var sdkReadyTimeStart: Int64 @@ -41,11 +42,12 @@ class DefaultSplitEventsManager: SplitEventsManager { self.executionTimes = [String: Int]() registerMaxAllowedExecutionTimesPerEvent() + // SDK Timeout Event if config.sdkReadyTimeOut > 0 { let readyTimedoutQueue = DispatchQueue(label: "split-event-timedout") readyTimedoutQueue.asyncAfter(deadline: .now() + .milliseconds(config.sdkReadyTimeOut)) { [weak self] in guard let self = self else { return } - self.notifyInternalEvent(SplitInternalEvent.sdkReadyTimeoutReached) + self.notifyInternalEvent(.sdkReadyTimeoutReached) } } } @@ -62,13 +64,9 @@ class DefaultSplitEventsManager: SplitEventsManager { func notifyInternalEvent(_ event: SplitInternalEvent) { notifyInternalEvent(SplitInternalEventWithMetadata(event, metadata: nil)) } - - func register(event: SplitEvent, task: SplitEventTask) { - register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) - } - func register(event: SplitEventWithMetadata, task: SplitEventTask) { - let eventName = event.type.toString() + func register(event: SplitEvent, task: SplitEventTask) { + let eventName = event.toString() processQueue.async { [weak self] in guard let self = self else { return } // If event is already triggered, execute the task @@ -76,7 +74,7 @@ class DefaultSplitEventsManager: SplitEventsManager { self.executeTask(event: event, task: task) return } - self.subscribe(task: task, to: event.type) + self.subscribe(task: task, to: event) } } @@ -111,7 +109,6 @@ class DefaultSplitEventsManager: SplitEventsManager { self.eventsQueue.stop() self.eventsQueue.stop() } - } } @@ -125,6 +122,7 @@ class DefaultSplitEventsManager: SplitEventsManager { executionTimes = [ SplitEvent.sdkReady.toString(): 1, SplitEvent.sdkUpdated.toString(): -1, + SplitEvent.sdkError.toString(): -1, SplitEvent.sdkReadyFromCache.toString(): 1, SplitEvent.sdkReadyTimedOut.toString(): 1] } @@ -159,7 +157,7 @@ class DefaultSplitEventsManager: SplitEventsManager { switch event.type { case .splitsUpdated, .mySegmentsUpdated, .myLargeSegmentsUpdated: if isTriggered(external: .sdkReady) { - trigger(event: SplitEventWithMetadata(type: .sdkUpdated, metadata: event.metadata)) + trigger(event: .sdkUpdated) continue } triggerSdkReadyIfNeeded() @@ -181,6 +179,9 @@ class DefaultSplitEventsManager: SplitEventsManager { if !isTriggered(external: .sdkReady) { trigger(event: .sdkReadyTimedOut) } + case .sdkError: + let eventWithMetadata = SplitEventWithMetadata(.sdkError, metadata: event.metadata) + trigger(event: eventWithMetadata) } } } @@ -211,7 +212,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } private func trigger(event: SplitEvent) { - trigger(event: SplitEventWithMetadata(type: event, metadata: nil)) + trigger(event: SplitEventWithMetadata(event, metadata: nil)) } private func trigger(event: SplitEventWithMetadata) { @@ -237,13 +238,14 @@ class DefaultSplitEventsManager: SplitEventsManager { } private func executeTask(event: SplitEvent, task: SplitEventTask) { - executeTask(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) + executeTask(event: SplitEventWithMetadata(event, metadata: nil), task: task) } private func executeTask(event: SplitEventWithMetadata, task: SplitEventTask) { let eventName = task.event.toString() + // RUN IN BG & RETURN if task.runInBackground { TimeChecker.logInterval("Previous to run \(eventName) in Background") @@ -255,6 +257,7 @@ class DefaultSplitEventsManager: SplitEventsManager { return } + // OR RUN ON MAIN DispatchQueue.main.async { TimeChecker.logInterval("Running event on main: \(eventName)") // UI Updates diff --git a/Split/Events/SplitInternalEvent.swift b/Split/Events/SplitInternalEvent.swift index e05385198..a26d4ccb6 100644 --- a/Split/Events/SplitInternalEvent.swift +++ b/Split/Events/SplitInternalEvent.swift @@ -30,14 +30,14 @@ struct SplitInternalEventWithMetadata { } enum EventMetadataType: Int { - case FEATURE_FLAGS_SYNC_ERROR - case SEGMENTS_SYNC_ERROR + case featureFlagsSyncError + case segmentsSyncError public func toString() -> String { switch self { - case .FEATURE_FLAGS_SYNC_ERROR: + case .featureFlagsSyncError: return "FEATURE_FLAGS_SYNC_ERROR" - case .SEGMENTS_SYNC_ERROR: + case .segmentsSyncError: return "SEGMENTS_SYNC_ERROR" } } @@ -53,4 +53,5 @@ enum SplitInternalEvent { case attributesLoadedFromCache case sdkReadyTimeoutReached case splitKilledNotification + case sdkError } diff --git a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift index b6fd71c81..5a93e76ed 100644 --- a/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/PeriodicSyncWorker.swift @@ -130,10 +130,13 @@ class BasePeriodicSyncWorker: PeriodicSyncWorker { Logger.i("Fetch from remote not implemented") } - func notifyUpdate(_ events: [SplitInternalEvent]) { - events.forEach { - eventsManager.notifyInternalEvent($0) - } + func notifyUpdate(_ event: SplitInternalEvent) { + let withMetadata = SplitInternalEventWithMetadata(event, metadata: nil) + notifyUpdate(withMetadata) + } + + func notifyUpdate(_ event: SplitInternalEventWithMetadata) { + eventsManager.notifyInternalEvent(event) } } @@ -181,10 +184,18 @@ class PeriodicSplitsSyncWorker: BasePeriodicSyncWorker { let changeNumber = splitsStorage.changeNumber let rbChangeNumber: Int64 = ruleBasedSegmentsStorage.changeNumber guard let result = try? syncHelper.sync(since: changeNumber, rbSince: rbChangeNumber) else { + let event = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .segmentsSyncError, data: [])) + notifyUpdate(event) return } + if result.success, result.featureFlagsUpdated || result.rbsUpdated { - notifyUpdate([.splitsUpdated]) + // Success + notifyUpdate(.splitsUpdated) + } else if !result.success { + // Fail + let event = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .featureFlagsSyncError, data: [])) + notifyUpdate(event) } } } @@ -225,10 +236,17 @@ class PeriodicMySegmentsSyncWorker: BasePeriodicSyncWorker { if result.success { if result.msUpdated || result.mlsUpdated { // For now is not necessary specify which entity was updated - notifyUpdate([.mySegmentsUpdated]) + notifyUpdate(.mySegmentsUpdated) } + } else { + // Fail + let event = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .segmentsSyncError, data: [])) + notifyUpdate(event) } } catch { + // Fail + let event = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .segmentsSyncError, data: [])) + notifyUpdate(event) Logger.e("Problem fetching segments: %@", error.localizedDescription) } } diff --git a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift index 36973b66b..1bb831c53 100644 --- a/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSegmentsSyncWorker.swift @@ -46,16 +46,23 @@ class RetryableMySegmentsSyncWorker: BaseRetryableSyncWorker { if result.success { if !isSdkReadyTriggered() { // Notifying both to trigger SDK Ready - notifyUpdate([.mySegmentsUpdated]) - notifyUpdate([.myLargeSegmentsUpdated]) + notifyUpdate(.mySegmentsUpdated) + notifyUpdate(.myLargeSegmentsUpdated) } else if result.msUpdated || result.mlsUpdated { // For now is not necessary specify which entity was updated - notifyUpdate([.mySegmentsUpdated]) + notifyUpdate(.mySegmentsUpdated) } return true + } else { + // Fail + let event = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .segmentsSyncError, data: [])) + notifyUpdate(event) } } catch { + // Fail Logger.e("Error while fetching segments in method: \(error.localizedDescription)") + let event = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .segmentsSyncError, data: [])) + notifyUpdate(event) errorHandler?(error) } return false diff --git a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift index 28c0a6f9c..3ba683b4d 100644 --- a/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift +++ b/Split/FetcherEngine/Refresh/RetryableSyncWorker.swift @@ -79,10 +79,12 @@ class BaseRetryableSyncWorker: RetryableSyncWorker { } } - func notifyUpdate(_ events: [SplitInternalEvent]) { - events.forEach { - eventsManager.notifyInternalEvent($0) - } + func notifyUpdate(_ event: SplitInternalEvent) { + notifyUpdate(SplitInternalEventWithMetadata(event, metadata: nil)) + } + + func notifyUpdate(_ event: SplitInternalEventWithMetadata) { + eventsManager.notifyInternalEvent(event) } func isSdkReadyTriggered() -> Bool { @@ -141,15 +143,23 @@ class RetryableSplitsSyncWorker: BaseRetryableSyncWorker { let rbChangeNumber = ruleBasedSegmentsStorage.changeNumber let result = try syncHelper.sync(since: changeNumber, rbSince: rbChangeNumber, clearBeforeUpdate: false) if result.success { + // Success if !isSdkReadyTriggered() || result.featureFlagsUpdated { - notifyUpdate([.splitsUpdated]) + notifyUpdate(.splitsUpdated) } resetBackoffCounter() return true + } else { + // Fail + let eventWithMetadata = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .featureFlagsSyncError, data: [])) + notifyUpdate(eventWithMetadata) } } catch { + // Fail Logger.e("Error while fetching splits in method: \(error.localizedDescription)") + let eventWithMetadata = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .featureFlagsSyncError, data: [])) + notifyUpdate(eventWithMetadata) errorHandler?(error) } return false @@ -217,14 +227,22 @@ class RetryableSplitsUpdateWorker: BaseRetryableSyncWorker { clearBeforeUpdate: false, headers: ServiceConstants.controlNoCacheHeader) if result.success { + // Success if result.featureFlagsUpdated { - notifyUpdate([.splitsUpdated]) + notifyUpdate(.splitsUpdated) } resetBackoffCounter() return true + } else { + // Fail + let eventWithMetadata = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .featureFlagsSyncError, data: [])) + notifyUpdate(eventWithMetadata) } } catch { + // Fail Logger.e("Error while fetching splits in method \(#function): \(error.localizedDescription)") + let eventWithMetadata = SplitInternalEventWithMetadata(.sdkError, metadata: EventMetadata(type: .featureFlagsSyncError, data: [])) + notifyUpdate(eventWithMetadata) errorHandler?(error) } Logger.d("Feature flag changes are not updated yet") diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 5aede0758..6b148d204 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -129,9 +129,9 @@ class SplitsUpdateWorker: UpdateWorker { previousChangeNumber: Int64, changeNumber: Int64) -> Bool { do { - let split = try self.payloadDecoder.decode( + let split = try payloadDecoder.decode( payload: payload, - compressionUtil: self.decomProvider.decompressor(for: compressionType)) + compressionUtil: decomProvider.decompressor(for: compressionType)) if !allRuleBasedSegmentsExist(in: split) { return false @@ -143,11 +143,11 @@ class SplitsUpdateWorker: UpdateWorker { Logger.v("Split update received: \(change)") - if self.splitsStorage.update(splitChange: self.splitChangeProcessor.process(change)) { - self.synchronizer.notifyFeatureFlagsUpdated() + if self.splitsStorage.update(splitChange: splitChangeProcessor.process(change)) { + synchronizer.notifyFeatureFlagsUpdated() } - self.telemetryProducer?.recordUpdatesFromSse(type: .splits) + telemetryProducer?.recordUpdatesFromSse(type: .splits) return true } catch { Logger.e("Error decoding feature flags payload from notification: \(error)") @@ -161,9 +161,9 @@ class SplitsUpdateWorker: UpdateWorker { previousChangeNumber: Int64, changeNumber: Int64) -> Bool { do { - let rbs = try self.ruleBasedSegmentsPayloadDecoder.decode( + let rbs = try ruleBasedSegmentsPayloadDecoder.decode( payload: payload, - compressionUtil: self.decomProvider.decompressor(for: compressionType)) + compressionUtil: decomProvider.decompressor(for: compressionType)) let change = RuleBasedSegmentChange(segments: [rbs], since: previousChangeNumber, @@ -173,13 +173,13 @@ class SplitsUpdateWorker: UpdateWorker { let processedChange = ruleBasedSegmentsChangeProcessor.process(change) - if self.ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, + if ruleBasedSegmentsStorage.update(toAdd: processedChange.toAdd, toRemove: processedChange.toRemove, changeNumber: processedChange.changeNumber) { - self.synchronizer.notifyFeatureFlagsUpdated() + synchronizer.notifyFeatureFlagsUpdated() } - self.telemetryProducer?.recordUpdatesFromSse(type: .splits) + telemetryProducer?.recordUpdatesFromSse(type: .splits) return true } catch { Logger.e("Error decoding rule based segments payload from notification: \(error)") diff --git a/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift b/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift index 80e9abd08..cd32813fd 100644 --- a/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift +++ b/SplitTests/Fake/Service/SplitEventsManagerCoordinatorStub.swift @@ -29,10 +29,14 @@ class SplitEventsManagerCoordinatorStub: SplitEventsManagerCoordinator { func register(event: SplitEventWithMetadata, task: SplitEventTask) { } - - var notifiedEvents = Set() + func notifyInternalEvent(_ event: SplitInternalEvent) { - notifiedEvents.insert(IntegrationHelper.describeEvent(event)) + notifyInternalEvent(SplitInternalEventWithMetadata(event, metadata: nil)) + } + + var notifiedEvents: [SplitInternalEventWithMetadata] = [] + func notifyInternalEvent(_ event: SplitInternalEventWithMetadata) { + notifiedEvents.append(event) } var startCalled = false diff --git a/SplitTests/Fake/SplitEventsManagerMock.swift b/SplitTests/Fake/SplitEventsManagerMock.swift index e6849f193..a82e250fa 100644 --- a/SplitTests/Fake/SplitEventsManagerMock.swift +++ b/SplitTests/Fake/SplitEventsManagerMock.swift @@ -32,28 +32,32 @@ class SplitEventsManagerMock: SplitEventsManager { var isSdkReadyChecked = false - func notifyInternalEvent(_ event:SplitInternalEvent) { - switch event { - case .mySegmentsUpdated: - isSegmentsReadyFired = true - case .splitsUpdated: - isSplitsReadyFired = true - isSplitUpdatedTriggered = true - if let exp = readyExp { - exp.fulfill() - } - case .sdkReadyTimeoutReached: - isSdkTimeoutFired = true - if let exp = timeoutExp { - exp.fulfill() - } - default: - print("\(event)") + func notifyInternalEvent(_ event: SplitInternalEvent) { + notifyInternalEvent(SplitInternalEventWithMetadata(event, metadata: nil)) + } + + func notifyInternalEvent(_ event: SplitInternalEventWithMetadata) { + switch event.type { + case .mySegmentsUpdated: + isSegmentsReadyFired = true + case .splitsUpdated: + isSplitsReadyFired = true + isSplitUpdatedTriggered = true + if let exp = readyExp { + exp.fulfill() + } + case .sdkReadyTimeoutReached: + isSdkTimeoutFired = true + if let exp = timeoutExp { + exp.fulfill() + } + default: + print("\(event)") } } func register(event: SplitEvent, task: SplitEventTask) { - register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) + register(event: SplitEventWithMetadata(event, metadata: nil), task: task) } var registeredEvents = [SplitEventWithMetadata: SplitEventTask]() diff --git a/SplitTests/Fake/SplitEventsManagerStub.swift b/SplitTests/Fake/SplitEventsManagerStub.swift index fe09fe197..44dc2a4ab 100644 --- a/SplitTests/Fake/SplitEventsManagerStub.swift +++ b/SplitTests/Fake/SplitEventsManagerStub.swift @@ -20,27 +20,31 @@ class SplitEventsManagerStub: SplitEventsManager { var stopCalled = false func notifyInternalEvent(_ event: SplitInternalEvent) { - switch event { - case .mySegmentsLoadedFromCache: - mySegmentsLoadedEventFiredCount+=1 - if let exp = mySegmentsLoadedEventExp { - exp.fulfill() - } - case .splitsLoadedFromCache: - splitsLoadedEventFiredCount+=1 - - case .splitKilledNotification: - splitsKilledEventFiredCount+=1 - - case .splitsUpdated: - splitsUpdatedEventFiredCount+=1 - default: - print("internal event fired: \(event)") + notifyInternalEvent(SplitInternalEventWithMetadata(event, metadata: nil)) + } + + func notifyInternalEvent(_ event: SplitInternalEventWithMetadata) { + switch event.type { + case .mySegmentsLoadedFromCache: + mySegmentsLoadedEventFiredCount+=1 + if let exp = mySegmentsLoadedEventExp { + exp.fulfill() + } + case .splitsLoadedFromCache: + splitsLoadedEventFiredCount+=1 + + case .splitKilledNotification: + splitsKilledEventFiredCount+=1 + + case .splitsUpdated: + splitsUpdatedEventFiredCount+=1 + default: + print("internal event fired: \(event)") } } func register(event: SplitEvent, task: SplitEventTask) { - register(event: SplitEventWithMetadata(type: event, metadata: nil), task: task) + register(event: SplitEventWithMetadata(event, metadata: nil), task: task) } var registeredEvents = [SplitEventWithMetadata: SplitEventTask]() diff --git a/SplitTests/Helpers/IntegrationHelper.swift b/SplitTests/Helpers/IntegrationHelper.swift index a40e360b0..6f34a5564 100644 --- a/SplitTests/Helpers/IntegrationHelper.swift +++ b/SplitTests/Helpers/IntegrationHelper.swift @@ -227,6 +227,8 @@ class IntegrationHelper { return "myLargeSegmentsUpdated" case .myLargeSegmentsLoadedFromCache: return "myLargeSegmentsLoadedFromCache" + case .sdkError: + return "sdkError" } } diff --git a/SplitTests/Integration/Sync/SplitChangesServerErrorTest.swift b/SplitTests/Integration/Sync/SplitChangesServerErrorTest.swift index 63169787f..cc6d3fb12 100644 --- a/SplitTests/Integration/Sync/SplitChangesServerErrorTest.swift +++ b/SplitTests/Integration/Sync/SplitChangesServerErrorTest.swift @@ -24,24 +24,222 @@ class SplitChangesServerErrorTest: XCTestCase { ] var serverUrl = "localhost" - let impExp = XCTestExpectation(description: "impressions") - var impHit: [ImpressionsTest]? + // Client config var httpClient: HttpClient! var streamingBinding: TestStreamResponseBinding? - + var splitConfig: SplitClientConfig? + let apiKey = "99049fd8653247c5ea42bc3c1ae2c6a42bc3_f" + let key: Key = Key(matchingKey: "CUSTOMER_ID", bucketingKey: nil) + let builder = DefaultSplitFactoryBuilder() + override func setUp() { + splitConfig = SplitClientConfig() + splitConfig!.streamingEnabled = false + splitConfig!.featuresRefreshRate = 3 + splitConfig!.impressionRefreshRate = kNeverRefreshRate + splitConfig!.sdkReadyTimeOut = 60000 + splitConfig!.trafficType = "client" + splitConfig!.streamingEnabled = false + splitConfig!.serviceEndpoints = ServiceEndpoints.builder().set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() + _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "SplitChangesServerErrorTest")) + + let session = HttpSessionMock() + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), streamingHandler: buildStreamingHandler()) + httpClient = DefaultHttpClient(session: session, requestManager: reqManager) + } + + // MARK: Getting changes from server and test treatments and change number + func testChangesError() throws { + var treatments = [String]() + let sdkReady = XCTestExpectation(description: "SDK READY Expectation") + + _ = builder.setHttpClient(httpClient) + var factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig!).build() + + let client = factory!.client + + var sdkReadyFired = false + + client.on(event: SplitEvent.sdkReady) { + sdkReadyFired = true + sdkReady.fulfill() + } + + wait(for: [sdkReady], timeout: 10) + + for i in 0..<4 { + wait(for: [spExp[i]], timeout: 40) + treatments.append(client.getTreatment("test_feature")) + } + + XCTAssertTrue(sdkReadyFired) + + XCTAssertEqual("on_0", treatments[0]) + XCTAssertEqual("on_0", treatments[1]) + XCTAssertEqual("on_0", treatments[2]) + XCTAssertEqual("off_1", treatments[3]) + + cleanup(client, &factory) + } + + // MARK: Getting segments from server and getting a server error + func testResponseSegmentsSyncError() throws { + + // Networking setup + let dispatcher: HttpClientTestDispatcher = { request in + if request.isSplitEndpoint() { + return TestDispatcherResponse(code: 200, data: try? Json.encodeToJsonData(self.loadSplitsChangeFile("splitchanges_int_test"))) // OK Splits + } + if request.isMySegmentsEndpoint() { + return TestDispatcherResponse(code: 500) // Error for Segments + } + return TestDispatcherResponse(code: 200) + } + let session = HttpSessionMock() + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: dispatcher, streamingHandler: buildStreamingHandler()) + httpClient = DefaultHttpClient(session: session, requestManager: reqManager) + + // Client config + _ = builder.setHttpClient(httpClient) + var factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig!).build() + let client = factory!.client + + let sdkError = XCTestExpectation(description: "SDK ERROR Expectation") + var errorType: EventMetadataType? + + // Listener + client.on(event: .sdkError) { error in + errorType = error.type + sdkError.fulfill() + } + + // Test + wait(for: [sdkError], timeout: 5) + XCTAssertEqual(errorType, .segmentsSyncError) + + cleanup(client, &factory) + } + + // MARK: Getting Flags from server and getting a server error + func testResponseFlagsSyncError() throws { + + // Networking setup + let dispatcher: HttpClientTestDispatcher = { request in + if request.isMySegmentsEndpoint() { + return TestDispatcherResponse(code: 200, data: Data(self.updatedSegments(index: 4).utf8)) // OK Segments + } + return TestDispatcherResponse(code: 500) // Error for Splits + } + let session = HttpSessionMock() + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: dispatcher, streamingHandler: buildStreamingHandler()) + httpClient = DefaultHttpClient(session: session, requestManager: reqManager) + + // Client config + _ = builder.setHttpClient(httpClient) + var factory: SplitFactory? = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig!).build() + let client = factory!.client + + let sdkError = XCTestExpectation(description: "SDK ERROR Expectation") + var errorType: EventMetadataType? + + // Listener + client.on(event: .sdkError) { error in + errorType = error.type + sdkError.fulfill() + } + + // Test + wait(for: [sdkError], timeout: 5) + XCTAssertEqual(errorType, .featureFlagsSyncError) + + cleanup(client, &factory) + } + + // MARK: Getting malformed flags from server + func testResponseFlagsParseError() throws { + + // Networking setup + let dispatcher: HttpClientTestDispatcher = { request in + if request.isMySegmentsEndpoint() { + return TestDispatcherResponse(code: 200, data: Data(self.updatedSegments(index: 4).utf8)) // OK for Segments + } + if request.isSplitEndpoint() { + return TestDispatcherResponse(code: 200, data: try? Json.encodeToJsonData(self.loadSplitsChangeFile("matchers"))) // OK for Splits, but bad JSON + } + return TestDispatcherResponse(code: 200) + } + let session = HttpSessionMock() + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: dispatcher, streamingHandler: buildStreamingHandler()) + httpClient = DefaultHttpClient(session: session, requestManager: reqManager) + + // Client config + _ = builder.setHttpClient(httpClient) + var factory: SplitFactory? = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig!).build() + let client = factory!.client + + let sdkError = XCTestExpectation(description: "SDK ERROR Expectation") + var errorType: EventMetadataType? + + // Listener + client.on(event: .sdkError) { error in + errorType = error.type + sdkError.fulfill() + } + + // Test + wait(for: [sdkError], timeout: 5) + XCTAssertEqual(errorType, .featureFlagsSyncError) + + cleanup(client, &factory) + } + + // MARK: Getting malformed segments from server + func testResponseSegmentsParseError() throws { + + // Networking setup + let dispatcher: HttpClientTestDispatcher = { request in + if request.isMySegmentsEndpoint() { + return TestDispatcherResponse(code: 200, data: Data("".utf8)) // OK for Segments, but bad JSON + } + if request.isSplitEndpoint() { + return TestDispatcherResponse(code: 200, data: try? Json.encodeToJsonData(self.loadSplitsChangeFile("splitchanges_int_test"))) // OK Splits + } + return TestDispatcherResponse(code: 500) + } let session = HttpSessionMock() - let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), - streamingHandler: buildStreamingHandler()) + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: dispatcher, streamingHandler: buildStreamingHandler()) httpClient = DefaultHttpClient(session: session, requestManager: reqManager) + + // Client config + _ = builder.setHttpClient(httpClient) + var factory: SplitFactory? = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig!).build() + let client = factory!.client + + let sdkError = XCTestExpectation(description: "SDK ERROR Expectation") + var errorType: EventMetadataType? + + // Listener + client.on(event: .sdkError) { error in + errorType = error.type + sdkError.fulfill() + } + + // Test + wait(for: [sdkError], timeout: 5) + XCTAssertEqual(errorType, .segmentsSyncError) + + cleanup(client, &factory) } +} +// MARK: Test Helpers +extension SplitChangesServerErrorTest { + private func buildTestDispatcher() -> HttpClientTestDispatcher { - - let respData = responseSplitChanges() + let respData = responseSplitChanges("splitchanges_int_test") var responses = [TestDispatcherResponse]() responses.append(TestDispatcherResponse(code: 200, data: Data(try! Json.encodeToJson( TargetingRulesChange(featureFlags: respData[0], ruleBasedSegments: RuleBasedSegmentChange(segments: [], since: -1, till: -1))).utf8))) @@ -49,8 +247,7 @@ class SplitChangesServerErrorTest: XCTestCase { responses.append(TestDispatcherResponse(code: 500)) responses.append(TestDispatcherResponse(code: 200, data: Data(try! Json.encodeToJson( TargetingRulesChange(featureFlags: respData[1], ruleBasedSegments: RuleBasedSegmentChange(segments: [], since: -1, till: -1))).utf8))) - - + return { request in if request.isSplitEndpoint() { let index = self.reqChangesIndex @@ -93,68 +290,11 @@ class SplitChangesServerErrorTest: XCTestCase { } } - // MARK: Test - /// Getting changes from server and test treatments and change number - func testChangesError() throws { - let apiKey = "99049fd8653247c5ea42bc3c1ae2c6a42bc3_f" - let matchingKey = "CUSTOMER_ID" - let trafficType = "client" - var treatments = [String]() - - let sdkReady = XCTestExpectation(description: "SDK READY Expectation") - - let splitConfig: SplitClientConfig = SplitClientConfig() - splitConfig.streamingEnabled = false - splitConfig.featuresRefreshRate = 3 - splitConfig.impressionRefreshRate = kNeverRefreshRate - splitConfig.sdkReadyTimeOut = 60000 - splitConfig.trafficType = trafficType - splitConfig.streamingEnabled = false - splitConfig.serviceEndpoints = ServiceEndpoints.builder() - .set(sdkEndpoint: serverUrl).set(eventsEndpoint: serverUrl).build() - - let key: Key = Key(matchingKey: matchingKey, bucketingKey: nil) - let builder = DefaultSplitFactoryBuilder() - _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "SplitChangesServerErrorTest")) - _ = builder.setHttpClient(httpClient) - var factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build() - - let client = factory!.client - - var sdkReadyFired = false - - client.on(event: SplitEvent.sdkReady) { - sdkReadyFired = true - sdkReady.fulfill() - } - - wait(for: [sdkReady], timeout: 10) - - for i in 0..<4 { - wait(for: [spExp[i]], timeout: 40) - treatments.append(client.getTreatment("test_feature")) - } - - XCTAssertTrue(sdkReadyFired) - - XCTAssertEqual("on_0", treatments[0]) - XCTAssertEqual("on_0", treatments[1]) - XCTAssertEqual("on_0", treatments[2]) - XCTAssertEqual("off_1", treatments[3]) - - let semaphore = DispatchSemaphore(value: 0) - client.destroy(completion: { - _ = semaphore.signal() - }) - semaphore.wait() - factory = nil - } - - private func responseSplitChanges() -> [SplitChange] { + private func responseSplitChanges(_ filename: String) -> [SplitChange] { var changes = [SplitChange]() for i in 0..<2 { - let c = loadSplitsChangeFile()! + let c = loadSplitsChangeFile(filename)! var prevChangeNumber = c.since c.since = prevChangeNumber + kChangeNbInterval c.till = c.since @@ -172,13 +312,36 @@ class SplitChangesServerErrorTest: XCTestCase { } return changes } + + private func updatedSegments(index: Int) -> String { + var resp = [String]() + let cn = 5 + for i in (1.. SplitChange? { - return FileHelper.loadSplitChangeFile(sourceClass: self, fileName: "splitchanges_int_test") + private func loadSplitsChangeFile(_ filename: String) -> SplitChange? { + FileHelper.loadSplitChangeFile(sourceClass: self, fileName: filename) } private func buildImpressionsFromJson(content: String) throws -> [ImpressionsTest] { - return try Json.decodeFrom(json: content, to: [ImpressionsTest].self) + try Json.decodeFrom(json: content, to: [ImpressionsTest].self) + } + + private func cleanup(_ client: SplitClient, _ factory: inout SplitFactory?) { + let semaphore = DispatchSemaphore(value: 0) + client.destroy(completion: { + _ = semaphore.signal() + }) + semaphore.wait() + factory = nil } } diff --git a/SplitTests/SplitEventsManagerTest.swift b/SplitTests/SplitEventsManagerTest.swift index 98acad400..8efc0267a 100644 --- a/SplitTests/SplitEventsManagerTest.swift +++ b/SplitTests/SplitEventsManagerTest.swift @@ -302,7 +302,7 @@ class SplitEventsManagerTest: XCTestCase { let data = ["TEST_DATA_123456"] // Build Task - let metadata = EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: data) + let metadata = EventMetadata(type: .featureFlagsSyncError, data: data) let handler: SplitActionWithMetadata = { handlerMetadata in XCTAssertEqual(metadata.type, handlerMetadata.type) diff --git a/SplitTests/SplitEventsTests.swift b/SplitTests/SplitEventsTests.swift index 0920480cd..c575d7162 100644 --- a/SplitTests/SplitEventsTests.swift +++ b/SplitTests/SplitEventsTests.swift @@ -4,10 +4,10 @@ import XCTest @testable import Split class SplitEventsTests: XCTestCase { - func testSplitInternalEventsWithMetadata() { - var event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .FEATURE_FLAGS_SYNC_ERROR, data: [])) + func testInternalEventsWithMetadataErrorType() { + var event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .featureFlagsSyncError, data: [])) XCTAssertEqual(event.metadata!.type.toString(), "FEATURE_FLAGS_SYNC_ERROR") - event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .SEGMENTS_SYNC_ERROR, data: [])) + event = SplitInternalEventWithMetadata(.splitsUpdated, metadata: EventMetadata(type: .segmentsSyncError, data: [])) XCTAssertEqual(event.metadata!.type.toString(), "SEGMENTS_SYNC_ERROR") } }