Skip to content

test: [SDK-5733] fix flaky IAM, user switch, and FileLogStore unit tests - #1805

Merged
fadi-george merged 4 commits into
mainfrom
fadi/sdk-5733
Nov 10, 2026
Merged

fadi-george merged 4 commits into
mainfrom
fadi/sdk-5733

Conversation

@fadi-george

@fadi-george fadi-george commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Description

One Line Summary

Fix five flaky iOS unit tests that failed on CI across unrelated branches.

Details

Motivation

These tests failed intermittently on CI and blocked unrelated PRs (e.g. #1803):

  • EarlyTriggerTrackingTests: a message displayed at the end of one test built its web view on the main queue during the next test. On a cold CI simulator that held the main thread for seconds, so the next test's observer callbacks (delivered via dispatch_async to main) missed the 5s wait and the IAM fetch never ran. Confirmed from the CI xcresult logs.
  • SwitchUserIntegrationTests.testAnonUser_thenIdentifyUserWithConflict_thenLogin_...: relied on a 300ms poll interval to batch each user's tag and language into one request. A poll landing between addTag and setLanguage split them and failed the single-request assertion.
  • FileLogStoreRecordDatingTests: the first file-store calls in the test process stalled for about 37s on CI, so whichever tests ran first hit their 5s waits. Confirmed from the CI xcresult; the exact cold-start source (likely first Kotlin runtime use) is not pinned down.
  • IamFetchIdentityVerificationTests.testAFetchIsHeldRatherThanSentUnsignedWhenTheUserHasNoToken: the login helper returned once the mock client looked idle, but the IAM fetch the new subscription id starts can reach the client after that. The test then invalidated token-a, and the late fetch landed signed with token-a instead of the expected token-b. Seen on this PR's first CI run.
  • CustomEventsIntegrationTests.testTrackEvent_withArrayProperties_sendsCorrectly: read executedRequests.first as the custom events request, but a late Fetch User from an earlier test reached the shared mock client first. Seen on this PR's second CI run.

Scope

Test-only changes:

  • EarlyTriggerTrackingTests: pause in-app messaging in these tests via a startPausedController() helper. The isTriggerChanged flags are set before the pause check, so what the tests check is unchanged. Force unwraps replaced with XCTUnwrap so a failure doesn't crash and restart the whole suite.
  • SwitchUserIntegrationTests: start, wait for the anonymous user to be created, pause the operation repo while queuing updates, then unpause and flush. This matches the sibling tests and drops the 300ms interval that leaked into later tests.
  • IamFetchIdentityVerificationTests: login also waits for the fetch it triggers to be answered or held.
  • Custom events tests: new MockOneSignalClient.firstExecutedRequest(ofType:) picks the request by type instead of position (7 call sites).
  • FileLogStoreTestCase: class-level warm-up (up to 120s) absorbs the one-off cold start so per-test 5s timeouts stay meaningful. If the warm-up times out, each test fails in setup with a clear error.

Not included: UserJwtLifecycleTests flakes. Every failing run predates #1794 (123cbea13), which already fixed that race, and there have been no failures since.

Testing

Unit testing

On a temporary iOS 26.5 simulator, the same runtime CI uses:

  • The changed test classes passed repeated runs (-test-iterations 3 to 10).
  • The full UnitTestApp_TestPlan_Reduced passed (664 tests).

Because these flakes only show up under CI load, passing locally doesn't prove they're gone.

Manual testing

Not applicable; test-only change.

Affected code checklist

  • Notifications
    • Display
    • Open
    • Push Processing
    • Confirm Deliveries
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes

Checklist

Overview

  • I have filled out all REQUIRED sections above
  • PR does one thing
  • Any Public API changes are explained in the PR details and conform to existing APIs

Testing

  • I have included test coverage for these changes, or explained why they are not needed
  • All automated tests pass, or I explained why that is not possible
  • I have personally tested this on my device, or explained why that is not possible

Final pass

  • Code is as readable as possible.
  • I have reviewed this PR myself, ensuring it meets each checklist item

🤖 Generated with Claude Code

- EarlyTriggerTrackingTests: pause IAM display so a prior test's queued
  web view build cannot stall main-queue observer callbacks.
- SwitchUserIntegrationTests: pause the operation repo while queuing
  updates instead of relying on a 300ms poll to batch them.
- FileLogStore suites: absorb the process's one-off cold start in a
  class-level warm-up so per-test 5s waits stay meaningful.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fadi-george
fadi-george requested review from abdulraqeeb33 and nan-li and removed request for nan-li November 10, 2026 18:17

@nan-li nan-li left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approving since as test only and it is an improvement

One non-blocking note on the SwitchUser test. The pause is not exclusive: OSUserExecutor.swift:538 sets operationRepo.paused = false on every successful Create User, and login(A) produces one about 100ms after the call once the 409 turns into a create. If the test thread is still queuing past that point, a 100ms poll can land between addTag and setLanguage and split the request. The window is much smaller than before and the two sibling tests share it, so this is fine to land as is. If the test flakes again, that unpause is the first place to look.

@abdulraqeeb33 abdulraqeeb33 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pause still sets isTriggerChanged before display is skipped, and the user-switch test now matches the sibling pause-and-flush. Two nits, neither blocks.

@abdulraqeeb33 abdulraqeeb33 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. The two nits on the earlier comment are non-blocking.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fadi-george

Copy link
Copy Markdown
Collaborator Author

@nan-li thanks, confirmed OSUserExecutor.swift:538 unpauses on create success. Leaving as is like the siblings; will look there first if it flakes again.

fadi-george and others added 2 commits November 10, 2026 11:22
…rificationTests

The login helper returned once the client looked idle, but the fetch the
new subscription id starts can reach the client after that. A test that
then invalidated the token could see that late fetch signed with the old
token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A late Fetch User from an earlier test can reach the shared mock client
first, so executedRequests.first is not always the custom events request.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fadi-george
fadi-george merged commit 99374b8 into main Nov 10, 2026
6 checks passed
@fadi-george
fadi-george deleted the fadi/sdk-5733 branch November 10, 2026 19:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants