Skip to content

Commit b000fdc

Browse files
committed
test: cover Identity Verification turned-off transition
Add per-executor tests asserting parked requests are released and sent without auth once Identity Verification is turned off, plus OSUserJwtConfig tests verifying a missing jwt_required remote param resolves to off (including the previously-on case) and that an unchanged value does not re-fire listeners.
1 parent b252e10 commit b000fdc

5 files changed

Lines changed: 246 additions & 0 deletions

File tree

iOS_SDK/OneSignalSDK/OneSignalUserTests/Executors/IdentityExecutorTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,4 +278,41 @@ final class IdentityExecutorTests: XCTestCase {
278278

279279
XCTAssertEqual(removeAliasRequests.count, 3)
280280
}
281+
282+
/// When Identity Verification is turned off, alias requests parked awaiting a JWT must be
283+
/// released and sent without a token (no JWT will ever arrive once auth is off).
284+
func testReleasePendingRequests_OnIdentityVerificationTurnedOff() {
285+
/* Setup */
286+
let mocks = Mocks()
287+
mocks.setAuthRequired(true)
288+
OneSignalUserManagerImpl.sharedInstance.operationRepo.paused = true
289+
290+
let user = mocks.setUserManagerInternalUser(externalId: userA_EUID, onesignalId: userA_OSID)
291+
// No JWT token, so the request is parked awaiting a JWT while Identity Verification is on.
292+
// Use the user manager's executor because the JWT-config callback fires only on subscribed
293+
// executors; start() initializes and subscribes them.
294+
OneSignalUserManagerImpl.sharedInstance.start()
295+
let executor = OneSignalUserManagerImpl.sharedInstance.identityExecutor!
296+
297+
let aliases = userA_Aliases
298+
MockUserRequests.setAddAliasesResponse(with: mocks.client, aliases: aliases)
299+
300+
let userJwtInvalidatedListener = MockUserJwtInvalidatedListener()
301+
OneSignalUserManagerImpl.sharedInstance.addUserJwtInvalidatedListener(userJwtInvalidatedListener)
302+
303+
executor.enqueueDelta(OSDelta(name: OS_ADD_ALIAS_DELTA, identityModelId: user.identityModel.modelId, model: user.identityModel, property: "aliases", value: aliases))
304+
305+
/* When: the request is parked because no token is present */
306+
executor.processDeltaQueue(inBackground: false)
307+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
308+
XCTAssertFalse(mocks.client.hasExecutedRequestOfType(OSRequestAddAliases.self))
309+
310+
/* When: Identity Verification is turned off */
311+
mocks.setAuthRequired(false)
312+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
313+
314+
/* Then: the parked request is released and sent, with no JWT invalidation */
315+
XCTAssertTrue(mocks.client.hasExecutedRequestOfType(OSRequestAddAliases.self))
316+
XCTAssertFalse(userJwtInvalidatedListener.invalidatedCallbackWasCalled)
317+
}
281318
}

iOS_SDK/OneSignalSDK/OneSignalUserTests/Executors/OSCustomEventsExecutorTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,4 +428,36 @@ final class OSCustomEventsExecutorTests: XCTestCase {
428428
// Verify user properties are still present
429429
XCTAssertEqual(payload["user_key"] as? String, "user_value")
430430
}
431+
432+
// MARK: - Identity Verification turned off
433+
434+
/// When Identity Verification is turned off, custom-event requests parked awaiting a JWT must
435+
/// be released and sent without a token (no JWT will ever arrive once auth is off).
436+
func testReleasePendingRequests_OnIdentityVerificationTurnedOff() {
437+
/* Setup */
438+
let mocks = CustomEventsMocks()
439+
// Turn Identity Verification on via the shared config, which the user manager's executor reads.
440+
OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired = true
441+
442+
let user = OneSignalUserMocks.setUserManagerInternalUser(externalId: userA_EUID, onesignalId: userA_OSID)
443+
// start() initializes sharedInstance.customEventsExecutor and subscribes it as a JWT listener.
444+
OneSignalUserManagerImpl.sharedInstance.start()
445+
let executor = OneSignalUserManagerImpl.sharedInstance.customEventsExecutor!
446+
447+
mocks.client.fireSuccessForAllRequests = true
448+
let delta = createCustomEventDelta(name: "iv_off_event", properties: ["key": "value"], identityModel: user.identityModel)
449+
executor.enqueueDelta(delta)
450+
451+
/* When: the request is parked because no token is present */
452+
executor.processDeltaQueue(inBackground: false)
453+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
454+
XCTAssertFalse(mocks.client.hasExecutedRequestOfType(OSRequestCustomEvents.self))
455+
456+
/* When: Identity Verification is turned off */
457+
OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired = false
458+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
459+
460+
/* Then: the parked request is released and sent */
461+
XCTAssertTrue(mocks.client.hasExecutedRequestOfType(OSRequestCustomEvents.self))
462+
}
431463
}

iOS_SDK/OneSignalSDK/OneSignalUserTests/Executors/PropertyExecutorTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,41 @@ final class PropertyExecutorTests: XCTestCase {
213213
}
214214
XCTAssertEqual(updateRequests.count, 3)
215215
}
216+
217+
/// When Identity Verification is turned off, requests parked awaiting a JWT must be
218+
/// released and sent without a token (no JWT will ever arrive once auth is off).
219+
func testReleasePendingRequests_OnIdentityVerificationTurnedOff() {
220+
/* Setup */
221+
let mocks = Mocks()
222+
mocks.setAuthRequired(true)
223+
OneSignalUserManagerImpl.sharedInstance.operationRepo.paused = true
224+
225+
let user = mocks.setUserManagerInternalUser(externalId: userA_EUID, onesignalId: userA_OSID)
226+
// No JWT token, so the request is parked awaiting a JWT while Identity Verification is on.
227+
// Use the user manager's executor because the JWT-config callback fires only on subscribed
228+
// executors; start() initializes and subscribes them.
229+
OneSignalUserManagerImpl.sharedInstance.start()
230+
let executor = OneSignalUserManagerImpl.sharedInstance.propertyExecutor!
231+
232+
let tags = ["testUserA": "true"]
233+
MockUserRequests.setAddTagsResponse(with: mocks.client, tags: tags)
234+
235+
let userJwtInvalidatedListener = MockUserJwtInvalidatedListener()
236+
OneSignalUserManagerImpl.sharedInstance.addUserJwtInvalidatedListener(userJwtInvalidatedListener)
237+
238+
executor.enqueueDelta(OSDelta(name: OS_UPDATE_PROPERTIES_DELTA, identityModelId: user.identityModel.modelId, model: OSPropertiesModel(changeNotifier: OSEventProducer()), property: "tags", value: tags))
239+
240+
/* When: the request is parked because no token is present */
241+
executor.processDeltaQueue(inBackground: false)
242+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
243+
XCTAssertFalse(mocks.client.hasExecutedRequestOfType(OSRequestUpdateProperties.self))
244+
245+
/* When: Identity Verification is turned off */
246+
mocks.setAuthRequired(false)
247+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
248+
249+
/* Then: the parked request is released and sent, with no JWT invalidation */
250+
XCTAssertTrue(mocks.client.hasExecutedRequestOfType(OSRequestUpdateProperties.self))
251+
XCTAssertFalse(userJwtInvalidatedListener.invalidatedCallbackWasCalled)
252+
}
216253
}

iOS_SDK/OneSignalSDK/OneSignalUserTests/Executors/SubscriptionsExecutorTests.swift

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,41 @@ final class SubscriptionExecutorTests: XCTestCase {
277277

278278
XCTAssertEqual(deleteRequests.count, 3)
279279
}
280+
281+
/// When Identity Verification is turned off, subscription requests parked awaiting a JWT must
282+
/// be released and sent without a token (no JWT will ever arrive once auth is off).
283+
func testReleasePendingRequests_OnIdentityVerificationTurnedOff() {
284+
/* Setup */
285+
let mocks = Mocks()
286+
mocks.setAuthRequired(true)
287+
OneSignalUserManagerImpl.sharedInstance.operationRepo.paused = true
288+
289+
let user = mocks.setUserManagerInternalUser(externalId: userA_EUID, onesignalId: userA_OSID)
290+
// No JWT token, so the request is parked awaiting a JWT while Identity Verification is on.
291+
// Use the user manager's executor because the JWT-config callback fires only on subscribed
292+
// executors; start() initializes and subscribes them.
293+
OneSignalUserManagerImpl.sharedInstance.start()
294+
let executor = OneSignalUserManagerImpl.sharedInstance.subscriptionExecutor!
295+
296+
let email = userA_email
297+
MockUserRequests.setAddEmailResponse(with: mocks.client, email: email)
298+
299+
let userJwtInvalidatedListener = MockUserJwtInvalidatedListener()
300+
OneSignalUserManagerImpl.sharedInstance.addUserJwtInvalidatedListener(userJwtInvalidatedListener)
301+
302+
executor.enqueueDelta(OSDelta(name: OS_ADD_SUBSCRIPTION_DELTA, identityModelId: user.identityModel.modelId, model: OSSubscriptionModel(type: .email, address: email, subscriptionId: nil, reachable: true, isDisabled: false, changeNotifier: OSEventProducer()), property: OSSubscriptionType.email.rawValue, value: email))
303+
304+
/* When: the request is parked because no token is present */
305+
executor.processDeltaQueue(inBackground: false)
306+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
307+
XCTAssertFalse(mocks.client.hasExecutedRequestOfType(OSRequestCreateSubscription.self))
308+
309+
/* When: Identity Verification is turned off */
310+
mocks.setAuthRequired(false)
311+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
312+
313+
/* Then: the parked request is released and sent, with no JWT invalidation */
314+
XCTAssertTrue(mocks.client.hasExecutedRequestOfType(OSRequestCreateSubscription.self))
315+
XCTAssertFalse(userJwtInvalidatedListener.invalidatedCallbackWasCalled)
316+
}
280317
}

iOS_SDK/OneSignalSDK/OneSignalUserTests/Executors/UserExecutorTests.swift

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,4 +433,107 @@ final class UserExecutorTests: XCTestCase {
433433
// >= because start() may fire incidental requests (e.g. language update from _user?.update())
434434
XCTAssertGreaterThanOrEqual(mocks.client.networkRequestCount, 3)
435435
}
436+
437+
/// When Identity Verification is turned off, requests parked awaiting a JWT must be released
438+
/// and sent without a token (no JWT will ever arrive once auth is off).
439+
func testReleasePendingRequests_OnIdentityVerificationTurnedOff() {
440+
/* Setup */
441+
let mocks = Mocks()
442+
mocks.setAuthRequired(true)
443+
444+
_ = mocks.setUserManagerInternalUser(externalId: userA_EUID)
445+
// start() initializes sharedInstance.userExecutor and subscribes it as a JWT listener.
446+
OneSignalUserManagerImpl.sharedInstance.start()
447+
let executor = OneSignalUserManagerImpl.sharedInstance.userExecutor!
448+
449+
// No JWT token, so the Fetch User request is parked awaiting a JWT while IV is on.
450+
let newIdentityModel = OSIdentityModel(aliases: [OS_ONESIGNAL_ID: userA_OSID, OS_EXTERNAL_ID: userA_EUID], changeNotifier: OSEventProducer())
451+
MockUserRequests.setDefaultFetchUserResponseForHydration(with: mocks.client, externalId: userA_EUID)
452+
453+
let userJwtInvalidatedListener = MockUserJwtInvalidatedListener()
454+
OneSignalUserManagerImpl.sharedInstance.addUserJwtInvalidatedListener(userJwtInvalidatedListener)
455+
456+
/* When: the request is parked because no token is present */
457+
executor.fetchUser(onesignalId: userA_OSID, identityModel: newIdentityModel)
458+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
459+
XCTAssertFalse(mocks.client.hasExecutedRequestOfType(OSRequestFetchUser.self))
460+
461+
/* When: Identity Verification is turned off */
462+
mocks.setAuthRequired(false)
463+
OneSignalCoreMocks.waitForBackgroundThreads(seconds: 0.5)
464+
465+
/* Then: the parked request is released and sent, with no JWT invalidation */
466+
XCTAssertTrue(mocks.client.hasExecutedRequestOfType(OSRequestFetchUser.self))
467+
XCTAssertFalse(userJwtInvalidatedListener.invalidatedCallbackWasCalled)
468+
}
469+
470+
// MARK: - Identity Verification config: a missing remote-params field always means off
471+
472+
/// The regression: a previously-ON app must turn OFF when remote params omit jwt_required.
473+
func testRemoteParamsMissingJwtRequired_TurnsOff_WhenPreviouslyOn() {
474+
/* Setup */
475+
OneSignalUserManagerImpl.sharedInstance.setRequiresUserAuth(true)
476+
XCTAssertEqual(OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired, true)
477+
478+
let listener = MockJwtConfigListener()
479+
OneSignalUserManagerImpl.sharedInstance.subscribeToJwtConfig(listener, key: "test_iv_off_previously_on")
480+
481+
/* When: remote params come back without the jwt_required field */
482+
OneSignalUserManagerImpl.sharedInstance.remoteParamsReturnedUnknownRequiresUserAuth()
483+
484+
/* Then: it flips to off and fires the transition exactly once */
485+
XCTAssertEqual(OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired, false)
486+
XCTAssertEqual(listener.changes.count, 1)
487+
XCTAssertEqual(listener.changes.first?.from, OSRequiresUserAuth.on)
488+
XCTAssertEqual(listener.changes.first?.to, OSRequiresUserAuth.off)
489+
}
490+
491+
/// An unknown (never-set) status resolves to off when remote params omit jwt_required.
492+
func testRemoteParamsMissingJwtRequired_TurnsOff_WhenPreviouslyUnknown() {
493+
/* Setup */
494+
OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired = nil // force unknown
495+
XCTAssertNil(OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired)
496+
497+
let listener = MockJwtConfigListener()
498+
OneSignalUserManagerImpl.sharedInstance.subscribeToJwtConfig(listener, key: "test_iv_off_previously_unknown")
499+
500+
/* When */
501+
OneSignalUserManagerImpl.sharedInstance.remoteParamsReturnedUnknownRequiresUserAuth()
502+
503+
/* Then */
504+
XCTAssertEqual(OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired, false)
505+
XCTAssertEqual(listener.changes.count, 1)
506+
XCTAssertEqual(listener.changes.first?.from, OSRequiresUserAuth.unknown)
507+
XCTAssertEqual(listener.changes.first?.to, OSRequiresUserAuth.off)
508+
}
509+
510+
/// Re-confirming an already-off status must not re-fire listeners (didSet equality guard).
511+
func testRemoteParamsMissingJwtRequired_DoesNotRefire_WhenAlreadyOff() {
512+
/* Setup */
513+
OneSignalUserManagerImpl.sharedInstance.setRequiresUserAuth(false)
514+
XCTAssertEqual(OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired, false)
515+
516+
let listener = MockJwtConfigListener()
517+
OneSignalUserManagerImpl.sharedInstance.subscribeToJwtConfig(listener, key: "test_iv_off_already_off")
518+
519+
/* When */
520+
OneSignalUserManagerImpl.sharedInstance.remoteParamsReturnedUnknownRequiresUserAuth()
521+
522+
/* Then: value unchanged, no transition fired */
523+
XCTAssertEqual(OneSignalUserManagerImpl.sharedInstance.jwtConfig.isRequired, false)
524+
XCTAssertEqual(listener.changes.count, 0)
525+
}
526+
}
527+
528+
private class MockJwtConfigListener: NSObject, OSUserJwtConfigListener {
529+
var changes: [(from: OSRequiresUserAuth, to: OSRequiresUserAuth)] = []
530+
var jwtUpdateCount = 0
531+
532+
func onRequiresUserAuthChanged(from: OSRequiresUserAuth, to: OSRequiresUserAuth) {
533+
changes.append((from: from, to: to))
534+
}
535+
536+
func onJwtUpdated(externalId: String, token: String?) {
537+
jwtUpdateCount += 1
538+
}
436539
}

0 commit comments

Comments
 (0)