diff --git a/Split/Events/SplitEventsManager.swift b/Split/Events/SplitEventsManager.swift index 359232b36..5781b3fff 100644 --- a/Split/Events/SplitEventsManager.swift +++ b/Split/Events/SplitEventsManager.swift @@ -176,7 +176,7 @@ class DefaultSplitEventsManager: SplitEventsManager { } case .splitKilledNotification: if isTriggered(external: .sdkReady) { - trigger(event: .sdkUpdated) + trigger(event: .sdkUpdated, metadata: event.metadata) continue } case .sdkReadyTimeoutReached: diff --git a/Split/Localhost/LocalhostSynchronizer.swift b/Split/Localhost/LocalhostSynchronizer.swift index 78fd2ed01..a7c642344 100644 --- a/Split/Localhost/LocalhostSynchronizer.swift +++ b/Split/Localhost/LocalhostSynchronizer.swift @@ -41,7 +41,7 @@ class LocalhostSynchronizer: FeatureFlagsSynchronizer { func stopPeriodicSync() { } - func notifyKilled() { + func notifyKilled(flag: String) { } func notifyUpdated(flagsList: [String]) { diff --git a/Split/Network/Streaming/SseNotificationProcessor.swift b/Split/Network/Streaming/SseNotificationProcessor.swift index 4f0240e84..50fd30d63 100644 --- a/Split/Network/Streaming/SseNotificationProcessor.swift +++ b/Split/Network/Streaming/SseNotificationProcessor.swift @@ -36,18 +36,18 @@ class DefaultSseNotificationProcessor: SseNotificationProcessor { func process(_ notification: IncomingNotification) { Logger.d("Received notification \(notification.type)") switch notification.type { - case .splitUpdate: - processTargetingRuleUpdate(notification) - case .ruleBasedSegmentUpdate: - processTargetingRuleUpdate(notification) - case .mySegmentsUpdate: - processSegmentsUpdate(notification, updateWorker: mySegmentsUpdateWorker) - case .myLargeSegmentsUpdate: - processSegmentsUpdate(notification, updateWorker: myLargeSegmentsUpdateWorker) - case .splitKill: - processSplitKill(notification) - default: - Logger.e("Unknown notification arrived: \(notification.jsonData ?? "null" )") + case .splitUpdate: + processTargetingRuleUpdate(notification) + case .ruleBasedSegmentUpdate: + processTargetingRuleUpdate(notification) + case .mySegmentsUpdate: + processSegmentsUpdate(notification, updateWorker: mySegmentsUpdateWorker) + case .myLargeSegmentsUpdate: + processSegmentsUpdate(notification, updateWorker: myLargeSegmentsUpdateWorker) + case .splitKill: + processSplitKill(notification) + default: + Logger.e("Unknown notification arrived: \(notification.jsonData ?? "null" )") } } diff --git a/Split/Network/Streaming/SyncUpdateWorker.swift b/Split/Network/Streaming/SyncUpdateWorker.swift index 7637230e6..bfab4ad83 100644 --- a/Split/Network/Streaming/SyncUpdateWorker.swift +++ b/Split/Network/Streaming/SyncUpdateWorker.swift @@ -227,7 +227,7 @@ class SplitKillWorker: UpdateWorker { splitToKill.changeNumber = notification.changeNumber splitToKill.killed = true splitsStorage.updateWithoutChecks(split: splitToKill) - synchronizer.notifySplitKilled() + synchronizer.notifySplitKilled(flag: splitToKill.name ?? "") } } synchronizer.synchronizeSplits(changeNumber: notification.changeNumber) diff --git a/Split/Network/Sync/FeatureFlagsSynchronizer.swift b/Split/Network/Sync/FeatureFlagsSynchronizer.swift index 05db133bf..91c28ea65 100644 --- a/Split/Network/Sync/FeatureFlagsSynchronizer.swift +++ b/Split/Network/Sync/FeatureFlagsSynchronizer.swift @@ -14,7 +14,7 @@ protocol FeatureFlagsSynchronizer { func synchronize(changeNumber: Int64?, rbsChangeNumber: Int64?) func startPeriodicSync() func stopPeriodicSync() - func notifyKilled() + func notifyKilled(flag: String) func notifyUpdated(flagsList: [String]) func pause() func resume() @@ -144,8 +144,8 @@ class DefaultFeatureFlagsSynchronizer: FeatureFlagsSynchronizer { periodicSplitsSyncWorker?.stop() } - func notifyKilled() { - splitEventsManager.notifyInternalEvent(.splitKilledNotification) + func notifyKilled(flag: String) { + splitEventsManager.notifyInternalEvent(.splitKilledNotification, metadata: EventMetadata(type: .FLAGS_KILLED, data: [flag])) } func notifyUpdated(flagsList: [String]) { diff --git a/Split/Network/Sync/Synchronizer.swift b/Split/Network/Sync/Synchronizer.swift index 321ca228c..8cf8f7ef0 100644 --- a/Split/Network/Sync/Synchronizer.swift +++ b/Split/Network/Sync/Synchronizer.swift @@ -33,7 +33,7 @@ protocol Synchronizer: ImpressionLogger { func notifyFeatureFlagsUpdated(flags: [String]) func notifySegmentsUpdated(forKey key: String) func notifyLargeSegmentsUpdated(forKey key: String) - func notifySplitKilled() + func notifySplitKilled(flag: String) func pause() func resume() func flush() @@ -219,8 +219,8 @@ class DefaultSynchronizer: Synchronizer { byKeySynchronizer.notifyMyLargeSegmentsUpdated(forKey: key) } - func notifySplitKilled() { - featureFlagsSynchronizer.notifyKilled() + func notifySplitKilled(flag: String) { + featureFlagsSynchronizer.notifyKilled(flag: flag) } func pause() { diff --git a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift index ad42cd21a..43435555c 100644 --- a/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/FeatureFlagsSynchronizerStub.swift @@ -35,7 +35,9 @@ class FeatureFlagsSynchronizerStub: FeatureFlagsSynchronizer { } var notifyKilledCalled = false - func notifyKilled() { + var killedFlag = "" + func notifyKilled(flag: String) { + killedFlag = flag notifyKilledCalled = true } diff --git a/SplitTests/Fake/Streaming/SynchronizerSpy.swift b/SplitTests/Fake/Streaming/SynchronizerSpy.swift index 88420a4d8..2926de42b 100644 --- a/SplitTests/Fake/Streaming/SynchronizerSpy.swift +++ b/SplitTests/Fake/Streaming/SynchronizerSpy.swift @@ -212,9 +212,11 @@ class SynchronizerSpy: Synchronizer { notifyFeatureFlagsUpdatedCalled = true } - func notifySplitKilled() { + var killedFlag = "" + func notifySplitKilled(flag: String) { notifySplitKilledCalled = true - splitSynchronizer.notifySplitKilled() + killedFlag = flag + splitSynchronizer.notifySplitKilled(flag: flag) } func start(forKey key: Key) { diff --git a/SplitTests/Fake/Streaming/SynchronizerStub.swift b/SplitTests/Fake/Streaming/SynchronizerStub.swift index 9b6cca381..701faeb6f 100644 --- a/SplitTests/Fake/Streaming/SynchronizerStub.swift +++ b/SplitTests/Fake/Streaming/SynchronizerStub.swift @@ -243,7 +243,9 @@ class SynchronizerStub: Synchronizer { notifyFeatureFlagsUpdatedCalled = true } - func notifySplitKilled() { + var killedFlag = "" + func notifySplitKilled(flag: String) { + killedFlag = flag notifySplitKilledCalled = true } diff --git a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift index ef12b0851..603241cf6 100644 --- a/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift +++ b/SplitTests/Integration/Sync/SplitSdkUpdatePollingTest.swift @@ -34,15 +34,15 @@ class SplitSdkUpdatePollingTest: XCTestCase { override func setUp() { let session = HttpSessionMock() - let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher("splitchanges_int_test"), streamingHandler: buildStreamingHandler()) httpClient = DefaultHttpClient(session: session, requestManager: reqManager) } - private func buildTestDispatcher() -> HttpClientTestDispatcher { + private func buildTestDispatcher(_ file: String) -> HttpClientTestDispatcher { - let respData = responseSplitChanges() + let respData = responseSplitChanges(file) var responses = [TestDispatcherResponse]() for data in respData { let rData = TargetingRulesChange(featureFlags: data, ruleBasedSegments: RuleBasedSegmentChange(segments: [], since: -1, till: -1)) @@ -307,12 +307,12 @@ class SplitSdkUpdatePollingTest: XCTestCase { semaphore.wait() } - private func responseSplitChanges() -> [SplitChange] { + private func responseSplitChanges(_ file: String) -> [SplitChange] { var changes = [SplitChange]() var prevChangeNumber: Int64 = 0 for i in 0..<4 { - let c = loadSplitsChangeFile()! + let c = loadSplitsChangeFile(file)! c.since = c.till if prevChangeNumber != 0 { c.till = prevChangeNumber + kChangeNbInterval @@ -331,8 +331,8 @@ class SplitSdkUpdatePollingTest: XCTestCase { return changes } - private func loadSplitsChangeFile() -> SplitChange? { - return FileHelper.loadSplitChangeFile(sourceClass: self, fileName: "splitchanges_int_test") + private func loadSplitsChangeFile(_ file: String) -> SplitChange? { + return FileHelper.loadSplitChangeFile(sourceClass: self, fileName: file) } private func getAndIncrement() -> Int { diff --git a/SplitTests/Integration/streaming/StreamingSplitKillTest.swift b/SplitTests/Integration/streaming/StreamingSplitKillTest.swift index 26e405131..50e25d1b4 100644 --- a/SplitTests/Integration/streaming/StreamingSplitKillTest.swift +++ b/SplitTests/Integration/streaming/StreamingSplitKillTest.swift @@ -27,17 +27,18 @@ class StreamingSplitKillTest: XCTestCase { var exp2: XCTestExpectation! var exp3: XCTestExpectation! var exp4: XCTestExpectation! + var exp5: XCTestExpectation! override func setUp() { expIndex = 1 let session = HttpSessionMock() - let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), - streamingHandler: buildStreamingHandler()) + let reqManager = HttpRequestManagerTestDispatcher(dispatcher: buildTestDispatcher(), streamingHandler: buildStreamingHandler()) httpClient = DefaultHttpClient(session: session, requestManager: reqManager) loadChanges() } - func testSplitKill() { + // MARK: Tests + func testSplitKill() throws { let splitConfig: SplitClientConfig = SplitClientConfig() splitConfig.featuresRefreshRate = 9999 splitConfig.segmentsRefreshRate = 9999 @@ -55,7 +56,7 @@ class StreamingSplitKillTest: XCTestCase { .setConfig(splitConfig).build()! let client = factory.client - let expTimeout: TimeInterval = 5 + let expTimeout: TimeInterval = 5 let sdkReadyExpectation = XCTestExpectation(description: "SDK READY Expectation") exp1 = XCTestExpectation(description: "Exp1") @@ -63,11 +64,11 @@ class StreamingSplitKillTest: XCTestCase { exp3 = XCTestExpectation(description: "Exp3") exp4 = XCTestExpectation(description: "Exp4") - client.on(event: SplitEvent.sdkReady) { + client.on(event: .sdkReady) { sdkReadyExpectation.fulfill() } - client.on(event: SplitEvent.sdkReadyTimedOut) { + client.on(event: .sdkReadyTimedOut) { IntegrationHelper.tlog("TIMEOUT") } @@ -80,28 +81,25 @@ class StreamingSplitKillTest: XCTestCase { let splitName = "workm" let treatmentReady = client.getTreatment(splitName) - streamingBinding?.push(message: - StreamingIntegrationHelper.splitKillMessagge(splitName: splitName, defaultTreatment: "conta", - timestamp: numbers[splitsChangesHits], - changeNumber: numbers[splitsChangesHits])) + streamingBinding?.push(message: StreamingIntegrationHelper.splitKillMessagge(splitName: splitName, defaultTreatment: "conta", + timestamp: numbers[splitsChangesHits], + changeNumber: numbers[splitsChangesHits])) wait(for: [exp2], timeout: expTimeout) waitForUpdate(secs: 1) let treatmentKill = client.getTreatment(splitName) - streamingBinding?.push(message: - StreamingIntegrationHelper.splitUpdateMessage(timestamp: numbers[splitsChangesHits], - changeNumber: numbers[splitsChangesHits])) + streamingBinding?.push(message: StreamingIntegrationHelper.splitUpdateMessage(timestamp: numbers[splitsChangesHits], + changeNumber: numbers[splitsChangesHits])) wait(for: [exp3], timeout: expTimeout) waitForUpdate(secs: 1) let treatmentNoKill = client.getTreatment(splitName) - streamingBinding?.push(message: - StreamingIntegrationHelper.splitKillMessagge(splitName: splitName, defaultTreatment: "conta", - timestamp: numbers[0], - changeNumber: numbers[0])) + streamingBinding?.push(message: StreamingIntegrationHelper.splitKillMessagge(splitName: splitName, defaultTreatment: "conta", + timestamp: numbers[0], + changeNumber: numbers[0])) ThreadUtils.delay(seconds: 2.0) // The server should not be hit here let treatmentOldKill = client.getTreatment(splitName) @@ -118,6 +116,60 @@ class StreamingSplitKillTest: XCTestCase { semaphore.wait() } + func testSplitKillWithMetadata() throws { + + // Setup + let splitConfig: SplitClientConfig = SplitClientConfig() + splitConfig.featuresRefreshRate = 9999 + splitConfig.segmentsRefreshRate = 9999 + splitConfig.impressionRefreshRate = 999999 + splitConfig.sdkReadyTimeOut = 60000 + splitConfig.eventsPushRate = 999999 + + let key: Key = Key(matchingKey: userKey) + let builder = DefaultSplitFactoryBuilder() + _ = builder.setHttpClient(httpClient) + _ = builder.setReachabilityChecker(ReachabilityMock()) + _ = builder.setTestDatabase(TestingHelper.createTestDatabase(name: "test")) + let factory = builder.setApiKey(apiKey).setKey(key).setConfig(splitConfig).build()! + let client = factory.client + let expTimeout: TimeInterval = 5 + + let sdkReadyExpectation = XCTestExpectation(description: "SDK READY Expectation") + exp1 = XCTestExpectation(description: "Streaming notification") + exp2 = XCTestExpectation(description: "Push notification") + exp5 = XCTestExpectation(description: "Wait for killed metadata event") + + client.on(event: .sdkReady) { sdkReadyExpectation.fulfill() } + + // Set listener + client.on(event: .sdkUpdated) { [weak self] metadata in + if metadata?.type == .FLAGS_KILLED { + XCTAssertEqual(metadata?.data, ["workm"]) + self?.exp5.fulfill() + } + } + + // Simulate Kill + wait(for: [sdkReadyExpectation, sseConnExp], timeout: expTimeout) + streamingBinding?.push(message: ":keepalive") // send keep alive to confirm streaming connection ok + wait(for: [exp1], timeout: expTimeout) + waitForUpdate(secs: 1) + streamingBinding?.push(message: StreamingIntegrationHelper.splitKillMessagge(splitName: "workm", defaultTreatment: "conta", + timestamp: numbers[splitsChangesHits], + changeNumber: numbers[splitsChangesHits])) + wait(for: [exp5, exp2], timeout: expTimeout) + waitForUpdate(secs: 1) + + // Cleanup + let semaphore = DispatchSemaphore(value: 0) + client.destroy(completion: { + _ = semaphore.signal() + }) + semaphore.wait() + } + + //MARK: Testing Helpers private func getChanges(for hitNumber: Int) -> Data { if hitNumber < 4 { return Data(self.changes[hitNumber].utf8)