Skip to content
Merged
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 @@ -211,16 +211,32 @@ public boolean readyForEvent(@NonNull final Event event) {
}

if (isAppendUrlEvent(event) || isGetUrlVarsEvent(event)) {
SharedStateResult configState =
getApi().getSharedState(
IdentityConstants.EventDataKeys.Configuration.MODULE_NAME,
event,
false,
SharedStateResolution.LAST_SET);
if (configState == null || configState.getStatus() != SharedStateStatus.SET) {
Log.trace(
IdentityConstants.LOG_TAG,
LOG_SOURCE,
"Waiting for the Configuration shared state to be set before processing"
+ " [event: %s].",
event.getName());
return false;
}

// analytics shared state will be null if analytics extension is not registered. Wait
// for analytics shared only if the status is pending or none
SharedStateResult sharedStateResult =
// for analytics shared state only if the extension is registered and the status is not
// set.
SharedStateResult analyticsState =
getApi().getSharedState(
IdentityConstants.EventDataKeys.Analytics.MODULE_NAME,
event,
false,
SharedStateResolution.LAST_SET);
if (sharedStateResult != null
&& sharedStateResult.getStatus() != SharedStateStatus.SET) {
if (analyticsState != null && analyticsState.getStatus() != SharedStateStatus.SET) {
Log.trace(
IdentityConstants.LOG_TAG,
LOG_SOURCE,
Expand All @@ -231,18 +247,7 @@ public boolean readyForEvent(@NonNull final Event event) {
}
}

if (!hasValidSharedState(
IdentityConstants.EventDataKeys.Configuration.MODULE_NAME, event)) {
Log.trace(
IdentityConstants.LOG_TAG,
LOG_SOURCE,
"Waiting for the Configuration shared state to get required configuration"
+ " fields before processing [event: %s].",
event.getName());
return false;
} else {
return true;
}
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

The check for a valid Configuration is still needed for the block above (lines 213-232) for appendToUrl and getUrlVariables. The logic for that block relied on the Configuration state check if the block evaluated to true and did not return false due to the Analytics state check. If the current Configuration state is not set then appendToUrl/getUrlVariables won't necessarily fail, but they may not include the org ID in their responses.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can add an explicit configuration check but the behaviour now is same as before. The forceSyncIdentifiers call only succeeds if the SDK has a valid configuration that includes an org-id. Right now, we only check the shared state in the context of the specific event, and the call is assumed to always pass if forceSyncIdentifiers succeeds. In this particular case, we had a valid (but empty) configuration, which will allow the call to proceed, but the org-id was still missing.

I think we should reconsider the broader approach of how we validate states in readyForEvent.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, I see your point. Adding this check back for configuration is just checking the configuration wasn't cleared which isn't really possible, so it's not necessary. Sorry for the confusion, you can roll back your last commit if you want.

}

@VisibleForTesting
Expand Down Expand Up @@ -307,17 +312,6 @@ boolean forceSyncIdentifiers(@NonNull final Event event) {
return hasSynced;
}

private boolean hasValidSharedState(final String extensionName, final Event event) {
SharedStateResult sharedStateResult =
getApi().getSharedState(
extensionName, event, false, SharedStateResolution.LAST_SET);
if (sharedStateResult == null || sharedStateResult.getStatus() != SharedStateStatus.SET) {
return false;
}
Map<String, Object> sharedStateValue = sharedStateResult.getValue();
return !MapUtils.isNullOrEmpty(sharedStateValue);
}

private void boot() {
loadVariablesFromPersistentData();
deleteDeprecatedV5HitDatabase();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,22 @@ class IdentityExtensionTests {
)
}

@Test
fun `readyForEvent() should not check for configuration shared state after forceSync for unrelated events`() {
val identityExtension = initializeSpiedIdentityExtension()
identityExtension.onRegistered()
identityExtension.setHasSynced(true)

val result = identityExtension.readyForEvent(
Event.Builder("event", "type", "source").build()
)
assertTrue(result)

// Verify that getSharedState is never called during readyForEvent()
Mockito.verify(mockedExtensionApi, never())
.getSharedState(any(), anyOrNull(), any(), any())
}

@Test
fun `readyForEvent() should return false if configuration is not registered`() {
val identityExtension = initializeSpiedIdentityExtension()
Expand Down Expand Up @@ -194,6 +210,30 @@ class IdentityExtensionTests {
)
}

@Test
fun `readyForEvent() should return false for appendUrl and urlVars events if Configuration shared state is pending`() {
val identityExtension = initializeSpiedIdentityExtension()
identityExtension.onRegistered()
identityExtension.setHasSynced(true)
Mockito.`when`(
mockedExtensionApi.getSharedState(any(), anyOrNull(), any(), any())
).thenAnswer { invocation ->
val extension = invocation.arguments[0] as? String
if ("com.adobe.module.configuration" === extension) {
return@thenAnswer SharedStateResult(
SharedStateStatus.PENDING,
null
)
}
if ("com.adobe.module.analytics" === extension) {
return@thenAnswer null
}
return@thenAnswer null
}
assertFalse(identityExtension.readyForEvent(appendToUrlEvent))
assertFalse(identityExtension.readyForEvent(urlVariablesEvent))
}

@Test
fun `readyForEvent() should return true for appendUrl and urlVars events if Analytics extension is not registered`() {
val identityExtension = initializeSpiedIdentityExtension()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,56 @@ class IdentityIntegrationTests {
assertTrue("Timeout waiting for force sync network request after updating configuration with valid org id.", countDownLatchSecondNetworkMonitor.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS))
}

@Test(timeout = 10_000)
fun testIdentityBehavior_whenInitialConfigurationSharedStateChange_isEmptyDictionary() {
//PLATIR-52517
var countDownLatchNetworkMonitor = CountDownLatch(1)
networkMonitor = { url ->
if (url.contains("https://test.com/id")) {
countDownLatchNetworkMonitor.countDown()
}
}

// This publishes the first configuration shared state as empty dictionary.
MobileCore.updateConfiguration(
emptyMap()
)
val configurationLatch = CountDownLatch(1)
configurationAwareness { configurationLatch.countDown() }
configurationLatch.await()

// No sync request is sent on empty configuration
assertFalse(countDownLatchNetworkMonitor.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS))

// Verify ECID retrieval fails when Configuration Shared State is `{}` (empty).
val result = getExperienceCouldId(TEST_TIMEOUT)
assertFalse(result.first)

countDownLatchNetworkMonitor = CountDownLatch(1)
networkMonitor = { url ->
if (url.contains("https://test.com/id")) {
assertTrue(url.contains("d_orgid=orgid"))
assertTrue(url.contains("d_mid="))
countDownLatchNetworkMonitor.countDown()
}
}

// Set valid configuration
MobileCore.updateConfiguration(
mapOf(
"experienceCloud.org" to "orgid",
"experienceCloud.server" to "test.com",
"global.privacy" to "optedin"
)
)

// Sync request should be sent now that the required configuration keys are present
assertTrue(countDownLatchNetworkMonitor.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS))

// ECID retrieval should succeed as Identity extension has generated ECID.
assertEquals(Pair(true, loadStoreMid()), getExperienceCouldId(TEST_TIMEOUT))
}

@Test(timeout = TEST_TIMEOUT)
fun testOptedout() {
val countDownLatch = CountDownLatch(1)
Expand Down Expand Up @@ -619,4 +669,29 @@ class IdentityIntegrationTests {
MonitorExtension.configurationAwareness(callback)
}

private fun getExperienceCouldId(timeoutMillis: Long = 1000): Pair<Boolean, String?> {
val latch = CountDownLatch(1)
var ecid: String? = null
var success = false

Identity.getExperienceCloudId(object : AdobeCallbackWithError<String> {
override fun call(value: String?) {
ecid = value
success = true
latch.countDown()
}

override fun fail(error: AdobeError?) {
success = false
latch.countDown()
}
})

return if (latch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
Pair(success, ecid)
} else {
Pair(false, null)
}
}

}
Loading