Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -338,24 +338,25 @@ static void shutdownBackgroundExecutor() {
* Used internally after initialization completes
*/
private static void shutdownBackgroundExecutorAsync() {
// Schedule shutdown on a separate thread to avoid blocking the executor thread
// Capture the current executor reference so the shutdown thread only shuts down
// THIS executor, not a replacement created by resetBackgroundInitializationState().
final ExecutorService executorToShutdown = backgroundExecutor;
if (executorToShutdown == null || executorToShutdown.isShutdown()) {
return;
}
new Thread(() -> {
synchronized (initLock) {
if (backgroundExecutor != null && !backgroundExecutor.isShutdown()) {
backgroundExecutor.shutdown();
try {
if (!backgroundExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
IterableLogger.w(TAG, "Background executor did not terminate gracefully, forcing shutdown");
backgroundExecutor.shutdownNow();
}
} catch (InterruptedException e) {
IterableLogger.w(TAG, "Interrupted while waiting for executor termination");
backgroundExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
IterableLogger.d(TAG, "Background executor shutdown completed");
try {
executorToShutdown.shutdown();
if (!executorToShutdown.awaitTermination(5, TimeUnit.SECONDS)) {
IterableLogger.w(TAG, "Background executor did not terminate gracefully, forcing shutdown");
executorToShutdown.shutdownNow();
}
} catch (InterruptedException e) {
IterableLogger.w(TAG, "Interrupted while waiting for executor termination");
executorToShutdown.shutdownNow();
Thread.currentThread().interrupt();
}
IterableLogger.d(TAG, "Background executor shutdown completed");
}, "IterableExecutorShutdown").start();
}

Expand Down Expand Up @@ -413,10 +414,13 @@ static void resetBackgroundInitializationState() {
pendingCallbacks.clear();
callbackManager.reset();

// Recreate executor if it was shut down
if (backgroundExecutor == null || backgroundExecutor.isShutdown()) {
backgroundExecutor = createExecutor();
// Always create a fresh executor. The old one may have a pending
// shutdownBackgroundExecutorAsync that hasn't run yet — if we kept it,
// the async shutdown would kill the executor under the next test.
if (backgroundExecutor != null && !backgroundExecutor.isShutdown()) {
backgroundExecutor.shutdownNow();
}
backgroundExecutor = createExecutor();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private void reInitIterableApi() {
authHandler = mock(IterableAuthHandler.class);
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testRefreshToken() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand Down Expand Up @@ -95,7 +95,7 @@ public void testRefreshToken() throws Exception {
timer = IterableApi.getInstance().getAuthManager().timer;
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testSetEmailWithToken() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand All @@ -119,7 +119,7 @@ public void testSetEmailWithToken() throws Exception {
shadowOf(getMainLooper()).runToEndOfTasks();
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testSetEmailWithTokenExpired() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand All @@ -133,7 +133,7 @@ public void testSetEmailWithTokenExpired() throws Exception {
assertEquals(IterableApi.getInstance().getAuthToken(), expiredJWT);
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testSetUserIdWithToken() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand All @@ -157,7 +157,7 @@ public void testSetUserIdWithToken() throws Exception {
assertEquals(expiredJWT, IterableApi.getInstance().getAuthToken());
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testSameEmailWithNewToken() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand All @@ -181,7 +181,7 @@ public void testSameEmailWithNewToken() throws Exception {
assertEquals(IterableApi.getInstance().getAuthToken(), newJWT);
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testSameUserIdWithNewToken() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand All @@ -200,7 +200,7 @@ public void testSameUserIdWithNewToken() throws Exception {
assertEquals(IterableApi.getInstance().getAuthToken(), newJWT);
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testSetSameEmailAndRemoveToken() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand All @@ -219,7 +219,7 @@ public void testSetSameEmailAndRemoveToken() throws Exception {
assertNull(IterableApi.getInstance().getAuthToken());
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testSetSameUserIdAndRemoveToken() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand Down Expand Up @@ -277,7 +277,7 @@ public void testSetSameUserId() throws Exception {
assertNull(IterableApi.getInstance().getAuthToken());
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testSetSameEmailWithSameToken() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand All @@ -297,7 +297,7 @@ public void testSetSameEmailWithSameToken() throws Exception {
assertEquals(IterableApi.getInstance().getAuthToken(), token);
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testSetSameUserIdWithSameToken() throws Exception {
IterableApi.initialize(getContext(), "apiKey");
Expand Down Expand Up @@ -352,7 +352,7 @@ public void testUserIdLogOut() throws Exception {
assertNull(IterableApi.getInstance().getAuthToken());
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testAuthTokenPresentInRequest() throws Exception {
// server.enqueue(new MockResponse().setResponseCode(200).setBody("{}"));
Expand Down Expand Up @@ -392,7 +392,7 @@ public void testAuthTokenPresentInRequest() throws Exception {
assertEquals(HEADER_SDK_AUTH_FORMAT + newJWT, getMessagesSet2Request.getHeader("Authorization"));
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testAuthFailureReturns401() throws InterruptedException {
doReturn(expiredJWT).when(authHandler).onAuthTokenRequested();
Expand All @@ -418,7 +418,7 @@ public void testAuthFailureReturns401() throws InterruptedException {
assertEquals(IterableApi.getInstance().getAuthToken(), expiredJWT);
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testAuthRequestedOnSetEmail() throws InterruptedException {
doReturn(expiredJWT).when(authHandler).onAuthTokenRequested();
Expand All @@ -433,7 +433,7 @@ public void testAuthRequestedOnSetEmail() throws InterruptedException {

}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testAuthRequestedOnUpdateEmail() throws InterruptedException {
doReturn(expiredJWT).when(authHandler).onAuthTokenRequested();
Expand All @@ -447,7 +447,7 @@ public void testAuthRequestedOnUpdateEmail() throws InterruptedException {
//TODO: Shouldn't the update call also update the authToken in IterableAPI class?
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testAuthRequestedOnSetUserId() throws InterruptedException {
doReturn(expiredJWT).when(authHandler).onAuthTokenRequested();
Expand All @@ -456,7 +456,7 @@ public void testAuthRequestedOnSetUserId() throws InterruptedException {
assertEquals(IterableApi.getInstance().getAuthToken(), expiredJWT);
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testAuthSetToNullOnLogOut() throws InterruptedException {
doReturn(expiredJWT).when(authHandler).onAuthTokenRequested();
Expand All @@ -469,7 +469,7 @@ public void testAuthSetToNullOnLogOut() throws InterruptedException {
assertNull(IterableApi.getInstance().getAuthToken());
}

@Ignore ("Ignoring the JWT Tests")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testRegisterForPushInvokedAfterTokenRefresh() throws InterruptedException {
doReturn(expiredJWT).when(authHandler).onAuthTokenRequested();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,21 @@ private void addResponse(String endPoint) {
dispatcher.enqueueResponse("/" + endPoint, new MockResponse().setResponseCode(200).setBody("{}"));
}

/**
* Takes the next request matching the expected endpoint, skipping any spurious
* in-app sync requests caused by cross-test state leakage.
*/
private RecordedRequest takeRequestWithPath(String expectedEndpoint) throws InterruptedException {
String expectedPath = "/" + expectedEndpoint;
RecordedRequest request;
do {
request = server.takeRequest(1, TimeUnit.SECONDS);
if (request == null) return null;
} while (request.getPath().startsWith("/" + IterableConstants.ENDPOINT_GET_INAPP_MESSAGES)
&& !expectedPath.startsWith("/" + IterableConstants.ENDPOINT_GET_INAPP_MESSAGES));
return request;
}

// all userId tests
@Test
public void testCriteriaNotMetUserIdDefault() throws Exception {
Expand Down Expand Up @@ -844,18 +859,18 @@ public void testCriteriaMetEmailMergeTrue() throws Exception {
triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3);
shadowOf(getMainLooper()).idle();

// check if request was sent to unknown user session endpoint
RecordedRequest unknownSessionRequest = server.takeRequest(1, TimeUnit.SECONDS);
// check if request was sent to unknown user session endpoint (skip any spurious in-app syncs)
RecordedRequest unknownSessionRequest = takeRequestWithPath(IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION);
assertNotNull("Unknown user session request should not be null", unknownSessionRequest);
assertEquals("/" + IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION, unknownSessionRequest.getPath());

// check if request was sent to track purchase endpoint
RecordedRequest purchaseRequest = server.takeRequest(1, TimeUnit.SECONDS);
RecordedRequest purchaseRequest = takeRequestWithPath(IterableConstants.ENDPOINT_TRACK_PURCHASE);
assertNotNull("Purchase request should not be null", purchaseRequest);
assertEquals("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE, purchaseRequest.getPath());

// check if request was sent to getInAppMessages endpoint (triggered by completeUserLogin)
RecordedRequest inAppRequest = server.takeRequest(1, TimeUnit.SECONDS);
RecordedRequest inAppRequest = takeRequestWithPath(IterableConstants.ENDPOINT_GET_INAPP_MESSAGES);
assertNotNull("InApp messages request should be sent", inAppRequest);
assertTrue("InApp messages request path should start with correct endpoint",
inAppRequest.getPath().startsWith("/" + IterableConstants.ENDPOINT_GET_INAPP_MESSAGES));
Expand All @@ -872,7 +887,7 @@ public void testCriteriaMetEmailMergeTrue() throws Exception {
IterableApi.getInstance().setEmail(email, identityResolution);

// check if request was sent to merge endpoint
RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS);
RecordedRequest mergeRequest = takeRequestWithPath(IterableConstants.ENDPOINT_MERGE_USER);
assertNotNull(mergeRequest);
assertEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ public void testPostRequestHeaders() throws Exception {
Assert.assertEquals("fake_key", request.getHeader(IterableConstants.HEADER_API_KEY));
}

@Ignore("Ignoring the JWT related test error")
@Ignore("Blocked: IterableAuthManager.executor is not injectable - auth token requests run on uncontrollable background thread")
@Test
public void testUpdateEmailRequest() throws Exception {
server.enqueue(new MockResponse().setResponseCode(200).setBody("{}"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public void testUpdateEmailWithUserId() throws Exception {
assertEquals("testUserId", IterableApi.getInstance().getUserId());
}

@Ignore
@Ignore("handleAppLink performs real HTTP redirect - needs MockWebServer to stub the redirect endpoint")
@Test
public void testHandleUniversalLinkRewrite() throws Exception {
IterableUrlHandler urlHandlerMock = mock(IterableUrlHandler.class);
Expand All @@ -262,6 +262,9 @@ public void testHandleUniversalLinkRewrite() throws Exception {
@Test
public void testSetEmailWithAutomaticPushRegistration() throws Exception {
IterableApi.initialize(getContext(), "fake_key", new IterableConfig.Builder().setPushIntegrationName("pushIntegration").setAutoPushRegistration(true).build());
// Flush any pending looper callbacks from initialize, then reset mock
shadowOf(getMainLooper()).idle();
Mockito.reset(IterablePushRegistration.instance);

// Check that setEmail calls registerForPush
IterableApi.getInstance().setEmail("test@email.com");
Expand Down Expand Up @@ -290,6 +293,8 @@ public void testSetEmailWithoutAutomaticPushRegistration() throws Exception {
@Test
public void testSetUserIdWithAutomaticPushRegistration() throws Exception {
IterableApi.initialize(getContext(), "fake_key", new IterableConfig.Builder().setPushIntegrationName("pushIntegration").setAutoPushRegistration(true).build());
// Reset after initialize since it may trigger push registration via background init
Mockito.reset(IterablePushRegistration.instance);

// Check that setUserId calls registerForPush
IterableApi.getInstance().setUserId("userId");
Expand Down Expand Up @@ -423,7 +428,7 @@ public void testInAppResetOnLogout() throws Exception {
verify(IterableApi.sharedInstance.getInAppManager(), times(2)).reset();
}

@Ignore("Ignoring this test as it fails on CI for some reason")
@Ignore("Fails on CI: likely IterableTaskStorage singleton state leakage between tests - needs investigation")
@Test
public void databaseClearOnLogout() throws Exception {
IterableTaskStorage taskStorage = IterableTaskStorage.sharedInstance(getContext());
Expand Down
Loading
Loading