From 028fde06b74dc1672c991e01ec69727b6a47053a Mon Sep 17 00:00:00 2001 From: Nan Date: Mon, 29 Jun 2026 09:23:24 -0700 Subject: [PATCH 1/4] fix: [SDK-4814] attribute Live Activity receive receipt to the delivered content Read the notificationId from the content yielded by this contentUpdates iteration instead of activity.content (the latest snapshot), which can advance while the loop is suspended and mis-attribute the receipt to a different update. Co-authored-by: Cursor --- .../Source/OneSignalLiveActivitiesManagerImpl.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/OneSignalLiveActivitiesManagerImpl.swift b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/OneSignalLiveActivitiesManagerImpl.swift index 64ee3d0d1..cdcf6fc8c 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/OneSignalLiveActivitiesManagerImpl.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/OneSignalLiveActivitiesManagerImpl.swift @@ -334,7 +334,7 @@ public class OneSignalLiveActivitiesManagerImpl: NSObject, OSLiveActivities { Task { for await content in activity.contentUpdates { // Don't track a live activity started / updated "in app" without a notification - if let notificationId = activity.content.state.onesignal?.notificationId { + if let notificationId = content.state.onesignal?.notificationId { OneSignalLiveActivitiesManagerImpl.addReceiveReceipts(notificationId: notificationId, activityType: "\(activityType)", activityId: activity.attributes.onesignal.activityId) } } From b36822e72f17a591be0e919361f4d2f1d4ba4a39 Mon Sep 17 00:00:00 2001 From: Nan Date: Mon, 29 Jun 2026 18:44:13 -0700 Subject: [PATCH 2/4] fix: [SDK-4814] dedup Live Activity receive receipts per session A receive receipt for a notificationId was re-sent on later content updates or app relaunches once the prior request was forgotten on success, inflating Confirmed Receipts above Delivered. Track sent notificationIds in an in-memory set, claimed at enqueue, so each is reported at most once per session, and shorten the unsent-receipt retry window from 30 to 7 days. Co-authored-by: Cursor --- .../Executors/OSLiveActivitiesExecutor.swift | 22 ++++++++-- ...OSRequestLiveActivityReceiveReceipts.swift | 1 + .../OSLiveActivitiesExecutorTests.swift | 44 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Executors/OSLiveActivitiesExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Executors/OSLiveActivitiesExecutor.swift index 23c7b3082..cd4e11109 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Executors/OSLiveActivitiesExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Executors/OSLiveActivitiesExecutor.swift @@ -115,11 +115,11 @@ class StartRequestCache: RequestCache { } class ReceiveReceiptsRequestCache: RequestCache { - // Keep receive receipts requests for up to 30 days. - static let OneMonthInSeconds = TimeInterval(60 * 60 * 24 * 30) + // Only pending receipts live here; de-duplication is handled by the executor's confirmedReceiptIds. + static let OneWeekInSeconds = TimeInterval(60 * 60 * 24 * 7) init() { - super.init(cacheKey: OS_LIVE_ACTIVITIES_EXECUTOR_RECEIVE_RECEIPTS_KEY, ttl: ReceiveReceiptsRequestCache.OneMonthInSeconds) + super.init(cacheKey: OS_LIVE_ACTIVITIES_EXECUTOR_RECEIVE_RECEIPTS_KEY, ttl: ReceiveReceiptsRequestCache.OneWeekInSeconds) } } @@ -138,6 +138,8 @@ class OSLiveActivitiesExecutor: OSPushSubscriptionObserver { let startTokens: StartRequestCache = StartRequestCache() let receiveReceipts: ReceiveReceiptsRequestCache = ReceiveReceiptsRequestCache() let clickEvents: ClickedRequestCache = ClickedRequestCache() + // Dedupes receipts per session so duplicate content-update listeners can't over-report. + var confirmedReceiptIds: Set = [] // The live activities request dispatch queue, serial. This synchronizes access to `updateTokens` and `startTokens`. private var requestDispatch: OSDispatchQueue @@ -172,6 +174,10 @@ class OSLiveActivitiesExecutor: OSPushSubscriptionObserver { func append(_ request: OSLiveActivityRequest) { self.requestDispatch.async { + if request is OSRequestLiveActivityReceiveReceipts, !self.claimReceipt(request.key) { + return + } + let cache = self.getCache(request) let existingRequest = cache.items[request.key] @@ -184,6 +190,16 @@ class OSLiveActivitiesExecutor: OSPushSubscriptionObserver { } } + // False if this notificationId was already claimed this session. Call on requestDispatch. + private func claimReceipt(_ notificationId: String) -> Bool { + if confirmedReceiptIds.contains(notificationId) { + OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OneSignal.LiveActivities duplicate receive receipt not sent for notificationId: \(notificationId)") + return false + } + confirmedReceiptIds.insert(notificationId) + return true + } + private func pollPendingRequests() { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OneSignal.LiveActivities pollPendingRequests") diff --git a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityReceiveReceipts.swift b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityReceiveReceipts.swift index f5ef0561b..2193726ef 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityReceiveReceipts.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityReceiveReceipts.swift @@ -36,6 +36,7 @@ class OSRequestLiveActivityReceiveReceipts: OneSignalRequest, OSLiveActivityRequ var activityType: String var activityId: String var requestSuccessful: Bool + // Forgotten once sent; duplicate suppression is handled by the executor's confirmedReceiptIds. var shouldForgetWhenSuccessful: Bool = true func prepareForExecution() -> Bool { diff --git a/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift b/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift index 6feb7c122..4c9b98cc8 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift @@ -173,7 +173,9 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { mockDispatchQueue.waitForDispatches(2) /* Then */ + // Forgotten from the cache, but its notificationId is remembered so it isn't reported again. XCTAssertEqual(executor.receiveReceipts.items.count, 0) + XCTAssertTrue(executor.confirmedReceiptIds.contains("notification-id")) XCTAssertEqual(mockClient.executedRequests.count, 1) XCTAssertTrue(mockClient.executedRequests[0] == request) } @@ -515,8 +517,50 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { mockDispatchQueue.waitForDispatches(3) /* Then */ + // request2 is suppressed as a duplicate of request1; the sent receipt is then forgotten. XCTAssertEqual(executor.receiveReceipts.items.count, 0) XCTAssertEqual(mockClient.executedRequests.count, 1) XCTAssertTrue(mockClient.executedRequests[0] == request1) } + + /** + A receive-receipt for a notificationId must be sent at most once per device. This covers the + cross-cycle case: unlike `testReceiveReceiptsRequestNotExecutedWithSameNotificationId` (both receipts + appended in one cycle), here the first fully completes and is forgotten before the second arrives, so + the duplicate is caught by the remembered notificationIds rather than by the request cache. + */ + func testReceiveReceiptsNotResentForSameNotificationIdAcrossDeliveryCycles() throws { + /* Setup */ + let mockDispatchQueue = MockDispatchQueue() + let mockClient = MockOneSignalClient() + OneSignalCoreImpl.setSharedClient(mockClient) + OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") + OneSignalUserManagerImpl.sharedInstance.start() + // Wait for any user setup requests to complete + OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) + mockClient.reset() + + let request1 = OSRequestLiveActivityReceiveReceipts(key: "my-notification-id", activityType: "my-activity-type", activityId: "my-activity-id") + let request2 = OSRequestLiveActivityReceiveReceipts(key: "my-notification-id", activityType: "my-activity-type", activityId: "my-activity-id") + mockClient.setMockResponseForRequest(request: String(describing: request1), response: [String: Any]()) + mockClient.setMockResponseForRequest(request: String(describing: request2), response: [String: Any]()) + + /* When */ + let executor = OSLiveActivitiesExecutor(requestDispatch: mockDispatchQueue) + + // First cycle: receipt is sent, succeeds, is forgotten, and its notificationId is remembered. + executor.append(request1) + mockDispatchQueue.waitForDispatches(2) + XCTAssertEqual(mockClient.executedRequests.count, 1) + XCTAssertEqual(executor.receiveReceipts.items.count, 0) + XCTAssertTrue(executor.confirmedReceiptIds.contains("my-notification-id")) + + // Second cycle, same notificationId (e.g. another content update / relaunch): a duplicate. + executor.append(request2) + mockDispatchQueue.waitForDispatches(3) + + /* Then */ + XCTAssertEqual(mockClient.executedRequests.count, 1, "Duplicate receive-receipt must not be sent for the same notificationId across delivery cycles") + XCTAssertTrue(mockClient.executedRequests[0] == request1) + } } From 350ae9dd4d571c3f25a285242f39bb7cbd6bb471 Mon Sep 17 00:00:00 2001 From: Nan Date: Tue, 30 Jun 2026 10:30:54 -0700 Subject: [PATCH 3/4] fix: [SDK-4814] persist Live Activity receive-receipt dedup across launches The in-memory dedup set did not survive app relaunches, so when ActivityKit re-emits an active activity's content on launch the same notificationId was reported again. Keep sent receipts in the persisted cache as dedup markers (shouldForgetWhenSuccessful = false); supersedes == false drops the re-send and pollPendingRequests skips successful entries, making the cache the single source of truth. Remove the redundant in-memory set and shorten the receive- receipts cache TTL from 7 to 3 days. Adds a relaunch regression test. Co-authored-by: Cursor --- .../Executors/OSLiveActivitiesExecutor.swift | 22 ++-------- ...OSRequestLiveActivityReceiveReceipts.swift | 4 +- .../OSLiveActivitiesExecutorTests.swift | 42 +++++++++---------- 3 files changed, 25 insertions(+), 43 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Executors/OSLiveActivitiesExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Executors/OSLiveActivitiesExecutor.swift index cd4e11109..93b768240 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Executors/OSLiveActivitiesExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Executors/OSLiveActivitiesExecutor.swift @@ -115,11 +115,11 @@ class StartRequestCache: RequestCache { } class ReceiveReceiptsRequestCache: RequestCache { - // Only pending receipts live here; de-duplication is handled by the executor's confirmedReceiptIds. - static let OneWeekInSeconds = TimeInterval(60 * 60 * 24 * 7) + // Sent receipts stay as dedup markers (not only pending retries), so re-emits after relaunch aren't re-sent. + static let ThreeDaysInSeconds = TimeInterval(60 * 60 * 24 * 3) init() { - super.init(cacheKey: OS_LIVE_ACTIVITIES_EXECUTOR_RECEIVE_RECEIPTS_KEY, ttl: ReceiveReceiptsRequestCache.OneWeekInSeconds) + super.init(cacheKey: OS_LIVE_ACTIVITIES_EXECUTOR_RECEIVE_RECEIPTS_KEY, ttl: ReceiveReceiptsRequestCache.ThreeDaysInSeconds) } } @@ -138,8 +138,6 @@ class OSLiveActivitiesExecutor: OSPushSubscriptionObserver { let startTokens: StartRequestCache = StartRequestCache() let receiveReceipts: ReceiveReceiptsRequestCache = ReceiveReceiptsRequestCache() let clickEvents: ClickedRequestCache = ClickedRequestCache() - // Dedupes receipts per session so duplicate content-update listeners can't over-report. - var confirmedReceiptIds: Set = [] // The live activities request dispatch queue, serial. This synchronizes access to `updateTokens` and `startTokens`. private var requestDispatch: OSDispatchQueue @@ -174,10 +172,6 @@ class OSLiveActivitiesExecutor: OSPushSubscriptionObserver { func append(_ request: OSLiveActivityRequest) { self.requestDispatch.async { - if request is OSRequestLiveActivityReceiveReceipts, !self.claimReceipt(request.key) { - return - } - let cache = self.getCache(request) let existingRequest = cache.items[request.key] @@ -190,16 +184,6 @@ class OSLiveActivitiesExecutor: OSPushSubscriptionObserver { } } - // False if this notificationId was already claimed this session. Call on requestDispatch. - private func claimReceipt(_ notificationId: String) -> Bool { - if confirmedReceiptIds.contains(notificationId) { - OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OneSignal.LiveActivities duplicate receive receipt not sent for notificationId: \(notificationId)") - return false - } - confirmedReceiptIds.insert(notificationId) - return true - } - private func pollPendingRequests() { OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OneSignal.LiveActivities pollPendingRequests") diff --git a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityReceiveReceipts.swift b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityReceiveReceipts.swift index 2193726ef..f1058b995 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityReceiveReceipts.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityReceiveReceipts.swift @@ -36,8 +36,8 @@ class OSRequestLiveActivityReceiveReceipts: OneSignalRequest, OSLiveActivityRequ var activityType: String var activityId: String var requestSuccessful: Bool - // Forgotten once sent; duplicate suppression is handled by the executor's confirmedReceiptIds. - var shouldForgetWhenSuccessful: Bool = true + // Kept after success so the persisted cache suppresses re-sends across relaunches. + var shouldForgetWhenSuccessful: Bool = false func prepareForExecution() -> Bool { guard let appId = OneSignalIdentifiers.currentAppId else { diff --git a/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift b/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift index 4c9b98cc8..11383a8bb 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift @@ -173,9 +173,9 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { mockDispatchQueue.waitForDispatches(2) /* Then */ - // Forgotten from the cache, but its notificationId is remembered so it isn't reported again. - XCTAssertEqual(executor.receiveReceipts.items.count, 0) - XCTAssertTrue(executor.confirmedReceiptIds.contains("notification-id")) + // The sent receipt is kept as a dedup marker so the same notificationId isn't reported again. + XCTAssertEqual(executor.receiveReceipts.items.count, 1) + XCTAssertTrue(executor.receiveReceipts.items["notification-id"]?.requestSuccessful ?? false) XCTAssertEqual(mockClient.executedRequests.count, 1) XCTAssertTrue(mockClient.executedRequests[0] == request) } @@ -517,21 +517,19 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { mockDispatchQueue.waitForDispatches(3) /* Then */ - // request2 is suppressed as a duplicate of request1; the sent receipt is then forgotten. - XCTAssertEqual(executor.receiveReceipts.items.count, 0) + // request2 is suppressed as a duplicate of request1, which is kept as a dedup marker. + XCTAssertEqual(executor.receiveReceipts.items.count, 1) XCTAssertEqual(mockClient.executedRequests.count, 1) XCTAssertTrue(mockClient.executedRequests[0] == request1) } /** - A receive-receipt for a notificationId must be sent at most once per device. This covers the - cross-cycle case: unlike `testReceiveReceiptsRequestNotExecutedWithSameNotificationId` (both receipts - appended in one cycle), here the first fully completes and is forgotten before the second arrives, so - the duplicate is caught by the remembered notificationIds rather than by the request cache. + A receive receipt must be sent at most once per device, even across app launches. The second executor + reloads the persisted cache with in-memory state gone, simulating a relaunch where ActivityKit re-emits + the active activity's current content and the same notificationId is reported again. */ - func testReceiveReceiptsNotResentForSameNotificationIdAcrossDeliveryCycles() throws { + func testReceiveReceiptsNotResentForSameNotificationIdAfterRelaunch() throws { /* Setup */ - let mockDispatchQueue = MockDispatchQueue() let mockClient = MockOneSignalClient() OneSignalCoreImpl.setSharedClient(mockClient) OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") @@ -546,21 +544,21 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { mockClient.setMockResponseForRequest(request: String(describing: request2), response: [String: Any]()) /* When */ - let executor = OSLiveActivitiesExecutor(requestDispatch: mockDispatchQueue) - - // First cycle: receipt is sent, succeeds, is forgotten, and its notificationId is remembered. - executor.append(request1) - mockDispatchQueue.waitForDispatches(2) + // First launch: the receipt is sent and succeeds. + let firstLaunch = MockDispatchQueue() + let executor1 = OSLiveActivitiesExecutor(requestDispatch: firstLaunch) + executor1.append(request1) + firstLaunch.waitForDispatches(2) XCTAssertEqual(mockClient.executedRequests.count, 1) - XCTAssertEqual(executor.receiveReceipts.items.count, 0) - XCTAssertTrue(executor.confirmedReceiptIds.contains("my-notification-id")) - // Second cycle, same notificationId (e.g. another content update / relaunch): a duplicate. - executor.append(request2) - mockDispatchQueue.waitForDispatches(3) + // Relaunch: a fresh executor reloads the persisted cache; ActivityKit re-emits the same content. + let secondLaunch = MockDispatchQueue() + let executor2 = OSLiveActivitiesExecutor(requestDispatch: secondLaunch) + executor2.append(request2) + secondLaunch.waitForDispatches(1) /* Then */ - XCTAssertEqual(mockClient.executedRequests.count, 1, "Duplicate receive-receipt must not be sent for the same notificationId across delivery cycles") + XCTAssertEqual(mockClient.executedRequests.count, 1, "Receive receipt must not be re-sent for the same notificationId after relaunch") XCTAssertTrue(mockClient.executedRequests[0] == request1) } } From 0a6d57e3522e89c71b2fecc6692cfa7e0c814d0a Mon Sep 17 00:00:00 2001 From: Nan Date: Tue, 30 Jun 2026 10:53:05 -0700 Subject: [PATCH 4/4] test: [SDK-4814] extract shared subscribed-user setup helper The relaunch regression test pushed OSLiveActivitiesExecutorTests over SwiftLint's type_body_length error threshold, failing the CI lint step. Collapse the repeated per-test subscribed-user setup into a single helper, which removes the duplication and keeps the class body under the limit. Co-authored-by: Cursor --- .../OSLiveActivitiesExecutorTests.swift | 132 ++++-------------- 1 file changed, 24 insertions(+), 108 deletions(-) diff --git a/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift b/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift index 11383a8bb..c98b878ea 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalLiveActivitiesTests/OSLiveActivitiesExecutorTests.swift @@ -49,9 +49,8 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { override func tearDownWithError() throws { } - func testAppendSetStartTokenWithSuccessfulRequest() throws { - /* Setup */ - let mockDispatchQueue = MockDispatchQueue() + // Subscribes a user, then resets the client so tests assert only on the requests they make. + private func setUpSubscribedUser() -> MockOneSignalClient { let mockClient = MockOneSignalClient() OneSignalCoreImpl.setSharedClient(mockClient) OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") @@ -59,6 +58,13 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { // Wait for any user setup requests to complete OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) mockClient.reset() + return mockClient + } + + func testAppendSetStartTokenWithSuccessfulRequest() throws { + /* Setup */ + let mockDispatchQueue = MockDispatchQueue() + let mockClient = setUpSubscribedUser() let request = OSRequestSetStartToken(key: "my-activity-type", token: "my-token") mockClient.setMockResponseForRequest(request: String(describing: request), response: [String: Any]()) @@ -79,13 +85,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testRemoveStartTokenWithSuccessfulRequest() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request = OSRequestRemoveStartToken(key: "my-activity-type") mockClient.setMockResponseForRequest(request: String(describing: request), response: [String: Any]()) @@ -104,13 +104,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testSetUpdateTokenWithSuccessfulRequest() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request = OSRequestSetUpdateToken(key: "my-activity-id", token: "my-token") mockClient.setMockResponseForRequest(request: String(describing: request), response: [String: Any]()) @@ -131,13 +125,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testRemoveUpdateTokenWithSuccessfulRequest() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request = OSRequestRemoveUpdateToken(key: "my-activity-id") mockClient.setMockResponseForRequest(request: String(describing: request), response: [String: Any]()) @@ -156,13 +144,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testReceiveReceiptsWithSuccessfulRequest() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request = OSRequestLiveActivityReceiveReceipts(key: "notification-id", activityType: "my-activity-type", activityId: "my-activity-id") mockClient.setMockResponseForRequest(request: String(describing: request), response: [String: Any]()) @@ -183,13 +165,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testClickEventWithSuccessfulRequest() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request = OSRequestLiveActivityClicked(key: "unique-click-id", activityType: "my-activity-type", activityId: "my-activity-id", notificationId: "my-notif-id") mockClient.setMockResponseForRequest(request: String(describing: request), response: [String: Any]()) @@ -227,13 +203,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testRequestStaysInCacheForRetryableError() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request = OSRequestSetStartToken(key: "my-activity-type", token: "my-token") mockClient.setMockFailureResponseForRequest(request: String(describing: request), error: OneSignalClientError(code: 500, message: "not-important", responseHeaders: nil, response: nil, underlyingError: nil)) @@ -254,13 +224,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testRequestRemovedFromCacheForNonRetryableError() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request = OSRequestSetStartToken(key: "my-activity-type", token: "my-token") mockClient.setMockFailureResponseForRequest(request: String(describing: request), error: OneSignalClientError(code: 401, message: "not-important", responseHeaders: nil, response: nil, underlyingError: nil)) @@ -321,13 +285,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testSetStartRequestNotExecutedWithSameActivityTypeAndToken() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request1 = OSRequestSetStartToken(key: "my-activity-type", token: "my-token") let request2 = OSRequestSetStartToken(key: "my-activity-type", token: "my-token") @@ -350,13 +308,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testSetStartRequestNotExecutedWithSameActivityTypeAndDiffToken() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request1 = OSRequestSetStartToken(key: "my-activity-type", token: "my-token-1") let request2 = OSRequestSetStartToken(key: "my-activity-type", token: "my-token-2") @@ -380,13 +332,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testSetStartRequestFollowedByRemoveStartIsSuccessful() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request1 = OSRequestSetStartToken(key: "my-activity-type", token: "my-token-1") let request2 = OSRequestRemoveStartToken(key: "my-activity-type") @@ -409,13 +355,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testSetUpdateRequestNotExecutedWithSameActivityIdAndToken() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request1 = OSRequestSetUpdateToken(key: "my-activity-id", token: "my-token") let request2 = OSRequestSetUpdateToken(key: "my-activity-id", token: "my-token") @@ -438,13 +378,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testSetUpdateRequestNotExecutedWithSameActivityIdAndDiffToken() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request1 = OSRequestSetUpdateToken(key: "my-activity-id", token: "my-token-1") let request2 = OSRequestSetUpdateToken(key: "my-activity-id", token: "my-token-2") @@ -468,13 +402,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testSetUpdateRequestFollowedByRemoveUpdateIsSuccessful() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request1 = OSRequestSetUpdateToken(key: "my-activity-id", token: "my-token-1") let request2 = OSRequestRemoveUpdateToken(key: "my-activity-id") @@ -497,13 +425,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { func testReceiveReceiptsRequestNotExecutedWithSameNotificationId() throws { /* Setup */ let mockDispatchQueue = MockDispatchQueue() - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request1 = OSRequestLiveActivityReceiveReceipts(key: "my-notification-id", activityType: "my-activity-type-1", activityId: "my-activity-id-1") let request2 = OSRequestLiveActivityReceiveReceipts(key: "my-notification-id", activityType: "my-activity-type-2", activityId: "my-activity-id-2") @@ -530,13 +452,7 @@ final class OSLiveActivitiesExecutorTests: XCTestCase { */ func testReceiveReceiptsNotResentForSameNotificationIdAfterRelaunch() throws { /* Setup */ - let mockClient = MockOneSignalClient() - OneSignalCoreImpl.setSharedClient(mockClient) - OneSignalUserDefaults.initShared().saveString(forKey: OSUD_LEGACY_PLAYER_ID, withValue: "my-subscription-id") - OneSignalUserManagerImpl.sharedInstance.start() - // Wait for any user setup requests to complete - OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.2) - mockClient.reset() + let mockClient = setUpSubscribedUser() let request1 = OSRequestLiveActivityReceiveReceipts(key: "my-notification-id", activityType: "my-activity-type", activityId: "my-activity-id") let request2 = OSRequestLiveActivityReceiveReceipts(key: "my-notification-id", activityType: "my-activity-type", activityId: "my-activity-id")