diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java index 0f25b4443..7623fa09d 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java @@ -619,7 +619,27 @@ protected void registerDeviceToken(@Nullable String email, @Nullable String user IterableLogger.e(TAG, "registerDeviceToken: applicationName is null, check that pushIntegrationName is set in IterableConfig"); } - apiClient.registerDeviceToken(email, userId, authToken, applicationName, deviceToken, dataFields, deviceAttributes, _setUserSuccessCallbackHandler, _setUserFailureCallbackHandler); + // Create a wrapper success handler that tracks consent before calling the original success handler + IterableHelper.SuccessHandler wrappedSuccessHandler = getSuccessHandler(); + + apiClient.registerDeviceToken(email, userId, authToken, applicationName, deviceToken, dataFields, deviceAttributes, wrappedSuccessHandler, _setUserFailureCallbackHandler); + } + + private IterableHelper.SuccessHandler getSuccessHandler() { + IterableHelper.SuccessHandler wrappedSuccessHandler = null; + if (_setUserSuccessCallbackHandler != null || (config.enableUnknownUserActivation && getVisitorUsageTracked() && config.identityResolution.getReplayOnVisitorToKnown())) { + final IterableHelper.SuccessHandler originalSuccessHandler = _setUserSuccessCallbackHandler; + wrappedSuccessHandler = data -> { + // Track consent now that user has been created/updated via device registration + trackConsentOnDeviceRegistration(); + + // Call the original success handler if it exists + if (originalSuccessHandler != null) { + originalSuccessHandler.onSuccess(data); + } + }; + } + return wrappedSuccessHandler; } //endregion @@ -825,6 +845,7 @@ public void setEmail(@Nullable String email, @Nullable String authToken, @Nullab if (email == null) { unknownUserManager.setCriteriaMatched(false); + setConsentLogged(false); } _setUserSuccessCallbackHandler = successHandler; @@ -893,6 +914,7 @@ public void setUserId(@Nullable String userId, @Nullable String authToken, @Null if (userId == null) { unknownUserManager.setCriteriaMatched(false); + setConsentLogged(false); } _setUserSuccessCallbackHandler = successHandler; @@ -919,10 +941,6 @@ private void attemptMergeAndEventReplay(@Nullable String emailOrUserId, boolean if (replay && (_userId != null || _email != null)) { unknownUserManager.syncEventsAndUserUpdate(); - - if (_userIdUnknown == null) { - trackConsentForUser(isEmail ? emailOrUserId : null, isEmail ? null : emailOrUserId, true); - } } if (!isUnknown) { @@ -1484,6 +1502,41 @@ public boolean getVisitorUsageTracked() { return sharedPreferences.getBoolean(IterableConstants.SHARED_PREFS_VISITOR_USAGE_TRACKED, false); } + private boolean getConsentLogged() { + if (_applicationContext == null) { + return false; + } + SharedPreferences sharedPreferences = _applicationContext.getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE); + return sharedPreferences.getBoolean(IterableConstants.SHARED_PREFS_CONSENT_LOGGED, false); + } + + private void setConsentLogged(boolean consentLogged) { + if (_applicationContext == null) { + return; + } + SharedPreferences sharedPref = _applicationContext.getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPref.edit(); + if (consentLogged) { + editor.putBoolean(IterableConstants.SHARED_PREFS_CONSENT_LOGGED, true); + } else { + editor.remove(IterableConstants.SHARED_PREFS_CONSENT_LOGGED); + } + editor.apply(); + } + + /** + * Tracks consent during device registration if conditions are met. + */ + private void trackConsentOnDeviceRegistration() { + if (config.enableUnknownUserActivation && getVisitorUsageTracked() && config.identityResolution.getReplayOnVisitorToKnown()) { + if (!getConsentLogged()) { + boolean isUserKnown = (_userIdUnknown == null); + trackConsentForUser(_email, _userId, isUserKnown); + setConsentLogged(true); + } + } + } + /** * Tracks user consent with the timestamp from when visitor usage was first tracked. * This should be called when transitioning from unknown to known user. diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java index 4272d78f6..85c4b7066 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableConstants.java @@ -143,6 +143,7 @@ public final class IterableConstants { public static final String SHARED_PREFS_VISITOR_USAGE_TRACKED = "itbl_visitor_usage_track"; public static final String SHARED_PREFS_VISITOR_USAGE_TRACKED_TIME = "itbl_visitor_usage_track_time"; public static final String SHARED_PREFS_DEVICE_NOTIFICATIONS_ENABLED = "itbl_notifications_enabled"; + public static final String SHARED_PREFS_CONSENT_LOGGED = "itbl_consent_logged"; //Action buttons public static final String ITBL_BUTTON_IDENTIFIER = "identifier"; diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/UnknownUserManager.java b/iterableapi/src/main/java/com/iterable/iterableapi/UnknownUserManager.java index ee3b1fc7c..c977e048f 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/UnknownUserManager.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/UnknownUserManager.java @@ -243,7 +243,6 @@ private void createUnknownUser(String criteriaId) { IterableApi.getInstance().config.iterableUnknownUserHandler.onUnknownUserCreated(userId); } IterableApi.getInstance().setUnknownUser(userId); - IterableApi.getInstance().trackConsentForUser(null, userId, false); }, (reason, data) -> handleTrackFailure(data)); } diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiMergeUserEmailTests.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiMergeUserEmailTests.java index 5183ed612..8c37c6bdf 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiMergeUserEmailTests.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiMergeUserEmailTests.java @@ -2,7 +2,6 @@ import static android.os.Looper.getMainLooper; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -15,7 +14,6 @@ import com.iterable.iterableapi.unit.PathBasedQueueDispatcher; import org.json.JSONException; -import org.json.JSONObject; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -213,16 +211,10 @@ public void testCriteriaNotMetUserIdDefault() throws Exception { assertNotNull(purchaseRequest2); assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE), purchaseRequest2.getPath()); - // check that request was not sent to merge endpoint and was sent to the consent tracking endpoint - RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull(consentRequest); - assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), consentRequest.getPath()); - assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_CONSENT), consentRequest.getPath()); - - // verify track consent request body contains proper user ID and isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertEquals(userId, consentRequestJson.getString(IterableConstants.KEY_USER_ID)); - assertTrue(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); + // check that request was not sent to merge endpoint + RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS); + assertNotNull(request); + assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), request.getPath()); // check that user id was set assertEquals(userId, IterableApi.getInstance().getUserId()); @@ -258,16 +250,10 @@ public void testCriteriaNotMetUserIdReplayTrueMergeFalse() throws Exception { assertNotNull(purchaseRequest2); assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE), purchaseRequest2.getPath()); - // check that request was not sent to merge endpoint and was sent to the consent tracking endpoint + // check that request was not sent to merge endpoint RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); assertNotNull(consentRequest); assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), consentRequest.getPath()); - assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_CONSENT), consentRequest.getPath()); - - // verify track consent request body contains proper user ID and isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertEquals(userId, consentRequestJson.getString(IterableConstants.KEY_USER_ID)); - assertTrue(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); // check that user id was set assertEquals(userId, IterableApi.getInstance().getUserId()); @@ -352,7 +338,6 @@ public void testCriteriaMetUserIdDefault() throws Exception { addResponse(IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION); addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE); addResponse(IterableConstants.ENDPOINT_GET_INAPP_MESSAGES); - addResponse(IterableConstants.ENDPOINT_TRACK_CONSENT); // trigger track purchase event triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3); @@ -374,15 +359,6 @@ public void testCriteriaMetUserIdDefault() throws Exception { assertTrue("InApp messages request path should start with correct endpoint", inAppRequest.getPath().startsWith("/" + IterableConstants.ENDPOINT_GET_INAPP_MESSAGES)); - // check if request was sent to track consent endpoint - RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull("Consent tracking request should be sent", consentRequest); - assertEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, consentRequest.getPath()); - - // verify track consent request body contains proper isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertFalse(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); - // clear any pending requests while (server.takeRequest(1, TimeUnit.SECONDS) != null) { } @@ -395,11 +371,6 @@ public void testCriteriaMetUserIdDefault() throws Exception { assertNotNull(mergeRequest); assertEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath()); - // check that a second consent tracking request was not sent - RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull(request); - assertNotEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, request.getPath()); - // check that user id was set assertEquals(userId, IterableApi.getInstance().getUserId()); } @@ -413,7 +384,6 @@ public void testCriteriaMetUserIdMergeFalse() throws Exception { addResponse(IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION); addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE); addResponse(IterableConstants.ENDPOINT_GET_INAPP_MESSAGES); - addResponse(IterableConstants.ENDPOINT_TRACK_CONSENT); // trigger track purchase event triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3); @@ -435,15 +405,6 @@ public void testCriteriaMetUserIdMergeFalse() throws Exception { assertTrue("InApp messages request path should start with correct endpoint", inAppRequest.getPath().startsWith("/" + IterableConstants.ENDPOINT_GET_INAPP_MESSAGES)); - // check if request was sent to track consent endpoint - RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull("Consent tracking request should be sent", consentRequest); - assertEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, consentRequest.getPath()); - - // verify track consent request body contains proper isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertFalse(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); - // clear any pending requests while (server.takeRequest(1, TimeUnit.SECONDS) != null) { } @@ -452,11 +413,10 @@ public void testCriteriaMetUserIdMergeFalse() throws Exception { IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, false); IterableApi.getInstance().setUserId(userId, identityResolution); - // check that request was not sent to merge endpoint or consent tracking endpoint + // check that request was not sent to merge endpoint RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS); assertNotNull(mergeRequest); assertNotEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath()); - assertNotEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, mergeRequest.getPath()); // check that user id was set assertEquals(userId, IterableApi.getInstance().getUserId()); @@ -471,7 +431,6 @@ public void testCriteriaMetUserIdMergeTrue() throws Exception { addResponse(IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION); addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE); addResponse(IterableConstants.ENDPOINT_GET_INAPP_MESSAGES); - addResponse(IterableConstants.ENDPOINT_TRACK_CONSENT); // trigger track purchase event triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3); @@ -493,15 +452,6 @@ public void testCriteriaMetUserIdMergeTrue() throws Exception { assertTrue("InApp messages request path should start with correct endpoint", inAppRequest.getPath().startsWith("/" + IterableConstants.ENDPOINT_GET_INAPP_MESSAGES)); - // check if request was sent to track consent endpoint - RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull("Consent tracking request should be sent", consentRequest); - assertEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, consentRequest.getPath()); - - // verify track consent request body contains proper isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertFalse(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); - // clear any pending requests while (server.takeRequest(1, TimeUnit.SECONDS) != null) { } @@ -515,11 +465,6 @@ public void testCriteriaMetUserIdMergeTrue() throws Exception { assertNotNull(mergeRequest); assertEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath()); - // check that a second consent tracking request was not sent - RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull(request); - assertNotEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, request.getPath()); - // check that user id was set assertEquals(userId, IterableApi.getInstance().getUserId()); } @@ -646,16 +591,11 @@ public void testCriteriaNotMetEmailDefault() throws Exception { assertNotNull(purchaseRequest2); assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE), purchaseRequest2.getPath()); - // check that request was not sent to merge endpoint and sent to consent tracking endpoint + // check that request was not sent to merge endpoint RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); assertNotNull(consentRequest); assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), consentRequest.getPath()); - assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_CONSENT), consentRequest.getPath()); - // verify track consent request body contains proper email and isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertEquals(email, consentRequestJson.getString(IterableConstants.KEY_EMAIL)); - assertTrue(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); // check that email was set assertEquals(email, IterableApi.getInstance().getEmail()); @@ -698,16 +638,10 @@ public void testCriteriaNotMetEmailReplayTrueMergeFalse() throws Exception { assertNotNull(purchaseRequest2); assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_PURCHASE), purchaseRequest2.getPath()); - // check that request was not sent to merge endpoint and sent to consent tracking endpoint + // check that request was not sent to merge endpoint RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); assertNotNull(consentRequest); assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), consentRequest.getPath()); - assertEquals(("/" + IterableConstants.ENDPOINT_TRACK_CONSENT), consentRequest.getPath()); - - // verify track consent request body contains proper email and isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertEquals(email, consentRequestJson.getString(IterableConstants.KEY_EMAIL)); - assertTrue(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); // check that email was set assertEquals(email, IterableApi.getInstance().getEmail()); @@ -806,7 +740,6 @@ public void testCriteriaMetEmailDefault() throws Exception { addResponse(IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION); addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE); addResponse(IterableConstants.ENDPOINT_GET_INAPP_MESSAGES); - addResponse(IterableConstants.ENDPOINT_TRACK_CONSENT); // trigger track purchase event triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3); @@ -828,15 +761,6 @@ public void testCriteriaMetEmailDefault() throws Exception { assertTrue("InApp messages request path should start with correct endpoint", inAppRequest.getPath().startsWith("/" + IterableConstants.ENDPOINT_GET_INAPP_MESSAGES)); - // check if request was sent to track consent endpoint - RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull("Consent tracking request should be sent", consentRequest); - assertEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, consentRequest.getPath()); - - // verify track consent request body contains proper isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertFalse(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); - // clear any pending requests while (server.takeRequest(1, TimeUnit.SECONDS) != null) { } @@ -852,11 +776,6 @@ public void testCriteriaMetEmailDefault() throws Exception { assertNotNull(mergeRequest); assertEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath()); - // check that a second consent tracking request was not sent - RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull(request); - assertNotEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, request.getPath()); - // check that email was set assertEquals(email, IterableApi.getInstance().getEmail()); } @@ -870,7 +789,6 @@ public void testCriteriaMetEmailMergeFalse() throws Exception { addResponse(IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION); addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE); addResponse(IterableConstants.ENDPOINT_GET_INAPP_MESSAGES); - addResponse(IterableConstants.ENDPOINT_TRACK_CONSENT); // trigger track purchase event triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3); @@ -892,15 +810,6 @@ public void testCriteriaMetEmailMergeFalse() throws Exception { assertTrue("InApp messages request path should start with correct endpoint", inAppRequest.getPath().startsWith("/" + IterableConstants.ENDPOINT_GET_INAPP_MESSAGES)); - // check if request was sent to track consent endpoint - RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull("Consent tracking request should be sent", consentRequest); - assertEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, consentRequest.getPath()); - - // verify track consent request body contains proper isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertFalse(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); - // clear any pending requests while (server.takeRequest(1, TimeUnit.SECONDS) != null) { } @@ -916,7 +825,6 @@ public void testCriteriaMetEmailMergeFalse() throws Exception { RecordedRequest mergeRequest = server.takeRequest(1, TimeUnit.SECONDS); assertNotNull(mergeRequest); assertNotEquals(("/" + IterableConstants.ENDPOINT_MERGE_USER), mergeRequest.getPath()); - assertNotEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, mergeRequest.getPath()); // check that email was set assertEquals(email, IterableApi.getInstance().getEmail()); @@ -931,7 +839,6 @@ public void testCriteriaMetEmailMergeTrue() throws Exception { addResponse(IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION); addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE); addResponse(IterableConstants.ENDPOINT_GET_INAPP_MESSAGES); - addResponse(IterableConstants.ENDPOINT_TRACK_CONSENT); // trigger track purchase event triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3); @@ -953,15 +860,6 @@ public void testCriteriaMetEmailMergeTrue() throws Exception { assertTrue("InApp messages request path should start with correct endpoint", inAppRequest.getPath().startsWith("/" + IterableConstants.ENDPOINT_GET_INAPP_MESSAGES)); - // check if request was sent to track consent endpoint - RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull("Consent tracking request should be sent", consentRequest); - assertEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, consentRequest.getPath()); - - // verify track consent request body contains proper isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertFalse(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); - // clear any pending requests while (server.takeRequest(1, TimeUnit.SECONDS) != null) { } @@ -978,11 +876,6 @@ public void testCriteriaMetEmailMergeTrue() throws Exception { assertNotNull(mergeRequest); assertEquals("/" + IterableConstants.ENDPOINT_MERGE_USER, mergeRequest.getPath()); - // check that a second consent tracking request was not sent - RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull(request); - assertNotEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, request.getPath()); - // check that email was set assertEquals(email, IterableApi.getInstance().getEmail()); } @@ -1080,7 +973,6 @@ public void testCriteriaMetTwice() throws Exception { addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE); addResponse(IterableConstants.ENDPOINT_TRACK_PURCHASE); addResponse(IterableConstants.ENDPOINT_GET_INAPP_MESSAGES); - addResponse(IterableConstants.ENDPOINT_TRACK_CONSENT); // trigger track purchase event triggerTrackPurchaseEvent("test", "keyboard", 4.67, 3); @@ -1107,14 +999,5 @@ public void testCriteriaMetTwice() throws Exception { 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)); - - // check if request was sent to track consent endpoint - RecordedRequest consentRequest = server.takeRequest(1, TimeUnit.SECONDS); - assertNotNull("Consent tracking request should be sent", consentRequest); - assertEquals("/" + IterableConstants.ENDPOINT_TRACK_CONSENT, consentRequest.getPath()); - - // verify track consent request body contains proper isUserKnown flag - JSONObject consentRequestJson = new JSONObject(consentRequest.getBody().readUtf8()); - assertFalse(consentRequestJson.getBoolean(IterableConstants.KEY_IS_USER_KNOWN)); } } diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java index e466b2536..e9e732910 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableApiTest.java @@ -2,6 +2,8 @@ import com.iterable.iterableapi.util.DeviceInfoUtils; import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; import android.net.Uri; import androidx.annotation.NonNull; @@ -39,6 +41,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -865,4 +868,335 @@ public void testFetchRemoteConfigurationCalledWhenInForeground() throws Exceptio IterableActivityMonitor.instance = new IterableActivityMonitor(); } + //region Consent Logging Tests - Direct Unit Tests + //--------------------------------------------------------------------------------------- + + @Test + public void testTrackConsentForUser_WithValidConditions() throws Exception { + // Setup: Configure for consent logging + IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true); + IterableConfig config = new IterableConfig.Builder() + .setEnableUnknownUserActivation(true) + .setIdentityResolution(identityResolution) + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + + // Set up conditions for consent logging + IterableApi.getInstance().setVisitorUsageTracked(true); + IterableApi.getInstance().setEmail("test@example.com"); + + // Mock the API client to verify trackConsent is called + IterableApiClient mockClient = mock(IterableApiClient.class); + IterableApi.getInstance().apiClient = mockClient; + + // Execute: Call trackConsentForUser directly + IterableApi.getInstance().trackConsentForUser("test@example.com", null, true); + + // Verify: trackConsent was called on the API client + verify(mockClient).trackConsent( + nullable(String.class), + eq("test@example.com"), + any(Long.class), + eq(true) + ); + } + + @Test + public void testTrackConsentForUser_DoesNotCallWhenUnknownUserActivationDisabled() throws Exception { + // Setup: Configure WITHOUT unknown user activation + IterableConfig config = new IterableConfig.Builder() + .setEnableUnknownUserActivation(false) // Disabled! + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + + // Set up other conditions + IterableApi.getInstance().setVisitorUsageTracked(true); + IterableApi.getInstance().setEmail("test@example.com"); + + // Mock the API client + IterableApiClient mockClient = mock(IterableApiClient.class); + IterableApi.getInstance().apiClient = mockClient; + + // Execute: Call trackConsentForUser + IterableApi.getInstance().trackConsentForUser("test@example.com", null, true); + + // Verify: trackConsent was NOT called (unknown user activation disabled) + verify(mockClient, never()).trackConsent(any(), any(), any(Long.class), any(Boolean.class)); + } + + @Test + public void testTrackConsentForUser_DoesNotCallWhenVisitorUsageNotTracked() throws Exception { + // Setup: Configure for consent logging + IterableConfig config = new IterableConfig.Builder() + .setEnableUnknownUserActivation(true) + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + + // Set visitor usage NOT tracked + IterableApi.getInstance().setVisitorUsageTracked(false); + IterableApi.getInstance().setEmail("test@example.com"); + + // Mock the API client + IterableApiClient mockClient = mock(IterableApiClient.class); + IterableApi.getInstance().apiClient = mockClient; + + // Execute: Call trackConsentForUser + IterableApi.getInstance().trackConsentForUser("test@example.com", null, true); + + // Verify: trackConsent was NOT called (visitor usage not tracked) + verify(mockClient, never()).trackConsent(any(), any(), any(Long.class), any(Boolean.class)); + } + + @Test + public void testConsentLoggingConditions_AllTrue() throws Exception { + // Setup: All conditions should be true + IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true); + IterableConfig config = new IterableConfig.Builder() + .setEnableUnknownUserActivation(true) + .setIdentityResolution(identityResolution) + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + IterableApi.getInstance().setVisitorUsageTracked(true); + + // Clear consent logged flag + SharedPreferences sharedPref = getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPref.edit(); + editor.remove(IterableConstants.SHARED_PREFS_CONSENT_LOGGED); + editor.apply(); + + IterableApi api = IterableApi.getInstance(); + + // Verify all conditions are met + assertTrue("enableUnknownUserActivation should be true", api.config.enableUnknownUserActivation); + assertTrue("getVisitorUsageTracked should be true", api.getVisitorUsageTracked()); + assertTrue("getReplayOnVisitorToKnown should be true", api.config.identityResolution.getReplayOnVisitorToKnown()); + assertFalse("getConsentLogged should be false", + sharedPref.getBoolean(IterableConstants.SHARED_PREFS_CONSENT_LOGGED, false)); + } + + @Test + public void testConsentLoggingConditions_ConsentAlreadyLogged() throws Exception { + // Setup + IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true); + IterableConfig config = new IterableConfig.Builder() + .setEnableUnknownUserActivation(true) + .setIdentityResolution(identityResolution) + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + IterableApi.getInstance().setVisitorUsageTracked(true); + + // Set consent already logged + SharedPreferences sharedPref = getContext().getSharedPreferences(IterableConstants.SHARED_PREFS_FILE, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = sharedPref.edit(); + editor.putBoolean(IterableConstants.SHARED_PREFS_CONSENT_LOGGED, true); + editor.apply(); + + // Verify consent is already logged + assertTrue("getConsentLogged should be true", + sharedPref.getBoolean(IterableConstants.SHARED_PREFS_CONSENT_LOGGED, false)); + } + + @Test + public void testRegisterDeviceTokenSuccessCallback_CreatesWrappedHandler() throws Exception { + // Setup + IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true); + IterableConfig config = new IterableConfig.Builder() + .setEnableUnknownUserActivation(true) + .setIdentityResolution(identityResolution) + .setAutoPushRegistration(false) + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + IterableApi.getInstance().setVisitorUsageTracked(true); + + // Create a mock success handler + IterableHelper.SuccessHandler originalHandler = mock(IterableHelper.SuccessHandler.class); + + // Set up user with success handler + IterableApi.getInstance().setEmail("test@example.com", originalHandler, null); + + // Spy on the API client to capture the success handler passed to registerDeviceToken + IterableApiClient mockClient = spy(IterableApi.getInstance().apiClient); + IterableApi.getInstance().apiClient = mockClient; + + // Execute: Call registerDeviceToken + IterableApi.getInstance().registerDeviceToken("test_token"); + + // Wait for async thread + Thread.sleep(100); + shadowOf(getMainLooper()).idle(); + + // Verify: registerDeviceToken was called with a success handler + ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableHelper.SuccessHandler.class); + verify(mockClient, timeout(1000)).registerDeviceToken( + eq("test@example.com"), + nullable(String.class), + nullable(String.class), + any(String.class), + eq("test_token"), + nullable(JSONObject.class), + any(), + successCaptor.capture(), + nullable(IterableHelper.FailureHandler.class) + ); + + // The captured handler should not be null (a wrapper was created) + assertNotNull("Success handler should not be null", successCaptor.getValue()); + } + + @Test + public void testRegisterDeviceTokenSuccessCallback_WithoutOriginalHandler() throws Exception { + // Setup + IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true); + IterableConfig config = new IterableConfig.Builder() + .setEnableUnknownUserActivation(true) + .setIdentityResolution(identityResolution) + .setAutoPushRegistration(false) + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + IterableApi.getInstance().setVisitorUsageTracked(true); + IterableApi.getInstance().setUserId("test_user_123"); + + // Spy on the API client to capture the success handler passed to registerDeviceToken + IterableApiClient mockClient = spy(IterableApi.getInstance().apiClient); + IterableApi.getInstance().apiClient = mockClient; + + // Execute: Call registerDeviceToken without setting an explicit success handler + IterableApi.getInstance().registerDeviceToken("test_token"); + + // Wait for async thread + Thread.sleep(100); + shadowOf(getMainLooper()).idle(); + + // Verify: registerDeviceToken was called with a success handler (the wrapper) + ArgumentCaptor successCaptor = ArgumentCaptor.forClass(IterableHelper.SuccessHandler.class); + verify(mockClient, timeout(1000)).registerDeviceToken( + nullable(String.class), + eq("test_user_123"), + nullable(String.class), + any(String.class), + eq("test_token"), + nullable(JSONObject.class), + any(), + successCaptor.capture(), + nullable(IterableHelper.FailureHandler.class) + ); + + // The captured handler should not be null (wrapper was created for consent logging) + assertNotNull("Success handler should not be null", successCaptor.getValue()); + } + + @Test + public void testRegisterDeviceTokenIntegration_ConsentLoggingTriggered() throws Exception { + // Setup: Mock server responses + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); // for registerDeviceToken + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); // for trackConsent + + // Setup: Configure for consent logging + IterableIdentityResolution identityResolution = new IterableIdentityResolution(true, true); + IterableConfig config = new IterableConfig.Builder() + .setEnableUnknownUserActivation(true) + .setIdentityResolution(identityResolution) + .setAutoPushRegistration(false) + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + + // Set up conditions for consent logging + IterableApi.getInstance().setVisitorUsageTracked(true); + + // Create a success handler and set user + IterableHelper.SuccessHandler successHandler = mock(IterableHelper.SuccessHandler.class); + IterableApi.getInstance().setEmail("test@example.com", successHandler, null); + + // Execute: Register device token + IterableApi.getInstance().registerDeviceToken("test_token"); + + // Wait for async operations + Thread.sleep(200); + shadowOf(getMainLooper()).idle(); + + // Verify: At least 2 requests were made (registerDeviceToken + consent, potentially others for auth) + assertTrue("Should have made at least 2 requests", server.getRequestCount() >= 2); + + // Verify: consent request was made (check all requests for it) + boolean foundRegisterRequest = false; + boolean foundConsentRequest = false; + + for (int i = 0; i < server.getRequestCount(); i++) { + RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS); + if (request != null) { + if (request.getPath().contains("registerDeviceToken")) { + foundRegisterRequest = true; + } else if (request.getPath().contains("unknownuser/consent")) { + foundConsentRequest = true; + } + } + } + + assertTrue("Should have made registerDeviceToken request", foundRegisterRequest); + assertTrue("Should have made consent request", foundConsentRequest); + + // Verify: Original success handler was called at least once + verify(successHandler, atLeastOnce()).onSuccess(any(JSONObject.class)); + } + + @Test + public void testRegisterDeviceTokenIntegration_ConsentLoggingNotTriggeredWhenDisabled() throws Exception { + // Setup: Mock server response + server.enqueue(new MockResponse().setResponseCode(200).setBody("{}")); // for registerDeviceToken only + + // Setup: Configure WITHOUT consent logging (unknown user activation disabled) + IterableConfig config = new IterableConfig.Builder() + .setEnableUnknownUserActivation(false) // Disabled + .setAutoPushRegistration(false) + .build(); + + IterableApi.initialize(getContext(), "apiKey", config); + + // Set up other conditions + IterableApi.getInstance().setVisitorUsageTracked(true); + IterableHelper.SuccessHandler successHandler = mock(IterableHelper.SuccessHandler.class); + IterableApi.getInstance().setEmail("test@example.com", successHandler, null); + + // Execute: Register device token + IterableApi.getInstance().registerDeviceToken("test_token"); + + // Wait for async operations + Thread.sleep(200); + shadowOf(getMainLooper()).idle(); + + // Verify: At least one request was made but no consent + assertTrue("Should have made at least 1 request", server.getRequestCount() >= 1); + + // Verify: No consent request was made (check all requests) + boolean foundRegisterRequest = false; + boolean foundConsentRequest = false; + + for (int i = 0; i < server.getRequestCount(); i++) { + RecordedRequest request = server.takeRequest(1, TimeUnit.SECONDS); + if (request != null) { + if (request.getPath().contains("registerDeviceToken")) { + foundRegisterRequest = true; + } else if (request.getPath().contains("unknownuser/consent")) { + foundConsentRequest = true; + } + } + } + + assertTrue("Should have made registerDeviceToken request", foundRegisterRequest); + assertFalse("Should NOT have made consent request", foundConsentRequest); + + // Verify: Original success handler was called at least once + verify(successHandler, atLeastOnce()).onSuccess(any(JSONObject.class)); + } + + //endregion + }