Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5298036
feat: persist Remote Config last-known-good
shameondev Aug 4, 2026
8c04fb2
fix: complete concurrent Remote Config fallbacks
shameondev Aug 4, 2026
cd401ac
test: make Remote Config enum tables deterministic
shameondev Aug 4, 2026
6954bce
fix: terminate Remote Config lists on identity failure
shameondev Aug 5, 2026
f4b7660
fix: serialize identity-bound remote config requests
shameondev Aug 5, 2026
c3d2d0a
test: pin same-user identity terminal boundary
shameondev Aug 5, 2026
77f6bb0
test: model asynchronous same-user identity response
shameondev Aug 5, 2026
2e7fe9d
fix: serialize logout and identity terminal boundaries
shameondev Aug 5, 2026
2f73d96
test: cover reentrant logout ownership
shameondev Aug 5, 2026
6166eca
fix: preserve outer logout ownership
shameondev Aug 5, 2026
5236eca
test: cover restore ownership and callback lock order
shameondev Aug 5, 2026
212c22a
fix: isolate identity callback locks
shameondev Aug 5, 2026
ff874bd
test: cover transaction restore ownership
shameondev Aug 5, 2026
4fff8c2
fix: reject superseded restore scope commits
shameondev Aug 5, 2026
2508829
test: cover launch ownership hardening
shameondev Aug 5, 2026
1b53c40
fix: guard all launch state writes
shameondev Aug 5, 2026
6ec36db
test: cover terminal launch drains
shameondev Aug 5, 2026
bd485ef
fix: drain terminal launch waiters
shameondev Aug 5, 2026
5c89f6d
fix: preserve terminal launch error ownership
shameondev Aug 5, 2026
208a1d1
fix: atomically publish terminal launch state
shameondev Aug 5, 2026
520da34
fix: cancel waiters for superseded identity launches
shameondev Aug 5, 2026
853e28a
test: cover inactive identity launch cancellation
shameondev Aug 5, 2026
bd78bf0
fix: preserve user state on waiterless stale launch
shameondev Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions QonversionTests/Managers/QNIdentityManagerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ - (void)testSuccessIdentity {
OCMStub([self.mockUserInfoService obtainUserID]).andReturn(anonUserID);
OCMStub([self.mockIdentityService identify:userID anonUserID:anonUserID completion:OCMOCK_ANY]).andDo(testBlock);

OCMExpect([self.mockUserInfoService storeIdentity:identityID]);

// when
[self.manager identify:userID completion:^(NSString * _Nullable result, NSError * _Nullable error) {
resultString = result;
Expand All @@ -77,8 +75,6 @@ - (void)testSuccessIdentity {
XCTAssertEqual(randomError, resultError);

OCMVerify([self.mockUserInfoService obtainUserID]);
OCMVerify([self.mockUserInfoService storeIdentity:identityID]);

OCMVerify([self.mockIdentityService identify:userID anonUserID:anonUserID completion:OCMOCK_ANY]);
}

Expand Down

Large diffs are not rendered by default.

2,358 changes: 2,204 additions & 154 deletions QonversionTests/Managers/QONRemoteConfigManagerInvalidationTests.m

Large diffs are not rendered by default.

34 changes: 32 additions & 2 deletions QonversionTests/ProductCenterManagerIdentifyContractTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,40 @@ - (void)testProcessIdentity_DifferentUid_ResetsCacheAndLaunchesWithIdentifyTrigg
[_partialManagerMock processIdentity:identityId];

// Then
OCMVerify([_mockClient setUserID:mergedUid]);
OCMVerify([_mockRemoteConfigManager userHasBeenChanged]);
OCMVerify([_mockRemoteConfigManager userHasBeenChangedToUserID:mergedUid]);
OCMReject([_mockClient setUserID:[OCMArg any]]);
OCMVerifyAll(_partialManagerMock); // resetActualPermissionsCache + launchWithTrigger
}

- (void)testProcessIdentity_DifferentUid_RemainsUnstableUntilRemoteConfigUserTransition {
NSString *identityId = @"user@example.com";
NSString *currentUid = @"uid_initial";
NSString *mergedUid = @"uid_merged_999";

OCMStub([_mockUserInfoService obtainUserID]).andReturn(currentUid);
OCMStub([_mockIdentityManager identify:identityId completion:[OCMArg invokeBlockWithArgs:mergedUid, [NSNull null], nil]]);
OCMStub([_partialManagerMock resetActualPermissionsCache]);
OCMStub([_partialManagerMock launchWithTrigger:QONRequestTriggerIdentify completion:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
__unsafe_unretained QONLaunchCompletionHandler completion = nil;
[invocation getArgument:&completion atIndex:3];
completion([QONLaunchResult new], nil);
});

_manager.launchingFinished = YES;
_manager.identityInProgress = YES;

__block BOOL stableDuringUserTransition = YES;
OCMStub([_mockRemoteConfigManager userHasBeenChangedToUserID:mergedUid]).andDo(^(NSInvocation *invocation) {
stableDuringUserTransition = [self.manager isUserStable];
});

[_partialManagerMock processIdentity:identityId];

XCTAssertFalse(stableDuringUserTransition,
@"the new RC scope must replace the old scope before callers can observe a stable identity");
XCTAssertTrue([_manager isUserStable]);
}

/*
* Same-uid case: the user "identifies" with what is effectively the same
* Qonversion uid (e.g. host app calls identify again with no real switch).
Expand Down Expand Up @@ -173,6 +202,7 @@ - (void)testProcessIdentity_IdentityError_DoesNotResetCacheOrRelaunch {
OCMReject([_partialManagerMock resetActualPermissionsCache]);
OCMReject([_partialManagerMock launchWithTrigger:QONRequestTriggerIdentify completion:[OCMArg any]]);
OCMReject([_mockRemoteConfigManager userHasBeenChanged]);
OCMReject([_mockRemoteConfigManager userHasBeenChangedToUserID:[OCMArg any]]);
OCMReject([_mockClient setUserID:[OCMArg any]]);

// When
Expand Down
Loading
Loading