diff --git a/DatadogSessionReplay/Sources/Feature/RUMContextReceiver.swift b/DatadogSessionReplay/Sources/Feature/RUMContextReceiver.swift index 0b1ab5a20a..80e4a3e337 100644 --- a/DatadogSessionReplay/Sources/Feature/RUMContextReceiver.swift +++ b/DatadogSessionReplay/Sources/Feature/RUMContextReceiver.swift @@ -19,18 +19,12 @@ internal protocol RUMContextObserver { } /// Receives RUM context from `DatadogCore` and notifies it through `RUMContextObserver` interface. -internal class RUMContextReceiver: FeatureMessageReceiver, RUMContextObserver { +internal final class RUMContextReceiver: BusMessageReceiver, RUMContextObserver { /// Notifies new `RUMContext` or `nil` if current RUM session is not sampled. private var onNew: ((RUMCoreContext?) -> Void)? private var previous: RUMCoreContext? - // MARK: - FeatureMessageReceiver - - func receive(message: FeatureMessage, from core: DatadogCoreProtocol) -> Bool { - guard case let .context(context) = message else { - return false - } - + func receive(message context: DatadogContext, from core: DatadogCoreProtocol) { let new = context.additionalContext(ofType: RUMCoreContext.self) // Notify only if it has changed: @@ -38,8 +32,6 @@ internal class RUMContextReceiver: FeatureMessageReceiver, RUMContextObserver { onNew?(new) previous = new } - - return true } // MARK: - RUMContextObserver diff --git a/DatadogSessionReplay/Sources/Feature/RecordingComponents.swift b/DatadogSessionReplay/Sources/Feature/RecordingComponents.swift index d7cee1fbc3..e8993b422f 100644 --- a/DatadogSessionReplay/Sources/Feature/RecordingComponents.swift +++ b/DatadogSessionReplay/Sources/Feature/RecordingComponents.swift @@ -11,6 +11,7 @@ import DatadogInternal internal struct RecordingComponents { let recordingCoordinator: any RecordingController let messageReceiver: any FeatureMessageReceiver + let contextReceiver: RUMContextReceiver init( core: DatadogCoreProtocol, @@ -30,10 +31,12 @@ internal struct RecordingComponents { private init( recordingCoordinator: any RecordingController, - messageReceiver: any FeatureMessageReceiver + messageReceiver: any FeatureMessageReceiver, + contextReceiver: RUMContextReceiver ) { self.recordingCoordinator = recordingCoordinator self.messageReceiver = messageReceiver + self.contextReceiver = contextReceiver } private static func viewTreeRecordingComponents( @@ -90,7 +93,8 @@ internal struct RecordingComponents { return .init( recordingCoordinator: recordingCoordinator, - messageReceiver: contextReceiver + messageReceiver: NOPFeatureMessageReceiver(), + contextReceiver: contextReceiver ) } @@ -128,7 +132,8 @@ internal struct RecordingComponents { return .init( recordingCoordinator: recordingCoordinator, - messageReceiver: recordingCoordinator + messageReceiver: recordingCoordinator, + contextReceiver: RUMContextReceiver() ) } } diff --git a/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift b/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift index 054732c088..314cd4cbb0 100644 --- a/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift +++ b/DatadogSessionReplay/Sources/Feature/SessionReplayFeature.swift @@ -11,6 +11,8 @@ import DatadogInternal internal class SessionReplayFeature: SessionReplayConfiguration, DatadogRemoteFeature { let requestBuilder: FeatureRequestBuilder let messageReceiver: FeatureMessageReceiver + let contextReceiver: RUMContextReceiver + let webViewRecordReceiver: WebViewRecordReceiver let performanceOverride: PerformancePresetOverride? let textAndInputPrivacyLevel: TextAndInputPrivacyLevel let imagePrivacyLevel: ImagePrivacyLevel @@ -32,16 +34,15 @@ internal class SessionReplayFeature: SessionReplayConfiguration, DatadogRemoteFe configuration: configuration ) + self.contextReceiver = recordingComponents.contextReceiver + self.webViewRecordReceiver = WebViewRecordReceiver( + scope: core.scope(for: SessionReplayFeature.self) + ) self.requestBuilder = SegmentRequestBuilder( customUploadURL: configuration.customEndpoint, telemetry: core.telemetry ) - self.messageReceiver = CombinedFeatureMessageReceiver( - [ - recordingComponents.messageReceiver, - WebViewRecordReceiver(scope: core.scope(for: SessionReplayFeature.self)) - ] - ) + self.messageReceiver = recordingComponents.messageReceiver self.performanceOverride = PerformancePresetOverride( maxFileSize: SessionReplay.maxObjectSize, maxObjectSize: SessionReplay.maxObjectSize, diff --git a/DatadogSessionReplay/Sources/Feature/WebViewRecordReceiver.swift b/DatadogSessionReplay/Sources/Feature/WebViewRecordReceiver.swift index 99853a32e5..4882e2e275 100644 --- a/DatadogSessionReplay/Sources/Feature/WebViewRecordReceiver.swift +++ b/DatadogSessionReplay/Sources/Feature/WebViewRecordReceiver.swift @@ -7,7 +7,7 @@ import Foundation import DatadogInternal -internal struct WebViewRecordReceiver: FeatureMessageReceiver { +internal final class WebViewRecordReceiver: BusMessageReceiver { internal struct WebRecord: Encodable { /// The RUM application ID of all records. let applicationID: String @@ -22,10 +22,13 @@ internal struct WebViewRecordReceiver: FeatureMessageReceiver { /// Session Replay feature scope. let scope: FeatureScope - func receive(message: DatadogInternal.FeatureMessage, from core: DatadogInternal.DatadogCoreProtocol) -> Bool { - guard case let .webview(.record(event, view)) = message else { - return false - } + init(scope: FeatureScope) { + self.scope = scope + } + + func receive(message: WebViewRecordMessage, from core: DatadogCoreProtocol) { + let event = message.event + let view = message.view scope.eventWriteContext { context, writer in // Extract the `RUMContext` or `nil` if RUM session is not sampled: @@ -53,7 +56,5 @@ internal struct WebViewRecordReceiver: FeatureMessageReceiver { writer.write(value: record) } - - return true } } diff --git a/DatadogSessionReplay/Sources/SessionReplay.swift b/DatadogSessionReplay/Sources/SessionReplay.swift index b53e93fc20..4baffea550 100644 --- a/DatadogSessionReplay/Sources/SessionReplay.swift +++ b/DatadogSessionReplay/Sources/SessionReplay.swift @@ -88,6 +88,11 @@ public enum SessionReplay { try core.register(feature: resources) let sessionReplay = try SessionReplayFeature(core: core, configuration: configuration) + + // Subscribe typed-bus receivers before registration so initial context push is received: + core.messageBus.subscribe(receiver: sessionReplay.contextReceiver) + core.messageBus.subscribe(receiver: sessionReplay.webViewRecordReceiver) + try core.register(feature: sessionReplay) core.set( context: SessionReplayCoreContext.Configuration( diff --git a/DatadogSessionReplay/Tests/Feature/RUMContextReceiverTests.swift b/DatadogSessionReplay/Tests/Feature/RUMContextReceiverTests.swift index 06aa56be35..ac5e52aa89 100644 --- a/DatadogSessionReplay/Tests/Feature/RUMContextReceiverTests.swift +++ b/DatadogSessionReplay/Tests/Feature/RUMContextReceiverTests.swift @@ -35,9 +35,7 @@ class RUMContextReceiverTests: XCTestCase { } // When - XCTAssert( - receiver.receive(message: .context(coreContext), from: core) - ) + receiver.receive(message: coreContext, from: core) // Then XCTAssertEqual(rumContext?.applicationID, "app-id") @@ -78,13 +76,8 @@ class RUMContextReceiverTests: XCTestCase { context.flatMap { rumContexts.append($0) } } // When - XCTAssert( - receiver.receive(message: .context(coreContext1), from: core) - ) - - XCTAssert( - receiver.receive(message: .context(coreContext2), from: core) - ) + receiver.receive(message: coreContext1, from: core) + receiver.receive(message: coreContext2, from: core) // Then XCTAssertEqual(rumContexts.count, 2) @@ -130,13 +123,8 @@ class RUMContextReceiverTests: XCTestCase { context.flatMap { rumContexts.append($0) } } // When - XCTAssert( - receiver.receive(message: .context(coreContext1), from: core) - ) - - XCTAssert( - receiver.receive(message: .context(coreContext2), from: core) - ) + receiver.receive(message: coreContext1, from: core) + receiver.receive(message: coreContext2, from: core) // Then XCTAssertEqual(rumContexts.count, 1) @@ -169,9 +157,7 @@ class RUMContextReceiverTests: XCTestCase { } // When - XCTAssert( - receiver.receive(message: .context(coreContext1), from: core) - ) + receiver.receive(message: coreContext1, from: core) XCTAssertEqual(rumContext?.applicationID, "app-id") XCTAssertEqual(rumContext?.sessionID, "session-id") @@ -179,29 +165,21 @@ class RUMContextReceiverTests: XCTestCase { XCTAssertEqual(rumContext?.viewServerTimeOffset, 123) // When - XCTAssert( - receiver.receive(message: .context(coreContext2), from: core) - ) + receiver.receive(message: coreContext2, from: core) // Then XCTAssertNil(rumContext) } - func testWhenMessageIsNotContext_itReturnsFalse() throws { + func testWhenNoMessageIsReceived_observerIsNotCalled() throws { // Given let expectation = expectation(description: "observe not called") expectation.isInverted = true - let core = PassthroughCoreMock() receiver.observe(on: NoQueue()) { _ in expectation.fulfill() } - // When - XCTAssertFalse( - receiver.receive(message: .payload("value"), from: core) - ) - - // Then + // Then — no message delivered, observer must stay silent waitForExpectations(timeout: 0.1) } } diff --git a/DatadogSessionReplay/Tests/Feature/WebViewRecordReceiverTests.swift b/DatadogSessionReplay/Tests/Feature/WebViewRecordReceiverTests.swift index df81feddb3..0fa31ee8df 100644 --- a/DatadogSessionReplay/Tests/Feature/WebViewRecordReceiverTests.swift +++ b/DatadogSessionReplay/Tests/Feature/WebViewRecordReceiverTests.swift @@ -42,8 +42,8 @@ class WebViewRecordReceiverTests: XCTestCase { // When - let message = WebViewMessage.record(webRecordMock, WebViewMessage.View(id: browserViewID)) - let result = receiver.receive(message: .webview(message), from: NOPDatadogCore()) + let message = WebViewRecordMessage(event: webRecordMock, view: WebViewMessage.View(id: browserViewID)) + receiver.receive(message: message, from: NOPDatadogCore()) // Then let expectedWebSegmentWritten: [String: Any] = [ @@ -58,7 +58,6 @@ class WebViewRecordReceiverTests: XCTestCase { ] ] - XCTAssertTrue(result, "It must accept the message") XCTAssertEqual(scope.eventsWritten.count, 1, "It must write web segment to core") let actualWebEventWritten = try XCTUnwrap(scope.eventsWritten.first) DDAssertJSONEqual(AnyCodable(actualWebEventWritten), AnyCodable(expectedWebSegmentWritten)) @@ -73,27 +72,12 @@ class WebViewRecordReceiverTests: XCTestCase { let receiver = WebViewRecordReceiver(scope: scope) // When - let record = WebViewMessage.record(mockRandomAttributes(), WebViewMessage.View(id: .mockRandom())) - let result = receiver.receive(message: .webview(record), from: NOPDatadogCore()) + let message = WebViewRecordMessage(event: mockRandomAttributes(), view: WebViewMessage.View(id: .mockRandom())) + receiver.receive(message: message, from: NOPDatadogCore()) // Then - XCTAssertTrue(result, "It must accept the message") XCTAssertTrue(scope.eventsWritten.isEmpty, "The event must be dropped") } - - func testWhenReceivingOtherMessage_itRejectsIt() throws { - let scope = FeatureScopeMock() - - // Given - let receiver = WebViewRecordReceiver(scope: scope) - - // When - let otherMessage: FeatureMessage = .payload(String.mockRandom()) - let result = receiver.receive(message: otherMessage, from: NOPDatadogCore()) - - // Then - XCTAssertFalse(result, "It must reject messages addressed to other receivers") - } } #endif diff --git a/DatadogSessionReplay/Tests/SessionReplayTests.swift b/DatadogSessionReplay/Tests/SessionReplayTests.swift index 2b5107d3a3..66387a1501 100644 --- a/DatadogSessionReplay/Tests/SessionReplayTests.swift +++ b/DatadogSessionReplay/Tests/SessionReplayTests.swift @@ -216,8 +216,9 @@ class SessionReplayTests: XCTestCase { let textAndInputPrivacyLevel: TextAndInputPrivacyLevel = .mockRandom() let imagePrivacyLevel: ImagePrivacyLevel = .mockRandom() let touchPrivacyLevel: TouchPrivacyLevel = .mockRandom() - let messageReceiver = FeatureMessageReceiverMock() - let core = PassthroughCoreMock(messageReceiver: messageReceiver) + let telemetryReceiver = TelemetryReceiverMock() + let core = PassthroughCoreMock() + core.subscribe(receiver: telemetryReceiver) // When SessionReplay.enable( @@ -232,7 +233,7 @@ class SessionReplayTests: XCTestCase { ) // Then - let configuration = try XCTUnwrap(messageReceiver.messages.firstTelemetry?.asConfiguration) + let configuration = try XCTUnwrap(telemetryReceiver.messages.firstConfiguration()) XCTAssertEqual(configuration.sessionReplaySampleRate, sampleRate) XCTAssertNil(configuration.defaultPrivacyLevel) XCTAssertEqual(configuration.textAndInputPrivacyLevel, textAndInputPrivacyLevel.rawValue) @@ -248,8 +249,9 @@ class SessionReplayTests: XCTestCase { let imageLevel: ImagePrivacyLevel = .mockRandom() let touchLevel: TouchPrivacyLevel = .mockRandom() let startRecordingImmediately: Bool = .mockRandom() - let messageReceiver = FeatureMessageReceiverMock() - let core = PassthroughCoreMock(messageReceiver: messageReceiver) + let telemetryReceiver = TelemetryReceiverMock() + let core = PassthroughCoreMock() + core.subscribe(receiver: telemetryReceiver) // When SessionReplay.enable( @@ -264,7 +266,7 @@ class SessionReplayTests: XCTestCase { ) // Then - let configuration = try XCTUnwrap(messageReceiver.messages.firstTelemetry?.asConfiguration) + let configuration = try XCTUnwrap(telemetryReceiver.messages.firstConfiguration()) XCTAssertEqual(configuration.sessionReplaySampleRate, sampleRate) XCTAssertNil(configuration.defaultPrivacyLevel) XCTAssertEqual(configuration.textAndInputPrivacyLevel, textAndInputLevel.rawValue) diff --git a/DatadogWebViewTracking/Sources/MessageEmitter.swift b/DatadogWebViewTracking/Sources/MessageEmitter.swift index e3b3a39f60..a1543c26df 100644 --- a/DatadogWebViewTracking/Sources/MessageEmitter.swift +++ b/DatadogWebViewTracking/Sources/MessageEmitter.swift @@ -66,15 +66,27 @@ internal final class MessageEmitter: InternalExtension.Abstract return } - core.send(message: .webview(message), else: { + guard case let .log(event) = message else { + return + } + core.messageBus.send(message: WebViewLogMessage(event: event), else: { DD.logger.warn("A WebView log is lost because Logging is disabled in the SDK") }) } private func send(rum message: WebViewMessage, in core: DatadogCoreProtocol) { - core.send(message: .webview(message), else: { - DD.logger.warn("A WebView RUM event is lost because RUM is disabled in the SDK") - }) + switch message { + case let .rum(event): + core.messageBus.send(message: WebViewRUMMessage(kind: .rum, event: event), else: { + DD.logger.warn("A WebView RUM event is lost because RUM is disabled in the SDK") + }) + case let .telemetry(event): + core.messageBus.send(message: WebViewRUMMessage(kind: .telemetry, event: event), else: { + DD.logger.warn("A WebView RUM telemetry event is lost because RUM is disabled in the SDK") + }) + default: + break + } } private func send(record event: WebViewMessage.Event, view: WebViewMessage.View, slotId: String?, in core: DatadogCoreProtocol) { @@ -82,7 +94,7 @@ internal final class MessageEmitter: InternalExtension.Abstract // inject the slotId event["slotId"] = slotId - core.send(message: .webview(.record(event, view)), else: { + core.messageBus.send(message: WebViewRecordMessage(event: event, view: view), else: { DD.logger.warn("A WebView Replay record is lost because Session Replay is disabled in the SDK") }) } diff --git a/DatadogWebViewTracking/Tests/MessageEmitterTests.swift b/DatadogWebViewTracking/Tests/MessageEmitterTests.swift index 9d1bb0fca1..5c9f5fa9c6 100644 --- a/DatadogWebViewTracking/Tests/MessageEmitterTests.swift +++ b/DatadogWebViewTracking/Tests/MessageEmitterTests.swift @@ -17,8 +17,9 @@ class MessageEmitterTests: XCTestCase { let sampler = Sampler(samplingRate: 100) // Given - let receiverMock = FeatureMessageReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiverMock) + let core = PassthroughCoreMock() + var received: [WebViewLogMessage] = [] + _ = core.messageBus.subscribe { (msg: WebViewLogMessage, _) in received.append(msg) } let emitter = MessageEmitter(logsSampler: sampler, core: core) // When @@ -34,11 +35,7 @@ class MessageEmitterTests: XCTestCase { """) // Then - let message = try XCTUnwrap(receiverMock.messages.firstWebViewMessage) - guard case let .log(event) = message else { - return XCTFail("not a log message") - } - + let event = try XCTUnwrap(received.first?.event) let json = JSONObjectMatcher(object: event) XCTAssertEqual(try json.value("attribute1"), 123) XCTAssertEqual(try json.value("attribute2"), "foo") @@ -47,33 +44,32 @@ class MessageEmitterTests: XCTestCase { func testGivenSampleRate0_whenReceivingLogEvent_itIsDropped() throws { let sampler = Sampler(samplingRate: 0) - let eventType = "log" // Given - let receiverMock = FeatureMessageReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiverMock) + let core = PassthroughCoreMock() + var received: [WebViewLogMessage] = [] + _ = core.messageBus.subscribe { (msg: WebViewLogMessage, _) in received.append(msg) } let emitter = MessageEmitter(logsSampler: sampler, core: core) // When emitter.send(body: """ { - "eventType": "\(eventType)", + "eventType": "log", "event": { - "attribute1": 123, - "attribute2": "foo", - "attribute3": ["foo", "bar", "bizz"] + "attribute1": 123 } } """) // Then - XCTAssertNil(receiverMock.messages.firstWebViewMessage) + XCTAssertTrue(received.isEmpty) } func testWhenReceivingRUMEvent_itForwardsToRUM() throws { // Given - let receiverMock = FeatureMessageReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiverMock) + let core = PassthroughCoreMock() + var received: [WebViewRUMMessage] = [] + _ = core.messageBus.subscribe { (msg: WebViewRUMMessage, _) in received.append(msg) } let emitter = MessageEmitter(logsSampler: .mockRandom(), core: core) // When @@ -89,12 +85,9 @@ class MessageEmitterTests: XCTestCase { """) // Then - let message = try XCTUnwrap(receiverMock.messages.firstWebViewMessage) - guard case let .rum(event) = message else { - return XCTFail("not a rum message") - } - - let json = JSONObjectMatcher(object: event) + let msg = try XCTUnwrap(received.first) + XCTAssertEqual(msg.kind, .rum) + let json = JSONObjectMatcher(object: msg.event) XCTAssertEqual(try json.value("attribute1"), 123) XCTAssertEqual(try json.value("attribute2"), "foo") XCTAssertEqual(try json.array("attribute3").values(), ["foo", "bar", "bizz"]) @@ -102,8 +95,9 @@ class MessageEmitterTests: XCTestCase { func testWhenReceivingTelemetryEvent_itForwardsToTelemetry() throws { // Given - let receiverMock = FeatureMessageReceiverMock() - let core = PassthroughCoreMock(messageReceiver: receiverMock) + let core = PassthroughCoreMock() + var received: [WebViewRUMMessage] = [] + _ = core.messageBus.subscribe { (msg: WebViewRUMMessage, _) in received.append(msg) } let emitter = MessageEmitter(logsSampler: .mockRandom(), core: core) // When @@ -119,12 +113,9 @@ class MessageEmitterTests: XCTestCase { """) // Then - let message = try XCTUnwrap(receiverMock.messages.firstWebViewMessage) - guard case let .telemetry(event) = message else { - return XCTFail("not a telemetry message") - } - - let json = JSONObjectMatcher(object: event) + let msg = try XCTUnwrap(received.first) + XCTAssertEqual(msg.kind, .telemetry) + let json = JSONObjectMatcher(object: msg.event) XCTAssertEqual(try json.value("attribute1"), 123) XCTAssertEqual(try json.value("attribute2"), "foo") XCTAssertEqual(try json.array("attribute3").values(), ["foo", "bar", "bizz"]) diff --git a/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift b/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift index b46c971cbc..1850907681 100644 --- a/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift +++ b/DatadogWebViewTracking/Tests/WebViewTrackingTests.swift @@ -583,25 +583,17 @@ class WebViewTrackingTests: XCTestCase { func testSendingWebEvents() throws { let logMessageExpectation = expectation(description: "Log message received") - let core = PassthroughCoreMock( - messageReceiver: FeatureMessageReceiverMock { message in - switch message { - case let .webview(.log(event)): - let matcher = JSONObjectMatcher(object: event) - XCTAssertEqual(try? matcher.value("date"), 1_635_932_927_012) - XCTAssertEqual(try? matcher.value("message"), "console error: error") - XCTAssertEqual(try? matcher.value("status"), "error") - XCTAssertEqual(try? matcher.value("view"), ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"]) - XCTAssertEqual(try? matcher.value("error"), ["origin": "console"]) - XCTAssertEqual(try? matcher.value("session_id"), "0110cab4-7471-480e-aa4e-7ce039ced355") - logMessageExpectation.fulfill() - case .context, .telemetry: - break - default: - XCTFail("Unexpected message received: \(message)") - } - } - ) + let core = PassthroughCoreMock() + let subscription = core.messageBus.subscribe { (message: WebViewLogMessage, _) in + let matcher = JSONObjectMatcher(object: message.event) + XCTAssertEqual(try? matcher.value("date"), 1_635_932_927_012) + XCTAssertEqual(try? matcher.value("message"), "console error: error") + XCTAssertEqual(try? matcher.value("status"), "error") + XCTAssertEqual(try? matcher.value("view"), ["referrer": "", "url": "https://datadoghq.dev/browser-sdk-test-playground"]) + XCTAssertEqual(try? matcher.value("error"), ["origin": "console"]) + XCTAssertEqual(try? matcher.value("session_id"), "0110cab4-7471-480e-aa4e-7ce039ced355") + logMessageExpectation.fulfill() + } let config = WKWebViewConfiguration() let controller = DDUserContentController() @@ -643,20 +635,12 @@ class WebViewTrackingTests: XCTestCase { func testSendingWebRUMEvent() throws { let rumMessageExpectation = expectation(description: "RUM message received") - let core = PassthroughCoreMock( - messageReceiver: FeatureMessageReceiverMock { message in - switch message { - case let .webview(.rum(event)): - let matcher = JSONObjectMatcher(object: event) - XCTAssertEqual(try? matcher.value("view.id"), "64308fd4-83f9-48cb-b3e1-1e91f6721230") - rumMessageExpectation.fulfill() - case .context, .telemetry: - break - default: - XCTFail("Unexpected message received: \(message)") - } - } - ) + let core = PassthroughCoreMock() + let subscription = core.messageBus.subscribe { (message: WebViewRUMMessage, _) in + let matcher = JSONObjectMatcher(object: message.event) + XCTAssertEqual(try? matcher.value("view.id"), "64308fd4-83f9-48cb-b3e1-1e91f6721230") + rumMessageExpectation.fulfill() + } let config = WKWebViewConfiguration() let controller = DDUserContentController() @@ -739,22 +723,14 @@ class WebViewTrackingTests: XCTestCase { config.userContentController = controller let webView = WKWebView(frame: .zero, configuration: config) - let core = PassthroughCoreMock( - messageReceiver: FeatureMessageReceiverMock { message in - switch message { - case let .webview(.record(event, view)): - XCTAssertEqual(view.id, "64308fd4-83f9-48cb-b3e1-1e91f6721230") - let matcher = JSONObjectMatcher(object: event) - XCTAssertEqual(try? matcher.value("date"), 1_635_932_927_012) - XCTAssertEqual(try? matcher.value("slotId"), String(webView.hash)) - recordMessageExpectation.fulfill() - case .context, .telemetry: - break - default: - XCTFail("Unexpected message received: \(message)") - } - } - ) + let core = PassthroughCoreMock() + let subscription = core.messageBus.subscribe { (message: WebViewRecordMessage, _) in + XCTAssertEqual(message.view.id, "64308fd4-83f9-48cb-b3e1-1e91f6721230") + let matcher = JSONObjectMatcher(object: message.event) + XCTAssertEqual(try? matcher.value("date"), 1_635_932_927_012) + XCTAssertEqual(try? matcher.value("slotId"), String(webView.hash)) + recordMessageExpectation.fulfill() + } try WebViewTracking.enableOrThrow( tracking: webView, @@ -781,14 +757,9 @@ class WebViewTrackingTests: XCTestCase { func testItSendsTrackWebViewUsageTelemetry() throws { // Given - var capturedUsage: UsageTelemetry? - let core = PassthroughCoreMock( - messageReceiver: FeatureMessageReceiverMock { message in - if case .telemetry(.usage(let usage)) = message { - capturedUsage = usage - } - } - ) + let telemetry = TelemetryReceiverMock() + let core = PassthroughCoreMock() + core.subscribe(receiver: telemetry) let config = WKWebViewConfiguration() let controller = DDUserContentController() config.userContentController = controller @@ -804,7 +775,7 @@ class WebViewTrackingTests: XCTestCase { ) // Then - let usage = try XCTUnwrap(capturedUsage, "Expected a usage telemetry event to be sent") + let usage = try XCTUnwrap(telemetry.messages.firstUsage(), "Expected a usage telemetry event to be sent") XCTAssertEqual(usage.sampleRate, UsageTelemetry.defaultSampleRate) guard case .trackWebView = usage.event else { XCTFail("Expected .trackWebView usage event") @@ -814,14 +785,9 @@ class WebViewTrackingTests: XCTestCase { func testItSendsTrackWebViewUsageTelemetryOnlyOnce() throws { // Given - var trackWebViewUsageCount = 0 - let core = PassthroughCoreMock( - messageReceiver: FeatureMessageReceiverMock { message in - if case .telemetry(.usage(let usage)) = message, case .trackWebView = usage.event { - trackWebViewUsageCount += 1 - } - } - ) + let telemetry = TelemetryReceiverMock() + let core = PassthroughCoreMock() + core.subscribe(receiver: telemetry) let config = WKWebViewConfiguration() let controller = DDUserContentController() config.userContentController = controller @@ -839,19 +805,21 @@ class WebViewTrackingTests: XCTestCase { } // Then - XCTAssertEqual(trackWebViewUsageCount, 1) + let usageCount = telemetry.messages.filter { + if case .usage = $0 { + return true + } else { + return false + } + }.count + XCTAssertEqual(usageCount, 1) } func testItSendsTrackWebViewUsageTelemetryAgainAfterDisable() throws { // Given - var trackWebViewUsageCount = 0 - let core = PassthroughCoreMock( - messageReceiver: FeatureMessageReceiverMock { message in - if case .telemetry(.usage(let usage)) = message, case .trackWebView = usage.event { - trackWebViewUsageCount += 1 - } - } - ) + let telemetry = TelemetryReceiverMock() + let core = PassthroughCoreMock() + core.subscribe(receiver: telemetry) let controller = DDUserContentController() let configuration = WKWebViewConfiguration() configuration.userContentController = controller @@ -859,13 +827,27 @@ class WebViewTrackingTests: XCTestCase { // When WebViewTracking.enable(webView: webview, in: core) - XCTAssertEqual(trackWebViewUsageCount, 1) + let countAfterFirstEnable = telemetry.messages.filter { + if case .usage = $0 { + return true + } else { + return false + } + }.count + XCTAssertEqual(countAfterFirstEnable, 1) WebViewTracking.disable(webView: webview) WebViewTracking.enable(webView: webview, in: core) // Then - disable clears user scripts so the duplicate guard passes again on re-enable - XCTAssertEqual(trackWebViewUsageCount, 2) + let countAfterSecondEnable = telemetry.messages.filter { + if case .usage = $0 { + return true + } else { + return false + } + }.count + XCTAssertEqual(countAfterSecondEnable, 2) } }