From 958f76721b177b226d44a8e7c9fa660f544ac30d Mon Sep 17 00:00:00 2001 From: pravin Date: Thu, 11 Mar 2021 09:36:15 -0800 Subject: [PATCH 01/19] [AMSDK-11081] - Rename listener tests --- ...est.java => ListenerGenericIdentityRequestContentTests.java} | 2 +- ...ntityTest.java => ListenerIdentityRequestIdentityTests.java} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/{ListenerGenericIdentityRequestContentTest.java => ListenerGenericIdentityRequestContentTests.java} (98%) rename code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/{ListenerIdentityRequestIdentityTest.java => ListenerIdentityRequestIdentityTests.java} (98%) diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTests.java similarity index 98% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java rename to code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTests.java index a607a779..fd93f91b 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTest.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerGenericIdentityRequestContentTests.java @@ -25,7 +25,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class ListenerGenericIdentityRequestContentTest { +public class ListenerGenericIdentityRequestContentTests { @Mock private IdentityEdgeExtension mockIdentityEdgeExtension; diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java similarity index 98% rename from code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java rename to code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java index 5d89f73f..3034a9e0 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTest.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityRequestIdentityTests.java @@ -25,7 +25,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class ListenerIdentityRequestIdentityTest { +public class ListenerIdentityRequestIdentityTests { @Mock private IdentityEdgeExtension mockIdentityEdgeExtension; From b3d6fc7598dcc21a009013f7b72e217c483693a5 Mon Sep 17 00:00:00 2001 From: pravin Date: Thu, 11 Mar 2021 09:38:05 -0800 Subject: [PATCH 02/19] [AMSDK-11081] - Listeners for remove and update Identity requests + tests --- .../ListenerIdentityEdgeRemoveIdentity.java | 66 +++++++++++++++ .../ListenerIdentityEdgeUpdateIdentity.java | 66 +++++++++++++++ ...stenerIdentityEdgeRemoveIdentityTests.java | 82 +++++++++++++++++++ ...stenerIdentityEdgeUpdateIdentityTests.java | 82 +++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java create mode 100644 code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java new file mode 100644 index 00000000..592f126c --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentity.java @@ -0,0 +1,66 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +public class ListenerIdentityEdgeRemoveIdentity extends ExtensionListener { + + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerIdentityEdgeRemoveIdentity(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + + /** + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and with event source {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY} is dispatched through eventHub. + * + * @param event the remove identity {@link Event} to be processed + */ + @Override + public void hear(final Event event) { + if (event == null || event.getEventData() == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerIdentityEdgeRemoveIdentity"); + return; + } + + final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + "The parent extension, associated with the ListenerIdentityEdgeRemoveIdentity is null, ignoring event."); + return; + } + + parentExtension.handleRemoveIdentity(event); + } + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityEdgeExtension} object registered with the eventHub + */ + IdentityEdgeExtension getIdentityEdgeExtension() { + return (IdentityEdgeExtension) getParentExtension(); + } +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java new file mode 100644 index 00000000..094a2645 --- /dev/null +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentity.java @@ -0,0 +1,66 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.ExtensionListener; +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; + +public class ListenerIdentityEdgeUpdateIdentity extends ExtensionListener { + + /** + * Constructor. + * + * @param extensionApi an instance of {@link ExtensionApi} + * @param type the {@link String} eventType this listener is registered to handle + * @param source the {@link String} eventSource this listener is registered to handle + */ + ListenerIdentityEdgeUpdateIdentity(final ExtensionApi extensionApi, final String type, final String source) { + super(extensionApi, type, source); + } + + + /** + * Method that gets called when event with event type {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and with event source {@link IdentityEdgeConstants.EventSource#UPDATE_IDENTITY} is dispatched through eventHub. + * + * @param event the udpate identity {@link Event} to be processed + */ + @Override + public void hear(final Event event) { + if (event == null || event.getEventData() == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, "Event or Event data is null. Ignoring the event listened by ListenerIdentityEdgeUpdateIdentity"); + return; + } + + final IdentityEdgeExtension parentExtension = getIdentityEdgeExtension(); + + if (parentExtension == null) { + MobileCore.log(LoggingMode.DEBUG, IdentityEdgeConstants.LOG_TAG, + "The parent extension, associated with the ListenerIdentityEdgeUpdateIdentity is null, ignoring event."); + return; + } + + parentExtension.handleUpdateIdentities(event); + } + + /** + * Returns the parent extension associated with the listener. + * + * @return a {@link IdentityEdgeExtension} object registered with the eventHub + */ + IdentityEdgeExtension getIdentityEdgeExtension() { + return (IdentityEdgeExtension) getParentExtension(); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java new file mode 100644 index 00000000..5b105b1d --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeRemoveIdentityTests.java @@ -0,0 +1,82 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerIdentityEdgeRemoveIdentityTests { + + @Mock + private IdentityEdgeExtension mockIdentityEdgeExtension; + + private ListenerIdentityEdgeRemoveIdentity listener; + + @Before + public void setup() { + mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + MobileCore.start(null); + listener = spy(new ListenerIdentityEdgeRemoveIdentity(null, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).build(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(1)).handleRemoveIdentity(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Remove Identity", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).build(); + doReturn(null).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleRemoveIdentity(any(Event.class)); + } + + @Test + public void testHear_WhenEventNull() { + // setup + doReturn(null).when(listener).getIdentityEdgeExtension(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(null); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleRemoveIdentity(any(Event.class)); + } +} diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java new file mode 100644 index 00000000..762a88bf --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/ListenerIdentityEdgeUpdateIdentityTests.java @@ -0,0 +1,82 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.MobileCore; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class ListenerIdentityEdgeUpdateIdentityTests { + + @Mock + private IdentityEdgeExtension mockIdentityEdgeExtension; + + private ListenerIdentityEdgeUpdateIdentity listener; + + @Before + public void setup() { + mockIdentityEdgeExtension = Mockito.mock(IdentityEdgeExtension.class); + MobileCore.start(null); + listener = spy(new ListenerIdentityEdgeUpdateIdentity(null, IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY)); + } + + @Test + public void testHear() { + // setup + Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).build(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(1)).handleUpdateIdentities(event); + } + + @Test + public void testHear_WhenParentExtensionNull() { + // setup + Event event = new Event.Builder("Update Identities", IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).build(); + doReturn(null).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(event); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleUpdateIdentities(any(Event.class)); + } + + @Test + public void testHear_WhenEventNull() { + // setup + doReturn(null).when(listener).getIdentityEdgeExtension(); + doReturn(mockIdentityEdgeExtension).when(listener).getIdentityEdgeExtension(); + + // test + listener.hear(null); + + // verify + verify(mockIdentityEdgeExtension, times(0)).handleUpdateIdentities(any(Event.class)); + } +} From 0e49313500386f914a18845cd30e1d1b59fa0443 Mon Sep 17 00:00:00 2001 From: pravin Date: Thu, 11 Mar 2021 09:40:04 -0800 Subject: [PATCH 03/19] [AMSDK-11081] - UpdateIdentity Public API --- .../mobile/identityedge/IdentityEdge.java | 42 +++++++++++++++--- .../identityedge/IdentityEdgeConstants.java | 4 ++ .../identityedge/IdentityEdgeExtension.java | 17 +++++++- .../identityedge/IdentityEdgeState.java | 3 -- .../mobile/identityedge/IdentityMap.java | 17 ++++++-- .../IdentityEdgeExtensionTests.java | 6 ++- .../identityedge/IdentityEdgeTests.java | 43 +++++++++++++++++++ 7 files changed, 118 insertions(+), 14 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index e4d19166..ee17fe79 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -20,6 +20,8 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; +import java.util.Map; + public class IdentityEdge { private static final String LOG_TAG = "IdentityEdge"; @@ -27,6 +29,7 @@ private IdentityEdge() {} /** * Returns the version of the {@link IdentityEdge} extension + * * @return The version as {@code String} */ public static String extensionVersion() { @@ -48,9 +51,10 @@ public void error(ExtensionError extensionError) { /** * Returns the Experience Cloud ID. An empty string is returned if the Experience Cloud ID was previously cleared. - * @param callback {@link AdobeCallback} of {@link String} invoked with the Experience Cloud ID - * If an {@link AdobeCallbackWithError} is provided, an {@link AdobeError} can be returned in the - * eventuality of any error that occurred while getting the Experience Cloud ID + * + * @param callback {@link AdobeCallback} of {@link String} invoked with the Experience Cloud ID + * If an {@link AdobeCallbackWithError} is provided, an {@link AdobeError} can be returned in the + * eventuality of any error that occurred while getting the Experience Cloud ID */ public static void getExperienceCloudId(final AdobeCallback callback) { if (callback == null) { @@ -97,12 +101,40 @@ public void call(Event responseEvent) { }, errorCallback); } + /** + * Updates the currently known {@link IdentityMap} within the SDK and XDM shared state. + * The IdentityEdge extension will merge the received identifiers with the previously saved one in an additive manner, no identifiers will be removed using this API. + * + * @param identityMap The identifiers to add or update. + */ + public static void updateIdentities(final IdentityMap identityMap) { + if (identityMap == null || identityMap.toObjectMap().isEmpty()) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to updateIdentities, IdentityMap is null or empty"); + return; + } + + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Consents.update() API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, + extensionError.getErrorName())); + } + }; + + + final Event updateIdentitiesEvent = new Event.Builder(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, + IdentityEdgeConstants.EventType.IDENTITY_EDGE, + IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).setEventData(identityMap.asEventData()).build(); + MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); + } + /** * When an {@link AdobeCallbackWithError} is provided, the fail method will be called with provided {@link AdobeError}. + * * @param callback should not be null, should be instance of {@code AdobeCallbackWithError} - * @param error the {@code AdobeError} returned back in the callback + * @param error the {@code AdobeError} returned back in the callback */ - private static void returnError (final AdobeCallback callback, final AdobeError error) { + private static void returnError(final AdobeCallback callback, final AdobeError error) { if (callback == null) { return; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index 78c06085..442acce7 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -23,6 +23,8 @@ final class EventSource { static final String REQUEST_CONTENT = "com.adobe.eventSource.requestContent"; static final String RESPONSE_CONTENT = "com.adobe.eventSource.responseContent"; static final String RESPONSE_IDENTITY = "com.adobe.eventSource.responseIdentity"; + static final String UPDATE_IDENTITY = "com.adobe.eventSource.updateIdentity"; + static final String REMOVE_IDENTITY = "com.adobe.eventSource.removeIdentity"; private EventSource() { } } @@ -36,6 +38,8 @@ private EventType() { } final class EventNames { static final String IDENTITY_REQUEST_IDENTITY_ECID = "Identity Edge Request ECID"; static final String IDENTITY_RESPONSE_CONTENT_ONE_TIME = "Identity Edge Response Content One Time"; + static final String UPDATE_IDENTITIES = "Identity Edge Update Identities"; + static final String REMOVE_IDENTITIES = "Idetity Edge Remove Identities"; private EventNames() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index ee349e6d..81dd3d84 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -36,7 +36,11 @@ class IdentityEdgeExtension extends Extension { *
  • Listener {@link ListenerIdentityRequestIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_IDENTITY}
  • *
  • Listener {@link ListenerGenericIdentityRequestContent} to listen for event with eventType {@link IdentityEdgeConstants.EventType#GENERIC_IDENTITY} - * * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT}
  • + * and EventSource {@link IdentityEdgeConstants.EventSource#REQUEST_CONTENT} + *
  • Listener {@link ListenerIdentityEdgeUpdateIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and EventSource {@link IdentityEdgeConstants.EventSource#UPDATE_IDENTITY}
  • + *
  • Listener {@link ListenerIdentityEdgeRemoveIdentity} to listen for event with eventType {@link IdentityEdgeConstants.EventType#IDENTITY_EDGE} + * and EventSource {@link IdentityEdgeConstants.EventSource#REMOVE_IDENTITY}
  • * *

    * Thread : Background thread created by MobileCore @@ -55,6 +59,8 @@ public void error(final ExtensionError extensionError) { extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REQUEST_IDENTITY, ListenerIdentityRequestIdentity.class, listenerErrorCallback); extensionApi.registerEventListener(IdentityEdgeConstants.EventType.GENERIC_IDENTITY, IdentityEdgeConstants.EventSource.REQUEST_CONTENT, ListenerGenericIdentityRequestContent.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY, ListenerIdentityEdgeUpdateIdentity.class, listenerErrorCallback); + extensionApi.registerEventListener(IdentityEdgeConstants.EventType.IDENTITY_EDGE, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY, ListenerIdentityEdgeRemoveIdentity.class, listenerErrorCallback); } /** @@ -75,6 +81,15 @@ protected String getVersion() { return IdentityEdgeConstants.EXTENSION_VERSION; } + // TODO: Docme + void handleUpdateIdentities(final Event event) { + // TODO + } + + // TODO: Docme + void handleRemoveIdentity(final Event event) { + // TODO + } void handleGenericIdentityRequest(final Event event) { // TODO diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index da062ce1..1282a907 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -11,12 +11,9 @@ package com.adobe.marketing.mobile.identityedge; -import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -import com.adobe.marketing.mobile.MobilePrivacyStatus; -import java.util.Map; /** * Manages the business logic of the Identity Edge extension diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index 73758593..f47f8930 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -28,7 +28,7 @@ * @see IdentityMap Schema */ @SuppressWarnings("unused") -class IdentityMap { +public class IdentityMap { private static final String LOG_TAG = "IdentityMap"; private static final String JSON_KEY_ID = "id"; @@ -72,7 +72,7 @@ AuthenticationState fromString(final String state) { * @param namespace namespace for the id * @return IdentityItem for the namespace, null if not found */ - List> getIdentityItemForNamespace(final String namespace) { + List> getIdentityItemsForNamespace(final String namespace) { return identityItems.get(namespace); } @@ -83,7 +83,7 @@ List> getIdentityItemForNamespace(final String namespace) { * @param namespace the namespace integration code or namespace ID of the identity * @param id identity of the consumer in the related namespace * @param state the state this identity is authenticated as for this observed ExperienceEvent. - * Default is {@link AuthenticationState.AMBIGUOUS}. + * Default is {@link AuthenticationState#AMBIGUOUS}. * @param primary Indicates this identity is the preferred identity. Is used as a hint to help * systems better organize how identities are queried. Default is false. * @@ -157,6 +157,15 @@ Map>> toObjectMap() { return identityItems; } + /** + * Use this method to cast the {@code IdentityMap} as eventData for an SDK Event. + * + * @return {@link Map} representation of IdentityMap + */ + Map asEventData() { + return new HashMap(identityItems); + } + static IdentityMap fromData(Map data) { if (data == null) { return null; } final Map identityMapDict = (HashMap) data.get(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP); @@ -183,7 +192,7 @@ static IdentityMap fromData(Map data) { * @return ECID stored in the IdentityMap or null if not found */ ECID getFirstECID() { - final List> ecidArr = getIdentityItemForNamespace(IdentityEdgeConstants.Namespaces.ECID); + final List> ecidArr = getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); if (ecidArr == null) { return null; } final Map ecidDict = ecidArr.get(0); if (ecidDict == null) { return null; } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index ad376397..1c70d12a 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -87,7 +87,7 @@ public void test_ListenersRegistration() { // constructor is called in the setup step() // verify 2 listeners are registered - verify(mockExtensionApi, times(2)).registerEventListener(anyString(), + verify(mockExtensionApi, times(4)).registerEventListener(anyString(), anyString(), any(Class.class), any(ExtensionErrorCallback.class)); // verify listeners are registered with correct event source and type @@ -95,6 +95,10 @@ public void test_ListenersRegistration() { eq(IdentityEdgeConstants.EventSource.REQUEST_IDENTITY), eq(ListenerIdentityRequestIdentity.class), callbackCaptor.capture()); verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.GENERIC_IDENTITY), eq(IdentityEdgeConstants.EventSource.REQUEST_CONTENT), eq(ListenerGenericIdentityRequestContent.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + eq(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY), eq(ListenerIdentityEdgeUpdateIdentity.class), callbackCaptor.capture()); + verify(mockExtensionApi, times(1)).registerEventListener(eq(IdentityEdgeConstants.EventType.IDENTITY_EDGE), + eq(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY), eq(ListenerIdentityEdgeRemoveIdentity.class), callbackCaptor.capture()); // verify the callback ExtensionErrorCallback extensionErrorCallback = callbackCaptor.getValue(); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index af3352b2..d034a0e1 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -244,6 +244,49 @@ public void call(Object o) { } assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } + // ======================================================================================== + // updateIdentities API + // ======================================================================================== + @Test + public void testUpdateIdentities() { + // setup + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); + + // test + IdentityMap map = new IdentityMap(); + map.addItem("mainspace", "id", IdentityMap.AuthenticationState.AUTHENTICATED, true); + map.addItem("secondspace", "idtwo", IdentityMap.AuthenticationState.LOGGED_OUT, false); + IdentityEdge.updateIdentities(map); + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEvent(eventCaptor.capture(), extensionErrorCallbackCaptor.capture()); + + // TODO - enable when ExtensionError creation is available + // should not crash on calling the callback + //extensionErrorCallback.error(ExtensionError.UNEXPECTED_ERROR); + + // verify the dispatched event details + Event dispatchedEvent = eventCaptor.getValue(); + assertEquals(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES,dispatchedEvent.getName()); + assertEquals(IdentityEdgeConstants.EventType.IDENTITY_EDGE.toLowerCase(),dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY.toLowerCase(),dispatchedEvent.getSource()); + assertEquals(map.asEventData(),dispatchedEvent.getEventData()); + } + + @Test + public void testUpdateIdentitiesNullAndEmptyMap() { + // test + IdentityMap map = new IdentityMap(); + IdentityEdge.updateIdentities(map); + IdentityEdge.updateIdentities(null); + + // verify no of these API calls dispatch an event + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); + MobileCore.dispatchEvent(any(Event.class), any(ExtensionErrorCallback.class)); + } + // ======================================================================================== // Private method // ======================================================================================== From 68b8e5b14b3ae087a8fb1b46048e89f6db39594d Mon Sep 17 00:00:00 2001 From: pravin Date: Thu, 11 Mar 2021 10:24:04 -0800 Subject: [PATCH 04/19] [AMSDK-11081] - Fix spacings in IdentityMap class --- .../mobile/identityedge/IdentityMap.java | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index f47f8930..4c802b68 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -69,6 +69,7 @@ AuthenticationState fromString(final String state) { /** * Gets the IdentityItem for the namespace + * * @param namespace namespace for the id * @return IdentityItem for the namespace, null if not found */ @@ -81,12 +82,11 @@ List> getIdentityItemsForNamespace(final String namespace) { * with digital experiences. * * @param namespace the namespace integration code or namespace ID of the identity - * @param id identity of the consumer in the related namespace - * @param state the state this identity is authenticated as for this observed ExperienceEvent. - * Default is {@link AuthenticationState#AMBIGUOUS}. - * @param primary Indicates this identity is the preferred identity. Is used as a hint to help - * systems better organize how identities are queried. Default is false. - * + * @param id identity of the consumer in the related namespace + * @param state the state this identity is authenticated as for this observed ExperienceEvent. + * Default is {@link AuthenticationState#AMBIGUOUS}. + * @param primary Indicates this identity is the preferred identity. Is used as a hint to help + * systems better organize how identities are queried. Default is false. * @see IdentityItem Schema */ void addItem(final String namespace, final String id, final AuthenticationState state, final boolean primary) { @@ -117,8 +117,7 @@ void addItem(final String namespace, final String id, final AuthenticationState * with digital experiences. Uses default authentication state and primary as defined by the Experience Platform. * * @param namespace the namespace integration code or namespace ID of the identity - * @param id identity of the consumer in the related namespace - * + * @param id identity of the consumer in the related namespace * @see IdentityItem Schema */ void addItem(final String namespace, final String id) { @@ -162,21 +161,25 @@ Map>> toObjectMap() { * * @return {@link Map} representation of IdentityMap */ - Map asEventData() { - return new HashMap(identityItems); + Map asEventData() { + return new HashMap(identityItems); } static IdentityMap fromData(Map data) { - if (data == null) { return null; } + if (data == null) { + return null; + } final Map identityMapDict = (HashMap) data.get(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP); - if (identityMapDict == null) { return null; } + if (identityMapDict == null) { + return null; + } final IdentityMap identityMap = new IdentityMap(); for (String namespace : identityMapDict.keySet()) { try { final ArrayList> idArr = (ArrayList>) identityMapDict.get(namespace); - for (Object idMap: idArr) { + for (Object idMap : idArr) { identityMap.addItemToMap(namespace, (Map) idMap); } } catch (ClassCastException e) { @@ -189,13 +192,18 @@ static IdentityMap fromData(Map data) { /** * Reads the ECID from an IdentityMap + * * @return ECID stored in the IdentityMap or null if not found */ ECID getFirstECID() { final List> ecidArr = getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); - if (ecidArr == null) { return null; } + if (ecidArr == null) { + return null; + } final Map ecidDict = ecidArr.get(0); - if (ecidDict == null) { return null; } + if (ecidDict == null) { + return null; + } String ecidStr = null; try { ecidStr = (String) ecidDict.get(IdentityEdgeConstants.XDMKeys.ID); @@ -203,7 +211,9 @@ ECID getFirstECID() { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create read ECID from IdentityMap"); } - if (ecidStr == null) { return null; } + if (ecidStr == null) { + return null; + } return new ECID(ecidStr); } } \ No newline at end of file From 6de193a2fd228a078a7f6461b4523d0e3ca15ec7 Mon Sep 17 00:00:00 2001 From: pravin Date: Thu, 11 Mar 2021 16:35:09 -0800 Subject: [PATCH 05/19] [Dev] - Add the functional test helpers + first valid functional test --- code/identityedge/build.gradle | 12 + .../marketing/mobile/ADBCountDownLatch.java | 59 ++ .../marketing/mobile/MonitorExtension.java | 277 ++++++++++ .../adobe/marketing/mobile/TestConstants.java | 39 ++ .../adobe/marketing/mobile/TestHelper.java | 506 ++++++++++++++++++ .../mobile/TestPersistenceHelper.java | 75 +++ .../identityedge/ExampleInstrumentedTest.java | 37 -- .../IdentityEdgePublicAPITest.java | 15 + .../mobile/identityedge/IdentityEdge.java | 3 +- .../IdentityEdgeTestConstants.java | 35 ++ .../identityedge/IdentityEdgeTestUtil.java | 92 ++++ 11 files changed, 1111 insertions(+), 39 deletions(-) create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java delete mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java create mode 100644 code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java create mode 100644 code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java create mode 100644 code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java diff --git a/code/identityedge/build.gradle b/code/identityedge/build.gradle index c79265d8..c4d5cda9 100644 --- a/code/identityedge/build.gradle +++ b/code/identityedge/build.gradle @@ -47,6 +47,18 @@ android { sourceCompatibility rootProject.ext.sourceCompatibility targetCompatibility rootProject.ext.targetCompatibility } + + sourceSets { + String sharedTestUtilJavaDir = 'src/sharedTestUtils/java' + + test { + java.srcDirs += [sharedTestUtilJavaDir] + } + + androidTest { + java.srcDirs += [sharedTestUtilJavaDir] + } + } } android.libraryVariants.all { variant -> diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java new file mode 100644 index 00000000..90af879d --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/ADBCountDownLatch.java @@ -0,0 +1,59 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class ADBCountDownLatch { + private final CountDownLatch latch; + private final int initialCount; + private final AtomicInteger currentCount; + + public ADBCountDownLatch(final int expectedCount) { + this.initialCount = expectedCount; + this.latch = new CountDownLatch(expectedCount); + this.currentCount = new AtomicInteger(); + } + + public void await() throws InterruptedException { + latch.await(); + } + + public boolean await(long timeout, TimeUnit unit) throws InterruptedException { + return latch.await(timeout, unit); + } + + public void countDown() { + currentCount.incrementAndGet(); + latch.countDown(); + } + + public long getCount() { + return latch.getCount(); + } + + public int getInitialCount() { + return initialCount; + } + + public int getCurrentCount() { + return currentCount.get(); + } + + @Override + public String toString() { + return String.format("%s, initial: %d, current: %d", latch.toString(), initialCount, currentCount.get()); + } + +} diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java new file mode 100644 index 00000000..11b9eab5 --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/MonitorExtension.java @@ -0,0 +1,277 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * A third party extension class aiding for assertion against dispatched events, shared state + * and XDM shared state. + */ +class MonitorExtension extends Extension { + private static final String LOG_TAG = "MonitorExtension"; + + private static final Map> receivedEvents = new HashMap<>(); + private static final Map expectedEvents = new HashMap<>(); + + protected MonitorExtension(ExtensionApi extensionApi) { + super(extensionApi); + + extensionApi.registerWildcardListener( + MonitorListener.class, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, + "There was an error registering Extension Listener: " + + extensionError.getErrorName()); + } + }); + } + + @Override + protected String getName() { + return "MonitorExtension"; + } + + public static void registerExtension() { + MobileCore.registerExtension(MonitorExtension.class, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, + "There was an error registering the Monitor extension: " + extensionError.getErrorName()); + } + }); + } + + /** + * Unregister the Monitor Extension from the EventHub. + */ + public static void unregisterExtension() { + Event event = new Event.Builder("Unregister Monitor Extension Request", TestConstants.EventType.MONITOR, + TestConstants.EventSource.UNREGISTER) + .build(); + MobileCore.dispatchEvent(event, new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, LOG_TAG, "Failed to unregister Monitor extension."); + } + }); + } + + /** + * Add an event to the list of expected events. + * @param type the type of the event. + * @param source the source of the event. + * @param count the number of events expected to be received. + */ + public static void setExpectedEvent(final String type, final String source, final int count) { + EventSpec eventSpec = new EventSpec(source, type); + expectedEvents.put(eventSpec, new ADBCountDownLatch(count)); + } + + public static Map getExpectedEvents() { + return expectedEvents; + } + + public static Map> getReceivedEvents() { + return receivedEvents; + } + + /** + * Resets the map of received and expected events. + */ + public static void reset() { + MobileCore.log(LoggingMode.VERBOSE, LOG_TAG, "Reset expected and received events."); + receivedEvents.clear(); + expectedEvents.clear(); + } + + /** + * Processor for all heard events. + * If the event type is of this Monitor Extension, then + * the action is performed per the event source. + * All other events are added to the map of received events. If the event is in the map + * of expected events, its latch is counted down. + * + * @param event + */ + public void wildcardProcessor(final Event event) { + if (TestConstants.EventType.MONITOR.equalsIgnoreCase(event.getType())) { + if (TestConstants.EventSource.SHARED_STATE_REQUEST.equalsIgnoreCase(event.getSource())) { + processSharedStateRequest(event); + } else if (TestConstants.EventSource.XDM_SHARED_STATE_REQUEST.equalsIgnoreCase(event.getSource())) { + processXDMSharedStateRequest(event); + } else if (TestConstants.EventSource.UNREGISTER.equalsIgnoreCase(event.getSource())) { + processUnregisterRequest(event); + } + + return; + } + + EventSpec eventSpec = new EventSpec(event.getSource(), event.getType()); + + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Received and processing event " + eventSpec); + + if (!receivedEvents.containsKey(eventSpec)) { + receivedEvents.put(eventSpec, new ArrayList()); + } + + receivedEvents.get(eventSpec).add(event); + + + if (expectedEvents.containsKey(eventSpec)) { + expectedEvents.get(eventSpec).countDown(); + } + } + + /** + * Processor which unregisters this extension. + * @param event + */ + private void processUnregisterRequest(final Event event) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unregistering the Monitor Extension."); + getApi().unregisterExtension(); + } + + /** + * Processor which retrieves and dispatches the XDM shared state for the state owner specified + * in the request. + * @param event + */ + private void processXDMSharedStateRequest(final Event event) { + EventData eventData = event.getData(); + + if (eventData == null) { + return; + } + + String stateOwner = eventData.optString(TestConstants.EventDataKey.STATE_OWNER, null); + + if (stateOwner == null) { + return; + } + + EventData sharedState = getApi().getXDMSharedEventState(stateOwner, event); + + Event responseEvent = new Event.Builder("Get Shared State Response", TestConstants.EventType.MONITOR, + TestConstants.EventSource.XDM_SHARED_STATE_RESPONSE) + .setEventData(sharedState == null ? null : sharedState.toObjectMap()) + .setPairID(event.getResponsePairID()) + .build(); + + MobileCore.dispatchResponseEvent(responseEvent, event, null); + } + + /** + * Processor which retrieves and dispatches the shared state for the state owner specified + * in the request. + * @param event + */ + private void processSharedStateRequest(final Event event) { + EventData eventData = event.getData(); + + if (eventData == null) { + return; + } + + String stateOwner = eventData.optString(TestConstants.EventDataKey.STATE_OWNER, null); + + if (stateOwner == null) { + return; + } + + EventData sharedState = getApi().getSharedEventState(stateOwner, event); + + Event responseEvent = new Event.Builder("Get Shared State Response", TestConstants.EventType.MONITOR, + TestConstants.EventSource.SHARED_STATE_RESPONSE) + .setEventData(sharedState == null ? null : sharedState.toObjectMap()) + .setPairID(event.getResponsePairID()) + .build(); + + MobileCore.dispatchResponseEvent(responseEvent, event, null); + } + + /** + * Listener class + */ + public static class MonitorListener extends ExtensionListener { + + protected MonitorListener(ExtensionApi extension, String type, String source) { + super(extension, type, source); + } + + @Override + public void hear(Event event) { + MonitorExtension extension = getParentExtension(); + + if (extension != null) { + extension.wildcardProcessor(event); + } + } + + @Override + protected MonitorExtension getParentExtension() { + return (MonitorExtension) super.getParentExtension(); + } + } + + /** + * Class defining {@link Event} specifications, contains Event's source and type. + */ + public static class EventSpec { + final String source; + final String type; + + public EventSpec(final String source, final String type) { + if (source == null || source.isEmpty()) { + throw new IllegalArgumentException("Event Source cannot be null or empty."); + } + + if (type == null || type.isEmpty()) { + throw new IllegalArgumentException("Event Type cannot be null or empty."); + } + + // Normalize strings + this.source = source.toLowerCase(); + this.type = type.toLowerCase(); + } + + @Override + public String toString() { + return "type '" + type + "' and source '" + source + "'"; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + EventSpec eventSpec = (EventSpec) o; + return Objects.equals(source, eventSpec.source) && + Objects.equals(type, eventSpec.type); + } + + @Override + public int hashCode() { + return Objects.hash(source, type); + } + } +} \ No newline at end of file diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java new file mode 100644 index 00000000..5980934e --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestConstants.java @@ -0,0 +1,39 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +/** + * Class to maintain test constants. + */ +public class TestConstants { + + public class EventType { + static final String MONITOR = "com.adobe.functional.eventType.monitor"; + private EventType() {} + } + + public class EventSource { + // Used by Monitor Extension + static final String XDM_SHARED_STATE_REQUEST = "com.adobe.eventSource.xdmsharedStateRequest"; + static final String XDM_SHARED_STATE_RESPONSE = "com.adobe.eventSource.xdmsharedStateResponse"; + static final String SHARED_STATE_REQUEST = "com.adobe.eventSource.sharedStateRequest"; + static final String SHARED_STATE_RESPONSE = "com.adobe.eventSource.sharedStateResponse"; + static final String UNREGISTER = "com.adobe.eventSource.unregister"; + private EventSource() {} + } + + public class EventDataKey { + static final String STATE_OWNER = "stateowner"; + private EventDataKey() {}; + } + +} diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java new file mode 100644 index 00000000..b893b213 --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestHelper.java @@ -0,0 +1,506 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +import android.app.Application; +import android.app.Instrumentation; +import android.content.Context; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import com.adobe.marketing.mobile.MonitorExtension.EventSpec; + +import androidx.test.platform.app.InstrumentationRegistry; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Test helper for functional testing to read, write, reset and assert against eventhub events, shared states and persistence data. + */ +public class TestHelper { + private static final String TAG = "TestHelper"; + static final int WAIT_TIMEOUT_MS = 1000; + static final int WAIT_EVENT_TIMEOUT_MS = 2000; + static Application defaultApplication; + + // List of threads to wait for after test execution + private static List knownThreads = new ArrayList(); + + { + knownThreads.add("pool"); // used for threads that execute the listeners code + knownThreads.add("ADB"); // module internal threads + } + + /** + * {@code TestRule} which sets up the MobileCore for testing before each test execution, and + * tearsdown the MobileCore after test execution. + *

    + * To use, add the following to your test class: + *

    +     *    @Rule
    +     *    public TestHelper.SetupCoreRule coreRule = new TestHelper.SetupCoreRule();
    +     * 
    + */ + public static class SetupCoreRule implements TestRule { + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + if (defaultApplication == null) { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + defaultApplication = Instrumentation.newApplication(CustomApplication.class, context); + } + + MobileCore.setLogLevel(LoggingMode.VERBOSE); + MobileCore.setApplication(defaultApplication); + + try { + base.evaluate(); + } catch (Throwable e) { + MobileCore.log(LoggingMode.DEBUG, "SetupCoreRule", "Wait after test failure."); + throw e; // rethrow test failure + } finally { + // After test execution + MobileCore.log(LoggingMode.DEBUG, "SetupCoreRule", "Finished '" + description.getMethodName() + "'"); + waitForThreads(5000); // wait to allow thread to run after test execution + Core core = MobileCore.getCore(); + + if (core != null && core.eventHub != null) { + core.eventHub.shutdown(); + core.eventHub = null; + } + MobileCore.setCore(null); + TestPersistenceHelper.resetKnownPersistence(); + resetTestExpectations(); + } + } + }; + } + } + + /** + * {@code TestRule} which registers the {@code MonitorExtension}, allowing test cases to assert + * events passing through the {@code EventHub}. This {@code TestRule} must be applied after + * the {@link SetupCoreRule} to ensure the {@code MobileCore} is setup for testing first. + *

    + * To use, add the following to your test class: + *

    +     *  @Rule
    +     *    public RuleChain rule = RuleChain.outerRule(new SetupCoreRule())
    +     * 							.around(new RegisterMonitorExtensionRule());
    +     * 
    + */ + public static class RegisterMonitorExtensionRule implements TestRule { + + @Override + public Statement apply(final Statement base, final Description description) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + MonitorExtension.registerExtension(); + + try { + base.evaluate(); + } finally { + MonitorExtension.reset(); + } + } + }; + } + } + + /** + * Waits for all the {@code #knownThreads} to finish or fails the test after timeoutMillis if some of them are still running + * when the timer expires. If timeoutMillis is 0, a default timeout will be set = 1000ms + * + * @param timeoutMillis max waiting time + */ + public static void waitForThreads(final int timeoutMillis) { + int TEST_DEFAULT_TIMEOUT_MS = 1000; + int TEST_DEFAULT_SLEEP_MS = 50; + int TEST_INITIAL_SLEEP_MS = 100; + + long startTime = System.currentTimeMillis(); + int timeoutTestMillis = timeoutMillis > 0 ? timeoutMillis : TEST_DEFAULT_TIMEOUT_MS; + int sleepTime = Math.min(timeoutTestMillis, TEST_DEFAULT_SLEEP_MS); + + sleep(TEST_INITIAL_SLEEP_MS); + Set threadSet = getEligibleThreads(); + + while (threadSet.size() > 0 && ((System.currentTimeMillis() - startTime) < timeoutTestMillis)) { + MobileCore.log(LoggingMode.DEBUG, TAG, "waitForThreads - Still waiting for " + threadSet.size() + " thread(s)"); + + for (Thread t : threadSet) { + + MobileCore.log(LoggingMode.DEBUG, TAG, "waitForThreads - Waiting for thread " + t.getName() + " (" + t.getId() + ")"); + boolean done = false; + boolean timedOut = false; + + while (!done && !timedOut) { + if (t.getState().equals(Thread.State.TERMINATED) + || t.getState().equals(Thread.State.TIMED_WAITING) + || t.getState().equals(Thread.State.WAITING)) { + //Cannot use the join() API since we use a cached thread pool, which + //means that we keep idle threads around for 60secs (default timeout). + done = true; + } else { + //blocking + sleep(sleepTime); + timedOut = (System.currentTimeMillis() - startTime) > timeoutTestMillis; + } + } + + if (timedOut) { + MobileCore.log(LoggingMode.DEBUG, TAG, + "waitForThreads - Timeout out waiting for thread " + t.getName() + " (" + t.getId() + ")"); + } else { + MobileCore.log(LoggingMode.DEBUG, TAG, + "waitForThreads - Done waiting for thread " + t.getName() + " (" + t.getId() + ")"); + } + } + + threadSet = getEligibleThreads(); + } + + MobileCore.log(LoggingMode.DEBUG, TAG, "waitForThreads - All known threads are terminated."); + } + + /** + * Retrieves all the known threads that are still running + * + * @return set of running tests + */ + private static Set getEligibleThreads() { + Set threadSet = Thread.getAllStackTraces().keySet(); + Set eligibleThreads = new HashSet(); + + for (Thread t : threadSet) { + if (isAppThread(t) && !t.getState().equals(Thread.State.WAITING) && !t.getState().equals(Thread.State.TERMINATED) + && !t.getState().equals(Thread.State.TIMED_WAITING)) { + eligibleThreads.add(t); + } + } + + return eligibleThreads; + } + + /** + * Checks if current thread is not a daemon and its name starts with one of the known thread names specified here + * {@link #knownThreads} + * + * @param t current thread to verify + * @return true if it is a known thread, false otherwise + */ + private static boolean isAppThread(final Thread t) { + if (t.isDaemon()) { + return false; + } + + for (String prefix : knownThreads) { + if (t.getName().startsWith(prefix)) { + return true; + } + } + + return false; + } + + + /** + * Resets the network and event test expectations. + */ + public static void resetTestExpectations() { + MobileCore.log(LoggingMode.DEBUG, TAG, "Resetting functional test expectations for events"); + MonitorExtension.reset(); + } + + // --------------------------------------------------------------------------------------------- + // Event Test Helpers + // --------------------------------------------------------------------------------------------- + + /** + * Sets an expectation for a specific event type and source and how many times the event should be dispatched. + * + * @param type the event type + * @param source the event source + * @param count the expected number of times the event is dispatched + * @throws IllegalArgumentException if {@code count} is less than 1 + */ + public static void setExpectationEvent(final String type, final String source, final int count) { + if (count < 1) { + throw new IllegalArgumentException("Cannot set expectation event count less than 1!"); + } + + MonitorExtension.setExpectedEvent(type, source, count); + } + + /** + * Asserts if all the expected events were received and fails if an unexpected event was seen. + * + * @param ignoreUnexpectedEvents if set on false, an assertion is made on unexpected events, otherwise the unexpected events are ignored + * @throws InterruptedException + * @see #setExpectationEvent(String, String, int) + * @see #assertUnexpectedEvents() + */ + public static void assertExpectedEvents(final boolean ignoreUnexpectedEvents) throws InterruptedException { + Map expectedEvents = MonitorExtension.getExpectedEvents(); + + if (expectedEvents.isEmpty()) { + fail("There are no event expectations set, use this API after calling setExpectationEvent"); + return; + } + + for (Map.Entry expected : expectedEvents.entrySet()) { + boolean awaitResult = expected.getValue().await(WAIT_EVENT_TIMEOUT_MS, + TimeUnit.MILLISECONDS); + assertTrue("Timed out waiting for event type " + expected.getKey().type + " and source " + expected.getKey().source, + awaitResult); + int expectedCount = expected.getValue().getInitialCount(); + int receivedCount = expected.getValue().getCurrentCount(); + String failMessage = String.format("Expected %d events for '%s', but received %d", expectedCount, expected.getKey(), + receivedCount); + assertEquals(failMessage, expectedCount, receivedCount); + } + + if (!ignoreUnexpectedEvents) { + assertUnexpectedEvents(false); + } + } + + /** + * Asserts if any unexpected event was received. Use this method to verify the received events + * are correct when setting event expectations. Waits a short time before evaluating received + * events to allow all events to come in. + * + * @see #setExpectationEvent + */ + public static void assertUnexpectedEvents() throws InterruptedException { + assertUnexpectedEvents(true); + } + + /** + * Asserts if any unexpected event was received. Use this method to verify the received events + * are correct when setting event expectations. + * + * @param shouldWait waits a short time to allow events to be received when true + * @see #setExpectationEvent + */ + public static void assertUnexpectedEvents(final boolean shouldWait) throws InterruptedException { + // Short wait to allow events to come in + if (shouldWait) { + sleep(WAIT_TIMEOUT_MS); + } + + int unexpectedEventsReceivedCount = 0; + StringBuilder unexpectedEventsErrorString = new StringBuilder(); + + Map> receivedEvents = MonitorExtension.getReceivedEvents(); + Map expectedEvents = MonitorExtension.getExpectedEvents(); + + for (Map.Entry> receivedEvent : receivedEvents.entrySet()) { + ADBCountDownLatch expectedEventLatch = expectedEvents.get(receivedEvent.getKey()); + + if (expectedEventLatch != null) { + expectedEventLatch.await(WAIT_EVENT_TIMEOUT_MS, TimeUnit.MILLISECONDS); + int expectedCount = expectedEventLatch.getInitialCount(); + int receivedCount = receivedEvent.getValue().size(); + String failMessage = String.format("Expected %d events for '%s', but received %d", expectedCount, + receivedEvent.getKey(), receivedCount); + assertEquals(failMessage, expectedCount, receivedCount); + } else { + unexpectedEventsReceivedCount += receivedEvent.getValue().size(); + unexpectedEventsErrorString.append(String.format("(%s,%s,%d)", + receivedEvent.getKey().type, + receivedEvent.getKey().source, + receivedEvent.getValue().size()) + ); + MobileCore.log(LoggingMode.DEBUG, TAG, + "Received unexpected event with type: " + receivedEvent.getKey().type + " source: " + + receivedEvent.getKey().source); + } + } + + assertEquals(String.format("Received %d unexpected event(s): %s", unexpectedEventsReceivedCount, + unexpectedEventsErrorString.toString()), + 0, unexpectedEventsReceivedCount); + } + + /** + * Returns the {@code Event}(s) dispatched through the Event Hub, or empty if none was found. + * Use this API after calling {@link #setExpectationEvent(String, String, int)} to wait for + * the expected events. The wait time for each event is {@link #WAIT_EVENT_TIMEOUT_MS}ms. + * + * @param type the event type as in the expectation + * @param source the event source as in the expectation + * @return list of events with the provided {@code type} and {@code source}, or empty if none was dispatched + * @throws InterruptedException + * @throws IllegalArgumentException if {@code type} or {@code source} are null or empty strings + */ + public static List getDispatchedEventsWith(final String type, final String source) throws InterruptedException { + return getDispatchedEventsWith(type, source, WAIT_EVENT_TIMEOUT_MS); + } + + /** + * Returns the {@code Event}(s) dispatched through the Event Hub, or empty if none was found. + * Use this API after calling {@link #setExpectationEvent(String, String, int)} to wait for the right amount of time + * + * @param type the event type as in the expectation + * @param source the event source as in the expectation + * @param timeout how long should this method wait for the expected event, in milliseconds. + * @return list of events with the provided {@code type} and {@code source}, or empty if none was dispatched + * @throws InterruptedException + * @throws IllegalArgumentException if {@code type} or {@code source} are null or empty strings + */ + public static List getDispatchedEventsWith(final String type, final String source, + int timeout) throws InterruptedException { + EventSpec eventSpec = new EventSpec(source, type); + + Map> receivedEvents = MonitorExtension.getReceivedEvents(); + Map expectedEvents = MonitorExtension.getExpectedEvents(); + + ADBCountDownLatch expectedEventLatch = expectedEvents.get(eventSpec); + + if (expectedEventLatch != null) { + boolean awaitResult = expectedEventLatch.await(timeout, TimeUnit.MILLISECONDS); + assertTrue("Timed out waiting for event type " + eventSpec.type + " and source " + eventSpec.source, awaitResult); + } else { + sleep(WAIT_TIMEOUT_MS); + } + + return receivedEvents.containsKey(eventSpec) ? receivedEvents.get(eventSpec) : Collections.emptyList(); + } + + + /** + * Synchronous call to get the shared state for the specified {@code stateOwner}. + * This API throws an assertion failure in case of timeout. + * + * @param stateOwner the owner extension of the shared state (typically the name of the extension) + * @param timeout how long should this method wait for the requested shared state, in milliseconds + * @return latest shared state of the given {@code stateOwner} or null if no shared state was found + * @throws InterruptedException + */ + public static Map getSharedStateFor(final String stateOwner, int timeout) throws InterruptedException { + Event event = new Event.Builder("Get Shared State Request", TestConstants.EventType.MONITOR, + TestConstants.EventSource.SHARED_STATE_REQUEST) + .setEventData(new HashMap() { + { + put(TestConstants.EventDataKey.STATE_OWNER, stateOwner); + } + }) + .build(); + + final ADBCountDownLatch latch = new ADBCountDownLatch(1); + final Map sharedState = new HashMap<>(); + MobileCore.dispatchEventWithResponseCallback(event, + new AdobeCallback() { + @Override + public void call(Event event) { + if (event.getEventData() != null) { + sharedState.putAll(event.getEventData()); + } + + latch.countDown(); + } + }, + new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, TAG, "Failed to get shared state for " + stateOwner + ": " + extensionError); + } + }); + + assertTrue("Timeout waiting for shared state " + stateOwner, latch.await(timeout, TimeUnit.MILLISECONDS)); + return sharedState.isEmpty() ? null : sharedState; + } + + /** + * Synchronous call to get the XDM shared state for the specified {@code stateOwner}. + * This API throws an assertion failure in case of timeout. + * + * @param stateOwner the owner extension of the shared state (typically the name of the extension) + * @param timeout how long should this method wait for the requested shared state, in milliseconds + * @return latest shared state of the given {@code stateOwner} or null if no shared state was found + * @throws InterruptedException + */ + public static Map getXDMSharedStateFor(final String stateOwner, int timeout) throws InterruptedException { + Event event = new Event.Builder("Get Shared State Request", TestConstants.EventType.MONITOR, + TestConstants.EventSource.XDM_SHARED_STATE_REQUEST) + .setEventData(new HashMap() { + { + put(TestConstants.EventDataKey.STATE_OWNER, stateOwner); + } + }) + .build(); + + final ADBCountDownLatch latch = new ADBCountDownLatch(1); + final Map sharedState = new HashMap<>(); + MobileCore.dispatchEventWithResponseCallback(event, + new AdobeCallback() { + @Override + public void call(Event event) { + if (event.getEventData() != null) { + sharedState.putAll(event.getEventData()); + } + + latch.countDown(); + } + }, + new ExtensionErrorCallback() { + @Override + public void error(ExtensionError extensionError) { + MobileCore.log(LoggingMode.ERROR, TAG, "Failed to get shared state for " + stateOwner + ": " + extensionError); + } + }); + + assertTrue("Timeout waiting for shared state " + stateOwner, latch.await(timeout, TimeUnit.MILLISECONDS)); + return sharedState.isEmpty() ? null : sharedState; + } + + + /** + * Pause test execution for the given {@code milliseconds} + * + * @param milliseconds the time to sleep the current thread. + */ + public static void sleep(int milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Dummy Application for the test instrumentation + */ + public static class CustomApplication extends Application { + public CustomApplication() { + } + } + +} \ No newline at end of file diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java new file mode 100644 index 00000000..0886c125 --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java @@ -0,0 +1,75 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; + + +import com.adobe.marketing.mobile.identityedge.IdentityEdgeTestConstants; + +import java.util.ArrayList; + +/** + * Helper class to update and remove persisted data to extension concerned with testing IdentityEdge. + */ +public class TestPersistenceHelper { + + private static ArrayList knownDatastoreName = new ArrayList() {{ + add(IdentityEdgeTestConstants.DataStoreKey.IDENTITYEDGE_DATASTORE); + add(IdentityEdgeTestConstants.DataStoreKey.CONFIG_DATASTORE); + }}; + + /** + * Helper method to update the {@link SharedPreferences} data. + * @param datastore the name of the datastore to be updated + * @param key the persisted data key that has to be updated + * @param value the new value + */ + public static void updatePersistence(final String datastore, final String key, final String value) { + final Application application = TestHelper.defaultApplication; + final Context context = application.getApplicationContext(); + SharedPreferences sharedPreferences = context.getSharedPreferences(datastore, Context.MODE_PRIVATE);; + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString(key,value); + editor.apply(); + } + + /** + * Reads the requested persisted data from datastore. + * @param datastore the name of the datastore to be read + * @param key that needs to be read + * @return the persisted data in {@code String} + */ + public static String readPersistedData(final String datastore, final String key) { + final Application application = TestHelper.defaultApplication; + final Context context = application.getApplicationContext(); + SharedPreferences sharedPreferences = context.getSharedPreferences(datastore, Context.MODE_PRIVATE);; + return sharedPreferences.getString(key, null); + } + + /** + * Clears the Configuration and Consent extension's persisted data + */ + public static void resetKnownPersistence() { + final Application application = TestHelper.defaultApplication; + final Context context = application.getApplicationContext(); + for (String eachDatastore : knownDatastoreName) { + SharedPreferences sharedPreferences = context.getSharedPreferences(eachDatastore, Context.MODE_PRIVATE);; + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.clear(); + editor.apply(); + } + } + +} diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java deleted file mode 100644 index e56522ac..00000000 --- a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/ExampleInstrumentedTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - Copyright 2021 Adobe. All rights reserved. - This file is licensed to you under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. You may obtain a copy - of the License at http://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under - the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS - OF ANY KIND, either express or implied. See the License for the specific language - governing permissions and limitations under the License. -*/ - -package com.adobe.marketing.mobile.identityedge; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.adobe.marketing.mobile.identityedge.test", appContext.getPackageName()); - } -} \ No newline at end of file diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java new file mode 100644 index 00000000..4620b1ac --- /dev/null +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java @@ -0,0 +1,15 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +public class IdentityEdgePublicAPITest { +} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 22c71259..11538bb5 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -20,7 +20,6 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; -import java.util.Map; public class IdentityEdge { private static final String LOG_TAG = "IdentityEdge"; @@ -116,7 +115,7 @@ public static void updateIdentities(final IdentityMap identityMap) { final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Consents.update() API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Update Identities API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, extensionError.getErrorName())); } }; diff --git a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java new file mode 100644 index 00000000..1d813450 --- /dev/null +++ b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestConstants.java @@ -0,0 +1,35 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +public class IdentityEdgeTestConstants { + + public final class DataStoreKey { + public static final String CONFIG_DATASTORE = "AdobeMobile_ConfigState"; + public static final String IDENTITYEDGE_DATASTORE = "com.adobe.identityEdge"; + private DataStoreKey() { } + } + + public final class SharedStateName { + public static final String CONFIG = "com.adobe.module.configuration"; + public static final String EVENT_HUB = "com.adobe.module.eventhub"; + private SharedStateName() { } + } + + public final class GetConsentHelper { + public static final String VALUE = "getConsentValue"; + public static final String ERROR = "getConsentError"; + private GetConsentHelper() { } + } + + +} diff --git a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java new file mode 100644 index 00000000..1a0db1f4 --- /dev/null +++ b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java @@ -0,0 +1,92 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + +package com.adobe.marketing.mobile.identityedge; + +import com.adobe.marketing.mobile.LoggingMode; +import com.adobe.marketing.mobile.MobileCore; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.ValueNode; + +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +class IdentityEdgeTestUtil { + /** + * Serialize the given {@code map} to a JSON Object, then flattens to {@code Map}. + * For example, a JSON such as "{xdm: {stitchId: myID, eventType: myType}}" is flattened + * to two map elements "xdm.stitchId" = "myID" and "xdm.eventType" = "myType". + * @param map map with JSON structure to flatten + * @return new map with flattened structure + */ + static Map flattenMap(final Map map) { + if (map == null || map.isEmpty()) { + return Collections.emptyMap(); + } + + try { + JSONObject jsonObject = new JSONObject(map); + Map payloadMap = new HashMap<>(); + addKeys("", new ObjectMapper().readTree(jsonObject.toString()), payloadMap); + return payloadMap; + } catch (IOException e) { + MobileCore.log(LoggingMode.ERROR, "FunctionalTestUtils", "Failed to parse JSON object to tree structure."); + } + + return Collections.emptyMap(); + } + + + /** + * Deserialize {@code JsonNode} and flatten to provided {@code map}. + * For example, a JSON such as "{xdm: {stitchId: myID, eventType: myType}}" is flattened + * to two map elements "xdm.stitchId" = "myID" and "xdm.eventType" = "myType". + * + * Method is called recursively. To use, call with an empty path such as + * {@code addKeys("", new ObjectMapper().readTree(JsonNodeAsString), map);} + * + * @param currentPath the path in {@code JsonNode} to process + * @param jsonNode {@link JsonNode} to deserialize + * @param map {@code Map} instance to store flattened JSON result + * + * @see Stack Overflow post + */ + private static void addKeys(String currentPath, JsonNode jsonNode, Map map) { + if (jsonNode.isObject()) { + ObjectNode objectNode = (ObjectNode) jsonNode; + Iterator> iter = objectNode.fields(); + String pathPrefix = currentPath.isEmpty() ? "" : currentPath + "."; + + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + addKeys(pathPrefix + entry.getKey(), entry.getValue(), map); + } + } else if (jsonNode.isArray()) { + ArrayNode arrayNode = (ArrayNode) jsonNode; + + for (int i = 0; i < arrayNode.size(); i++) { + addKeys(currentPath + "[" + i + "]", arrayNode.get(i), map); + } + } else if (jsonNode.isValueNode()) { + ValueNode valueNode = (ValueNode) jsonNode; + map.put(currentPath, valueNode.asText()); + } + } + +} From 8ce362c04f7d284d387996aed53849b014662cbb Mon Sep 17 00:00:00 2001 From: pravin Date: Thu, 11 Mar 2021 16:35:28 -0800 Subject: [PATCH 06/19] [Dev] - First functional test --- .../IdentityEdgePublicAPITest.java | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java index 4620b1ac..0aa9eebb 100644 --- a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePublicAPITest.java @@ -11,5 +11,73 @@ package com.adobe.marketing.mobile.identityedge; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.adobe.marketing.mobile.AdobeCallback; +import com.adobe.marketing.mobile.MobileCore; +import com.adobe.marketing.mobile.TestHelper; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.runner.RunWith; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import static com.adobe.marketing.mobile.TestHelper.getSharedStateFor; +import static com.adobe.marketing.mobile.TestHelper.resetTestExpectations; +import static com.adobe.marketing.mobile.identityedge.IdentityEdgeTestUtil.flattenMap; +import static org.junit.Assert.assertEquals; + +@RunWith(AndroidJUnit4.class) public class IdentityEdgePublicAPITest { + + @Rule + public RuleChain rule = RuleChain.outerRule(new TestHelper.SetupCoreRule()) + .around(new TestHelper.RegisterMonitorExtensionRule()); + + + // -------------------------------------------------------------------------------------------- + // Setup + // -------------------------------------------------------------------------------------------- + + @Before + public void setup() throws Exception { + IdentityEdge.registerExtension(); + + final CountDownLatch latch = new CountDownLatch(1); + MobileCore.start(new AdobeCallback() { + @Override + public void call(Object o) { + latch.countDown(); + } + }); + + latch.await(); + resetTestExpectations(); + } + + // -------------------------------------------------------------------------------------------- + // Tests for GetExtensionVersion API + // -------------------------------------------------------------------------------------------- + @Test + public void testGetExtensionVersionAPI() { + assertEquals(IdentityEdgeConstants.EXTENSION_VERSION, IdentityEdge.extensionVersion()); + } + + // -------------------------------------------------------------------------------------------- + // Tests for Register extension API + // -------------------------------------------------------------------------------------------- + @Test + public void testRegisterExtensionAPI() throws InterruptedException { + // test + // Consent.registerExtension() is called in the setup method + + // verify that the extension is registered with the correct version details + Map sharedStateMap = flattenMap(getSharedStateFor(IdentityEdgeTestConstants.SharedStateName.EVENT_HUB, 1000)); + assertEquals(IdentityEdgeConstants.EXTENSION_VERSION, sharedStateMap.get("extensions.com.adobe.identityedge.version")); + } } From 1b7d9c5e75fbc4bddd95c0c16aac396a4bb39b56 Mon Sep 17 00:00:00 2001 From: pravin Date: Fri, 12 Mar 2021 09:41:45 -0800 Subject: [PATCH 07/19] [Dev] - Assertion fail on misread of persistence in TestPersistence helper method --- .../mobile/TestPersistenceHelper.java | 58 ++++++++++++++++--- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java index 0886c125..bf1040d9 100644 --- a/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java +++ b/code/identityedge/src/androidTest/java/com/adobe/marketing/mobile/TestPersistenceHelper.java @@ -20,6 +20,8 @@ import java.util.ArrayList; +import static org.junit.Assert.fail; + /** * Helper class to update and remove persisted data to extension concerned with testing IdentityEdge. */ @@ -32,29 +34,56 @@ public class TestPersistenceHelper { /** * Helper method to update the {@link SharedPreferences} data. + * * @param datastore the name of the datastore to be updated - * @param key the persisted data key that has to be updated - * @param value the new value + * @param key the persisted data key that has to be updated + * @param value the new value */ public static void updatePersistence(final String datastore, final String key, final String value) { final Application application = TestHelper.defaultApplication; + if (application == null) { + fail("Unable to updatePersistence by TestPersistenceHelper. Application is null, fast failing the test case."); + } + final Context context = application.getApplicationContext(); - SharedPreferences sharedPreferences = context.getSharedPreferences(datastore, Context.MODE_PRIVATE);; + if (context == null) { + fail("Unable to updatePersistence by TestPersistenceHelper. Context is null, fast failing the test case."); + } + + SharedPreferences sharedPreferences = context.getSharedPreferences(datastore, Context.MODE_PRIVATE); + + if (sharedPreferences == null) { + fail("Unable to updatePersistence by TestPersistenceHelper. sharedPreferences is null, fast failing the test case."); + } + SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putString(key,value); + editor.putString(key, value); editor.apply(); } /** * Reads the requested persisted data from datastore. + * * @param datastore the name of the datastore to be read - * @param key that needs to be read - * @return the persisted data in {@code String} + * @param key the key that needs to be read + * @return {@link String} value of persisted data. Null if data is not found in {@link SharedPreferences} */ public static String readPersistedData(final String datastore, final String key) { final Application application = TestHelper.defaultApplication; + if (application == null) { + fail("Unable to readPersistedData by TestPersistenceHelper. Application is null, fast failing the test case."); + } + final Context context = application.getApplicationContext(); - SharedPreferences sharedPreferences = context.getSharedPreferences(datastore, Context.MODE_PRIVATE);; + if (context == null) { + fail("Unable to readPersistedData by TestPersistenceHelper. Context is null, fast failing the test case."); + } + + SharedPreferences sharedPreferences = context.getSharedPreferences(datastore, Context.MODE_PRIVATE); + if (sharedPreferences == null) { + fail("Unable to readPersistedData by TestPersistenceHelper. sharedPreferences is null, fast failing the test case."); + } + return sharedPreferences.getString(key, null); } @@ -62,10 +91,23 @@ public static String readPersistedData(final String datastore, final String key) * Clears the Configuration and Consent extension's persisted data */ public static void resetKnownPersistence() { + final Application application = TestHelper.defaultApplication; + if (application == null) { + fail("Unable to resetPersistence by TestPersistenceHelper. Application is null, fast failing the test case."); + } + final Context context = application.getApplicationContext(); + if (context == null) { + fail("Unable to resetPersistence by TestPersistenceHelper. Context is null, fast failing the test case."); + } + for (String eachDatastore : knownDatastoreName) { - SharedPreferences sharedPreferences = context.getSharedPreferences(eachDatastore, Context.MODE_PRIVATE);; + SharedPreferences sharedPreferences = context.getSharedPreferences(eachDatastore, Context.MODE_PRIVATE); + if (sharedPreferences == null) { + fail("Unable to resetPersistence by TestPersistenceHelper. sharedPreferences is null, fast failing the test case."); + } + SharedPreferences.Editor editor = sharedPreferences.edit(); editor.clear(); editor.apply(); From 062e5f1b1d3681538e7eda3fc3efc89c88e39407 Mon Sep 17 00:00:00 2001 From: pravin Date: Wed, 17 Mar 2021 08:12:29 -0700 Subject: [PATCH 08/19] [AMSDK-11081] - Update/Remove Identity API implementation --- .../mobile/identityedge/IdentityEdge.java | 37 +++++ .../identityedge/IdentityEdgeConstants.java | 2 + .../identityedge/IdentityEdgeExtension.java | 43 ++++- .../identityedge/IdentityEdgeProperties.java | 62 +++++++- .../identityedge/IdentityEdgeState.java | 21 +++ .../IdentityEdgeStorageService.java | 5 +- .../mobile/identityedge/IdentityMap.java | 150 +++++++++++++++--- .../mobile/identityedge/IdentityMapTests.java | 4 + 8 files changed, 294 insertions(+), 30 deletions(-) create mode 100644 code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 84c9f7bb..7a97c530 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -128,6 +128,43 @@ public void error(final ExtensionError extensionError) { MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); } + /** + * Removes the identity from the stored client-side {@link IdentityMap} and XDM shared state. The IdentityEdge extension will stop sending this identifier. + * This does not clear the identifier from the User Profile Graph. + * Identifiers which have an empty `id` or empty `namespace` are not allowed and are ignored. + * + * @param item {@link IdentityItem} representing the identity to remove. + * @param namespace The namespace the identity to remove is under. + */ + public static void removeIdentity(final IdentityItem item, final String namespace) { + if (namespace == null || namespace.isEmpty()) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to removeIdentity, namespace is null or empty"); + return; + } + + if (item == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to updateIdentities, IdentityItem is null"); + return; + } + + IdentityMap identityMap = new IdentityMap(); + identityMap.addItem(namespace, item); + + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Update Identities API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, + extensionError.getErrorName())); + } + }; + + + final Event updateIdentitiesEvent = new Event.Builder(IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, + IdentityEdgeConstants.EventType.EDGE_IDENTITY, + IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).setEventData(identityMap.asEventData()).build(); + MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); + } + /** * Returns all identifiers, including customer identifiers which were previously added. * @param callback {@link AdobeCallback} invoked with the current {@link IdentityMap} diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index f586518e..b8c44f36 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -52,6 +52,8 @@ private EventDataKeys() { } final class Namespaces { static final String ECID = "ECID"; + static final String IDFA = "IDFA"; + static final String GAID = "GAID"; private Namespaces() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index 70e87b01..d5a4e9a3 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -85,14 +85,39 @@ protected String getVersion() { return IdentityEdgeConstants.EXTENSION_VERSION; } - // TODO: Docme + /** + * Handles update identity requests to add/update customer identifiers. + * + * @param event the edge update identity {@link Event} + */ void handleUpdateIdentities(final Event event) { - // TODO + final Map eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners + final IdentityMap map = IdentityMap.fromData(eventData); + if (map == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to update identifiers as no identifiers were found in the event data."); + return; + } + + state.updateCustomerIdentifiers(map); + updateIdentityXDMSharedState(event); } - // TODO: Docme + + /** + * Handles remove identity requests to remove customer identifiers. + * + * @param event the edge remove identity request {@link Event} + */ void handleRemoveIdentity(final Event event) { - // TODO + final Map eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners + final IdentityMap map = IdentityMap.fromData(eventData); + if (map == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to remove identifiers as no identifiers were found in the event data."); + return; + } + + state.removeCustomerIdentifiers(map); + updateIdentityXDMSharedState(event); } void handleGenericIdentityRequest(final Event event) { @@ -134,7 +159,15 @@ void handleRequestReset(final Event event) { return; } state.resetIdentifiers(); + updateIdentityXDMSharedState(event); + } + /** + * Fetches the latest Identity Edge properties and shares the EdgeIdentity's XDMSharedState. + * + * @param event the {@link Event} that triggered the XDM shared state change + */ + void updateIdentityXDMSharedState(final Event event) { final ExtensionApi extensionApi = super.getApi(); if (extensionApi == null ) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to share XDM shared state for reset identities"); @@ -142,7 +175,7 @@ void handleRequestReset(final Event event) { } // set the shared state - ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { + final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed create XDM shared state. Error : %s.", extensionError.getErrorName())); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 99bad7e3..0022b85d 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -14,6 +14,7 @@ import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -24,14 +25,21 @@ class IdentityEdgeProperties { private static final String LOG_TAG = "IdentityEdgeProperties"; + private static final List reservedNamespaces = new ArrayList() {{ + add(IdentityEdgeConstants.Namespaces.ECID); + add(IdentityEdgeConstants.Namespaces.GAID); + add(IdentityEdgeConstants.Namespaces.IDFA); + }}; // The current Experience Cloud ID private ECID ecid; + private IdentityMap identityMap = new IdentityMap(); IdentityEdgeProperties() { } /** * Creates a identity edge properties instance based on the map + * * @param xdmData a map representing an identity edge properties instance */ IdentityEdgeProperties(final Map xdmData) { @@ -39,7 +47,7 @@ class IdentityEdgeProperties { return; } - IdentityMap identityMap = IdentityMap.fromData(xdmData); + identityMap = IdentityMap.fromData(xdmData); if (identityMap != null) { final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); boolean containsEcid = ecidItems != null && !ecidItems.isEmpty() && ecidItems.get(0).getId() != null; @@ -51,6 +59,7 @@ class IdentityEdgeProperties { /** * Sets the current {@link ECID} + * * @param ecid the new {@link ECID} */ void setECID(final ECID ecid) { @@ -59,15 +68,50 @@ void setECID(final ECID ecid) { /** * Retrieves the current {@link ECID} + * * @return current {@link ECID} */ ECID getECID() { return ecid; } + /** + * Update the customer identifiers by merging the passed in {@link IdentityMap} with the current identifiers. + *

    + * Any identifier in the passed in {@link IdentityMap} which has the same id in the same namespace will update the current identifier. + * Any new identifier in the passed in {@link IdentityMap} will be added to the current identifiers + * Certain namespaces are not allowed to be modified and if exist in the given customer identifiers will be removed before the update operation is executed. + * The namespaces which cannot be modified through this function call include: + * - ECID + * - IDFA + * - GAID + * + * @param map the {@link IdentityMap} containing customer identifiers to add or update with the current customer identifiers + */ + void updateCustomerIdentifiers(final IdentityMap map) { + removeIdentitiesWithReservedNamespaces(map); + identityMap.merge(map); + } + + /** + * Remove customer identifiers specified in passed in {@link IdentityMap} from the current identifiers. + *

    + * Identifiers with following namespaces are prohibited from removing using the API + * - ECID + * - IDFA + * - GAID + * + * @param map the {@link IdentityMap} with items to remove from current identifiers + */ + void removeCustomerIdentifiers(final IdentityMap map) { + removeIdentitiesWithReservedNamespaces(map); + identityMap.remove(map); + } + /** * Converts this into an event data representation in XDM format - * @param allowEmpty If this {@link IdentityEdgeProperties} contains no data, return a dictionary with a single {@link IdentityMap} key + * + * @param allowEmpty If this {@link IdentityEdgeProperties} contains no data, return a dictionary with a single {@link IdentityMap} key * @return A dictionary representing this in XDM format */ Map toXDMData(final boolean allowEmpty) { @@ -87,4 +131,18 @@ Map toXDMData(final boolean allowEmpty) { return map; } + /** + * Filter out any items contained in reserved namespaces from the given {@link IdentityMap}. + * The list of reserved namespaces can be found at {@link #reservedNamespaces}. + * + * @param identityMap the {@code IdentityMap} to filter out items contained in reserved namespaces. + */ + private void removeIdentitiesWithReservedNamespaces(final IdentityMap identityMap) { + for (final String namespace : reservedNamespaces) { + if (identityMap.removeAllIdentityItemsForNamespace(namespace)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Updating/Removing identifiers in namespace %s is not allowed.", namespace)); + } + } + } + } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index 4e0b78df..46e46e4c 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -82,4 +82,25 @@ void resetIdentifiers() { // TODO: AMSDK-11208 Use return value to tell IdentityEdge to dispatch consent ad id update } + + /** + * Update the customer identifiers by merging the passed in {@link IdentityMap} with the current identifiers present in {@link #identityProperties}. + * + * @param map the {@link IdentityMap} containing customer identifiers to add or update with the current customer identifiers + */ + void updateCustomerIdentifiers(final IdentityMap map) { + identityProperties.updateCustomerIdentifiers(map); + IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + } + + /** + * Remove customer identifiers specified in passed in {@link IdentityMap} from the current identifiers present in {@link #identityProperties}. + * + * @param map the {@link IdentityMap} with items to remove from current identifiers + */ + void removeCustomerIdentifiers(final IdentityMap map) { + identityProperties.removeCustomerIdentifiers(map); + IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); + } + } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java index 2d515236..fb1b746c 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageService.java @@ -31,6 +31,7 @@ class IdentityEdgeStorageService { /** * Loads identity edge properties from local storage, returns null if not found. + * * @return properties stored in local storage if present, otherwise null. */ static IdentityEdgeProperties loadPropertiesFromPersistence() { @@ -50,8 +51,7 @@ static IdentityEdgeProperties loadPropertiesFromPersistence() { try { final JSONObject jsonObject = new JSONObject(jsonString); final Map propertyMap = Utils.toMap(jsonObject); - final IdentityEdgeProperties loadedProperties = new IdentityEdgeProperties(propertyMap); - return loadedProperties; + return new IdentityEdgeProperties(propertyMap); } catch (JSONException exception) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Serialization error while reading properties jsonString from persistence. Unable to load saved identity properties from persistence."); return null; @@ -60,6 +60,7 @@ static IdentityEdgeProperties loadPropertiesFromPersistence() { /** * Saves the properties to local storage + * * @param properties properties to be stored */ static void savePropertiesToPersistence(final IdentityEdgeProperties properties) { diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index 8f621312..756d36c1 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -39,17 +40,27 @@ public class IdentityMap { /** * Gets the {@link IdentityItem}s for the namespace + * returns an empty list if no {@link IdentityItem}s were found for the namespace * * @param namespace namespace for the id - * @return IdentityItem for the namespace, null if not found + * @return IdentityItem for the namespace, */ - public List getIdentityItemsForNamespace(final String namespace) { - final List items = new ArrayList<>(); - for (IdentityItem item : identityItems.get(namespace)) { - items.add(new IdentityItem((item))); + public List getIdentityItemsForNamespace(final String namespace) { + final List copyItems = new ArrayList<>(); + if(Utils.isNullOrEmpty(namespace)) { + return copyItems; } - return identityItems.get(namespace); + final List items = identityItems.get(namespace); + if (items == null) { + return copyItems; + } + + for (IdentityItem item : items) { + copyItems.add(new IdentityItem((item))); + } + + return copyItems; } /** @@ -57,8 +68,7 @@ public List getIdentityItemsForNamespace(final String namespace) { * with digital experiences. * * @param namespace the namespace integration code or namespace ID of the identity - * @param item {@link IdentityItem} to be added to the namespace - * + * @param item {@link IdentityItem} to be added to the namespace * @see IdentityItem Schema */ public void addItem(final String namespace, final IdentityItem item) { @@ -76,27 +86,41 @@ public void addItem(final String namespace, final IdentityItem item) { addItemToMap(namespace, item); } - private void addItemToMap(final String namespace, final IdentityItem item) { - // check if namespace exists - final List itemList; + /** + * Remove a single {@link IdentityItem} from this map. + * + * @param namespace The {@code IdentityItem} to remove from the given namespace + * @param item {@link IdentityItem} to be added to the namespace + */ + public void removeItem(final String namespace, final IdentityItem item) { + if (item == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap remove item ignored as must contain a non-null IdentityItem."); + return; + } - if (identityItems.containsKey(namespace)) { - itemList = identityItems.get(namespace); - } else { - itemList = new ArrayList<>(); + if (Utils.isNullOrEmpty(namespace)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, + "IdentityMap remove item ignored as must contain a non-null/non-empty namespace."); + return; } - itemList.add(item); - this.identityItems.put(namespace, itemList); + removeItemFromMap(namespace, item); } + // ======================================================================================== + // protected methods + // ======================================================================================== + + /** + * @return a {@link Map} representing this {@link IdentityMap} object + */ Map>> toObjectMap() { - final Map>> map = new HashMap>>(); + final Map>> map = new HashMap<>(); for (String namespace : identityItems.keySet()) { final List> namespaceIds = new ArrayList<>(); - for(IdentityItem identityItem: identityItems.get(namespace)) { + for (IdentityItem identityItem : identityItems.get(namespace)) { namespaceIds.add(identityItem.toObjectMap()); } @@ -106,6 +130,51 @@ Map>> toObjectMap() { return map; } + /** + * Merge the given map on to this {@link IdentityMap}. Any {@link IdentityItem} in map which shares the same + * namespace and id as an item in this {@code IdentityMap} will replace that {@code IdentityItem}. + * + * @param map {@link IdentityMap} to be merged into this object + */ + void merge(final IdentityMap map) { + if (map == null) { + return; + } + + for (final String namespace : map.identityItems.keySet()) { + for (IdentityItem identityItem : map.identityItems.get(namespace)) { + addItem(namespace, identityItem); + } + } + } + + /** + * Remove identities present in passed in map from this {@link IdentityMap}. + * Identities are removed which match the same namespace and id. + * + * @param map Identities to remove from this {@code IdentityMap} + */ + void remove(final IdentityMap map) { + if (map == null) { + return; + } + + for (final String namespace : map.identityItems.keySet()) { + for (IdentityItem identityItem : map.identityItems.get(namespace)) { + removeItem(namespace, identityItem); + } + } + } + + boolean removeAllIdentityItemsForNamespace(final String namespace) { + if (identityItems.containsKey(namespace)) { + identityItems.remove(namespace); + return true; + } + + return false; + } + /** * Use this method to cast the {@code IdentityMap} as eventData for an SDK Event. * @@ -126,10 +195,10 @@ static IdentityMap fromData(Map data) { final IdentityMap identityMap = new IdentityMap(); - for (String namespace : identityMapDict.keySet()) { + for (final String namespace : identityMapDict.keySet()) { try { final ArrayList> idArr = (ArrayList>) identityMapDict.get(namespace); - for (Object idMap: idArr) { + for (Object idMap : idArr) { final IdentityItem item = IdentityItem.fromData((Map) idMap); if (item != null) { identityMap.addItemToMap(namespace, item); @@ -142,4 +211,43 @@ static IdentityMap fromData(Map data) { return identityMap; } + + // ======================================================================================== + // private methods + // ======================================================================================== + private void addItemToMap(final String namespace, final IdentityItem item) { + // check if namespace exists + final List itemList; + + if (identityItems.containsKey(namespace)) { + itemList = identityItems.get(namespace); + } else { + itemList = new ArrayList<>(); + } + + itemList.add(item); + this.identityItems.put(namespace, itemList); + } + + private void removeItemFromMap(final String namespace, final IdentityItem item) { + // check if namespace exists + if (!identityItems.containsKey(namespace)) { + return; + } + + final List itemList = identityItems.get(namespace); + + Iterator it = itemList.iterator(); + while (it.hasNext()) { + IdentityItem eachItem = it.next(); + if (item.equals(eachItem)) { + it.remove(); + break; + } + } + + if(itemList.isEmpty()) { + identityItems.remove(namespace); + } + } } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java new file mode 100644 index 00000000..f2458dbe --- /dev/null +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java @@ -0,0 +1,4 @@ +package com.adobe.marketing.mobile.identityedge; + +public class IdentityMapTests { +} From ce35e5f387e4d87d33a1735e3dfc61bb02c5630d Mon Sep 17 00:00:00 2001 From: pravin Date: Wed, 17 Mar 2021 08:12:57 -0700 Subject: [PATCH 09/19] [AMSDK-11081] - Unit test for IdentityMap and RemoveIdentity Public API --- .../IdentityEdgeExtensionTests.java | 51 +++ .../identityedge/IdentityEdgeTests.java | 86 ++++- .../mobile/identityedge/IdentityMapTests.java | 304 ++++++++++++++++++ 3 files changed, 424 insertions(+), 17 deletions(-) diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index 4e38284a..0036f9ff 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -233,10 +233,61 @@ public void test_handleIdentityResetRequest() { assertTrue(sharedEcid.length() > 0); } + // ======================================================================================== + // handleIdentityRequest + // ======================================================================================== + @Test + public void test_handleUpdateIdentities() throws Exception { + // setup + final String updatedIdentitiesJSON = "{\n" + + " \"identityMap\": {\n" + + " \"ECID\": [\n" + + " {\n" + + " \"id\":" + "randomECID" + ",\n" + + " \"authenticatedState\": \"ambiguous\",\n" + + " \"primary\": true\n" + + " }\n" + + " ],\n" + + " \"USERID\": [\n" + + " {\n" + + " \"id\":" + "someUserID" + ",\n" + + " \"authenticatedState\": \"authenticated\",\n" + + " \"primary\": false\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); + + // test + extension.handleIdentityRequest(buildUpdateIdentityRequest(updatedIdentitiesJSON)); + + + // verify + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchResponseEvent(responseEventCaptor.capture(), requestEventCaptor.capture(), any(ExtensionErrorCallback.class)); + + // verify response event containing ECID is dispatched + Event ecidResponseEvent = responseEventCaptor.getAllValues().get(0); + final IdentityMap identityMap = IdentityMap.fromData(ecidResponseEvent.getEventData()); + final String ecid = identityMap.getIdentityItemsForNamespace("ECID").get(0).getId(); + + assertNotNull(ecid); + assertTrue(ecid.length() > 0); + } + // ======================================================================================== // private helper methods // ======================================================================================== + private Event buildUpdateIdentityRequest(final String jsonStr) throws Exception { + final JSONObject jsonObject = new JSONObject(jsonStr); + final Map xdmData = Utils.toMap(jsonObject); + return new Event.Builder("Update Identity Event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).setEventData(xdmData).build(); + } + + private void setupExistingIdentityEdgeProps(final ECID ecid) { IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); persistedProps.setECID(ecid); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index d76f30e8..ef673277 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -11,6 +11,8 @@ package com.adobe.marketing.mobile.identityedge; +import android.provider.ContactsContract; + import com.adobe.marketing.mobile.AdobeCallback; import com.adobe.marketing.mobile.AdobeCallbackWithError; import com.adobe.marketing.mobile.AdobeError; @@ -157,7 +159,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -171,7 +174,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(null); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -190,7 +193,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -206,7 +210,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(eventData)); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -225,7 +229,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -240,7 +245,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(emptyXDMData)); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -295,10 +300,10 @@ public void testUpdateIdentities() { // verify the dispatched event details Event dispatchedEvent = eventCaptor.getValue(); - assertEquals(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES,dispatchedEvent.getName()); - assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(),dispatchedEvent.getType()); - assertEquals(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY.toLowerCase(),dispatchedEvent.getSource()); - assertEquals(map.asEventData(),dispatchedEvent.getEventData()); + assertEquals(IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, dispatchedEvent.getName()); + assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventSource.UPDATE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + assertEquals(map.asEventData(), dispatchedEvent.getEventData()); } @Test @@ -308,11 +313,55 @@ public void testUpdateIdentitiesNullAndEmptyMap() { IdentityEdge.updateIdentities(map); IdentityEdge.updateIdentities(null); - // verify no of these API calls dispatch an event + // verify none of these API calls dispatch an event PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); MobileCore.dispatchEvent(any(Event.class), any(ExtensionErrorCallback.class)); } + + @Test + public void testRemoveIdentity() { + // setup + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); + IdentityItem sampleItem = new IdentityItem("sample", AuthenticationState.AMBIGUOUS, false); + + // test + IdentityEdge.removeIdentity(sampleItem, "namespace"); + + // verify dispatch event + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); + MobileCore.dispatchEvent(eventCaptor.capture(), extensionErrorCallbackCaptor.capture()); + + Event dispatchedEvent = eventCaptor.getValue(); + assertEquals(IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, dispatchedEvent.getName()); + assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); + assertEquals(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); + IdentityMap sampleInputIdentitymap = new IdentityMap(); + sampleInputIdentitymap.addItem("namespace", sampleItem); + assertEquals(sampleInputIdentitymap.asEventData(), dispatchedEvent.getEventData()); + + // TODO - enable when ExtensionError creation is available + // should not crash on calling the callback + //extensionErrorCallback.error(ExtensionError.UNEXPECTED_ERROR); + } + + @Test + public void testRemoveIdentity_WithInvalidInputs() { + // setup + IdentityItem sampleItem = new IdentityItem("sample", AuthenticationState.AMBIGUOUS, false); + + // test + IdentityEdge.removeIdentity(null, "namespace"); + IdentityEdge.removeIdentity(sampleItem, ""); + IdentityEdge.removeIdentity(sampleItem, null); + + // verify none of these API calls dispatch an event + PowerMockito.verifyStatic(MobileCore.class, Mockito.times(0)); + MobileCore.dispatchEvent(any(Event.class), any(ExtensionErrorCallback.class)); + } + + // ======================================================================================== // getIdentities API // ======================================================================================== @@ -410,7 +459,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -424,7 +474,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(null); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -443,7 +493,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -459,7 +510,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(eventData)); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } @@ -478,7 +529,8 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Object o) { } + public void call(Object o) { + } }; // test @@ -493,7 +545,7 @@ public void call(Object o) { } adobeCallbackCaptor.getValue().call(buildIdentityResponseEvent(emptyXDMData)); // verify - assertTrue((boolean)errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); + assertTrue((boolean) errorCapture.get(KEY_IS_ERRORCALLBACK_CALLED)); assertEquals(AdobeError.UNEXPECTED_ERROR, errorCapture.get(KEY_CAPTUREDERRORCALLBACK)); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java index f2458dbe..c0f92bf1 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java @@ -1,4 +1,308 @@ +/* + Copyright 2021 Adobe. All rights reserved. + This file is licensed to you under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under + the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + OF ANY KIND, either express or implied. See the License for the specific language + governing permissions and limitations under the License. +*/ + package com.adobe.marketing.mobile.identityedge; +import org.json.JSONObject; +import org.junit.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + + public class IdentityMapTests { + + @Test + public void test_AddItem() { + // test + IdentityMap map = new IdentityMap(); + map.addItem("location", new IdentityItem("California")); + + // verify + assertEquals("California", map.toObjectMap().get("location").get(0).get("id")); + } + + @Test + public void test_AddItem_InvalidInputs() { + // test + IdentityMap map = new IdentityMap(); + map.addItem("", new IdentityItem("California")); + map.addItem(null, new IdentityItem("California")); + map.addItem("namespace", null); + + // verify + assertTrue(map.toObjectMap().isEmpty()); + } + + @Test + public void test_getIdentityItemsForNamespace() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + List locationItems = sampleUserMap.getIdentityItemsForNamespace("location"); + + // verify + assertEquals(2, locationItems.size()); + assertEquals("280 Highway Lane", locationItems.get(0).getId()); + assertEquals("California", locationItems.get(1).getId()); + + // verify the login namespace returns 3 items + assertEquals(3, sampleUserMap.getIdentityItemsForNamespace("login").size()); + } + + + @Test + public void test_getIdentityItemsForNamespace_InvalidInputs() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test 1 + List items = sampleUserMap.getIdentityItemsForNamespace(""); + assertEquals(0,items.size()); + + // test 2 + items = sampleUserMap.getIdentityItemsForNamespace(null); + assertEquals(0,items.size()); + + // test 3 + items = sampleUserMap.getIdentityItemsForNamespace("unavailable"); + assertEquals(0,items.size()); + } + + + @Test + public void test_RemoveItem() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + sampleUserMap.removeItem("location", new IdentityItem("California")); + + // verify + assertEquals(1, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + + // test 2 + sampleUserMap.removeItem("location", new IdentityItem("280 Highway Lane")); + sampleUserMap.removeItem("login", new IdentityItem("Student")); + + // verify + assertNull(sampleUserMap.toObjectMap().get("location")); + assertEquals(2, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_RemoveItem_InvalidInputs() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + sampleUserMap.removeItem("", new IdentityItem("California")); + sampleUserMap.removeItem(null, new IdentityItem("California")); + sampleUserMap.removeItem("location",null); + + // verify the existing identityMap is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + + @Test + public void test_merge() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + IdentityMap newMap = new IdentityMap(); + newMap.addItem("location", new IdentityItem("doorNumber:544")); + sampleUserMap.merge(newMap); + + // verify the existing identityMap is unchanged + assertEquals(3, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + @Test + public void test_merge_EmptyIdentityMap() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" + + // test + sampleUserMap.merge(new IdentityMap()); + + // verify the existing identityMap is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_merge_nullMap() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + + // test + sampleUserMap.merge(null); + + // verify the existing identityMap is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + @Test + public void test_removeAllIdentityItemsForNamespace() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + + // test + sampleUserMap.removeAllIdentityItemsForNamespace("location"); + + // verify the existing identityMap is unchanged + assertNull(sampleUserMap.toObjectMap().get("location")); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + @Test + public void test_removeAllIdentityItemsForNamespace_InvalidNamespace() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + + // test + sampleUserMap.removeAllIdentityItemsForNamespace(null); + sampleUserMap.removeAllIdentityItemsForNamespace(""); + + // verify + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_removeAllIdentityItemsForNamespace_onEmptyMap() { + // setup + IdentityMap emptyMap = new IdentityMap(); + + // test + emptyMap.removeAllIdentityItemsForNamespace("location"); + assertTrue(emptyMap.toObjectMap().isEmpty()); + } + + @Test + public void test_remove() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + IdentityMap tobeRemovedMap = new IdentityMap(); + tobeRemovedMap.addItem("login", new IdentityItem("Student")); + tobeRemovedMap.addItem("location", new IdentityItem("California")); + + // test + sampleUserMap.remove(tobeRemovedMap); + + // verify + assertEquals(1, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(2, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_remove_NullAndEmptyMap() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + + // test + sampleUserMap.remove(null); + sampleUserMap.remove(new IdentityMap()); + + // verify that the existing map is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + @Test + public void test_remove_nonexistentNamespaceAndItems() { + // setup + IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" + IdentityMap tobeRemovedMap = new IdentityMap(); + tobeRemovedMap.addItem("nonexistentNamespace", new IdentityItem("California")); + tobeRemovedMap.addItem("login", new IdentityItem("nonexistentID")); + + // test + sampleUserMap.remove(tobeRemovedMap); + + // verify that the existing map is unchanged + assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); + assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); + } + + + @Test + public void test_FromData() throws Exception { + // setup + final String jsonStr = "{\n" + + " \"identityMap\": {\n" + + " \"ECID\": [\n" + + " {\n" + + " \"id\":" + "randomECID" + ",\n" + + " \"authenticatedState\": \"ambiguous\",\n" + + " \"primary\": true\n" + + " }\n" + + " ],\n" + + " \"USERID\": [\n" + + " {\n" + + " \"id\":" + "someUserID" + ",\n" + + " \"authenticatedState\": \"authenticated\",\n" + + " \"primary\": false\n" + + " }\n" + + " ]\n" + + " }\n" + + "}"; + + final JSONObject jsonObject = new JSONObject(jsonStr); + final Map xdmData = Utils.toMap(jsonObject); + + // test + IdentityMap map = IdentityMap.fromData(xdmData); + + //Map flattenedMap = IdentityEdgeTestUtil.flattenMap(map.asEventData()); + + + + } + + + private IdentityMap buildSampleIdentityMap(){ + // User Login Identity Items + IdentityItem email = new IdentityItem("john@doe", AuthenticationState.AUTHENTICATED, true); + IdentityItem userName = new IdentityItem("John Doe", AuthenticationState.AUTHENTICATED, false); + IdentityItem accountType = new IdentityItem("Student", AuthenticationState.AUTHENTICATED, false); + + // User Location Address Identity Items + IdentityItem street = new IdentityItem("280 Highway Lane", AuthenticationState.AMBIGUOUS, false); + IdentityItem state = new IdentityItem("California", AuthenticationState.AMBIGUOUS, false); + + IdentityMap adobeIdentityMap = new IdentityMap(); + adobeIdentityMap.addItem("login", email); + adobeIdentityMap.addItem("login", userName); + adobeIdentityMap.addItem("login", accountType); + + adobeIdentityMap.addItem("location", street); + adobeIdentityMap.addItem("location", state); + return adobeIdentityMap; + } + } From 6d142faca182e9ddfef02890b1ad121fdf9297f2 Mon Sep 17 00:00:00 2001 From: pravin Date: Wed, 17 Mar 2021 16:49:58 -0700 Subject: [PATCH 10/19] [AMSDK-11081] - More Unit test for update/Remove --- .../identityedge/IdentityEdgeConstants.java | 2 + .../identityedge/IdentityEdgeExtension.java | 20 +- .../identityedge/IdentityEdgeProperties.java | 8 +- .../mobile/identityedge/IdentityItem.java | 19 +- .../mobile/identityedge/IdentityMap.java | 4 + .../identityedge/IdentityEdgeTestUtil.java | 101 +++++++- .../IdentityEdgeExtensionTests.java | 221 +++++++++++++----- .../mobile/identityedge/IdentityMapTests.java | 57 ++++- 8 files changed, 339 insertions(+), 93 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java index b8c44f36..110324fc 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeConstants.java @@ -60,6 +60,8 @@ private Namespaces() { } final class XDMKeys { static final String IDENTITY_MAP = "identityMap"; static final String ID = "id"; + static final String AUTHENTICATED_STATE = "authenticatedState"; + static final String PRIMARY = "primary"; private XDMKeys() { } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index d5a4e9a3..0725bf24 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -91,6 +91,10 @@ protected String getVersion() { * @param event the edge update identity {@link Event} */ void handleUpdateIdentities(final Event event) { + if (!canProcessEvents()) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process update identity event. canProcessEvents returned false."); + return; + } final Map eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners final IdentityMap map = IdentityMap.fromData(eventData); if (map == null) { @@ -102,13 +106,16 @@ void handleUpdateIdentities(final Event event) { updateIdentityXDMSharedState(event); } - /** * Handles remove identity requests to remove customer identifiers. * * @param event the edge remove identity request {@link Event} */ void handleRemoveIdentity(final Event event) { + if (!canProcessEvents()) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process remove identity event. canProcessEvents returned false."); + return; + } final Map eventData = event.getEventData(); // do not need to null check on eventData, as they are done on listeners final IdentityMap map = IdentityMap.fromData(eventData); if (map == null) { @@ -129,7 +136,10 @@ void handleGenericIdentityRequest(final Event event) { * @param event the identity request event */ void handleIdentityRequest(final Event event) { - if (!canProcessEvents(event)) { return; } + if (!canProcessEvents()) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request reset event. canProcessEvents returned false."); + return; + } Map xdmData = state.getIdentityEdgeProperties().toXDMData(true); Event responseEvent = new Event.Builder(IdentityEdgeConstants.EventNames.IDENTITY_RESPONSE_CONTENT_ONE_TIME, @@ -154,7 +164,7 @@ public void error(ExtensionError extensionError) { * @param event the identity request reset event */ void handleRequestReset(final Event event) { - if (!canProcessEvents(event)) { + if (!canProcessEvents()) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to process request reset event. canProcessEvents returned false."); return; } @@ -187,10 +197,10 @@ public void error(final ExtensionError extensionError) { /** * Determines if Identity Edge is ready to handle events, this is determined by if the Identity Edge extension has booted up - * @param event An {@link Event} + * * @return True if we can process events, false otherwise */ - private boolean canProcessEvents(final Event event) { + private boolean canProcessEvents() { if (state.hasBooted()) { return true; } // we have booted, return true final ExtensionApi extensionApi = super.getApi(); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 0022b85d..ca2afef7 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -64,6 +64,8 @@ class IdentityEdgeProperties { */ void setECID(final ECID ecid) { this.ecid = ecid; + IdentityItem ecidItem = new IdentityItem(ecid.toString(), AuthenticationState.AMBIGUOUS, true); + identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID ,ecidItem); } /** @@ -116,12 +118,6 @@ void removeCustomerIdentifiers(final IdentityMap map) { */ Map toXDMData(final boolean allowEmpty) { final Map map = new HashMap<>(); - final IdentityMap identityMap = new IdentityMap(); - - if (ecid != null) { - IdentityItem ecidItem = new IdentityItem(ecid.toString()); - identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecidItem); - } final Map>> dict = identityMap.toObjectMap(); if (dict != null && (!dict.isEmpty() || allowEmpty)) { diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java index 20c46bc7..67fa8898 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java @@ -27,9 +27,6 @@ public final class IdentityItem { private boolean primary; private static final String LOG_TAG = "IdentityItem"; - private static final String JSON_KEY_ID = "id"; - private static final String JSON_KEY_AUTHENTICATION_STATE = "authenticatedState"; - private static final String JSON_KEY_PRIMARY = "primary"; /** * Creates a new {@link IdentityItem} @@ -75,16 +72,16 @@ public IdentityItem(final IdentityItem item) { Map toObjectMap() { Map map = new HashMap<>(); if (id != null) { - map.put(JSON_KEY_ID, id); + map.put(IdentityEdgeConstants.XDMKeys.ID, id); } if (authenticationState != null) { - map.put(JSON_KEY_AUTHENTICATION_STATE, authenticationState.toString()); + map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, authenticationState.toString()); } else { - map.put(JSON_KEY_AUTHENTICATION_STATE, AuthenticationState.AMBIGUOUS.toString()); + map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, AuthenticationState.AMBIGUOUS.toString()); } - map.put(JSON_KEY_PRIMARY, primary); + map.put(IdentityEdgeConstants.XDMKeys.PRIMARY, primary); return map; } @@ -118,15 +115,15 @@ static IdentityItem fromData(final Map data) { if (data == null) { return null; } try { - final String id = (String) data.get(JSON_KEY_ID); - AuthenticationState authenticationState = AuthenticationState.fromString((String) data.get(JSON_KEY_AUTHENTICATION_STATE)); + final String id = (String) data.get(IdentityEdgeConstants.XDMKeys.ID); + AuthenticationState authenticationState = AuthenticationState.fromString((String) data.get(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE)); if (authenticationState == null) { authenticationState = AuthenticationState.AMBIGUOUS; } boolean primary = false; - if (data.get(JSON_KEY_PRIMARY) != null) { - primary = (boolean) data.get(JSON_KEY_PRIMARY); + if (data.get(IdentityEdgeConstants.XDMKeys.PRIMARY) != null) { + primary = (boolean) data.get(IdentityEdgeConstants.XDMKeys.PRIMARY); } return new IdentityItem(id, authenticationState, primary); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index 756d36c1..274a9c36 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -184,6 +184,10 @@ Map asEventData() { return new HashMap(identityItems); } + boolean isEmpty() { + return identityItems.isEmpty(); + } + static IdentityMap fromData(Map data) { if (data == null) { return null; diff --git a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java index 1a0db1f4..bf6ece73 100644 --- a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java +++ b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java @@ -11,6 +11,7 @@ package com.adobe.marketing.mobile.identityedge; +import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; import com.fasterxml.jackson.databind.JsonNode; @@ -19,19 +20,97 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ValueNode; +import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; + +/** + * Util class used by both Functional and Unit tests + */ class IdentityEdgeTestUtil { + + /** + * Helper method to create IdentityXDM Map using {@link TestItem}s + */ + static Map createXDMIdentityMap(TestItem... items) { + final Map>> allItems = new HashMap<>(); + for (TestItem item : items) { + final Map itemMap = new HashMap<>(); + itemMap.put(IdentityEdgeConstants.XDMKeys.ID, item.id); + itemMap.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, "ambiguous"); + itemMap.put(IdentityEdgeConstants.XDMKeys.PRIMARY, false); + List> nameSpaceItems = allItems.get(item.namespace); + if (nameSpaceItems == null) { + nameSpaceItems = new ArrayList<>(); + } + nameSpaceItems.add(itemMap); + allItems.put(item.namespace, nameSpaceItems); + } + + final Map identityMapDict = new HashMap<>(); + identityMapDict.put(IdentityEdgeConstants.XDMKeys.IDENTITY_MAP, allItems); + return identityMapDict; + } + + /** + * Helper method to build remove identity request event with XDM formatted Identity jsonString + */ + static Event buildRemoveIdentityRequestWithJSONString(final String jsonStr) throws Exception { + final JSONObject jsonObject = new JSONObject(jsonStr); + final Map xdmData = Utils.toMap(jsonObject); + return buildRemoveIdentityRequest(xdmData); + } + + /** + * Helper method to build remove identity request event with XDM formatted Identity map + */ + static Event buildRemoveIdentityRequest(final Map map) { + return new Event.Builder("Remove Identity Event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).setEventData(map).build(); + } + + /** + * Helper method to build update identity request event with XDM formatted Identity jsonString + */ + static Event buildUpdateIdentityRequestJSONString(final String jsonStr) throws Exception { + final JSONObject jsonObject = new JSONObject(jsonStr); + final Map xdmData = Utils.toMap(jsonObject); + return buildUpdateIdentityRequest(xdmData); + } + + /** + * Helper method to build update identity request event with XDM formatted Identity map + */ + static Event buildUpdateIdentityRequest(final Map map) { + return new Event.Builder("Update Identity Event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).setEventData(map).build(); + } + + + /** + * Serialize the given {@code jsonString} to a JSON Object, then flattens to {@code Map}. + * If the provided string is not in JSON structure an {@link JSONException} is thrown. + * + * @param jsonString the string in JSON structure to flatten + * @return new map with flattened structure + */ + static Map flattenJSONString(final String jsonString) throws JSONException { + JSONObject jsonObject = new JSONObject(jsonString); + Map persistenceValueMap = Utils.toMap(jsonObject); + return flattenMap(persistenceValueMap); + } + /** * Serialize the given {@code map} to a JSON Object, then flattens to {@code Map}. * For example, a JSON such as "{xdm: {stitchId: myID, eventType: myType}}" is flattened * to two map elements "xdm.stitchId" = "myID" and "xdm.eventType" = "myType". + * * @param map map with JSON structure to flatten * @return new map with flattened structure */ @@ -57,14 +136,13 @@ static Map flattenMap(final Map map) { * Deserialize {@code JsonNode} and flatten to provided {@code map}. * For example, a JSON such as "{xdm: {stitchId: myID, eventType: myType}}" is flattened * to two map elements "xdm.stitchId" = "myID" and "xdm.eventType" = "myType". - * + *

    * Method is called recursively. To use, call with an empty path such as * {@code addKeys("", new ObjectMapper().readTree(JsonNodeAsString), map);} * * @param currentPath the path in {@code JsonNode} to process - * @param jsonNode {@link JsonNode} to deserialize - * @param map {@code Map} instance to store flattened JSON result - * + * @param jsonNode {@link JsonNode} to deserialize + * @param map {@code Map} instance to store flattened JSON result * @see Stack Overflow post */ private static void addKeys(String currentPath, JsonNode jsonNode, Map map) { @@ -89,4 +167,19 @@ private static void addKeys(String currentPath, JsonNode jsonNode, Map sharedState = sharedStateCaptor.getValue(); - String sharedEcid = ecidFromIdentityMap(sharedState); - assertTrue(sharedEcid.length() > 0); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertTrue(sharedState.get("identityMap.ECID[0].id").length() > 0); } // ======================================================================================== @@ -239,55 +239,176 @@ public void test_handleIdentityResetRequest() { @Test public void test_handleUpdateIdentities() throws Exception { // setup - final String updatedIdentitiesJSON = "{\n" + - " \"identityMap\": {\n" + - " \"ECID\": [\n" + - " {\n" + - " \"id\":" + "randomECID" + ",\n" + - " \"authenticatedState\": \"ambiguous\",\n" + - " \"primary\": true\n" + - " }\n" + - " ],\n" + - " \"USERID\": [\n" + - " {\n" + - " \"id\":" + "someUserID" + ",\n" + - " \"authenticatedState\": \"authenticated\",\n" + - " \"primary\": false\n" + - " }\n" + - " ]\n" + - " }\n" + - "}"; - final ArgumentCaptor responseEventCaptor = ArgumentCaptor.forClass(Event.class); - final ArgumentCaptor requestEventCaptor = ArgumentCaptor.forClass(Event.class); + Map identityXDM = createXDMIdentityMap( + new TestItem("UserId", "secretID") + ); + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); + // test - extension.handleIdentityRequest(buildUpdateIdentityRequest(updatedIdentitiesJSON)); + Event updateIdentityEvent = buildUpdateIdentityRequest(identityXDM); + extension.handleUpdateIdentities(updateIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(updateIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertEquals("secretID", sharedState.get("identityMap.UserId[0].id")); + assertEquals("AMBIGUOUS", sharedState.get("identityMap.UserId[0].authenticatedState")); + assertEquals("false", sharedState.get("identityMap.UserId[0].primary")); + + // verify persistence + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); + assertEquals("secretID", persistedData.get("identityMap.UserId[0].id")); + assertEquals("AMBIGUOUS", persistedData.get("identityMap.UserId[0].authenticatedState")); + assertEquals("false", persistedData.get("identityMap.UserId[0].primary")); + } - // verify - PowerMockito.verifyStatic(MobileCore.class, Mockito.times(1)); - MobileCore.dispatchResponseEvent(responseEventCaptor.capture(), requestEventCaptor.capture(), any(ExtensionErrorCallback.class)); + @Test + public void test_handleUpdateIdentities_DoNotUpdateReservedNamespace() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("ECID", "somevalue"), + new TestItem("GAID", "somevalue"), + new TestItem("IDFA", "somevalue"), + new TestItem("UserId", "somevalue") + ); - // verify response event containing ECID is dispatched - Event ecidResponseEvent = responseEventCaptor.getAllValues().get(0); - final IdentityMap identityMap = IdentityMap.fromData(ecidResponseEvent.getEventData()); - final String ecid = identityMap.getIdentityItemsForNamespace("ECID").get(0).getId(); + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); - assertNotNull(ecid); - assertTrue(ecid.length() > 0); + + // test + Event updateIdentityEvent = buildUpdateIdentityRequest(identityXDM); + extension.handleUpdateIdentities(updateIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(updateIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertEquals(6, sharedState.size()); // 6 represents id, primary and authState of USERID identifier and generated ECID + assertEquals("somevalue", sharedState.get("identityMap.UserId[0].id")); + assertNotEquals("somevalue", sharedState.get("identityMap.ECID[0].id")); + + // verify persistence + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); + assertEquals(6, persistedData.size()); // 3 represents id, primary and authState of USERID identifier and generated ECID + assertEquals("somevalue", persistedData.get("identityMap.UserId[0].id")); } + + @Test + public void test_handleUpdateIdentities_nullEventData() { + // test + Event updateIdentityEvent = buildUpdateIdentityRequest(null); + extension.handleUpdateIdentities(updateIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), any(Event.class), any(ExtensionErrorCallback.class)); + + // verify persistence + verify(mockSharedPreferenceEditor, times(1)).putString(anyString(), anyString()); // called only once while generating ECID + } + + // ======================================================================================== - // private helper methods + // handleRemoveRequest // ======================================================================================== + @Test + public void test_handleRemoveIdentity() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("UserId", "secretID"), + new TestItem("PushId", "token") + ); + JSONObject identityXDMJSON = new JSONObject(identityXDM); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); + + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); + extension = new IdentityEdgeExtension(mockExtensionApi); - private Event buildUpdateIdentityRequest(final String jsonStr) throws Exception { - final JSONObject jsonObject = new JSONObject(jsonStr); - final Map xdmData = Utils.toMap(jsonObject); - return new Event.Builder("Update Identity Event", IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.UPDATE_IDENTITY).setEventData(xdmData).build(); + // test + Map removedIdentityXDM = createXDMIdentityMap( + new TestItem("UserId", "secretID") + ); + Event removeIdentityEvent = buildRemoveIdentityRequest(removedIdentityXDM); + extension.handleRemoveIdentity(removeIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(removeIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertNull(sharedState.get("identityMap.UserId[0].id")); + assertEquals("token", sharedState.get("identityMap.PushId[0].id")); + + + // verify persistence + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); + assertNull(persistedData.get("identityMap.UserId[0].id")); + assertEquals("token", persistedData.get("identityMap.PushId[0].id")); } + @Test + public void test_handleRemoveIdentity_DoNotRemoveReservedNamespace() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("GAID", "someGAID") + ); + JSONObject identityXDMJSON = new JSONObject(identityXDM); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); + extension = new IdentityEdgeExtension(mockExtensionApi); + + // test + Map removedIdentityXDM = createXDMIdentityMap( + new TestItem("GAID", "someGAID") + ); + Event removeIdentityEvent = buildRemoveIdentityRequest(removedIdentityXDM); + extension.handleRemoveIdentity(removeIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(removeIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertEquals("someGAID", sharedState.get("identityMap.GAID[0].id")); + + + // verify persistence + verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); + assertEquals("someGAID", sharedState.get("identityMap.GAID[0].id")); + } + + + @Test + public void test_handleRemoveIdentity_NullData() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("PushId", "token") + ); + JSONObject identityXDMJSON = new JSONObject(identityXDM); + Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); + extension = new IdentityEdgeExtension(mockExtensionApi); + + // test + Event removeIdentityEvent = buildRemoveIdentityRequest(null); + extension.handleRemoveIdentity(removeIdentityEvent); + + // verify shared state + verify(mockExtensionApi, times(0)).setXDMSharedEventState(any(Map.class), eq(removeIdentityEvent), any(ExtensionErrorCallback.class)); + + // verify persistence + verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), anyString()); + } + + + // ======================================================================================== + // private helper methods + // ======================================================================================== private void setupExistingIdentityEdgeProps(final ECID ecid) { IdentityEdgeProperties persistedProps = new IdentityEdgeProperties(); persistedProps.setECID(ecid); @@ -296,17 +417,5 @@ private void setupExistingIdentityEdgeProps(final ECID ecid) { Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(propsJSON); } - private String ecidFromIdentityMap(Map xdmMap) { - if (xdmMap == null) { return null; } - Map identityMap = (HashMap) xdmMap.get("identityMap"); - if (identityMap == null) { return null; } - List ecidArr = (ArrayList) identityMap.get("ECID"); - if (ecidArr == null) { return null; } - Map ecidDict = (HashMap) ecidArr.get(0); - if (ecidDict == null) { return null; } - String ecid = (String) ecidDict.get("id"); - return ecid; - } - } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java index c0f92bf1..95a160ac 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java @@ -29,7 +29,7 @@ public class IdentityMapTests { public void test_AddItem() { // test IdentityMap map = new IdentityMap(); - map.addItem("location", new IdentityItem("California")); + map.addItem("location", new IdentityItem("California")); // verify assertEquals("California", map.toObjectMap().get("location").get(0).get("id")); @@ -39,8 +39,8 @@ public void test_AddItem() { public void test_AddItem_InvalidInputs() { // test IdentityMap map = new IdentityMap(); - map.addItem("", new IdentityItem("California")); - map.addItem(null, new IdentityItem("California")); + map.addItem("", new IdentityItem("California")); + map.addItem(null, new IdentityItem("California")); map.addItem("namespace", null); // verify @@ -72,15 +72,15 @@ public void test_getIdentityItemsForNamespace_InvalidInputs() { // test 1 List items = sampleUserMap.getIdentityItemsForNamespace(""); - assertEquals(0,items.size()); + assertEquals(0, items.size()); // test 2 items = sampleUserMap.getIdentityItemsForNamespace(null); - assertEquals(0,items.size()); + assertEquals(0, items.size()); // test 3 items = sampleUserMap.getIdentityItemsForNamespace("unavailable"); - assertEquals(0,items.size()); + assertEquals(0, items.size()); } @@ -113,7 +113,7 @@ public void test_RemoveItem_InvalidInputs() { // test sampleUserMap.removeItem("", new IdentityItem("California")); sampleUserMap.removeItem(null, new IdentityItem("California")); - sampleUserMap.removeItem("location",null); + sampleUserMap.removeItem("location", null); // verify the existing identityMap is unchanged assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); @@ -121,7 +121,6 @@ public void test_RemoveItem_InvalidInputs() { } - @Test public void test_merge() { // setup @@ -129,7 +128,7 @@ public void test_merge() { // test IdentityMap newMap = new IdentityMap(); - newMap.addItem("location", new IdentityItem("doorNumber:544")); + newMap.addItem("location", new IdentityItem("doorNumber:544")); sampleUserMap.merge(newMap); // verify the existing identityMap is unchanged @@ -278,14 +277,50 @@ public void test_FromData() throws Exception { // test IdentityMap map = IdentityMap.fromData(xdmData); - //Map flattenedMap = IdentityEdgeTestUtil.flattenMap(map.asEventData()); + // verify + Map flattenedMap = IdentityEdgeTestUtil.flattenMap(map.asEventData()); + assertEquals("randomECID", flattenedMap.get("ECID[0].id")); + assertEquals("AMBIGUOUS", flattenedMap.get("ECID[0].authenticationState")); + assertEquals("true", flattenedMap.get("ECID[0].primary")); + assertEquals("someUserID", flattenedMap.get("USERID[0].id")); + assertEquals("AUTHENTICATED", flattenedMap.get("USERID[0].authenticationState")); + assertEquals("false", flattenedMap.get("USERID[0].primary")); + } + + + @Test + public void test_FromData_NullAndEmptyData() { + assertNull(IdentityMap.fromData(null)); + assertNull(IdentityMap.fromData(new HashMap())); + } + + @Test + public void test_FromData_InvalidXDMData() throws Exception { + // setup + // ECID is map instead of list + final String invalidJsonStr = "{\n" + + " \"identityMap\": {\n" + + " \"ECID\": {\n" + + " \"id\": \"randomECID\",\n" + + " \"authenticatedState\": \"ambiguous\",\n" + + " \"primary\": true\n" + + " }\n" + + " }\n" + + "}"; + + final JSONObject jsonObject = new JSONObject(invalidJsonStr); + final Map xdmData = Utils.toMap(jsonObject); + // test + IdentityMap map = IdentityMap.fromData(xdmData); + // verify + assertTrue(map.isEmpty()); } - private IdentityMap buildSampleIdentityMap(){ + private IdentityMap buildSampleIdentityMap() { // User Login Identity Items IdentityItem email = new IdentityItem("john@doe", AuthenticationState.AUTHENTICATED, true); IdentityItem userName = new IdentityItem("John Doe", AuthenticationState.AUTHENTICATED, false); From fd8aa6c315be4b95e0fc4d0a46501946f775e379 Mon Sep 17 00:00:00 2001 From: pravin Date: Wed, 17 Mar 2021 16:58:50 -0700 Subject: [PATCH 11/19] [AMSDK-11081] - Few more edits to unittests --- .../IdentityEdgePropertiesTests.java | 35 ++++++++----------- .../identityedge/IdentityEdgeStateTests.java | 10 ++++++ .../IdentityEdgeStorageServiceTests.java | 1 - 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java index 5ddcf3f8..7ecb34df 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java @@ -13,11 +13,9 @@ import org.junit.Test; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import static com.adobe.marketing.mobile.identityedge.IdentityEdgeTestUtil.flattenMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -45,7 +43,7 @@ public void testIdentityEdgeProperties_toXDMDataFull() { Map xdmData = props.toXDMData(false); // verify - assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmData)); + assertEquals(props.getECID().toString(), flattenMap(xdmData).get("identityMap.ECID[0].id")); } @Test @@ -57,7 +55,7 @@ public void testIdentityEdgeProperties_toXDMDataMissingECID() { Map xdmData = props.toXDMData(false); // verify - assertNull(ecidFromIdentityMap(xdmData)); + assertNull(flattenMap(xdmData).get("identityMap.ECID[0].id")); } @Test @@ -70,7 +68,7 @@ public void testIdentityEdgeProperties_toXDMDataMissingPrivacy() { Map xdmData = props.toXDMData(false); // verify - assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmData)); + assertEquals(props.getECID().toString(), flattenMap(xdmData).get("identityMap.ECID[0].id")); } @Test @@ -84,7 +82,7 @@ public void testIdentityEdgeProperties_fromXDMDataFull() { IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(xdmData); // verify - assertEquals(ecidFromIdentityMap(xdmData), loadedProps.getECID().toString()); + assertEquals(loadedProps.getECID().toString(), flattenMap(xdmData).get("identityMap.ECID[0].id")); } @Test @@ -111,7 +109,7 @@ public void testIdentityEdgeProperties_fromXDMDataMissingPrivacy() { IdentityEdgeProperties loadedProps = new IdentityEdgeProperties(xdmMap); // verify - assertEquals(ecidFromIdentityMap(xdmMap), loadedProps.getECID().toString()); + assertEquals(props.getECID().toString(), flattenMap(xdmMap).get("identityMap.ECID[0].id")); } @Test @@ -124,19 +122,16 @@ public void testIdentityEdgeProperties_toXDMDataWithECID() { Map xdmMap = props.toXDMData(false); // verify - assertEquals(props.getECID().toString(), ecidFromIdentityMap(xdmMap)); + assertEquals(props.getECID().toString(), flattenMap(xdmMap).get("identityMap.ECID[0].id")); } - private String ecidFromIdentityMap(Map xdmMap) { - if (xdmMap == null) { return null; } - Map identityMap = (HashMap) xdmMap.get("identityMap"); - if (identityMap == null) { return null; } - List ecidArr = (ArrayList) identityMap.get("ECID"); - if (ecidArr == null) { return null; } - Map ecidDict = (HashMap) ecidArr.get(0); - if (ecidDict == null) { return null; } - String ecid = (String) ecidDict.get("id"); - return ecid; - } + // ====================================================================================================================== + // Tests for "updateCustomerIdentifiers" is already covered in "handleUpdateRequest" tests in IdentityEdgeExtensionTests + // ====================================================================================================================== + + + // ====================================================================================================================== + // Tests for "removeCustomerIdentifiers" is already covered in handleRemoveRequest tests in IdentityEdgeExtensionTests + // ====================================================================================================================== } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java index c926e9cf..b98c910a 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java @@ -113,4 +113,14 @@ public void testIdentityEdgeState_resetIdentifiers() { assertFalse(state.getIdentityEdgeProperties().getECID().toString().isEmpty()); // ECID should not be empty verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); // should save to data store } + + + // ====================================================================================================================== + // Tests for "updateCustomerIdentifiers" is already covered in "handleUpdateRequest" tests in IdentityEdgeExtensionTests + // ====================================================================================================================== + + + // ====================================================================================================================== + // Tests for "removeCustomerIdentifiers" is already covered in handleRemoveRequest tests in IdentityEdgeExtensionTests + // ====================================================================================================================== } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java index d21121a2..67b45fe1 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStorageServiceTests.java @@ -11,7 +11,6 @@ package com.adobe.marketing.mobile.identityedge; - import android.app.Application; import android.content.Context; import android.content.SharedPreferences; From b10ff0ebfbadd0a3864ee373a35ae8c2bbe1195e Mon Sep 17 00:00:00 2001 From: pravin Date: Wed, 17 Mar 2021 17:20:15 -0700 Subject: [PATCH 12/19] [AMSDK-11081] - better naming and typo fixes --- .../mobile/identityedge/IdentityEdge.java | 12 +++++----- .../identityedge/IdentityEdgeExtension.java | 22 ++++++++++++------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 7c49ef0c..1ae88388 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -109,7 +109,7 @@ public void call(Event responseEvent) { * @param identityMap The identifiers to add or update. */ public static void updateIdentities(final IdentityMap identityMap) { - if (identityMap == null || identityMap.toObjectMap().isEmpty()) { + if (identityMap == null || identityMap.isEmpty()) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to updateIdentities, IdentityMap is null or empty"); return; } @@ -138,13 +138,13 @@ public void error(final ExtensionError extensionError) { * @param namespace The namespace the identity to remove is under. */ public static void removeIdentity(final IdentityItem item, final String namespace) { - if (namespace == null || namespace.isEmpty()) { + if (Utils.isNullOrEmpty(namespace)) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to removeIdentity, namespace is null or empty"); return; } if (item == null) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to updateIdentities, IdentityItem is null"); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Unable to removeIdentity, IdentityItem is null"); return; } @@ -154,16 +154,16 @@ public static void removeIdentity(final IdentityItem item, final String namespac final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Update Identities API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.UPDATE_IDENTITIES, + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("removeIdentity API. Failed to dispatch %s event: Error : %s.", IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, extensionError.getErrorName())); } }; - final Event updateIdentitiesEvent = new Event.Builder(IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, + final Event removeIdentitiesEvent = new Event.Builder(IdentityEdgeConstants.EventNames.REMOVE_IDENTITIES, IdentityEdgeConstants.EventType.EDGE_IDENTITY, IdentityEdgeConstants.EventSource.REMOVE_IDENTITY).setEventData(identityMap.asEventData()).build(); - MobileCore.dispatchEvent(updateIdentitiesEvent, errorCallback); + MobileCore.dispatchEvent(removeIdentitiesEvent, errorCallback); } /** diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index 0725bf24..7b5316d1 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -23,7 +23,7 @@ class IdentityEdgeExtension extends Extension { - private final String LOG_TAG = "IdentityEdgeExtension"; + private static final String LOG_TAG = "IdentityEdgeExtension"; private IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); /** @@ -69,6 +69,7 @@ public void error(final ExtensionError extensionError) { /** * Required override. Each extension must have a unique name within the application. + * * @return unique name of this extension */ @Override @@ -78,6 +79,7 @@ protected String getName() { /** * Optional override. + * * @return the version of this extension */ @Override @@ -103,7 +105,7 @@ void handleUpdateIdentities(final Event event) { } state.updateCustomerIdentifiers(map); - updateIdentityXDMSharedState(event); + shareIdentityXDMSharedState(event); } /** @@ -124,7 +126,7 @@ void handleRemoveIdentity(final Event event) { } state.removeCustomerIdentifiers(map); - updateIdentityXDMSharedState(event); + shareIdentityXDMSharedState(event); } void handleGenericIdentityRequest(final Event event) { @@ -133,6 +135,7 @@ void handleGenericIdentityRequest(final Event event) { /** * Handles events requesting for identifiers. Dispatches response event containing the identifiers. Called by listener registered with event hub. + * * @param event the identity request event */ void handleIdentityRequest(final Event event) { @@ -161,6 +164,7 @@ public void error(ExtensionError extensionError) { /** * Handles IdentityEdge request reset events. + * * @param event the identity request reset event */ void handleRequestReset(final Event event) { @@ -169,7 +173,7 @@ void handleRequestReset(final Event event) { return; } state.resetIdentifiers(); - updateIdentityXDMSharedState(event); + shareIdentityXDMSharedState(event); } /** @@ -177,9 +181,9 @@ void handleRequestReset(final Event event) { * * @param event the {@link Event} that triggered the XDM shared state change */ - void updateIdentityXDMSharedState(final Event event) { + void shareIdentityXDMSharedState(final Event event) { final ExtensionApi extensionApi = super.getApi(); - if (extensionApi == null ) { + if (extensionApi == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to share XDM shared state for reset identities"); return; } @@ -201,10 +205,12 @@ public void error(final ExtensionError extensionError) { * @return True if we can process events, false otherwise */ private boolean canProcessEvents() { - if (state.hasBooted()) { return true; } // we have booted, return true + if (state.hasBooted()) { + return true; + } // we have booted, return true final ExtensionApi extensionApi = super.getApi(); - if (extensionApi == null ) { + if (extensionApi == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to process events"); return false; } From b45302dfd77c51545411583ecbcc8c3c3d189b5b Mon Sep 17 00:00:00 2001 From: pravin Date: Thu, 18 Mar 2021 23:55:17 -0700 Subject: [PATCH 13/19] [AMSDK-11081] - rearrange parameters, setECID handling, case-insensitive search and more --- .../mobile/identityedge/IdentityEdge.java | 2 +- .../identityedge/IdentityEdgeExtension.java | 17 +-- .../identityedge/IdentityEdgeProperties.java | 71 +++++---- .../identityedge/IdentityEdgeState.java | 4 +- .../mobile/identityedge/IdentityMap.java | 92 +++++++---- .../identityedge/IdentityEdgeTestUtil.java | 17 ++- .../IdentityEdgeExtensionTests.java | 6 +- .../IdentityEdgePropertiesTests.java | 143 +++++++++++++++++- .../identityedge/IdentityEdgeTests.java | 6 +- .../mobile/identityedge/IdentityMapTests.java | 40 ++--- 10 files changed, 295 insertions(+), 103 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 1ae88388..90cb57c6 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -149,7 +149,7 @@ public static void removeIdentity(final IdentityItem item, final String namespac } IdentityMap identityMap = new IdentityMap(); - identityMap.addItem(namespace, item); + identityMap.addItem(item,namespace); final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index 15ccc67c..de079118 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -71,6 +71,7 @@ public void error(final ExtensionError extensionError) { /** * Required override. Each extension must have a unique name within the application. + * * @return unique name of this extension */ @Override @@ -80,6 +81,7 @@ protected String getName() { /** * Optional override. + * * @return the version of this extension */ @Override @@ -137,6 +139,7 @@ void handleGenericIdentityRequest(final Event event) { * Handles events of type {@code com.adobe.eventType.hub} and source {@code com.adobe.eventSource.sharedState}. * If the state change event is for the direct Identity extension, get the direct Identity shared state and attempt * to update the legacy ECID with the direct Identity extension ECID. + * * @param event an event of type {@code com.adobe.eventType.hub} and source {@code com.adobe.eventSource.sharedState} */ void handleHubSharedState(final Event event) { @@ -146,7 +149,7 @@ void handleHubSharedState(final Event event) { } final ExtensionApi extensionApi = getApi(); - if (extensionApi == null ) { + if (extensionApi == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to process direct Identity shared state change event."); return; } @@ -183,13 +186,7 @@ public void error(final ExtensionError extensionError) { final ECID legacyEcid = legacyEcidString == null ? null : new ECID(legacyEcidString); if (state.updateLegacyExperienceCloudId(legacyEcid)) { - final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed to create XDM shared state. Error : %s.", extensionError.getErrorName())); - } - }; - extensionApi.setXDMSharedEventState(state.getIdentityEdgeProperties().toXDMData(false), event, errorCallback); + shareIdentityXDMSharedState(event); } } catch (ClassCastException e) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Could not process direct Identity shared state change event, failed to parse stored ECID as String: " + e.getLocalizedMessage()); @@ -199,7 +196,7 @@ public void error(final ExtensionError extensionError) { /** * Handles events requesting for identifiers. Dispatches response event containing the identifiers. Called by listener registered with event hub. * - * @param event the identity request event + * @param event the identity request {@link Event} */ void handleIdentityRequest(final Event event) { if (!canProcessEvents()) { @@ -228,7 +225,7 @@ public void error(ExtensionError extensionError) { /** * Handles IdentityEdge request reset events. * - * @param event the identity request reset event + * @param event the identity request reset {@link Event} */ void handleRequestReset(final Event event) { if (!canProcessEvents()) { diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 9beee98c..645e900b 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -33,11 +33,12 @@ class IdentityEdgeProperties { // The current Experience Cloud ID private ECID ecid; - private IdentityMap identityMap = new IdentityMap(); // A secondary (non-primary) Experience Cloud ID private ECID ecidSecondary; + private IdentityMap identityMap = new IdentityMap(); + IdentityEdgeProperties() { } /** @@ -67,18 +68,28 @@ class IdentityEdgeProperties { /** * Sets the current {@link ECID} * - * @param ecid the new {@link ECID} + * @param newEcid the new {@code ECID} */ - void setECID(final ECID ecid) { - this.ecid = ecid; - IdentityItem ecidItem = new IdentityItem(ecid.toString(), AuthenticationState.AMBIGUOUS, true); - identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID ,ecidItem); + void setECID(final ECID newEcid) { + // delete the previous ECID from the identity map if exist + if (ecid != null) { + final IdentityItem previousECIDItem = new IdentityItem(ecid.toString()); + identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); + } + + // And add the new primary ECID to Identity map + if (newEcid != null) { + final IdentityItem newECIDItem = new IdentityItem(newEcid.toString(), AuthenticationState.AMBIGUOUS, false); + identityMap.addItem(newECIDItem, IdentityEdgeConstants.Namespaces.ECID, true); + } + + this.ecid = newEcid; // keep the local variable up to date } /** * Retrieves the current {@link ECID} * - * @return current {@link ECID} + * @return current {@code ECID} */ ECID getECID() { return ecid; @@ -86,14 +97,28 @@ ECID getECID() { /** * Sets a secondary {@link ECID} - * @param ecid a new secondary {@code ECID} + * + * @param newSecondaryEcid a new secondary {@code ECID} */ - void setECIDSecondary(final ECID ecid) { - this.ecidSecondary = ecid; + void setECIDSecondary(final ECID newSecondaryEcid) { + // delete the previous secondary ECID from the identity map if exist + if (ecidSecondary != null) { + final IdentityItem previousECIDItem = new IdentityItem(ecidSecondary.toString()); + identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); + } + + // add the new secondary ECID to Identity map + if (newSecondaryEcid != null) { + final IdentityItem newSecondaryECIDItem = new IdentityItem(newSecondaryEcid.toString(), AuthenticationState.AMBIGUOUS, false); + identityMap.addItem(newSecondaryECIDItem, IdentityEdgeConstants.Namespaces.ECID); + } + + this.ecidSecondary = newSecondaryEcid; // keep the local variable up to date } /** * Retrieves the secondary {@link ECID}. + * * @return secondary {@code ECID} */ ECID getECIDSecondary() { @@ -103,15 +128,15 @@ ECID getECIDSecondary() { /** * Update the customer identifiers by merging the passed in {@link IdentityMap} with the current identifiers. *

    - * Any identifier in the passed in {@link IdentityMap} which has the same id in the same namespace will update the current identifier. - * Any new identifier in the passed in {@link IdentityMap} will be added to the current identifiers + * Any identifier in the passed in {@code IdentityMap} which has the same id in the same namespace will update the current identifier. + * Any new identifier in the passed in {@code IdentityMap} will be added to the current identifiers * Certain namespaces are not allowed to be modified and if exist in the given customer identifiers will be removed before the update operation is executed. * The namespaces which cannot be modified through this function call include: * - ECID * - IDFA * - GAID * - * @param map the {@link IdentityMap} containing customer identifiers to add or update with the current customer identifiers + * @param map the {@code IdentityMap} containing customer identifiers to add or update with the current customer identifiers */ void updateCustomerIdentifiers(final IdentityMap map) { removeIdentitiesWithReservedNamespaces(map); @@ -126,7 +151,7 @@ void updateCustomerIdentifiers(final IdentityMap map) { * - IDFA * - GAID * - * @param map the {@link IdentityMap} with items to remove from current identifiers + * @param map the {@code IdentityMap} with items to remove from current identifiers */ void removeCustomerIdentifiers(final IdentityMap map) { removeIdentitiesWithReservedNamespaces(map); @@ -141,18 +166,6 @@ void removeCustomerIdentifiers(final IdentityMap map) { */ Map toXDMData(final boolean allowEmpty) { final Map map = new HashMap<>(); - final IdentityMap identityMap = new IdentityMap(); - - if (ecid != null) { - final IdentityItem ecidItem = new IdentityItem(ecid.toString()); - identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecidItem); - - // set second ECID only if primary exists - if (ecidSecondary != null) { - final IdentityItem ecidSecondaryItem = new IdentityItem(ecidSecondary.toString()); - identityMap.addItem(IdentityEdgeConstants.Namespaces.ECID, ecidSecondaryItem); - } - } final Map>> dict = identityMap.toObjectMap(); if (dict != null && (!dict.isEmpty() || allowEmpty)) { @@ -169,9 +182,9 @@ Map toXDMData(final boolean allowEmpty) { * @param identityMap the {@code IdentityMap} to filter out items contained in reserved namespaces. */ private void removeIdentitiesWithReservedNamespaces(final IdentityMap identityMap) { - for (final String namespace : reservedNamespaces) { - if (identityMap.removeAllIdentityItemsForNamespace(namespace)) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Updating/Removing identifiers in namespace %s is not allowed.", namespace)); + for (final String reservedNamespace : reservedNamespaces) { + if (identityMap.removeAllIdentityItemsForNamespace(reservedNamespace)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Updating/Removing identifiers in namespace %s is not allowed.", reservedNamespace)); } } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index 08e86f17..3e5678ec 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -94,7 +94,7 @@ void resetIdentifiers() { /** * Update the customer identifiers by merging the passed in {@link IdentityMap} with the current identifiers present in {@link #identityProperties}. * - * @param map the {@link IdentityMap} containing customer identifiers to add or update with the current customer identifiers + * @param map the {@code IdentityMap} containing customer identifiers to add or update with the current customer identifiers */ void updateCustomerIdentifiers(final IdentityMap map) { identityProperties.updateCustomerIdentifiers(map); @@ -104,7 +104,7 @@ void updateCustomerIdentifiers(final IdentityMap map) { /** * Remove customer identifiers specified in passed in {@link IdentityMap} from the current identifiers present in {@link #identityProperties}. * - * @param map the {@link IdentityMap} with items to remove from current identifiers + * @param map the {@code IdentityMap} with items to remove from current identifiers */ void removeCustomerIdentifiers(final IdentityMap map) { identityProperties.removeCustomerIdentifiers(map); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index 274a9c36..42336dd1 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -16,9 +16,9 @@ import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.TreeMap; /** * Defines a map containing a set of end user identities, keyed on either namespace integration @@ -31,11 +31,10 @@ @SuppressWarnings("unused") public class IdentityMap { private static final String LOG_TAG = "IdentityMap"; - private Map> identityItems; IdentityMap() { - identityItems = new HashMap<>(); + identityItems = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); } /** @@ -57,21 +56,35 @@ public List getIdentityItemsForNamespace(final String namespace) } for (IdentityItem item : items) { - copyItems.add(new IdentityItem((item))); + copyItems.add(new IdentityItem(item)); } return copyItems; } + /** * Add an identity item which is used to clearly distinguish entities that are interacting * with digital experiences. * + * @param item {@link IdentityItem} to be added to the namespace * @param namespace the namespace integration code or namespace ID of the identity + * @see IdentityItem Schema + */ + public void addItem(final IdentityItem item, final String namespace) { + addItem(item, namespace, false); + } + + /** + * Add an identity item which is used to clearly distinguish entities that are interacting + * with digital experiences. + * * @param item {@link IdentityItem} to be added to the namespace + * @param namespace the namespace integration code or namespace ID of the identity + * @param isFirstItem on {@code true} keeps the provided {@code IdentityItem} as the first element of the identity list for this namespace * @see IdentityItem Schema */ - public void addItem(final String namespace, final IdentityItem item) { + public void addItem(final IdentityItem item, final String namespace, final boolean isFirstItem) { if (item == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null IdentityItem."); return; @@ -83,16 +96,18 @@ public void addItem(final String namespace, final IdentityItem item) { return; } - addItemToMap(namespace, item); + addItemToMap(item, namespace, isFirstItem); } + + /** * Remove a single {@link IdentityItem} from this map. * - * @param namespace The {@code IdentityItem} to remove from the given namespace * @param item {@link IdentityItem} to be added to the namespace + * @param namespace The {@code IdentityItem} to remove from the given namespace */ - public void removeItem(final String namespace, final IdentityItem item) { + public void removeItem(final IdentityItem item, final String namespace) { if (item == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap remove item ignored as must contain a non-null IdentityItem."); return; @@ -104,7 +119,16 @@ public void removeItem(final String namespace, final IdentityItem item) { return; } - removeItemFromMap(namespace, item); + removeItemFromMap(item, namespace); + } + + /** + * Determines if this {@link IdentityMap} has no identities. + * + * @return {@code true} if this {@code IdentityMap} contains no identifiers + */ + public boolean isEmpty() { + return identityItems.isEmpty(); } // ======================================================================================== @@ -143,7 +167,7 @@ void merge(final IdentityMap map) { for (final String namespace : map.identityItems.keySet()) { for (IdentityItem identityItem : map.identityItems.get(namespace)) { - addItem(namespace, identityItem); + addItem(identityItem, namespace); } } } @@ -161,18 +185,21 @@ void remove(final IdentityMap map) { for (final String namespace : map.identityItems.keySet()) { for (IdentityItem identityItem : map.identityItems.get(namespace)) { - removeItem(namespace, identityItem); + removeItem(identityItem, namespace); } } } + /** + * Removes all the {@link IdentityItem} on this {@link IdentityMap} linked to the specified namespace + * + * @return a {@code boolean} representing a successful removal of all {@code IdentityItem} in a provided namespace + */ boolean removeAllIdentityItemsForNamespace(final String namespace) { - if (identityItems.containsKey(namespace)) { - identityItems.remove(namespace); - return true; + if(namespace == null){ + return false; } - - return false; + return (identityItems.remove(namespace) != null) ; } /** @@ -184,10 +211,12 @@ Map asEventData() { return new HashMap(identityItems); } - boolean isEmpty() { - return identityItems.isEmpty(); - } + /** + * Creates an {@link IdentityMap} from the + * + * @return {@link Map} representation of IdentityMap + */ static IdentityMap fromData(Map data) { if (data == null) { return null; @@ -205,7 +234,7 @@ static IdentityMap fromData(Map data) { for (Object idMap : idArr) { final IdentityItem item = IdentityItem.fromData((Map) idMap); if (item != null) { - identityMap.addItemToMap(namespace, item); + identityMap.addItemToMap(item, namespace, false); } } } catch (ClassCastException e) { @@ -219,7 +248,7 @@ static IdentityMap fromData(Map data) { // ======================================================================================== // private methods // ======================================================================================== - private void addItemToMap(final String namespace, final IdentityItem item) { + private void addItemToMap(final IdentityItem item, final String namespace, final boolean keepAsFirstItem) { // check if namespace exists final List itemList; @@ -229,26 +258,23 @@ private void addItemToMap(final String namespace, final IdentityItem item) { itemList = new ArrayList<>(); } - itemList.add(item); - this.identityItems.put(namespace, itemList); + if ((keepAsFirstItem)) { + itemList.add(0, item); + } else { + itemList.add(item); + } + + identityItems.put(namespace, itemList); } - private void removeItemFromMap(final String namespace, final IdentityItem item) { + private void removeItemFromMap(final IdentityItem item, final String namespace) { // check if namespace exists if (!identityItems.containsKey(namespace)) { return; } final List itemList = identityItems.get(namespace); - - Iterator it = itemList.iterator(); - while (it.hasNext()) { - IdentityItem eachItem = it.next(); - if (item.equals(eachItem)) { - it.remove(); - break; - } - } + itemList.remove(item); if(itemList.isEmpty()) { identityItems.remove(namespace); diff --git a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java index bf6ece73..f3891c5b 100644 --- a/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java +++ b/code/identityedge/src/sharedTestUtils/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTestUtil.java @@ -46,7 +46,7 @@ static Map createXDMIdentityMap(TestItem... items) { final Map itemMap = new HashMap<>(); itemMap.put(IdentityEdgeConstants.XDMKeys.ID, item.id); itemMap.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, "ambiguous"); - itemMap.put(IdentityEdgeConstants.XDMKeys.PRIMARY, false); + itemMap.put(IdentityEdgeConstants.XDMKeys.PRIMARY, item.isPrimary); List> nameSpaceItems = allItems.get(item.namespace); if (nameSpaceItems == null) { nameSpaceItems = new ArrayList<>(); @@ -175,6 +175,7 @@ private static void addKeys(String currentPath, JsonNode jsonNode, Map sharedState = flattenMap(sharedStateCaptor.getValue()); - assertEquals(6, sharedState.size()); // 6 represents id, primary and authState of USERID identifier and generated ECID + assertEquals(3, sharedState.size()); // 6 represents id, primary and authState of USERID identifier and generated ECID assertEquals("somevalue", sharedState.get("identityMap.UserId[0].id")); assertNotEquals("somevalue", sharedState.get("identityMap.ECID[0].id")); // verify persistence verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); - assertEquals(6, persistedData.size()); // 3 represents id, primary and authState of USERID identifier and generated ECID + assertEquals(3, persistedData.size()); // 3 represents id, primary and authState of USERID identifier and generated ECID assertEquals("somevalue", persistedData.get("identityMap.UserId[0].id")); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java index f3591342..1111724e 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java @@ -15,9 +15,8 @@ import java.util.Map; -import static com.adobe.marketing.mobile.identityedge.IdentityEdgeTestUtil.flattenMap; +import static com.adobe.marketing.mobile.identityedge.IdentityEdgeTestUtil.*; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; public class IdentityEdgePropertiesTests { @@ -144,6 +143,146 @@ public void testIdentityEdgeProperties_toXDMDataWithECID() { assertEquals(props.getECID().toString(), flattenMap(xdmMap).get("identityMap.ECID[0].id")); } + + // ====================================================================================================================== + // Tests for constructor : IdentityEdgeProperties(final Map xdmData) + // ====================================================================================================================== + + @Test + public void testConstruct_IdentityEdgeProperties_LoadingDataFromPersistence() { + // setup + Map persistedIdentifiers = createXDMIdentityMap( + new TestItem("UserId", "secretID"), + new TestItem("PushId", "token"), + new TestPrimaryECIDItem("primaryECID"), + new TestSecondaryECIDItem("secondaryECID") + ); + + // test + IdentityEdgeProperties props = new IdentityEdgeProperties(persistedIdentifiers); + + // verify + Map flatMap = flattenMap(props.toXDMData(false)); + assertEquals(12,flatMap.size()); // 4x3 + assertEquals("primaryECID", props.getECID().toString()); + assertEquals("secondaryECID", props.getECIDSecondary().toString()); + assertEquals("secretID", flatMap.get("identityMap.UserId[0].id")); + assertEquals("token", flatMap.get("identityMap.PushId[0].id")); + } + + @Test + public void testConstruct_IdentityEdgeProperties_NothingFromPersistence() { + // test + IdentityEdgeProperties props = new IdentityEdgeProperties(null); + Map xdmMap = props.toXDMData(false); + + // verify + Map flatMap = flattenMap(xdmMap); + assertEquals(0,flatMap.size()); + } + + + // ====================================================================================================================== + // Tests for method : setECID(final ECID newEcid) + // ====================================================================================================================== + + @Test + public void test_setECID_WillReplaceTheOldECID() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + + // test 1 + props.setECID(new ECID("primary")); + + // verify + Map flatMap = flattenMap(props.toXDMData(false)); + assertEquals(3,flatMap.size()); + assertEquals("primary", flatMap.get("identityMap.ECID[0].id")); + assertEquals("true", flatMap.get("identityMap.ECID[0].primary")); + assertEquals("primary", props.getECID().toString()); + + // test 2 - call setECID again to replace the old one + props.setECID(new ECID("primaryAgain")); + + // verify + flatMap = flattenMap(props.toXDMData(false)); + assertEquals(3,flatMap.size()); + assertEquals("primaryAgain", flatMap.get("identityMap.ECID[0].id")); + assertEquals("true", flatMap.get("identityMap.ECID[0].primary")); + assertEquals("primaryAgain", props.getECID().toString()); + } + + @Test + public void test_setECID_NullRemovesFromIdentityMap() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + + // test 1 - set a valid ECID and then to null + props.setECID(new ECID("primary")); + props.setECID(null); + + // verify + Map flatMap = flattenMap(props.toXDMData(false)); + assertEquals(0,flatMap.size()); + assertNull(props.getECID()); + } + + + + // ====================================================================================================================== + // Tests for method : setECIDSecondary(final ECID newEcid) + // ====================================================================================================================== + + @Test + public void test_setECIDSecondary_WillReplaceTheOldECID() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(); + + // test 1 + props.setECIDSecondary(new ECID("secondary")); + + // verify + Map flatMap = flattenMap(props.toXDMData(false)); + assertEquals(3,flatMap.size()); + assertEquals("secondary", flatMap.get("identityMap.ECID[0].id")); + assertEquals("false", flatMap.get("identityMap.ECID[0].primary")); + assertEquals("secondary", props.getECIDSecondary().toString()); + + // test 2 - call setECIDSecondary again to replace the old one + props.setECIDSecondary(new ECID("secondaryAgain")); + + // verify + flatMap = flattenMap(props.toXDMData(false)); + assertEquals(3,flatMap.size()); + assertEquals("secondaryAgain", flatMap.get("identityMap.ECID[0].id")); + assertEquals("false", flatMap.get("identityMap.ECID[0].primary")); + assertEquals("secondaryAgain", props.getECIDSecondary().toString()); + } + + + @Test + public void test_setECIDSecondary_NullRemovesFromIdentityMap() { + // setup + IdentityEdgeProperties props = new IdentityEdgeProperties(createXDMIdentityMap( + new TestSecondaryECIDItem("secondaryECID") + )); + + // test - set secondary ECID to null + props.setECIDSecondary(null); + + // verify + Map flatMap = flattenMap(props.toXDMData(false)); + assertEquals(0,flatMap.size()); + assertNull(props.getECIDSecondary()); + } + + + + + + + + // ====================================================================================================================== // Tests for "updateCustomerIdentifiers" is already covered in "handleUpdateRequest" tests in IdentityEdgeExtensionTests // ====================================================================================================================== diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index ef673277..6f149e1e 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -286,8 +286,8 @@ public void testUpdateIdentities() { // test IdentityMap map = new IdentityMap(); - map.addItem("mainspace", new IdentityItem("id", AuthenticationState.AUTHENTICATED, true)); - map.addItem("secondspace", new IdentityItem("idtwo", AuthenticationState.LOGGED_OUT, false)); + map.addItem(new IdentityItem("id", AuthenticationState.AUTHENTICATED, true),"mainspace"); + map.addItem(new IdentityItem("idtwo", AuthenticationState.LOGGED_OUT, false),"secondspace"); IdentityEdge.updateIdentities(map); // verify @@ -338,7 +338,7 @@ public void testRemoveIdentity() { assertEquals(IdentityEdgeConstants.EventType.EDGE_IDENTITY.toLowerCase(), dispatchedEvent.getType()); assertEquals(IdentityEdgeConstants.EventSource.REMOVE_IDENTITY.toLowerCase(), dispatchedEvent.getSource()); IdentityMap sampleInputIdentitymap = new IdentityMap(); - sampleInputIdentitymap.addItem("namespace", sampleItem); + sampleInputIdentitymap.addItem(sampleItem,"namespace"); assertEquals(sampleInputIdentitymap.asEventData(), dispatchedEvent.getEventData()); // TODO - enable when ExtensionError creation is available diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java index 95a160ac..44e8af4d 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java @@ -29,7 +29,7 @@ public class IdentityMapTests { public void test_AddItem() { // test IdentityMap map = new IdentityMap(); - map.addItem("location", new IdentityItem("California")); + map.addItem(new IdentityItem("California"),"location"); // verify assertEquals("California", map.toObjectMap().get("location").get(0).get("id")); @@ -39,9 +39,9 @@ public void test_AddItem() { public void test_AddItem_InvalidInputs() { // test IdentityMap map = new IdentityMap(); - map.addItem("", new IdentityItem("California")); - map.addItem(null, new IdentityItem("California")); - map.addItem("namespace", null); + map.addItem( new IdentityItem("California"),""); + map.addItem(new IdentityItem("California"),null); + map.addItem( null,"namespace"); // verify assertTrue(map.toObjectMap().isEmpty()); @@ -90,15 +90,15 @@ public void test_RemoveItem() { IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" // test - sampleUserMap.removeItem("location", new IdentityItem("California")); + sampleUserMap.removeItem( new IdentityItem("California"),"location"); // verify assertEquals(1, sampleUserMap.toObjectMap().get("location").size()); assertEquals(3, sampleUserMap.toObjectMap().get("login").size()); // test 2 - sampleUserMap.removeItem("location", new IdentityItem("280 Highway Lane")); - sampleUserMap.removeItem("login", new IdentityItem("Student")); + sampleUserMap.removeItem(new IdentityItem("280 Highway Lane"),"location"); + sampleUserMap.removeItem(new IdentityItem("Student"), "login"); // verify assertNull(sampleUserMap.toObjectMap().get("location")); @@ -111,9 +111,9 @@ public void test_RemoveItem_InvalidInputs() { IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "Location", 3 items with namespace "Login" // test - sampleUserMap.removeItem("", new IdentityItem("California")); - sampleUserMap.removeItem(null, new IdentityItem("California")); - sampleUserMap.removeItem("location", null); + sampleUserMap.removeItem(new IdentityItem("California"),""); + sampleUserMap.removeItem(new IdentityItem("California"),null); + sampleUserMap.removeItem(null,"location"); // verify the existing identityMap is unchanged assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); @@ -128,7 +128,7 @@ public void test_merge() { // test IdentityMap newMap = new IdentityMap(); - newMap.addItem("location", new IdentityItem("doorNumber:544")); + newMap.addItem(new IdentityItem("doorNumber:544"),"location"); sampleUserMap.merge(newMap); // verify the existing identityMap is unchanged @@ -207,8 +207,8 @@ public void test_remove() { // setup IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" IdentityMap tobeRemovedMap = new IdentityMap(); - tobeRemovedMap.addItem("login", new IdentityItem("Student")); - tobeRemovedMap.addItem("location", new IdentityItem("California")); + tobeRemovedMap.addItem(new IdentityItem("Student"),"login"); + tobeRemovedMap.addItem(new IdentityItem("California"),"location"); // test sampleUserMap.remove(tobeRemovedMap); @@ -237,8 +237,8 @@ public void test_remove_nonexistentNamespaceAndItems() { // setup IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" IdentityMap tobeRemovedMap = new IdentityMap(); - tobeRemovedMap.addItem("nonexistentNamespace", new IdentityItem("California")); - tobeRemovedMap.addItem("login", new IdentityItem("nonexistentID")); + tobeRemovedMap.addItem(new IdentityItem("California"),"nonexistentNamespace"); + tobeRemovedMap.addItem(new IdentityItem("nonexistentID"),"login"); // test sampleUserMap.remove(tobeRemovedMap); @@ -331,12 +331,12 @@ private IdentityMap buildSampleIdentityMap() { IdentityItem state = new IdentityItem("California", AuthenticationState.AMBIGUOUS, false); IdentityMap adobeIdentityMap = new IdentityMap(); - adobeIdentityMap.addItem("login", email); - adobeIdentityMap.addItem("login", userName); - adobeIdentityMap.addItem("login", accountType); + adobeIdentityMap.addItem(email,"login"); + adobeIdentityMap.addItem(userName, "login"); + adobeIdentityMap.addItem(accountType, "login"); - adobeIdentityMap.addItem("location", street); - adobeIdentityMap.addItem("location", state); + adobeIdentityMap.addItem(street, "location"); + adobeIdentityMap.addItem(state,"location"); return adobeIdentityMap; } From ea6e55df4b9adf4438940d7791df0959cea28144 Mon Sep 17 00:00:00 2001 From: pravin Date: Fri, 19 Mar 2021 03:19:56 -0700 Subject: [PATCH 14/19] [AMSDK-11081] - Caseinsensitive removal of reserved namespace items + cleanup --- .../identityedge/IdentityEdgeExtension.java | 38 +++++++++------ .../identityedge/IdentityEdgeProperties.java | 8 ++-- .../mobile/identityedge/IdentityMap.java | 22 +++++++-- .../IdentityEdgeExtensionTests.java | 47 +++++++++++++++---- .../identityedge/IdentityEdgeStateTests.java | 5 ++ .../mobile/identityedge/IdentityMapTests.java | 8 ++-- 6 files changed, 92 insertions(+), 36 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java index b623a828..9949e660 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtension.java @@ -150,12 +150,6 @@ void handleHubSharedState(final Event event) { return; } - final ExtensionApi extensionApi = getApi(); - if (extensionApi == null) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to process direct Identity shared state change event."); - return; - } - if (event == null || event.getEventData() == null) { return; } @@ -170,16 +164,11 @@ void handleHubSharedState(final Event event) { return; } - final ExtensionErrorCallback getSharedStateCallback = new ExtensionErrorCallback() { - @Override - public void error(final ExtensionError extensionError) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed getting direct Identity shared state. Error : %s.", extensionError.getErrorName())); - } - }; - final Map identityState = extensionApi.getSharedEventState(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT, event, getSharedStateCallback); + final Map identityState = getSharedState(IdentityEdgeConstants.SharedStateKeys.IDENTITY_DIRECT, event); if (identityState == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Could not process direct Identity shared state change event, Identity shared state is null"); return; } @@ -238,12 +227,33 @@ void handleRequestReset(final Event event) { shareIdentityXDMSharedState(event); } + /** + * Retrieves the shared state for the given state owner + * + * @param stateOwner the state owner for the requested shared state + * @param event the {@link Event} for which is shared state is to be retrieved + */ + private Map getSharedState(final String stateOwner, final Event event) { + final ExtensionApi extensionApi = getApi(); + if (extensionApi == null) { + return null; + } + final ExtensionErrorCallback getSharedStateCallback = new ExtensionErrorCallback() { + @Override + public void error(final ExtensionError extensionError) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Failed getting direct Identity shared state. Error : %s.", extensionError.getErrorName())); + } + }; + + return extensionApi.getSharedEventState(stateOwner, event, getSharedStateCallback); + } + /** * Fetches the latest Identity Edge properties and shares the EdgeIdentity's XDMSharedState. * * @param event the {@link Event} that triggered the XDM shared state change */ - void shareIdentityXDMSharedState(final Event event) { + private void shareIdentityXDMSharedState(final Event event) { final ExtensionApi extensionApi = super.getApi(); if (extensionApi == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "ExtensionApi is null, unable to share XDM shared state for reset identities"); diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 7c63f96f..5a85a5f3 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -79,10 +79,10 @@ void setECID(final ECID newEcid) { identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); } - + // if primary ecid is null, clear off all the existing ECID's if (newEcid == null) { - setECIDSecondary(newEcid); - identityMap.removeAllIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + setECIDSecondary(null); + identityMap.clearItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); } else { // And add the new primary Ecid as a first element of Identity map final IdentityItem newECIDItem = new IdentityItem(newEcid.toString(), AuthenticationState.AMBIGUOUS, false); @@ -196,7 +196,7 @@ Map toXDMData(final boolean allowEmpty) { */ private void removeIdentitiesWithReservedNamespaces(final IdentityMap identityMap) { for (final String reservedNamespace : reservedNamespaces) { - if (identityMap.removeAllIdentityItemsForNamespace(reservedNamespace)) { + if (identityMap.clearItemsForNamespace(reservedNamespace)) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, String.format("Updating/Removing identifiers in namespace %s is not allowed.", reservedNamespace)); } } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index 6691c8f6..7f88e8e0 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -31,7 +31,7 @@ @SuppressWarnings("unused") public class IdentityMap { private static final String LOG_TAG = "IdentityMap"; - private final Map> identityItems = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final Map> identityItems = new HashMap<>(); /** * Gets the {@link IdentityItem}s for the namespace @@ -187,15 +187,29 @@ void remove(final IdentityMap map) { } /** - * Removes all the {@link IdentityItem} on this {@link IdentityMap} linked to the specified namespace + * Removes all the {@link IdentityItem} on this {@link IdentityMap} linked to the specified namespace (case insensitive) * * @return a {@code boolean} representing a successful removal of all {@code IdentityItem} in a provided namespace */ - boolean removeAllIdentityItemsForNamespace(final String namespace) { + boolean clearItemsForNamespace(final String namespace) { if(namespace == null){ return false; } - return (identityItems.remove(namespace) != null) ; + + boolean isRemoved = false; + final List filteredNamespaces = new ArrayList<>(); + for(final String eachNamespace : identityItems.keySet()) { + if (namespace.equalsIgnoreCase(eachNamespace)) { + isRemoved = true; + filteredNamespaces.add(eachNamespace); + } + } + + for (final String eachNamespace : filteredNamespaces) { + identityItems.remove(eachNamespace); + } + + return isRemoved; } /** diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index ca9f8fc9..f43cd3d8 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -262,7 +262,7 @@ public void test_handleHubSharedState_updateLegacyEcidOnDirectIdentityStateChang final ArgumentCaptor> sharedStateCaptor = ArgumentCaptor.forClass(Map.class); verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(event), any(ExtensionErrorCallback.class)); Map sharedState = sharedStateCaptor.getValue(); - assertEquals("1234", flattenMap(sharedState).get("identityMap.ECID[0].id")); + assertEquals("1234", flattenMap(sharedState).get("identityMap.ECID[1].id")); // Legacy ECID is set as a secondary ECID } @Test @@ -411,7 +411,6 @@ public void test_handleUpdateIdentities_DoNotUpdateReservedNamespace() throws Ex final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); final ArgumentCaptor persistenceValueCaptor = ArgumentCaptor.forClass(String.class); - // test Event updateIdentityEvent = buildUpdateIdentityRequest(identityXDM); extension.handleUpdateIdentities(updateIdentityEvent); @@ -419,18 +418,38 @@ public void test_handleUpdateIdentities_DoNotUpdateReservedNamespace() throws Ex // verify shared state verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(updateIdentityEvent), any(ExtensionErrorCallback.class)); Map sharedState = flattenMap(sharedStateCaptor.getValue()); - assertEquals(3, sharedState.size()); // 6 represents id, primary and authState of USERID identifier and generated ECID + assertEquals(6, sharedState.size()); // 6 represents id, primary and authState of USERID identifier and generated ECID assertEquals("somevalue", sharedState.get("identityMap.UserId[0].id")); - assertNotEquals("somevalue", sharedState.get("identityMap.ECID[0].id")); + assertNotEquals("somevalue", sharedState.get("identityMap.ECID[0].id")); // verify that the ECID is not disturbed // verify persistence verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); - assertEquals(3, persistedData.size()); // 3 represents id, primary and authState of USERID identifier and generated ECID + assertEquals(6, persistedData.size()); // 3 represents id, primary and authState of USERID identifier and generated ECID assertEquals("somevalue", persistedData.get("identityMap.UserId[0].id")); + assertNotEquals("somevalue", persistedData.get("identityMap.ECID[0].id")); // verify that the ECID is not disturbed } + @Test + public void test_handleUpdateIdentities_CaseSensitiveNamespace_OnCustomIdentifiers() throws Exception { + // setup + Map identityXDM = createXDMIdentityMap( + new TestItem("pushToken", "somevalue"), + new TestItem("PUSHTOKEN", "SOMEVALUE") + ); + Event updateIdentityEvent = buildUpdateIdentityRequest(identityXDM); + extension.handleUpdateIdentities(updateIdentityEvent); + + final ArgumentCaptor sharedStateCaptor = ArgumentCaptor.forClass(Map.class); + + // verify shared state + verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(updateIdentityEvent), any(ExtensionErrorCallback.class)); + Map sharedState = flattenMap(sharedStateCaptor.getValue()); + assertEquals("somevalue", sharedState.get("identityMap.pushToken[0].id")); + assertEquals("SOMEVALUE", sharedState.get("identityMap.PUSHTOKEN[0].id")); + } + @Test public void test_handleUpdateIdentities_nullEventData() { // test @@ -487,7 +506,9 @@ public void test_handleRemoveIdentity() throws Exception { public void test_handleRemoveIdentity_DoNotRemoveReservedNamespace() throws Exception { // setup Map identityXDM = createXDMIdentityMap( - new TestItem("GAID", "someGAID") + new TestItem("GAID", "someGAID"), + new TestItem("ECID", "someECID"), + new TestItem("IDFA", "someIDFA") ); JSONObject identityXDMJSON = new JSONObject(identityXDM); Mockito.when(mockSharedPreference.getString(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES, null)).thenReturn(identityXDMJSON.toString()); @@ -498,7 +519,9 @@ public void test_handleRemoveIdentity_DoNotRemoveReservedNamespace() throws Exce // test Map removedIdentityXDM = createXDMIdentityMap( - new TestItem("GAID", "someGAID") + new TestItem("GAID", "someGAID"), + new TestItem("ecid", "someECID"), + new TestItem("Idfa", "someIDFA") ); Event removeIdentityEvent = buildRemoveIdentityRequest(removedIdentityXDM); extension.handleRemoveIdentity(removeIdentityEvent); @@ -507,12 +530,16 @@ public void test_handleRemoveIdentity_DoNotRemoveReservedNamespace() throws Exce verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(removeIdentityEvent), any(ExtensionErrorCallback.class)); Map sharedState = flattenMap(sharedStateCaptor.getValue()); assertEquals("someGAID", sharedState.get("identityMap.GAID[0].id")); + assertEquals("someECID", sharedState.get("identityMap.ECID[0].id")); + assertEquals("someIDFA", sharedState.get("identityMap.IDFA[0].id")); // verify persistence - verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); - Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); - assertEquals("someGAID", sharedState.get("identityMap.GAID[0].id")); + verify(mockSharedPreferenceEditor, times(1)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); + Map persistedData = flattenJSONString(persistenceValueCaptor.getValue()); + assertEquals("someGAID", persistedData.get("identityMap.GAID[0].id")); + assertEquals("someECID", persistedData.get("identityMap.ECID[0].id")); + assertEquals("someIDFA", persistedData.get("identityMap.IDFA[0].id")); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java index 1dd8a8ed..a052abd3 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeStateTests.java @@ -190,10 +190,13 @@ public void testIdentityEdgeState_resetIdentifiers() { @Test public void testIdentityEdgeState_updateLegacyExperienceCloudId() { IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + state.getIdentityEdgeProperties().setECID(new ECID()); ECID legacyEcid = new ECID(); + // test state.updateLegacyExperienceCloudId(legacyEcid); + // verify assertEquals(legacyEcid, state.getIdentityEdgeProperties().getECIDSecondary()); verify(mockSharedPreferenceEditor, Mockito.times(1)).apply(); } @@ -206,6 +209,7 @@ public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenECIDSa state.updateLegacyExperienceCloudId(legacyEcid); + // verify assertNull(state.getIdentityEdgeProperties().getECIDSecondary()); verify(mockSharedPreferenceEditor, Mockito.times(0)).apply(); } @@ -213,6 +217,7 @@ public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenECIDSa @Test public void testIdentityEdgeState_updateLegacyExperienceCloudId_notSetWhenSecondaryECIDSame() { IdentityEdgeState state = new IdentityEdgeState(new IdentityEdgeProperties()); + state.getIdentityEdgeProperties().setECID(new ECID()); ECID legacyEcid = new ECID(); state.getIdentityEdgeProperties().setECIDSecondary(legacyEcid); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java index 44e8af4d..c707cb80 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java @@ -170,7 +170,7 @@ public void test_removeAllIdentityItemsForNamespace() { IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" // test - sampleUserMap.removeAllIdentityItemsForNamespace("location"); + sampleUserMap.clearItemsForNamespace("location"); // verify the existing identityMap is unchanged assertNull(sampleUserMap.toObjectMap().get("location")); @@ -184,8 +184,8 @@ public void test_removeAllIdentityItemsForNamespace_InvalidNamespace() { IdentityMap sampleUserMap = buildSampleIdentityMap(); // 2 items with namespace "location", 3 items with namespace "login" // test - sampleUserMap.removeAllIdentityItemsForNamespace(null); - sampleUserMap.removeAllIdentityItemsForNamespace(""); + sampleUserMap.clearItemsForNamespace(null); + sampleUserMap.clearItemsForNamespace(""); // verify assertEquals(2, sampleUserMap.toObjectMap().get("location").size()); @@ -198,7 +198,7 @@ public void test_removeAllIdentityItemsForNamespace_onEmptyMap() { IdentityMap emptyMap = new IdentityMap(); // test - emptyMap.removeAllIdentityItemsForNamespace("location"); + emptyMap.clearItemsForNamespace("location"); assertTrue(emptyMap.toObjectMap().isEmpty()); } From 706b36ed2d0ac7326d3420a70e7d5fee2bb4f258 Mon Sep 17 00:00:00 2001 From: pravin Date: Fri, 19 Mar 2021 11:48:39 -0700 Subject: [PATCH 15/19] [AMSDK-11081] - cleanup and renaming --- .../mobile/identityedge/IdentityEdge.java | 3 +- .../identityedge/IdentityEdgeProperties.java | 2 - .../mobile/identityedge/IdentityMap.java | 56 +++++++++---------- .../IdentityEdgeExtensionTests.java | 2 +- 4 files changed, 30 insertions(+), 33 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java index 9f764575..ce83ef48 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdge.java @@ -149,7 +149,7 @@ public static void removeIdentity(final IdentityItem item, final String namespac } IdentityMap identityMap = new IdentityMap(); - identityMap.addItem(item,namespace); + identityMap.addItem(item, namespace); final ExtensionErrorCallback errorCallback = new ExtensionErrorCallback() { @Override @@ -168,6 +168,7 @@ public void error(final ExtensionError extensionError) { /** * Returns all identifiers, including customer identifiers which were previously added. + * * @param callback {@link AdobeCallback} invoked with the current {@link IdentityMap} * If an {@link AdobeCallbackWithError} is provided, an {@link AdobeError} can be returned in the * eventuality of any error that occurred while getting the stored identities. diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 5a85a5f3..49163d75 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -11,8 +11,6 @@ package com.adobe.marketing.mobile.identityedge; -import android.util.Log; - import com.adobe.marketing.mobile.LoggingMode; import com.adobe.marketing.mobile.MobileCore; diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java index 7f88e8e0..597b9569 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityMap.java @@ -18,7 +18,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.TreeMap; /** * Defines a map containing a set of end user identities, keyed on either namespace integration @@ -71,32 +70,6 @@ public void addItem(final IdentityItem item, final String namespace) { addItem(item, namespace, false); } - /** - * Add an identity item which is used to clearly distinguish entities that are interacting - * with digital experiences. - * - * @param item {@link IdentityItem} to be added to the namespace - * @param namespace the namespace integration code or namespace ID of the identity - * @param isFirstItem on {@code true} keeps the provided {@code IdentityItem} as the first element of the identity list for this namespace - * @see IdentityItem Schema - */ - public void addItem(final IdentityItem item, final String namespace, final boolean isFirstItem) { - if (item == null) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null IdentityItem."); - return; - } - - if (Utils.isNullOrEmpty(namespace)) { - MobileCore.log(LoggingMode.DEBUG, LOG_TAG, - "IdentityMap add item ignored as must contain a non-null/non-empty namespace."); - return; - } - - addItemToMap(item, namespace, isFirstItem); - } - - - /** * Remove a single {@link IdentityItem} from this map. * @@ -131,6 +104,31 @@ public boolean isEmpty() { // protected methods // ======================================================================================== + /** + * Add an identity item which is used to clearly distinguish entities that are interacting + * with digital experiences. + * + * @param item {@link IdentityItem} to be added to the namespace + * @param namespace the namespace integration code or namespace ID of the identity + * @param isFirstItem on {@code true} keeps the provided {@code IdentityItem} as the first element of the identity list for this namespace + * @see IdentityItem Schema + */ + void addItem(final IdentityItem item, final String namespace, final boolean isFirstItem) { + if (item == null) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "IdentityMap add item ignored as must contain a non-null IdentityItem."); + return; + } + + if (Utils.isNullOrEmpty(namespace)) { + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, + "IdentityMap add item ignored as must contain a non-null/non-empty namespace."); + return; + } + + addItemToMap(item, namespace, isFirstItem); + } + + /** * @return a {@link Map} representing this {@link IdentityMap} object */ @@ -258,7 +256,7 @@ static IdentityMap fromData(Map data) { // ======================================================================================== // private methods // ======================================================================================== - private void addItemToMap(final IdentityItem item, final String namespace, final boolean keepAsFirstItem) { + private void addItemToMap(final IdentityItem item, final String namespace, final boolean isFirstItem) { // check if namespace exists final List itemList; @@ -268,7 +266,7 @@ private void addItemToMap(final IdentityItem item, final String namespace, final itemList = new ArrayList<>(); } - if ((keepAsFirstItem)) { + if (isFirstItem) { itemList.add(0, item); } else { itemList.add(item); diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index f43cd3d8..6a3b402e 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -238,7 +238,7 @@ public void test_handleIdentityResetRequest() { MobileCore.dispatchEvent(eventCaptor.capture(), any(ExtensionErrorCallback.class)); Map sharedState = flattenMap(sharedStateCaptor.getValue()); - Assert.assertTrue(sharedState.get("identityMap.ECID[0].id").length() > 0); + assertTrue(sharedState.get("identityMap.ECID[0].id").length() > 0); } @Test From 00803126863ec4f9aa470d908120c8c1716d7ab1 Mon Sep 17 00:00:00 2001 From: pravin Date: Fri, 19 Mar 2021 12:53:13 -0700 Subject: [PATCH 16/19] [Dev] - Rename enum to AuthenticatedState and fix its toString --- ...tionState.java => AuthenticatedState.java} | 0 .../identityedge/IdentityEdgeProperties.java | 4 +-- .../mobile/identityedge/IdentityItem.java | 34 +++++++++---------- .../IdentityEdgeExtensionTests.java | 4 +-- .../IdentityEdgePropertiesTests.java | 4 +-- .../identityedge/IdentityEdgeTests.java | 14 ++++---- .../identityedge/IdentityItemTests.java | 22 ++++++------ .../mobile/identityedge/IdentityMapTests.java | 14 ++++---- 8 files changed, 47 insertions(+), 49 deletions(-) rename code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/{AuthenticationState.java => AuthenticatedState.java} (100%) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticationState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java similarity index 100% rename from code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticationState.java rename to code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 49163d75..2ddf0ac1 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -83,7 +83,7 @@ void setECID(final ECID newEcid) { identityMap.clearItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); } else { // And add the new primary Ecid as a first element of Identity map - final IdentityItem newECIDItem = new IdentityItem(newEcid.toString(), AuthenticationState.AMBIGUOUS, false); + final IdentityItem newECIDItem = new IdentityItem(newEcid.toString(), AuthenticatedState.AMBIGUOUS, false); identityMap.addItem(newECIDItem, IdentityEdgeConstants.Namespaces.ECID, true); } @@ -120,7 +120,7 @@ void setECIDSecondary(final ECID newSecondaryEcid) { // add the new secondary ECID to Identity map if (newSecondaryEcid != null) { - final IdentityItem newSecondaryECIDItem = new IdentityItem(newSecondaryEcid.toString(), AuthenticationState.AMBIGUOUS, false); + final IdentityItem newSecondaryECIDItem = new IdentityItem(newSecondaryEcid.toString(), AuthenticatedState.AMBIGUOUS, false); identityMap.addItem(newSecondaryECIDItem, IdentityEdgeConstants.Namespaces.ECID); } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java index df83070d..7dc845ff 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityItem.java @@ -23,7 +23,7 @@ */ public final class IdentityItem { private final String id; - private final AuthenticationState authenticationState; + private final AuthenticatedState authenticatedState; private final boolean primary; private static final String LOG_TAG = "IdentityItem"; @@ -31,27 +31,27 @@ public final class IdentityItem { /** * Creates a new {@link IdentityItem} * @param id id for the item - * @param authenticationState {@link AuthenticationState} for the item + * @param authenticatedState {@link AuthenticatedState} for the item * @param primary primary flag for the item * @throws IllegalArgumentException if id is null */ - public IdentityItem(final String id, final AuthenticationState authenticationState, final boolean primary) { + public IdentityItem(final String id, final AuthenticatedState authenticatedState, final boolean primary) { if (id == null) { throw new IllegalArgumentException("id must be non-null"); } this.id = id; - this.authenticationState = authenticationState != null ? authenticationState : AuthenticationState.AMBIGUOUS; + this.authenticatedState = authenticatedState != null ? authenticatedState : AuthenticatedState.AMBIGUOUS; this.primary = primary; } /** * Creates a new {@link IdentityItem} with default values - * authenticationState is set to AMBIGUOUS + * authenticatedState is set to AMBIGUOUS * primary is set to false * @param id the id for this {@link IdentityItem} */ public IdentityItem(final String id) { - this(id, AuthenticationState.AMBIGUOUS, false); + this(id, AuthenticatedState.AMBIGUOUS, false); } /** @@ -59,7 +59,7 @@ public IdentityItem(final String id) { * @param item A {@link IdentityItem} to be copied */ public IdentityItem(final IdentityItem item) { - this(item.id, item.authenticationState, item.primary); + this(item.id, item.authenticatedState, item.primary); } /** @@ -72,10 +72,10 @@ Map toObjectMap() { map.put(IdentityEdgeConstants.XDMKeys.ID, id); } - if (authenticationState != null) { - map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, authenticationState.toString()); + if (authenticatedState != null) { + map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, authenticatedState.getName()); } else { - map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, AuthenticationState.AMBIGUOUS.toString()); + map.put(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE, AuthenticatedState.AMBIGUOUS.getName()); } map.put(IdentityEdgeConstants.XDMKeys.PRIMARY, primary); @@ -90,10 +90,10 @@ public String getId() { } /** - * @return Current {@link AuthenticationState} for this item + * @return Current {@link AuthenticatedState} for this item */ - public AuthenticationState getAuthenticationState() { - return authenticationState; + public AuthenticatedState getAuthenticatedState() { + return authenticatedState; } /** @@ -113,9 +113,9 @@ static IdentityItem fromData(final Map data) { try { final String id = (String) data.get(IdentityEdgeConstants.XDMKeys.ID); - AuthenticationState authenticationState = AuthenticationState.fromString((String) data.get(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE)); - if (authenticationState == null) { - authenticationState = AuthenticationState.AMBIGUOUS; + AuthenticatedState authenticatedState = AuthenticatedState.fromString((String) data.get(IdentityEdgeConstants.XDMKeys.AUTHENTICATED_STATE)); + if (authenticatedState == null) { + authenticatedState = AuthenticatedState.AMBIGUOUS; } boolean primary = false; @@ -123,7 +123,7 @@ static IdentityItem fromData(final Map data) { primary = (boolean) data.get(IdentityEdgeConstants.XDMKeys.PRIMARY); } - return new IdentityItem(id, authenticationState, primary); + return new IdentityItem(id, authenticatedState, primary); } catch (ClassCastException e) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Failed to create IdentityItem from data."); return null; diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java index 6a3b402e..472d401e 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeExtensionTests.java @@ -384,14 +384,14 @@ public void test_handleUpdateIdentities() throws Exception { verify(mockExtensionApi, times(1)).setXDMSharedEventState(sharedStateCaptor.capture(), eq(updateIdentityEvent), any(ExtensionErrorCallback.class)); Map sharedState = flattenMap(sharedStateCaptor.getValue()); assertEquals("secretID", sharedState.get("identityMap.UserId[0].id")); - assertEquals("AMBIGUOUS", sharedState.get("identityMap.UserId[0].authenticatedState")); + assertEquals("ambiguous", sharedState.get("identityMap.UserId[0].authenticatedState")); assertEquals("false", sharedState.get("identityMap.UserId[0].primary")); // verify persistence verify(mockSharedPreferenceEditor, times(2)).putString(eq(IdentityEdgeConstants.DataStoreKey.IDENTITY_PROPERTIES), persistenceValueCaptor.capture()); Map persistedData = flattenJSONString(persistenceValueCaptor.getAllValues().get(1)); assertEquals("secretID", persistedData.get("identityMap.UserId[0].id")); - assertEquals("AMBIGUOUS", persistedData.get("identityMap.UserId[0].authenticatedState")); + assertEquals("ambiguous", persistedData.get("identityMap.UserId[0].authenticatedState")); assertEquals("false", persistedData.get("identityMap.UserId[0].primary")); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java index f8e53cf8..04f685a4 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgePropertiesTests.java @@ -51,12 +51,12 @@ public void test_toXDMData_Full() { // verify primary ECID assertEquals(props.getECID().toString(), flatMap.get("identityMap.ECID[0].id")); - assertEquals("AMBIGUOUS", flatMap.get("identityMap.ECID[0].authenticatedState")); + assertEquals("ambiguous", flatMap.get("identityMap.ECID[0].authenticatedState")); assertEquals("false", flatMap.get("identityMap.ECID[0].primary")); // verify secondary ECID assertEquals(props.getECIDSecondary().toString(), flatMap.get("identityMap.ECID[1].id")); - assertEquals("AMBIGUOUS", flatMap.get("identityMap.ECID[1].authenticatedState")); + assertEquals("ambiguous", flatMap.get("identityMap.ECID[1].authenticatedState")); assertEquals("false", flatMap.get("identityMap.ECID[1].primary")); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java index e0858a79..e20453fe 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeTests.java @@ -11,8 +11,6 @@ package com.adobe.marketing.mobile.identityedge; -import android.provider.ContactsContract; - import com.adobe.marketing.mobile.AdobeCallback; import com.adobe.marketing.mobile.AdobeCallbackWithError; import com.adobe.marketing.mobile.AdobeError; @@ -260,8 +258,8 @@ public void testUpdateIdentities() { // test IdentityMap map = new IdentityMap(); - map.addItem(new IdentityItem("id", AuthenticationState.AUTHENTICATED, true),"mainspace"); - map.addItem(new IdentityItem("idtwo", AuthenticationState.LOGGED_OUT, false),"secondspace"); + map.addItem(new IdentityItem("id", AuthenticatedState.AUTHENTICATED, true),"mainspace"); + map.addItem(new IdentityItem("idtwo", AuthenticatedState.LOGGED_OUT, false),"secondspace"); IdentityEdge.updateIdentities(map); // verify @@ -298,7 +296,7 @@ public void testRemoveIdentity() { // setup final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); final ArgumentCaptor extensionErrorCallbackCaptor = ArgumentCaptor.forClass(ExtensionErrorCallback.class); - IdentityItem sampleItem = new IdentityItem("sample", AuthenticationState.AMBIGUOUS, false); + IdentityItem sampleItem = new IdentityItem("sample", AuthenticatedState.AMBIGUOUS, false); // test IdentityEdge.removeIdentity(sampleItem, "namespace"); @@ -323,7 +321,7 @@ public void testRemoveIdentity() { @Test public void testRemoveIdentity_WithInvalidInputs() { // setup - IdentityItem sampleItem = new IdentityItem("sample", AuthenticationState.AMBIGUOUS, false); + IdentityItem sampleItem = new IdentityItem("sample", AuthenticatedState.AMBIGUOUS, false); // test IdentityEdge.removeIdentity(null, "namespace"); @@ -396,11 +394,11 @@ public void call(IdentityMap map) { IdentityItem coreItem = callbackReturnValues.get(0).getIdentityItemsForNamespace("CORE").get(0); assertEquals(ecid.toString(), ecidItem.getId()); - assertEquals(AuthenticationState.AMBIGUOUS, ecidItem.getAuthenticationState()); + assertEquals(AuthenticatedState.AMBIGUOUS, ecidItem.getAuthenticatedState()); assertEquals(true, ecidItem.isPrimary()); assertEquals(coreId, coreItem.getId()); - assertEquals(AuthenticationState.AUTHENTICATED, coreItem.getAuthenticationState()); + assertEquals(AuthenticatedState.AUTHENTICATED, coreItem.getAuthenticatedState()); assertEquals(false, coreItem.isPrimary()); // TODO - enable when ExtensionError creation is available diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java index d32ea27f..0d08d833 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityItemTests.java @@ -26,21 +26,21 @@ public class IdentityItemTests { @Test public void testIdentityItem_toObjectMap_full() { // setup - IdentityItem item = new IdentityItem("id", AuthenticationState.AUTHENTICATED, true); + IdentityItem item = new IdentityItem("id", AuthenticatedState.AUTHENTICATED, true); // test Map data = item.toObjectMap(); // verify assertEquals("id", (String) data.get("id")); - assertEquals("AUTHENTICATED", (String) data.get("authenticatedState")); + assertEquals("authenticated", (String) data.get("authenticatedState")); assertEquals(true, (boolean) data.get("primary")); } @Test(expected = IllegalArgumentException.class) public void testIdentityItem_toObjectMap_missingId() { // setup - IdentityItem item = new IdentityItem(null, AuthenticationState.AUTHENTICATED, true); + IdentityItem item = new IdentityItem(null, AuthenticatedState.AUTHENTICATED, true); // test Map data = item.toObjectMap(); @@ -56,7 +56,7 @@ public void testIdentityItem_toObjectMap_missingAuthState() { // verify assertEquals("id", (String) data.get("id")); - assertEquals("AMBIGUOUS", (String) data.get("authenticatedState")); + assertEquals("ambiguous", (String) data.get("authenticatedState")); assertEquals(true, (boolean) data.get("primary")); } @@ -73,7 +73,7 @@ public void testIdentityItem_fromData_full() { // verify assertEquals("test-id", item.getId()); - assertEquals("LOGGED_OUT", item.getAuthenticationState().toString()); + assertEquals("loggedOut", item.getAuthenticatedState().getName()); assertEquals(true, item.isPrimary()); } @@ -89,7 +89,7 @@ public void testIdentityItem_fromData_missingAuthState() { // verify assertEquals("test-id", item.getId()); - assertEquals("AMBIGUOUS", item.getAuthenticationState().toString()); + assertEquals("ambiguous", item.getAuthenticatedState().getName()); assertEquals(true, item.isPrimary()); } @@ -105,22 +105,22 @@ public void testIdentityItem_fromData_missingPrimary() { // verify assertEquals("test-id", item.getId()); - assertEquals("LOGGED_OUT", item.getAuthenticationState().toString()); + assertEquals("loggedOut", item.getAuthenticatedState().getName()); assertEquals(false, item.isPrimary()); } @Test public void testIdentityItem_isEqualShouldReturnTrue() { - IdentityItem item1 = new IdentityItem("id", AuthenticationState.AMBIGUOUS , false); - IdentityItem item2 = new IdentityItem("id", AuthenticationState.AUTHENTICATED , true); + IdentityItem item1 = new IdentityItem("id", AuthenticatedState.AMBIGUOUS , false); + IdentityItem item2 = new IdentityItem("id", AuthenticatedState.AUTHENTICATED , true); assertTrue(item1.equals(item2)); } @Test public void testIdentityItem_isEqualShouldReturnFalse() { - IdentityItem item1 = new IdentityItem("id", AuthenticationState.AMBIGUOUS , false); - IdentityItem item2 = new IdentityItem("id2", AuthenticationState.AUTHENTICATED , true); + IdentityItem item1 = new IdentityItem("id", AuthenticatedState.AMBIGUOUS , false); + IdentityItem item2 = new IdentityItem("id2", AuthenticatedState.AUTHENTICATED , true); assertFalse(item1.equals(item2)); } diff --git a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java index c707cb80..62f30408 100644 --- a/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java +++ b/code/identityedge/src/test/java/com/adobe/marketing/mobile/identityedge/IdentityMapTests.java @@ -280,10 +280,10 @@ public void test_FromData() throws Exception { // verify Map flattenedMap = IdentityEdgeTestUtil.flattenMap(map.asEventData()); assertEquals("randomECID", flattenedMap.get("ECID[0].id")); - assertEquals("AMBIGUOUS", flattenedMap.get("ECID[0].authenticationState")); + assertEquals("AMBIGUOUS", flattenedMap.get("ECID[0].authenticatedState")); assertEquals("true", flattenedMap.get("ECID[0].primary")); assertEquals("someUserID", flattenedMap.get("USERID[0].id")); - assertEquals("AUTHENTICATED", flattenedMap.get("USERID[0].authenticationState")); + assertEquals("AUTHENTICATED", flattenedMap.get("USERID[0].authenticatedState")); assertEquals("false", flattenedMap.get("USERID[0].primary")); } @@ -322,13 +322,13 @@ public void test_FromData_InvalidXDMData() throws Exception { private IdentityMap buildSampleIdentityMap() { // User Login Identity Items - IdentityItem email = new IdentityItem("john@doe", AuthenticationState.AUTHENTICATED, true); - IdentityItem userName = new IdentityItem("John Doe", AuthenticationState.AUTHENTICATED, false); - IdentityItem accountType = new IdentityItem("Student", AuthenticationState.AUTHENTICATED, false); + IdentityItem email = new IdentityItem("john@doe", AuthenticatedState.AUTHENTICATED, true); + IdentityItem userName = new IdentityItem("John Doe", AuthenticatedState.AUTHENTICATED, false); + IdentityItem accountType = new IdentityItem("Student", AuthenticatedState.AUTHENTICATED, false); // User Location Address Identity Items - IdentityItem street = new IdentityItem("280 Highway Lane", AuthenticationState.AMBIGUOUS, false); - IdentityItem state = new IdentityItem("California", AuthenticationState.AMBIGUOUS, false); + IdentityItem street = new IdentityItem("280 Highway Lane", AuthenticatedState.AMBIGUOUS, false); + IdentityItem state = new IdentityItem("California", AuthenticatedState.AMBIGUOUS, false); IdentityMap adobeIdentityMap = new IdentityMap(); adobeIdentityMap.addItem(email,"login"); From 052d8fa370c9cd4159e63e10f50b17c78635bf9c Mon Sep 17 00:00:00 2001 From: pravin Date: Fri, 19 Mar 2021 12:53:37 -0700 Subject: [PATCH 17/19] [Dev] - Enum AuthenticatedState --- .../marketing/mobile/identityedge/AuthenticatedState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java index df6b0f46..c8172f7e 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java @@ -14,14 +14,14 @@ /** * Represents the authentication state for an {@link IdentityItem} */ -public enum AuthenticationState { +public enum AuthenticatedState { AMBIGUOUS("ambiguous"), AUTHENTICATED("authenticated"), LOGGED_OUT("loggedOut"); private String name; - private AuthenticationState(final String name) { + private AuthenticatedState(final String name) { this.name = name; } @@ -29,7 +29,7 @@ public String getName() { return name; } - public static AuthenticationState fromString(final String state) { + public static AuthenticatedState fromString(final String state) { if ("authenticated".equalsIgnoreCase(state)) { return AUTHENTICATED; } else if ("loggedOut".equalsIgnoreCase(state)) { From 1284996303a856a0e8fcebb520e0883668fcf125 Mon Sep 17 00:00:00 2001 From: pravin Date: Fri, 19 Mar 2021 13:45:13 -0700 Subject: [PATCH 18/19] [Dev] - removed local ecid and secondaryECID local instances variables --- .../identityedge/IdentityEdgeProperties.java | 50 +++++++++---------- .../identityedge/IdentityEdgeState.java | 18 +++++-- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index 2ddf0ac1..ee1b3d63 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -31,15 +31,9 @@ class IdentityEdgeProperties { add(IdentityEdgeConstants.Namespaces.IDFA); }}; - // The current Experience Cloud ID - private ECID ecid; - - // A secondary (non-primary) Experience Cloud ID - private ECID ecidSecondary; - private IdentityMap identityMap = new IdentityMap(); - IdentityEdgeProperties() { } + IdentityEdgeProperties() {} /** * Creates a identity edge properties instance based on the map @@ -52,16 +46,8 @@ class IdentityEdgeProperties { } identityMap = IdentityMap.fromData(xdmData); - if (identityMap != null) { - final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); - if (ecidItems != null) { - if (ecidItems.size() > 0 && ecidItems.get(0) != null && ecidItems.get(0).getId() != null) { - ecid = new ECID(ecidItems.get(0).getId()); - } - if (ecidItems.size() > 1 && ecidItems.get(1) != null && ecidItems.get(1).getId() != null) { - ecidSecondary = new ECID(ecidItems.get(1).getId()); - } - } + if (identityMap == null) { + identityMap = new IdentityMap(); // always keep an empty identity map so there is no need for null check } } @@ -72,8 +58,9 @@ class IdentityEdgeProperties { */ void setECID(final ECID newEcid) { // delete the previous ECID from the identity map if exist - if (ecid != null) { - final IdentityItem previousECIDItem = new IdentityItem(ecid.toString()); + final ECID currentECID = getECID(); + if (currentECID != null) { + final IdentityItem previousECIDItem = new IdentityItem(currentECID.toString()); identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); } @@ -86,8 +73,6 @@ void setECID(final ECID newEcid) { final IdentityItem newECIDItem = new IdentityItem(newEcid.toString(), AuthenticatedState.AMBIGUOUS, false); identityMap.addItem(newECIDItem, IdentityEdgeConstants.Namespaces.ECID, true); } - - this.ecid = newEcid; // keep the local variable up to date } /** @@ -96,7 +81,13 @@ void setECID(final ECID newEcid) { * @return current {@code ECID} */ ECID getECID() { - return ecid; + final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + if (ecidItems != null) { + if (ecidItems.size() > 0 && ecidItems.get(0) != null && ecidItems.get(0).getId() != null) { + return new ECID(ecidItems.get(0).getId()); + } + } + return null; } /** @@ -106,15 +97,15 @@ ECID getECID() { */ void setECIDSecondary(final ECID newSecondaryEcid) { // delete the previous secondary ECID from the identity map if exist + final ECID ecidSecondary = getECIDSecondary(); if (ecidSecondary != null) { final IdentityItem previousECIDItem = new IdentityItem(ecidSecondary.toString()); identityMap.removeItem(previousECIDItem, IdentityEdgeConstants.Namespaces.ECID); } // do not set secondary ECID if primary ECID is not set - if (ecid == null) { + if (getECID() == null) { MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Cannot set secondary ECID value as no primary ECID exists."); - this.ecidSecondary = null; return; } @@ -123,8 +114,6 @@ void setECIDSecondary(final ECID newSecondaryEcid) { final IdentityItem newSecondaryECIDItem = new IdentityItem(newSecondaryEcid.toString(), AuthenticatedState.AMBIGUOUS, false); identityMap.addItem(newSecondaryECIDItem, IdentityEdgeConstants.Namespaces.ECID); } - - this.ecidSecondary = newSecondaryEcid; // keep the local variable up to date } /** @@ -133,7 +122,14 @@ void setECIDSecondary(final ECID newSecondaryEcid) { * @return secondary {@code ECID} */ ECID getECIDSecondary() { - return ecidSecondary; + final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); + if (ecidItems != null) { + if (ecidItems.size() > 1 && ecidItems.get(1) != null && ecidItems.get(1).getId() != null) { + return new ECID(ecidItems.get(1).getId()); + + } + } + return null; } /** diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index 3e5678ec..e1af5a2b 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -24,7 +24,8 @@ class IdentityEdgeState { private IdentityEdgeProperties identityProperties; /** - * Creates a new {@link IdentityEdgeState} with the given {@link IdentityEdgeProperties} + * Creates a new {@link IdentityEdgeState} with the given {@link IdentityEdgeProperties} + * * @param identityProperties identity edge properties */ IdentityEdgeState(final IdentityEdgeProperties identityProperties) { @@ -47,10 +48,13 @@ boolean hasBooted() { /** * Completes init for the Identity Edge extension. + * * @return True if we should share state after bootup, false otherwise */ boolean bootupIfReady() { - if (hasBooted) { return true; } + if (hasBooted) { + return true; + } // Load properties from local storage identityProperties = IdentityEdgeStorageService.loadPropertiesFromPersistence(); @@ -114,17 +118,23 @@ void removeCustomerIdentifiers(final IdentityMap map) { /** * Update the legacy ECID property with {@code legacyEcid} provided it does not equal the primary or secondary ECIDs * currently in {@code IdentityEdgePoperties}. + * * @param legacyEcid the current ECID from the direct Identity extension * @return true if the legacy ECID was updated in {@code IdentityEdgeProperties} */ boolean updateLegacyExperienceCloudId(final ECID legacyEcid) { - if (legacyEcid == identityProperties.getECID() || legacyEcid == identityProperties.getECIDSecondary()) { + if ((legacyEcid != null) && (legacyEcid.equals(identityProperties.getECID()) || legacyEcid.equals(identityProperties.getECIDSecondary()))) { + return false; + } + + // no need to clear secondaryECID if its already null + if (legacyEcid == null && identityProperties.getECIDSecondary() == null){ return false; } identityProperties.setECIDSecondary(legacyEcid); IdentityEdgeStorageService.savePropertiesToPersistence(identityProperties); - MobileCore.log(LoggingMode.DEBUG, LOG_TAG,"Identity direct ECID updated to '" + legacyEcid + "', updating the IdentityMap"); + MobileCore.log(LoggingMode.DEBUG, LOG_TAG, "Identity direct ECID updated to '" + legacyEcid + "', updating the IdentityMap"); return true; } From f4f3739290bc432aa59b845a32e99771db149664 Mon Sep 17 00:00:00 2001 From: pravin Date: Mon, 22 Mar 2021 09:43:31 -0700 Subject: [PATCH 19/19] [AMSDK-11081] - final on IdentityMap, enum string comparison change --- .../identityedge/AuthenticatedState.java | 4 +-- .../identityedge/IdentityEdgeProperties.java | 29 +++++++------------ .../identityedge/IdentityEdgeState.java | 7 +++-- 3 files changed, 17 insertions(+), 23 deletions(-) diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java index c8172f7e..f6773eb4 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/AuthenticatedState.java @@ -30,9 +30,9 @@ public String getName() { } public static AuthenticatedState fromString(final String state) { - if ("authenticated".equalsIgnoreCase(state)) { + if (AUTHENTICATED.getName().equalsIgnoreCase(state)) { return AUTHENTICATED; - } else if ("loggedOut".equalsIgnoreCase(state)) { + } else if (LOGGED_OUT.getName().equalsIgnoreCase(state)) { return LOGGED_OUT; } else { return AMBIGUOUS; diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java index ee1b3d63..6346b13b 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeProperties.java @@ -31,9 +31,11 @@ class IdentityEdgeProperties { add(IdentityEdgeConstants.Namespaces.IDFA); }}; - private IdentityMap identityMap = new IdentityMap(); + private final IdentityMap identityMap; - IdentityEdgeProperties() {} + IdentityEdgeProperties() { + this.identityMap = new IdentityMap(); + } /** * Creates a identity edge properties instance based on the map @@ -41,14 +43,8 @@ class IdentityEdgeProperties { * @param xdmData a map representing an identity edge properties instance */ IdentityEdgeProperties(final Map xdmData) { - if (Utils.isNullOrEmpty(xdmData)) { - return; - } - - identityMap = IdentityMap.fromData(xdmData); - if (identityMap == null) { - identityMap = new IdentityMap(); // always keep an empty identity map so there is no need for null check - } + IdentityMap map = IdentityMap.fromData(xdmData); + this.identityMap = map == null ? new IdentityMap() : map; // always keep an empty identity map so there is no need for null check } /** @@ -82,10 +78,8 @@ void setECID(final ECID newEcid) { */ ECID getECID() { final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); - if (ecidItems != null) { - if (ecidItems.size() > 0 && ecidItems.get(0) != null && ecidItems.get(0).getId() != null) { - return new ECID(ecidItems.get(0).getId()); - } + if (ecidItems != null && !ecidItems.isEmpty() && ecidItems.get(0) != null && !Utils.isNullOrEmpty(ecidItems.get(0).getId())) { + return new ECID(ecidItems.get(0).getId()); } return null; } @@ -123,11 +117,8 @@ void setECIDSecondary(final ECID newSecondaryEcid) { */ ECID getECIDSecondary() { final List ecidItems = identityMap.getIdentityItemsForNamespace(IdentityEdgeConstants.Namespaces.ECID); - if (ecidItems != null) { - if (ecidItems.size() > 1 && ecidItems.get(1) != null && ecidItems.get(1).getId() != null) { - return new ECID(ecidItems.get(1).getId()); - - } + if (ecidItems != null && ecidItems.size() > 1 && ecidItems.get(1) != null && !Utils.isNullOrEmpty(ecidItems.get(1).getId())) { + return new ECID(ecidItems.get(1).getId()); } return null; } diff --git a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java index e1af5a2b..a03b574d 100644 --- a/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java +++ b/code/identityedge/src/main/java/com/adobe/marketing/mobile/identityedge/IdentityEdgeState.java @@ -123,12 +123,15 @@ void removeCustomerIdentifiers(final IdentityMap map) { * @return true if the legacy ECID was updated in {@code IdentityEdgeProperties} */ boolean updateLegacyExperienceCloudId(final ECID legacyEcid) { - if ((legacyEcid != null) && (legacyEcid.equals(identityProperties.getECID()) || legacyEcid.equals(identityProperties.getECIDSecondary()))) { + final ECID ecid = identityProperties.getECID(); + final ECID ecidSecondary = identityProperties.getECIDSecondary(); + + if ((legacyEcid != null) && (legacyEcid.equals(ecid) || legacyEcid.equals(ecidSecondary))) { return false; } // no need to clear secondaryECID if its already null - if (legacyEcid == null && identityProperties.getECIDSecondary() == null){ + if (legacyEcid == null && ecidSecondary == null){ return false; }