diff --git a/README.md b/README.md index 24ca7f72..8d9a8e64 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,6 @@ Additional documentation about API usage and SDK architecture can be found under | Project | Description | | ------------------------------------------------------------ | ------------------------------------------------------------ | -| [AEP SDK Sample App for Android](https://github.com/adobe/aepsdk-sample-app-android) | Contains Android sample app for the AEP SDK. | | [Core extensions](https://github.com/adobe/aepsdk-core-android) | The Mobile Core represents the foundation of the Experience Platform Mobile SDK. | | [Adobe Experience Platform Edge Network Mobile Extension](https://github.com/adobe/aepsdk-edge-android) | The Adobe Experience Platform Edge Network mobile extension allows you to send data to the Experience Platform Edge Network from a mobile application. | | [Identity for Edge Network extension](https://github.com/adobe/aepsdk-edgeidentity-android) | The Identity for Edge Network extension enables handling of user identity data from a mobile app when using the Experience Platform Mobile SDK and the Edge Network extension. | diff --git a/code/app/build.gradle b/code/app/build.gradle index 4b9c1963..127592c5 100644 --- a/code/app/build.gradle +++ b/code/app/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' -def compose_version = '1.3.1' +def compose_version = '1.3.2' android { compileSdkVersion 33 @@ -9,7 +9,6 @@ android { defaultConfig { applicationId "com.adobe.marketing.optimizeapp" minSdkVersion 21 - buildToolsVersion '30.0.2' targetSdkVersion 33 versionCode 1 versionName "1.0" @@ -41,7 +40,7 @@ android { } composeOptions { - kotlinCompilerVersion '1.7.10' + kotlinCompilerVersion '1.7.20' kotlinCompilerExtensionVersion compose_version } @@ -57,7 +56,7 @@ dependencies { implementation 'androidx.annotation:annotation:1.0.0' implementation 'com.google.android.material:material:1.7.0' implementation "androidx.compose.ui:ui:$compose_version" - implementation "androidx.compose.material:material:$compose_version" + implementation 'androidx.compose.material:material:1.3.1' implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha04" implementation "androidx.navigation:navigation-compose:2.6.0-alpha04" @@ -70,10 +69,10 @@ dependencies { // Adobe mobile SDKs implementation project(":optimize") - implementation 'com.adobe.marketing.mobile:core:2.3.0' + implementation 'com.adobe.marketing.mobile:core:2.5.0' implementation 'com.adobe.marketing.mobile:lifecycle:2.0.4' implementation 'com.adobe.marketing.mobile:signal:2.0.1' implementation 'com.adobe.marketing.mobile:assurance:2.1.1' - implementation 'com.adobe.marketing.mobile:edge:2.0.0' + implementation 'com.adobe.marketing.mobile:edge:2.3.0' implementation 'com.adobe.marketing.mobile:edgeidentity:2.0.1' } \ No newline at end of file diff --git a/code/build.gradle b/code/build.gradle index 57130184..ccf7a42a 100644 --- a/code/build.gradle +++ b/code/build.gradle @@ -7,8 +7,8 @@ buildscript { maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } } dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' + classpath 'com.android.tools.build:gradle:7.3.1' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -44,7 +44,6 @@ ext { // dependencies junitVersion = "1.1.3" mockitoCoreVersion = "4.5.1" - buildToolsVersion = "30.0.2" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" jacocoVersion = "0.8.7" } \ No newline at end of file diff --git a/code/gradle.properties b/code/gradle.properties index ef11f2f5..580720dc 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -16,13 +16,13 @@ android.useAndroidX=true moduleProjectName=optimize moduleName=optimize moduleAARName=optimize-phone-release.aar -moduleVersion=2.0.1 +moduleVersion=2.0.2 mavenRepoName=AdobeMobileOptimizeSdk mavenRepoDescription=Adobe Experience Platform Optimize extension for the Adobe Experience Platform Mobile SDK mavenUploadDryRunFlag=false -mavenCoreVersion=2.0.0 -mavenEdgeVersion=2.0.0 +mavenCoreVersion=2.4.0 +mavenEdgeVersion=2.3.0 android.disableAutomaticComponentCreation=true diff --git a/code/gradle/wrapper/gradle-wrapper.properties b/code/gradle/wrapper/gradle-wrapper.properties index d46b5290..71f6290b 100644 --- a/code/gradle/wrapper/gradle-wrapper.properties +++ b/code/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/code/optimize/build.gradle b/code/optimize/build.gradle index 1a741dcf..a2a071c8 100644 --- a/code/optimize/build.gradle +++ b/code/optimize/build.gradle @@ -15,7 +15,6 @@ jacoco { android { compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion rootProject.ext.buildToolsVersion defaultConfig { minSdkVersion rootProject.ext.minSdkVersion @@ -207,7 +206,7 @@ task platformFunctionalTestJacocoReport(type: JacocoReport, dependsOn: "createPh dependencies { implementation 'androidx.annotation:annotation:1.0.0' - implementation 'com.adobe.marketing.mobile:core:2.0.0' + implementation 'com.adobe.marketing.mobile:core:2.4.0' testImplementation "androidx.test.ext:junit:${rootProject.ext.junitVersion}" testImplementation "org.mockito:mockito-core:${rootProject.ext.mockitoCoreVersion}" @@ -216,10 +215,10 @@ dependencies { testImplementation 'org.json:json:20180813' androidTestImplementation "androidx.test.ext:junit:${rootProject.ext.junitVersion}" - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'com.fasterxml.jackson.core:jackson-databind:2.9.9' - androidTestImplementation 'com.adobe.marketing.mobile:edge:2.0.0' - androidTestImplementation 'com.adobe.marketing.mobile:edgeidentity:2.0.0' + androidTestImplementation 'com.adobe.marketing.mobile:edge:2.3.0' + androidTestImplementation 'com.adobe.marketing.mobile:edgeidentity:2.0.1' } tasks.withType(Test) { diff --git a/code/optimize/src/androidTest/java/com/adobe/marketing/mobile/optimize/OptimizeFunctionalTests.java b/code/optimize/src/androidTest/java/com/adobe/marketing/mobile/optimize/OptimizeFunctionalTests.java index abe3fe1b..4fa776ad 100644 --- a/code/optimize/src/androidTest/java/com/adobe/marketing/mobile/optimize/OptimizeFunctionalTests.java +++ b/code/optimize/src/androidTest/java/com/adobe/marketing/mobile/optimize/OptimizeFunctionalTests.java @@ -397,12 +397,19 @@ public void testUpdatePropositions_validAndInvalidDecisionScopes() throws Interr @Test public void testGetPropositions_decisionScopeInCache() throws InterruptedException, IOException { //setup - //Send Edge Response event so that propositions will get cached by the Optimize SDK final Map configData = new HashMap<>(); configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); updateConfiguration(configData); + final String decisionScopeString = "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="; + Optimize.updatePropositions(Collections.singletonList(new DecisionScope(decisionScopeString)), null, null); + List eventsListEdge = TestHelper.getDispatchedEventsWith(OptimizeTestConstants.EventType.EDGE, OptimizeTestConstants.EventSource.REQUEST_CONTENT, 1000); + Assert.assertEquals(1, eventsListEdge.size()); + Event edgeEvent = eventsListEdge.get(0); + final String requestEventId = edgeEvent.getUniqueIdentifier(); + Assert.assertFalse(requestEventId.isEmpty()); + // Send Edge Response event final String edgeResponseData = "{\n" + " \"payload\": [\n" + " {\n" + @@ -434,7 +441,7 @@ public void testGetPropositions_decisionScopeInCache() throws InterruptedExcepti " ]\n" + " }\n" + " ],\n" + - " \"requestEventId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA\",\n" + + " \"requestEventId\":\"" + requestEventId + "\",\n" + " \"requestId\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" + " \"type\": \"personalization:decisions\"\n" + " }"; @@ -452,6 +459,21 @@ public void testGetPropositions_decisionScopeInCache() throws InterruptedExcepti //Action MobileCore.dispatchEvent(event); + Thread.sleep(1000); + + // Send completion event + Map completionEventData = new HashMap() { + { + put("completedUpdateRequestForEventId", requestEventId); + } + }; + Event completionEvent = new Event.Builder( + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE). + setEventData(completionEventData).build(); + MobileCore.dispatchEvent(completionEvent); + Thread.sleep(1000); TestHelper.resetTestExpectations(); DecisionScope decisionScope = new DecisionScope(decisionScopeString); @@ -499,12 +521,19 @@ public void call(Map decisionScopePropositionMap) { @Test public void testGetPropositions_decisionScopeInCacheFromTargetResponseWithClickTracking() throws ClassCastException, InterruptedException,IOException { //setup - //Send Edge Response event so that propositions will get cached by the Optimize SDK final Map configData = new HashMap<>(); configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); updateConfiguration(configData); + final String decisionScopeString = "myMbox1"; + Optimize.updatePropositions(Collections.singletonList(new DecisionScope(decisionScopeString)), null, null); + List eventsListEdge = TestHelper.getDispatchedEventsWith(OptimizeTestConstants.EventType.EDGE, OptimizeTestConstants.EventSource.REQUEST_CONTENT, 1000); + Assert.assertEquals(1, eventsListEdge.size()); + Event edgeEvent = eventsListEdge.get(0); + final String requestEventId = edgeEvent.getUniqueIdentifier(); + Assert.assertFalse(requestEventId.isEmpty()); + // Send Edge response event final String edgeResponseData = "{\n" + " \"payload\": [\n" + " {\n" + @@ -560,7 +589,7 @@ public void testGetPropositions_decisionScopeInCacheFromTargetResponseWithClickT " ]\n" + " }\n" + " ],\n" + - " \"requestEventId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA\",\n" + + " \"requestEventId\":\"" + requestEventId + "\",\n" + " \"requestId\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" + " \"type\": \"personalization:decisions\"\n" + " }"; @@ -578,6 +607,21 @@ public void testGetPropositions_decisionScopeInCacheFromTargetResponseWithClickT //Action MobileCore.dispatchEvent(event); + Thread.sleep(1000); + + // Send completion event + Map completionEventData = new HashMap() { + { + put("completedUpdateRequestForEventId", requestEventId); + } + }; + Event completionEvent = new Event.Builder( + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE). + setEventData(completionEventData).build(); + MobileCore.dispatchEvent(completionEvent); + Thread.sleep(1000); TestHelper.resetTestExpectations(); final DecisionScope decisionScope = new DecisionScope(decisionScopeString); @@ -659,12 +703,19 @@ public void call(Map decisionScopePropositionMap) { @Test public void testGetPropositions_notAllDecisionScopesInCache() throws IOException, InterruptedException { //setup - //Send Edge Response event so that propositions will get cached by the Optimize SDK final Map configData = new HashMap<>(); configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); updateConfiguration(configData); + final String decisionScopeString = "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="; + Optimize.updatePropositions(Collections.singletonList(new DecisionScope(decisionScopeString)), null, null); + List eventsListEdge = TestHelper.getDispatchedEventsWith(OptimizeTestConstants.EventType.EDGE, OptimizeTestConstants.EventSource.REQUEST_CONTENT, 1000); + Assert.assertEquals(1, eventsListEdge.size()); + Event edgeEvent = eventsListEdge.get(0); + final String requestEventId = edgeEvent.getUniqueIdentifier(); + Assert.assertFalse(requestEventId.isEmpty()); + // Send Edge response event final String edgeResponseData = "{\n" + " \"payload\": [\n" + " {\n" + @@ -695,7 +746,7 @@ public void testGetPropositions_notAllDecisionScopesInCache() throws IOException " ]\n" + " }\n" + " ],\n" + - " \"requestEventId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA\",\n" + + " \"requestEventId\": \"" + requestEventId + "\",\n" + " \"requestId\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" + " \"type\": \"personalization:decisions\"\n" + " }"; @@ -712,6 +763,21 @@ public void testGetPropositions_notAllDecisionScopesInCache() throws IOException //Action MobileCore.dispatchEvent(event); + Thread.sleep(1000); + + // Send completion event + Map completionEventData = new HashMap() { + { + put("completedUpdateRequestForEventId", requestEventId); + } + }; + Event completionEvent = new Event.Builder( + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE). + setEventData(completionEventData).build(); + MobileCore.dispatchEvent(completionEvent); + Thread.sleep(1000); TestHelper.resetTestExpectations(); DecisionScope decisionScope1 = new DecisionScope(decisionScopeString); @@ -1078,12 +1144,19 @@ public void testTrackPropositions_validPropositionInteractionsWithDatasetConfig( @Test public void testClearCachedPropositions() throws InterruptedException, IOException { //setup - //Send Edge Response event so that propositions will get cached by the Optimize SDK final Map configData = new HashMap<>(); configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); updateConfiguration(configData); + final String decisionScopeString = "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="; + Optimize.updatePropositions(Collections.singletonList(new DecisionScope(decisionScopeString)), null, null); + List eventsListEdge = TestHelper.getDispatchedEventsWith(OptimizeTestConstants.EventType.EDGE, OptimizeTestConstants.EventSource.REQUEST_CONTENT, 1000); + Assert.assertEquals(1, eventsListEdge.size()); + Event edgeEvent = eventsListEdge.get(0); + final String requestEventId = edgeEvent.getUniqueIdentifier(); + Assert.assertFalse(requestEventId.isEmpty()); + // Send Edge response event final String edgeResponseData = "{\n" + " \"payload\": [\n" + " {\n" + @@ -1114,7 +1187,7 @@ public void testClearCachedPropositions() throws InterruptedException, IOExcepti " ]\n" + " }\n" + " ],\n" + - " \"requestEventId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA\",\n" + + " \"requestEventId\":\"" + requestEventId + "\",\n" + " \"requestId\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" + " \"type\": \"personalization:decisions\"\n" + " }"; @@ -1134,6 +1207,20 @@ public void testClearCachedPropositions() throws InterruptedException, IOExcepti Thread.sleep(1000); + // Send completion event + Map completionEventData = new HashMap() { + { + put("completedUpdateRequestForEventId", requestEventId); + } + }; + Event completionEvent = new Event.Builder( + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE). + setEventData(completionEventData).build(); + MobileCore.dispatchEvent(completionEvent); + + Thread.sleep(1000); TestHelper.resetTestExpectations(); DecisionScope decisionScope = new DecisionScope(decisionScopeString); final Map propositionMap = new HashMap<>(); @@ -1183,12 +1270,19 @@ public void call(Map decisionScopePropositionMap) { @Test public void testCoreResetIdentities() throws InterruptedException, IOException { //setup - //Send Edge Response event so that propositions will get cached by the Optimize SDK final Map configData = new HashMap<>(); configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); updateConfiguration(configData); + final String decisionScopeString = "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="; + Optimize.updatePropositions(Collections.singletonList(new DecisionScope(decisionScopeString)), null, null); + List eventsListEdge = TestHelper.getDispatchedEventsWith(OptimizeTestConstants.EventType.EDGE, OptimizeTestConstants.EventSource.REQUEST_CONTENT, 1000); + Assert.assertEquals(1, eventsListEdge.size()); + Event edgeEvent = eventsListEdge.get(0); + final String requestEventId = edgeEvent.getUniqueIdentifier(); + Assert.assertFalse(requestEventId.isEmpty()); + // Send Edge response event final String edgeResponseData = "{\n" + " \"payload\": [\n" + " {\n" + @@ -1219,7 +1313,7 @@ public void testCoreResetIdentities() throws InterruptedException, IOException { " ]\n" + " }\n" + " ],\n" + - " \"requestEventId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA\",\n" + + " \"requestEventId\":\"" + requestEventId + "\",\n" + " \"requestId\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" + " \"type\": \"personalization:decisions\"\n" + " }"; @@ -1239,6 +1333,20 @@ public void testCoreResetIdentities() throws InterruptedException, IOException { Thread.sleep(1000); + // Send completion event + Map completionEventData = new HashMap() { + { + put("completedUpdateRequestForEventId", requestEventId); + } + }; + Event completionEvent = new Event.Builder( + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE). + setEventData(completionEventData).build(); + MobileCore.dispatchEvent(completionEvent); + + Thread.sleep(1000); TestHelper.resetTestExpectations(); DecisionScope decisionScope = new DecisionScope(decisionScopeString); final Map propositionMap = new HashMap<>(); diff --git a/code/optimize/src/androidTest/java/com/adobe/marketing/mobile/optimize/OptimizeTestConstants.java b/code/optimize/src/androidTest/java/com/adobe/marketing/mobile/optimize/OptimizeTestConstants.java index d63e0de7..a125849b 100644 --- a/code/optimize/src/androidTest/java/com/adobe/marketing/mobile/optimize/OptimizeTestConstants.java +++ b/code/optimize/src/androidTest/java/com/adobe/marketing/mobile/optimize/OptimizeTestConstants.java @@ -14,7 +14,7 @@ public class OptimizeTestConstants { - static final String EXTENSION_VERSION = "2.0.1"; + static final String EXTENSION_VERSION = "2.0.2"; public static final String LOG_TAG = "OptimizeTest"; static final String CONFIG_DATA_STORE = "AdobeMobile_ConfigState"; @@ -37,6 +37,7 @@ public final static class EventSource { public static final String EDGE_ERROR_RESPONSE = "com.adobe.eventSource.errorResponseContent"; public static final String RESPONSE_CONTENT = "com.adobe.eventSource.responseContent"; public static final String REQUEST_RESET = "com.adobe.eventSource.requestReset"; + public static final String CONTENT_COMPLETE = "com.adobe.eventSource.contentComplete"; } public final static class EventDataKeys { diff --git a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Optimize.java b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Optimize.java index 9e1f52b9..e637c87f 100644 --- a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Optimize.java +++ b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Optimize.java @@ -164,7 +164,8 @@ public static void getPropositions(@NonNull final List decisionSc .setEventData(eventData) .build(); - MobileCore.dispatchEventWithResponseCallback(event, OptimizeConstants.DEFAULT_RESPONSE_CALLBACK_TIMEOUT, new AdobeCallbackWithError() { + // Increased default response callback timeout to 10s to ensure prior update propositions requests have enough time to complete. + MobileCore.dispatchEventWithResponseCallback(event, OptimizeConstants.GET_RESPONSE_CALLBACK_TIMEOUT, new AdobeCallbackWithError() { @Override public void fail(final AdobeError adobeError) { failWithError(callback, adobeError); diff --git a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeConstants.java b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeConstants.java index 3f449d26..9d34e7eb 100644 --- a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeConstants.java +++ b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeConstants.java @@ -14,10 +14,12 @@ class OptimizeConstants { static final String LOG_TAG = "Optimize"; - static final String EXTENSION_VERSION = "2.0.1"; + static final String EXTENSION_VERSION = "2.0.2"; static final String EXTENSION_NAME = "com.adobe.optimize"; static final String FRIENDLY_NAME = "Optimize"; static final long DEFAULT_RESPONSE_CALLBACK_TIMEOUT = 500L; + static final long GET_RESPONSE_CALLBACK_TIMEOUT = 10000L; + static final long EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT = 5000L; static final String ACTIVITY_ID = "activityId"; static final String XDM_ACTIVITY_ID = "xdm:activityId"; @@ -40,6 +42,7 @@ static final class EventNames { static final String EDGE_PERSONALIZATION_REQUEST = "Edge Optimize Personalization Request"; static final String EDGE_PROPOSITION_INTERACTION_REQUEST = "Edge Optimize Proposition Interaction Request"; static final String OPTIMIZE_RESPONSE = "Optimize Response"; + static final String OPTIMIZE_UPDATE_COMPLETE = "Optimize Update Propositions Complete"; private EventNames() {} } @@ -60,6 +63,7 @@ static final class EventSource { static final String ERROR_RESPONSE_CONTENT = "com.adobe.eventSource.errorResponseContent"; static final String NOTIFICATION = "com.adobe.eventSource.notification"; static final String EDGE_PERSONALIZATION_DECISIONS = "personalization:decisions"; + static final String CONTENT_COMPLETE = "com.adobe.eventSource.contentComplete"; private EventSource() {} } @@ -73,6 +77,8 @@ static final class EventDataKeys { static final String PROPOSITIONS = "propositions"; static final String RESPONSE_ERROR = "responseerror"; static final String PROPOSITION_INTERACTIONS = "propositioninteractions"; + static final String REQUEST_EVENT_ID = "requestEventId"; + static final String COMPLETED_UPDATE_EVENT_ID = "completedUpdateRequestForEventId"; private EventDataKeys() {} } @@ -143,6 +149,8 @@ static final class JsonKeys { static final String DECISIONING_PROPOSITIONS_SCOPEDETAILS = "scopeDetails"; static final String DECISIONING_PROPOSITIONS_ITEMS = "items"; static final String DECISIONING_PROPOSITIONS_ITEMS_ID = "id"; + static final String REQUEST = "request"; + static final String REQUEST_SEND_COMPLETION = "sendCompletion"; private JsonKeys() {} } diff --git a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeExtension.java b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeExtension.java index a3c0926c..2b114851 100644 --- a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeExtension.java +++ b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeExtension.java @@ -12,15 +12,19 @@ package com.adobe.marketing.mobile.optimize; +import com.adobe.marketing.mobile.AdobeCallbackWithError; import com.adobe.marketing.mobile.AdobeError; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.Extension; import com.adobe.marketing.mobile.ExtensionApi; +import com.adobe.marketing.mobile.MobileCore; import com.adobe.marketing.mobile.SharedStateResolution; import com.adobe.marketing.mobile.SharedStateResult; import com.adobe.marketing.mobile.SharedStateStatus; import com.adobe.marketing.mobile.services.Log; import com.adobe.marketing.mobile.util.DataReader; +import com.adobe.marketing.mobile.util.DataReaderException; +import com.adobe.marketing.mobile.util.SerialWorkDispatcher; import java.util.ArrayList; import java.util.Arrays; @@ -36,6 +40,29 @@ class OptimizeExtension extends Extension { private static final String SELF_TAG = "OptimizeExtension"; private Map cachedPropositions; + // Events dispatcher used to maintain the processing order of update and get propositions events. + // It ensures any update propositions requests issued before a get propositions call are completed + // and the get propositions request is fulfilled from the latest cached content. + private SerialWorkDispatcher eventsDispatcher = + new SerialWorkDispatcher("OptimizeEventsDispatcher", + new SerialWorkDispatcher.WorkHandler() { + @Override + public boolean doWork(final Event event) { + if (OptimizeUtils.isGetEvent(event)) { + handleGetPropositions(event); + } else if (event.getType().equalsIgnoreCase(OptimizeConstants.EventType.EDGE)) { + return !updateRequestEventIdsInProgress.containsKey(event.getUniqueIdentifier()); + } + return true; + } + }); + + // Map containing the update event IDs (and corresponding requested scopes) for Edge events that haven't yet received an Edge completion response. + private Map> updateRequestEventIdsInProgress = new HashMap<>(); + + // a dictionary to accumulate propositions returned in various personalization:decisions events for the same Edge personalization request. + private Map propositionsInProgress = new HashMap<>(); + // List containing the schema strings for the proposition items supported by the SDK, sent in the personalization query request. final static List supportedSchemas = Arrays.asList( // Target schemas @@ -62,6 +89,7 @@ class OptimizeExtension extends Extension { * Listener for {@code Event} type {@value OptimizeConstants.EventType#EDGE} and source {@value OptimizeConstants.EventSource#ERROR_RESPONSE_CONTENT} * Listener for {@code Event} type {@value OptimizeConstants.EventType#OPTIMIZE} and source {@value OptimizeConstants.EventSource#REQUEST_RESET} * Listener for {@code Event} type {@value OptimizeConstants.EventType#GENERIC_IDENTITY} and source {@value OptimizeConstants.EventSource#REQUEST_RESET} + * Listener for {@code Event} type {@value OptimizeConstants.EventType#OPTIMIZE} and source {@value OptimizeConstants.EventSource#CONTENT_COMPLETE} * * * @@ -86,6 +114,9 @@ protected void onRegistered() { // Register listener - Mobile Core `resetIdentities()` API dispatches generic identity request reset event. getApi().registerEventListener(OptimizeConstants.EventType.GENERIC_IDENTITY, OptimizeConstants.EventSource.REQUEST_RESET, this::handleClearPropositions); + getApi().registerEventListener(OptimizeConstants.EventType.OPTIMIZE, OptimizeConstants.EventSource.CONTENT_COMPLETE, this::handleUpdatePropositionsCompleted); + + eventsDispatcher.start(); } @Override @@ -153,7 +184,9 @@ void handleOptimizeRequestContent(@NonNull final Event event) { handleUpdatePropositions(event); break; case OptimizeConstants.EventDataValues.REQUEST_TYPE_GET: - handleGetPropositions(event); + // Queue the get propositions event in the events dispatcher to ensure any prior update requests are completed + // before it is processed. + eventsDispatcher.offer(event); break; case OptimizeConstants.EventDataValues.REQUEST_TYPE_TRACK: handleTrackPropositions(event); @@ -186,8 +219,8 @@ void handleUpdatePropositions(@NonNull final Event event) { try { final List> decisionScopesData = DataReader.getTypedListOfMap(Object.class, eventData, OptimizeConstants.EventDataKeys.DECISION_SCOPES); - final List validScopeNames = retrieveValidDecisionScopes(decisionScopesData); - if (OptimizeUtils.isNullOrEmpty(validScopeNames)) { + final List validScopes = retrieveValidDecisionScopes(decisionScopesData); + if (OptimizeUtils.isNullOrEmpty(validScopes)) { Log.debug(OptimizeConstants.LOG_TAG, SELF_TAG, "handleUpdatePropositions - Cannot process the update propositions request event, provided list of decision scopes has no valid scope."); @@ -199,7 +232,13 @@ void handleUpdatePropositions(@NonNull final Event event) { // Add query final Map queryPersonalization = new HashMap<>(); queryPersonalization.put(OptimizeConstants.JsonKeys.SCHEMAS, supportedSchemas); + + final List validScopeNames = new ArrayList<>(); + for (final DecisionScope scope: validScopes) { + validScopeNames.add(scope.getName()); + } queryPersonalization.put(OptimizeConstants.JsonKeys.DECISION_SCOPES, validScopeNames); + final Map query = new HashMap<>(); query.put(OptimizeConstants.JsonKeys.QUERY_PERSONALIZATION, queryPersonalization); edgeEventData.put(OptimizeConstants.JsonKeys.QUERY, query); @@ -225,6 +264,11 @@ void handleUpdatePropositions(@NonNull final Event event) { } } + // Add the flag to request sendCompletion + final Map request = new HashMap<>(); + request.put(OptimizeConstants.JsonKeys.REQUEST_SEND_COMPLETION, true); + edgeEventData.put(OptimizeConstants.JsonKeys.REQUEST, request); + // Add override datasetId if (configData.containsKey(OptimizeConstants.Configuration.OPTIMIZE_OVERRIDE_DATASET_ID)) { final String overrideDatasetId = DataReader.getString(configData, OptimizeConstants.Configuration.OPTIMIZE_OVERRIDE_DATASET_ID); @@ -237,15 +281,114 @@ void handleUpdatePropositions(@NonNull final Event event) { OptimizeConstants.EventType.EDGE, OptimizeConstants.EventSource.REQUEST_CONTENT) .setEventData(edgeEventData) + .chainToParentEvent(event) .build(); - getApi().dispatch(edgeEvent); + // In AEP Response Event handle, `requestEventId` corresponds to the unique identifier for the Edge request. + // Storing the request event unique identifier to compare and process only the anticipated response in the extension. + updateRequestEventIdsInProgress.put(edgeEvent.getUniqueIdentifier(), validScopes); + + // add the Edge event to update propositions in the events queue. + eventsDispatcher.offer(edgeEvent); + + MobileCore.dispatchEventWithResponseCallback(edgeEvent, OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT, new AdobeCallbackWithError() { + @Override + public void fail(final AdobeError error) { + // response event failed or timed out, remove this event's unique identifier from the requested event IDs dictionary and kick-off queue. + updateRequestEventIdsInProgress.remove(edgeEvent.getUniqueIdentifier()); + propositionsInProgress.clear(); + + eventsDispatcher.resume(); + } + + @Override + public void call(final Event event) { + final String requestEventId = OptimizeUtils.getRequestEventId(event); + if (OptimizeUtils.isNullOrEmpty(requestEventId)) { + fail(AdobeError.UNEXPECTED_ERROR); + return; + } + + final Event updateCompleteEvent = new Event.Builder(OptimizeConstants.EventNames.OPTIMIZE_UPDATE_COMPLETE, + OptimizeConstants.EventType.OPTIMIZE, + OptimizeConstants.EventSource.CONTENT_COMPLETE) + .setEventData(new HashMap(){ + { + put(OptimizeConstants.EventDataKeys.COMPLETED_UPDATE_EVENT_ID, requestEventId); + } + }) + .chainToParentEvent(event) + .build(); + + getApi().dispatch(updateCompleteEvent); + } + }); } catch (final Exception e) { Log.warning(OptimizeConstants.LOG_TAG, SELF_TAG, "handleUpdatePropositions - Failed to process update propositions request event due to an exception (%s)!", e.getLocalizedMessage()); } } + /** + * Handles the event with type {@value OptimizeConstants.EventType#OPTIMIZE} and source {@value OptimizeConstants.EventSource#CONTENT_COMPLETE}. + *

+ * The event is dispatched internally upon receiving an Edge content complete response for an update propositions request. + * + * @param event incoming {@link Event} object to be processed. + */ + void handleUpdatePropositionsCompleted(@NonNull final Event event) { + try { + final String requestCompletedForEventId = DataReader.getString(event.getEventData(), OptimizeConstants.EventDataKeys.COMPLETED_UPDATE_EVENT_ID); + if(OptimizeUtils.isNullOrEmpty(requestCompletedForEventId)) { + Log.debug(OptimizeConstants.LOG_TAG, SELF_TAG, + "handleUpdatePropositionsCompleted - Ignoring Optimize complete event, event Id for the completed event is not present in event data"); + return; + } + + final List requestedScopes = updateRequestEventIdsInProgress.get(requestCompletedForEventId); + if(OptimizeUtils.isNullOrEmpty(requestedScopes)) { + Log.debug(OptimizeConstants.LOG_TAG, SELF_TAG, + "handleUpdatePropositionsCompleted - Ignoring Optimize complete event, event Id is not being tracked for completion as requested scopes is null or empty."); + return; + } + + // Update propositions in cache + updateCachedPropositions(requestedScopes); + + // remove completed event's ID from the request event IDs dictionary. + updateRequestEventIdsInProgress.remove(requestCompletedForEventId); + } catch (final DataReaderException e) { + Log.warning(OptimizeConstants.LOG_TAG, SELF_TAG, + "handleUpdatePropositionsCompleted - Cannot process the update propositions complete event due to an exception (%s)!", e.getLocalizedMessage()); + } finally { + propositionsInProgress.clear(); + + // Resume events dispatcher processing after update propositions request is completed. + eventsDispatcher.resume(); + } + } + + /** + * Updates the in-memory propositions cache with the returned propositions. + *

+ * Any requested scopes for which no propositions are returned in personalization: decisions events are removed from the cache. + * + * @param requestedScopes a {@code List} for which propositions are requested. + */ + private void updateCachedPropositions(@NonNull final List requestedScopes) { + // update cache with accumulated propositions + cachedPropositions.putAll(propositionsInProgress); + + // remove cached propositions for requested scopes for which no propositions are returned. + final List returnedScopes = new ArrayList<>(propositionsInProgress.keySet()); + final List scopesToRemove = new ArrayList<>(requestedScopes); + scopesToRemove.removeAll(returnedScopes); + + for (final DecisionScope scope: scopesToRemove) { + cachedPropositions.remove(scope); + } + } + /** * Handles the event with type {@value OptimizeConstants.EventType#EDGE} and source {@value OptimizeConstants.EventSource#EDGE_PERSONALIZATION_DECISIONS}. *

@@ -255,20 +398,16 @@ void handleUpdatePropositions(@NonNull final Event event) { * @param event incoming {@link Event} object to be processed. */ void handleEdgeResponse(@NonNull final Event event) { - if (OptimizeUtils.isNullOrEmpty(event.getEventData())) { - Log.debug(OptimizeConstants.LOG_TAG, SELF_TAG, - "handleEdgeResponse - Ignoring the Edge personalization:decisions event, either event is null or event data is null/ empty."); - return; - } - try { final Map eventData = event.getEventData(); + final String requestEventId = OptimizeUtils.getRequestEventId(event); - // Verify the Edge response event handle - final String edgeEventHandleType = DataReader.getString(eventData, OptimizeConstants.Edge.EVENT_HANDLE); - if (!OptimizeConstants.Edge.EVENT_HANDLE_TYPE_PERSONALIZATION.equals(edgeEventHandleType)) { + if (!OptimizeUtils.isPersonalizationDecisionsResponse(event) || + OptimizeUtils.isNullOrEmpty(requestEventId) || + !updateRequestEventIdsInProgress.containsKey(requestEventId)) { Log.debug(OptimizeConstants.LOG_TAG, SELF_TAG, - "handleEdgeResponse - Cannot process the Edge personalization:decisions event, event handle type is not personalization:decisions."); + "handleEdgeResponse - Ignoring Edge event, either handle type is not personalization:decisions, or the response isn't intended for this extension."); + propositionsInProgress.clear(); return; } @@ -292,8 +431,8 @@ void handleEdgeResponse(@NonNull final Event event) { return; } - // Update propositions cache - cachedPropositions.putAll(propositionsMap); + // accumulate propositions in in-progress propositions dictionary + propositionsInProgress.putAll(propositionsMap); final List> propositionsList = new ArrayList<>(); for (final Proposition proposition : propositionsMap.values()) { @@ -351,8 +490,8 @@ void handleGetPropositions(@NonNull final Event event) { try { final List> decisionScopesData = DataReader.getTypedListOfMap(Object.class, eventData, OptimizeConstants.EventDataKeys.DECISION_SCOPES); - final List validScopeNames = retrieveValidDecisionScopes(decisionScopesData); - if (OptimizeUtils.isNullOrEmpty(validScopeNames)) { + final List validScopes = retrieveValidDecisionScopes(decisionScopesData); + if (OptimizeUtils.isNullOrEmpty(validScopes)) { Log.debug(OptimizeConstants.LOG_TAG, SELF_TAG, "handleGetPropositions - Cannot process the get propositions request event, provided list of decision scopes has no valid scope."); getApi().dispatch(createResponseEventWithError(event, AdobeError.UNEXPECTED_ERROR)); @@ -360,8 +499,7 @@ void handleGetPropositions(@NonNull final Event event) { } final List> propositionsList = new ArrayList<>(); - for (final String scopeName : validScopeNames) { - final DecisionScope scope = new DecisionScope(scopeName); + for (final DecisionScope scope : validScopes) { if (cachedPropositions.containsKey(scope)) { final Proposition proposition = cachedPropositions.get(scope); propositionsList.add(proposition.toEventData()); @@ -465,36 +603,36 @@ private Map retrieveConfigurationSharedState(final Event event) } /** - * Retrieves the {@code List} containing valid scope names. + * Retrieves the {@code List} containing valid scopes. *

* This method returns null if the given {@code decisionScopesData} list is null, or empty, or if there is no valid decision scope in the * provided list. * * @param decisionScopesData input {@code List>} containing scope data. - * @return {@code List} containing valid scope names. + * @return {@code List} containing valid scopes. * @see DecisionScope#isValid() */ - private List retrieveValidDecisionScopes(final List> decisionScopesData) { + private List retrieveValidDecisionScopes(final List> decisionScopesData) { if (OptimizeUtils.isNullOrEmpty(decisionScopesData)) { Log.debug(OptimizeConstants.LOG_TAG, SELF_TAG, "retrieveValidDecisionScopes - No valid decision scopes are retrieved, provided decision scopes list is null or empty."); return null; } - final List validScopeNames = new ArrayList<>(); + final List validScopes = new ArrayList<>(); for (final Map scopeData: decisionScopesData) { final DecisionScope scope = DecisionScope.fromEventData(scopeData); if (scope == null || !scope.isValid()) { continue; } - validScopeNames.add(scope.getName()); + validScopes.add(scope); } - if (validScopeNames.size() == 0) { + if (validScopes.size() == 0) { Log.warning(OptimizeConstants.LOG_TAG, SELF_TAG, "retrieveValidDecisionScopes - No valid decision scopes are retrieved, provided list of decision scopes has no valid scope."); return null; } - return validScopeNames; + return validScopes; } /** @@ -524,4 +662,29 @@ Map getCachedPropositions() { void setCachedPropositions(final Map cachedPropositions) { this.cachedPropositions = cachedPropositions; } + + @VisibleForTesting + Map getPropositionsInProgress() { + return propositionsInProgress; + } + + @VisibleForTesting + void setPropositionsInProgress(final Map propositionsInProgress) { + this.propositionsInProgress = propositionsInProgress; + } + + @VisibleForTesting + Map> getUpdateRequestEventIdsInProgress() { + return updateRequestEventIdsInProgress; + } + + @VisibleForTesting + void setUpdateRequestEventIdsInProgress(final String eventId, final List expectedScopes) { + updateRequestEventIdsInProgress.put(eventId, expectedScopes); + } + + @VisibleForTesting + void setEventsDispatcher(final SerialWorkDispatcher eventsDispatcher) { + this.eventsDispatcher = eventsDispatcher; + } } \ No newline at end of file diff --git a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeUtils.java b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeUtils.java index bac94a00..803c414b 100644 --- a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeUtils.java +++ b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/OptimizeUtils.java @@ -15,7 +15,9 @@ import android.util.Base64; import com.adobe.marketing.mobile.AdobeError; +import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.services.Log; +import com.adobe.marketing.mobile.util.DataReader; import java.util.Collection; import java.util.Map; @@ -117,4 +119,43 @@ static AdobeError convertToAdobeError(final int errorCode) { } return error; } + + /** + * Checks whether the given event is a personalization: decisions response returned from the Edge network. + * + * @param event instance of {@link Event} + * @return {@code boolean} containing true if event is a personalization: decisions event, false otherwise. + */ + static boolean isPersonalizationDecisionsResponse(final Event event) { + return OptimizeConstants.EventType.EDGE.equalsIgnoreCase(event.getType()) && + OptimizeConstants.EventSource.EDGE_PERSONALIZATION_DECISIONS.equalsIgnoreCase(event.getSource()); + } + + /** + * Checks whether the given event is an Optimize request content event for retrieving cached propositions. + *

+ * The get request should have {@code requesttype} set to {@literal getpropositions} in the event's data. + * + * @param event instance of {@link Event} + * @return true if event is a Personalization Decision Response event, false otherwise + */ + static boolean isGetEvent(final Event event) { + final String requestType = DataReader.optString(event.getEventData(), OptimizeConstants.EventDataKeys.REQUEST_TYPE, ""); + return OptimizeConstants.EventType.OPTIMIZE.equalsIgnoreCase(event.getType()) && + OptimizeConstants.EventSource.REQUEST_CONTENT.equalsIgnoreCase(event.getSource()) && + requestType.equalsIgnoreCase(OptimizeConstants.EventDataValues.REQUEST_TYPE_GET); + } + + /** + * Returns event's parentID or {@code requestEventId} present in the event's data. + * @param event instance of {@link Event} + * @return {@link String} containing request event ID. + */ + static String getRequestEventId(final Event event) { + String requestEventId = event.getParentID(); + if (OptimizeUtils.isNullOrEmpty(requestEventId)) { + requestEventId = DataReader.optString(event.getEventData(), OptimizeConstants.EventDataKeys.REQUEST_EVENT_ID, null); + } + return requestEventId; + } } diff --git a/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OptimizeExtensionTests.java b/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OptimizeExtensionTests.java index a682668c..fb541dd9 100644 --- a/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OptimizeExtensionTests.java +++ b/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OptimizeExtensionTests.java @@ -14,7 +14,6 @@ import android.util.Base64; -import com.adobe.marketing.mobile.AdobeError; import com.adobe.marketing.mobile.Event; import com.adobe.marketing.mobile.ExtensionApi; import com.adobe.marketing.mobile.ExtensionEventListener; @@ -22,6 +21,7 @@ import com.adobe.marketing.mobile.SharedStateResult; import com.adobe.marketing.mobile.SharedStateStatus; import com.adobe.marketing.mobile.services.Log; +import com.adobe.marketing.mobile.util.SerialWorkDispatcher; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.Assert; @@ -50,9 +50,15 @@ public class OptimizeExtensionTests { @Mock ExtensionApi mockExtensionApi; + @Mock + SerialWorkDispatcher mockEventsDispatcher; + @Before public void setup() { extension = new OptimizeExtension(mockExtensionApi); + extension.onRegistered(); + + Mockito.clearInvocations(mockExtensionApi); } @Test @@ -66,7 +72,14 @@ public void test_getName() { public void test_getVersion() { // test final String extensionVersion = extension.getVersion(); - Assert.assertEquals("getVersion should return the correct extension version.", "2.0.1", extensionVersion); + Assert.assertEquals("getVersion should return the correct extension version.", "2.0.2", extensionVersion); + } + + @Test + public void test_getFriendlyName() { + // test + final String extensionFriendlyName = extension.getFriendlyName(); + Assert.assertEquals("getFriendlyName should return the correct extension friendly name.", "Optimize", extensionFriendlyName); } @Test @@ -99,6 +112,10 @@ public void test_registration() { ArgumentMatchers.eq("com.adobe.eventType.generic.identity"), ArgumentMatchers.eq("com.adobe.eventSource.requestReset"), ArgumentMatchers.any(ExtensionEventListener.class)); + Mockito.verify(mockExtensionApi, Mockito.times(1)).registerEventListener( + ArgumentMatchers.eq("com.adobe.eventType.optimize"), + ArgumentMatchers.eq("com.adobe.eventSource.contentComplete"), + ArgumentMatchers.any(ExtensionEventListener.class)); } @Test @@ -260,8 +277,6 @@ public void testHandleOptimizeRequestContent_handleUpdatePropositions_validDecis add(testScope.toEventData()); } }); - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - final Event testEvent = new Event.Builder("Optimize Update Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") .setEventData(testEventData) .build(); @@ -271,35 +286,14 @@ public void testHandleOptimizeRequestContent_handleUpdatePropositions_validDecis extension.handleOptimizeRequestContent(testEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); - - final Event dispatchedEvent = eventCaptor.getValue(); - Assert.assertEquals("com.adobe.eventType.edge", dispatchedEvent.getType()); - Assert.assertEquals("com.adobe.eventSource.requestContent", dispatchedEvent.getSource()); - - final Map query = (Map) dispatchedEvent.getEventData().get("query"); - Assert.assertNotNull(query); - final Map queryPersonalization = (Map) query.get("personalization"); - Assert.assertNotNull(queryPersonalization); - final List schemas = (List) queryPersonalization.get("schemas"); - Assert.assertNotNull(schemas); - Assert.assertEquals(7, schemas.size()); - Assert.assertEquals(OptimizeExtension.supportedSchemas, schemas); - final List scopes = (List) queryPersonalization.get("decisionScopes"); - Assert.assertNotNull(scopes); - Assert.assertEquals(1, scopes.size()); - Assert.assertEquals(testScope.getName(), scopes.get(0)); - - final Map xdm = (Map) dispatchedEvent.getEventData().get("xdm"); - Assert.assertNotNull(xdm); - Assert.assertEquals(1, xdm.size()); - Assert.assertEquals("personalization.request", xdm.get("eventType")); - - final Map data = (Map) dispatchedEvent.getEventData().get("data"); - Assert.assertNull(data); - - final String datasetId = (String) dispatchedEvent.getEventData().get("datasetId"); - Assert.assertNull(datasetId); + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(1, updateEventIdsInProgress.size()); + Assert.assertTrue(updateEventIdsInProgress.containsValue(new ArrayList() { + { + add(testScope); + } + })); } } @@ -335,8 +329,6 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_validDecis put("myKey", "myValue"); } }); - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - final Event testEvent = new Event.Builder("Optimize Update Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") .setEventData(testEventData) .build(); @@ -345,38 +337,14 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_validDecis extension.handleOptimizeRequestContent(testEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); - - final Event dispatchedEvent = eventCaptor.getValue(); - Assert.assertEquals("com.adobe.eventType.edge", dispatchedEvent.getType()); - Assert.assertEquals("com.adobe.eventSource.requestContent", dispatchedEvent.getSource()); - - final Map query = (Map) dispatchedEvent.getEventData().get("query"); - Assert.assertNotNull(query); - final Map queryPersonalization = (Map) query.get("personalization"); - Assert.assertNotNull(queryPersonalization); - final List schemas = (List) queryPersonalization.get("schemas"); - Assert.assertNotNull(schemas); - Assert.assertEquals(7, schemas.size()); - Assert.assertEquals(OptimizeExtension.supportedSchemas, schemas); - final List scopes = (List) queryPersonalization.get("decisionScopes"); - Assert.assertNotNull(scopes); - Assert.assertEquals(1, scopes.size()); - Assert.assertEquals(testScope.getName(), scopes.get(0)); - - final Map xdm = (Map) dispatchedEvent.getEventData().get("xdm"); - Assert.assertNotNull(xdm); - Assert.assertEquals(2, xdm.size()); - Assert.assertEquals("personalization.request", xdm.get("eventType")); - Assert.assertEquals("myXdmValue", xdm.get("myXdmKey")); - - final Map data = (Map) dispatchedEvent.getEventData().get("data"); - Assert.assertNotNull(data); - Assert.assertEquals(1, data.size()); - Assert.assertEquals("myValue", data.get("myKey")); - - final String datasetId = (String) dispatchedEvent.getEventData().get("datasetId"); - Assert.assertEquals("111111111111111111111111", datasetId); + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(1, updateEventIdsInProgress.size()); + Assert.assertTrue(updateEventIdsInProgress.containsValue(new ArrayList() { + { + add(testScope); + } + })); } } @@ -411,8 +379,6 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_validDecis put("myKey", "myValue"); } }); - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - final Event testEvent = new Event.Builder("Optimize Update Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") .setEventData(testEventData) .build(); @@ -421,38 +387,14 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_validDecis extension.handleOptimizeRequestContent(testEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); - - final Event dispatchedEvent = eventCaptor.getValue(); - Assert.assertEquals("com.adobe.eventType.edge", dispatchedEvent.getType()); - Assert.assertEquals("com.adobe.eventSource.requestContent", dispatchedEvent.getSource()); - - final Map query = (Map) dispatchedEvent.getEventData().get("query"); - Assert.assertNotNull(query); - final Map queryPersonalization = (Map) query.get("personalization"); - Assert.assertNotNull(queryPersonalization); - final List schemas = (List) queryPersonalization.get("schemas"); - Assert.assertNotNull(schemas); - Assert.assertEquals(7, schemas.size()); - Assert.assertEquals(OptimizeExtension.supportedSchemas, schemas); - final List scopes = (List) queryPersonalization.get("decisionScopes"); - Assert.assertNotNull(scopes); - Assert.assertEquals(1, scopes.size()); - Assert.assertEquals(testScope.getName(), scopes.get(0)); - - final Map xdm = (Map) dispatchedEvent.getEventData().get("xdm"); - Assert.assertNotNull(xdm); - Assert.assertEquals(2, xdm.size()); - Assert.assertEquals("personalization.request", xdm.get("eventType")); - Assert.assertEquals("myXdmValue", xdm.get("myXdmKey")); - - final Map data = (Map) dispatchedEvent.getEventData().get("data"); - Assert.assertNotNull(data); - Assert.assertEquals(1, data.size()); - Assert.assertEquals("myValue", data.get("myKey")); - - final String datasetId = (String) dispatchedEvent.getEventData().get("datasetId"); - Assert.assertNull(datasetId); + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(1, updateEventIdsInProgress.size()); + Assert.assertTrue(updateEventIdsInProgress.containsValue(new ArrayList() { + { + add(testScope); + } + })); } } @@ -479,8 +421,6 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_multipleVa add(testScope2.toEventData()); } }); - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - final Event testEvent = new Event.Builder("Optimize Update Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") .setEventData(testEventData) .build(); @@ -490,36 +430,15 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_multipleVa extension.handleOptimizeRequestContent(testEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); - - final Event dispatchedEvent = eventCaptor.getValue(); - Assert.assertEquals("com.adobe.eventType.edge", dispatchedEvent.getType()); - Assert.assertEquals("com.adobe.eventSource.requestContent", dispatchedEvent.getSource()); - - final Map query = (Map) dispatchedEvent.getEventData().get("query"); - Assert.assertNotNull(query); - final Map queryPersonalization = (Map) query.get("personalization"); - Assert.assertNotNull(queryPersonalization); - final List schemas = (List) queryPersonalization.get("schemas"); - Assert.assertNotNull(schemas); - Assert.assertEquals(7, schemas.size()); - Assert.assertEquals(OptimizeExtension.supportedSchemas, schemas); - final List scopes = (List) queryPersonalization.get("decisionScopes"); - Assert.assertNotNull(scopes); - Assert.assertEquals(2, scopes.size()); - Assert.assertEquals(testScope1.getName(), scopes.get(0)); - Assert.assertEquals(testScope2.getName(), scopes.get(1)); - - final Map xdm = (Map) dispatchedEvent.getEventData().get("xdm"); - Assert.assertNotNull(xdm); - Assert.assertEquals(1, xdm.size()); - Assert.assertEquals("personalization.request", xdm.get("eventType")); - - final Map data = (Map) dispatchedEvent.getEventData().get("data"); - Assert.assertNull(data); - - final String datasetId = (String) dispatchedEvent.getEventData().get("datasetId"); - Assert.assertNull(datasetId); + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(1, updateEventIdsInProgress.size()); + Assert.assertTrue(updateEventIdsInProgress.containsValue(new ArrayList() { + { + add(testScope1); + add(testScope2); + } + })); } } @@ -547,6 +466,10 @@ public void testHandleOptimizeRequestContent_UpdatePropositions_configurationNot // verify Mockito.verify(mockExtensionApi, Mockito.times(0)).dispatch(ArgumentMatchers.any()); logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())); + + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(0, updateEventIdsInProgress.size()); } } @@ -578,6 +501,10 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_noDecision // verify Mockito.verify(mockExtensionApi, Mockito.times(0)).dispatch(ArgumentMatchers.any()); logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()), Mockito.times(2)); + + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(0, updateEventIdsInProgress.size()); } } @@ -615,6 +542,10 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_invalidDec Mockito.verify(mockExtensionApi, Mockito.times(0)).dispatch(ArgumentMatchers.any()); logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.any()), Mockito.times(2)); logMockedStatic.verify(() -> Log.warning(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())); + + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(0, updateEventIdsInProgress.size()); } } @@ -641,8 +572,6 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_validAndIn add(testScope2.toEventData()); } }); - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - final Event testEvent = new Event.Builder("Optimize Update Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") .setEventData(testEventData) .build(); @@ -652,41 +581,25 @@ public void testHandleOptimizeRequestContent_HandleUpdatePropositions_validAndIn extension.handleOptimizeRequestContent(testEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); - - final Event dispatchedEvent = eventCaptor.getValue(); - Assert.assertEquals("com.adobe.eventType.edge", dispatchedEvent.getType()); - Assert.assertEquals("com.adobe.eventSource.requestContent", dispatchedEvent.getSource()); - - final Map query = (Map) dispatchedEvent.getEventData().get("query"); - Assert.assertNotNull(query); - final Map queryPersonalization = (Map) query.get("personalization"); - Assert.assertNotNull(queryPersonalization); - final List schemas = (List) queryPersonalization.get("schemas"); - Assert.assertNotNull(schemas); - Assert.assertEquals(7, schemas.size()); - Assert.assertEquals(OptimizeExtension.supportedSchemas, schemas); - final List scopes = (List) queryPersonalization.get("decisionScopes"); - Assert.assertNotNull(scopes); - Assert.assertEquals(1, scopes.size()); - Assert.assertEquals(testScope2.getName(), scopes.get(0)); - - final Map xdm = (Map) dispatchedEvent.getEventData().get("xdm"); - Assert.assertNotNull(xdm); - Assert.assertEquals(1, xdm.size()); - Assert.assertEquals("personalization.request", xdm.get("eventType")); - - final Map data = (Map) dispatchedEvent.getEventData().get("data"); - Assert.assertNull(data); - - final String datasetId = (String) dispatchedEvent.getEventData().get("datasetId"); - Assert.assertNull(datasetId); + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(1, updateEventIdsInProgress.size()); + Assert.assertTrue(updateEventIdsInProgress.containsValue(new ArrayList() { + { + add(testScope2); + } + })); } } @Test public void testHandleEdgeResponse_validProposition() throws Exception{ // setup + extension.setUpdateRequestEventIdsInProgress("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", new ArrayList() { + { + add(new DecisionScope("eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==")); + } + }); final Map edgeResponseData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/EVENT_DATA_EDGE_RESPONSE_VALID.json"), HashMap.class); final Event testEvent = new Event.Builder("AEP Response Event Handle", "com.adobe.eventType.edge", "personalization:decisions") .setEventData(edgeResponseData) @@ -728,16 +641,19 @@ public void testHandleEdgeResponse_validProposition() throws Exception{ Assert.assertEquals("true", offer.getCharacteristics().get("testing")); Assert.assertNull(offer.getLanguage()); - final Map cachedPropositions = extension.getCachedPropositions(); - Assert.assertNotNull(cachedPropositions); - Assert.assertEquals(1, cachedPropositions.size()); - final DecisionScope cachedScope = new DecisionScope("eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); - Assert.assertEquals(proposition, cachedPropositions.get(cachedScope)); + // incoming proposition is accumulated, not cached yet + Assert.assertEquals(1, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getCachedPropositions().size()); } @Test public void testHandleEdgeResponse_validPropositionFromTargetWithClickTracking() throws Exception { // setup + extension.setUpdateRequestEventIdsInProgress("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", new ArrayList() { + { + add(new DecisionScope("myMbox")); + } + }); final Map edgeResponseData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/EVENT_DATA_EDGE_RESPONSE_VALID_TARGET_WITH_CLICK_TRACKING.json"), HashMap.class); final Event testEvent = new Event.Builder("AEP Response Event Handle", "com.adobe.eventType.edge", "personalization:decisions") .setEventData(edgeResponseData) @@ -817,15 +733,14 @@ public void testHandleEdgeResponse_validPropositionFromTargetWithClickTracking() Assert.assertNull(offer.getCharacteristics()); Assert.assertNull(offer.getLanguage()); - final Map cachedPropositions = extension.getCachedPropositions(); - Assert.assertNotNull(cachedPropositions); - Assert.assertEquals(1, cachedPropositions.size()); - final DecisionScope cachedScope = new DecisionScope("myMbox"); - Assert.assertEquals(proposition, cachedPropositions.get(cachedScope)); + // incoming proposition is accumulated, not cached yet + Assert.assertEquals(1, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getCachedPropositions().size()); } @Test public void testHandleEdgeResponse_emptyProposition() throws Exception{ + extension.setUpdateRequestEventIdsInProgress("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", new ArrayList()); try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { // setup final Map edgeResponseData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/EVENT_DATA_EDGE_RESPONSE_EMPTY_PAYLOAD.json"), HashMap.class); @@ -839,14 +754,18 @@ public void testHandleEdgeResponse_emptyProposition() throws Exception{ // verify logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())); - final Map cachedPropositions = extension.getCachedPropositions(); - Assert.assertNotNull(cachedPropositions); - Assert.assertTrue(cachedPropositions.isEmpty()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getCachedPropositions().size()); } } @Test public void testHandleEdgeResponse_unsupportedItemInProposition() throws Exception{ + extension.setUpdateRequestEventIdsInProgress("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", new ArrayList() { + { + add(new DecisionScope("myMbox")); + } + }); try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { // setup final Map edgeResponseData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/EVENT_DATA_EDGE_RESPONSE_UNSUPPORTED_ITEM_IN_PAYLOAD.json"), HashMap.class); @@ -860,39 +779,36 @@ public void testHandleEdgeResponse_unsupportedItemInProposition() throws Excepti // verify logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()), Mockito.times(2)); - final Map cachedPropositions = extension.getCachedPropositions(); - Assert.assertNotNull(cachedPropositions); - Assert.assertTrue(cachedPropositions.isEmpty()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getCachedPropositions().size()); } } @Test - public void testHandleEdgeResponse_missingEventHandleInData() throws Exception{ + public void testHandleEdgeResponse_nullEventData(){ try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { // setup - final Map edgeResponseData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/EVENT_DATA_EDGE_RESPONSE_MISSING_EVENT_HANDLE.json"), HashMap.class); final Event testEvent = new Event.Builder("AEP Response Event Handle", "com.adobe.eventType.edge", "personalization:decisions") - .setEventData(edgeResponseData) + .setEventData(null) .build(); // test extension.handleEdgeResponse(testEvent); // verify - logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())); + logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.any())); - final Map cachedPropositions = extension.getCachedPropositions(); - Assert.assertNotNull(cachedPropositions); - Assert.assertTrue(cachedPropositions.isEmpty()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getCachedPropositions().size()); } } @Test - public void testHandleEdgeResponse_nullEventData(){ + public void testHandleEdgeResponse_emptyEventData() { try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { // setup final Event testEvent = new Event.Builder("AEP Response Event Handle", "com.adobe.eventType.edge", "personalization:decisions") - .setEventData(null) + .setEventData(new HashMap<>()) .build(); // test @@ -901,18 +817,18 @@ public void testHandleEdgeResponse_nullEventData(){ // verify logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.any())); - final Map cachedPropositions = extension.getCachedPropositions(); - Assert.assertNotNull(cachedPropositions); - Assert.assertTrue(cachedPropositions.isEmpty()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getCachedPropositions().size()); } } @Test - public void testHandleEdgeResponse_emptyEventData() { + public void testHandleEdgeResponse_requestEventIdNotBeingTracked() throws Exception { try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { // setup + final Map edgeResponseData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/EVENT_DATA_EDGE_RESPONSE_VALID.json"), HashMap.class); final Event testEvent = new Event.Builder("AEP Response Event Handle", "com.adobe.eventType.edge", "personalization:decisions") - .setEventData(new HashMap<>()) + .setEventData(edgeResponseData) .build(); // test @@ -921,9 +837,8 @@ public void testHandleEdgeResponse_emptyEventData() { // verify logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.any())); - final Map cachedPropositions = extension.getCachedPropositions(); - Assert.assertNotNull(cachedPropositions); - Assert.assertTrue(cachedPropositions.isEmpty()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getCachedPropositions().size()); } } @@ -942,9 +857,8 @@ public void testHandleEdgeErrorResponse() throws Exception{ // verify logMockedStatic.verify(() -> Log.warning(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.any())); - final Map cachedPropositions = extension.getCachedPropositions(); - Assert.assertNotNull(cachedPropositions); - Assert.assertTrue(cachedPropositions.isEmpty()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getCachedPropositions().size()); } } @@ -982,86 +896,65 @@ public void testHandleEdgeErrorResponse_emptyEventData(){ // verify logMockedStatic.verify(() -> Log.debug(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.any())); - final Map cachedPropositions = extension.getCachedPropositions(); - Assert.assertNotNull(cachedPropositions); - Assert.assertTrue(cachedPropositions.isEmpty()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getCachedPropositions().size()); } } @Test - public void testHandleOptimizeRequestContent_HandleGetPropositions_decisionScopeInCache() throws Exception{ - try (MockedStatic base64MockedStatic = Mockito.mockStatic(Base64.class)) { - base64MockedStatic.when(() -> Base64.decode(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt())) - .thenAnswer((Answer) invocation -> java.util.Base64.getDecoder().decode((String) invocation.getArguments()[0])); - // setup - setConfigurationSharedState(SharedStateStatus.SET, new HashMap() { - { - put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); - } - }); - - final Map testPropositionData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/PROPOSITION_VALID.json"), HashMap.class); - final Proposition testProposition = Proposition.fromEventData(testPropositionData); - Assert.assertNotNull(testProposition); - final Map cachedPropositions = new HashMap<>(); - cachedPropositions.put(new DecisionScope(testProposition.getScope()), testProposition); - extension.setCachedPropositions(cachedPropositions); - - final DecisionScope testScope = new DecisionScope("eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); - final Map testEventData = new HashMap<>(); - testEventData.put("requesttype", "getpropositions"); - testEventData.put("decisionscopes", new ArrayList>() { - { - add(testScope.toEventData()); - } - }); - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + public void testHandleOptimizeRequestContent_GetPropositionsEvent_shouldAddToSerialDispatcher() throws Exception{ + extension.setEventsDispatcher(mockEventsDispatcher); + setConfigurationSharedState(SharedStateStatus.SET, new HashMap() { + { + put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + } + }); - final Event testEvent = new Event.Builder("Optimize Get Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") - .setEventData(testEventData) - .build(); + final DecisionScope testScope = new DecisionScope("eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); + final Map testEventData = new HashMap<>(); + testEventData.put("requesttype", "getpropositions"); + testEventData.put("decisionscopes", new ArrayList>() { + { + add(testScope.toEventData()); + } + }); + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - // test - extension.handleOptimizeRequestContent(testEvent); + final Event testEvent = new Event.Builder("Optimize Get Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") + .setEventData(testEventData) + .build(); - // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); + // test + extension.handleOptimizeRequestContent(testEvent); - final Event dispatchedEvent = eventCaptor.getValue(); - Assert.assertEquals("com.adobe.eventType.optimize", dispatchedEvent.getType()); - Assert.assertEquals("com.adobe.eventSource.responseContent", dispatchedEvent.getSource()); - Assert.assertEquals(testEvent.getUniqueIdentifier(), dispatchedEvent.getResponseID()); - - final List> propositionsList = (List>) dispatchedEvent.getEventData().get("propositions"); - Assert.assertNotNull(propositionsList); - Assert.assertEquals(1, propositionsList.size()); - - final Map propositionData = propositionsList.get(0); - Assert.assertNotNull(propositionData); - final Proposition proposition = Proposition.fromEventData(propositionData); - Assert.assertNotNull(proposition); - - Assert.assertEquals("de03ac85-802a-4331-a905-a57053164d35", proposition.getId()); - Assert.assertEquals("eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==", proposition.getScope()); - Assert.assertTrue(proposition.getScopeDetails().isEmpty()); - Assert.assertEquals(1, proposition.getOffers().size()); - - Offer offer = proposition.getOffers().get(0); - Assert.assertEquals("xcore:personalized-offer:1111111111111111", offer.getId()); - Assert.assertEquals("10", offer.getEtag()); - Assert.assertEquals("https://ns.adobe.com/experience/offer-management/content-component-html", offer.getSchema()); - Assert.assertEquals(OfferType.HTML, offer.getType()); - Assert.assertEquals("

This is a HTML content

", offer.getContent()); - Assert.assertNull(offer.getLanguage()); - Assert.assertNull(offer.getCharacteristics()); + // verify + Mockito.verify(mockEventsDispatcher, Mockito.times(1)).offer(eventCaptor.capture()); + + final Event queuedEvent = eventCaptor.getValue(); + Assert.assertEquals("Optimize Get Propositions Request", queuedEvent.getName()); + Assert.assertEquals("com.adobe.eventType.optimize", queuedEvent.getType()); + Assert.assertEquals("com.adobe.eventSource.requestContent", queuedEvent.getSource()); + + final String requestType = (String) queuedEvent.getEventData().get("requesttype"); + Assert.assertEquals("getpropositions", requestType); + + final List> scopesData = (List>) queuedEvent.getEventData().get("decisionscopes"); + Assert.assertNotNull(scopesData); + final List scopes = new ArrayList<>(); + for (final Map scopeData: scopesData) { + final DecisionScope scope = DecisionScope.fromEventData(scopeData); + scopes.add(scope); } + Assert.assertEquals(1, scopes.size()); + Assert.assertEquals(testScope, scopes.get(0)); } @Test - public void testHandleOptimizeRequestContent_HandleGetPropositions_notAllDecisionScopesInCache() throws Exception{ + public void testHandleOptimizeRequestContent_GetPropositionsEvent_whenUpdateIsInProgress() { try (MockedStatic base64MockedStatic = Mockito.mockStatic(Base64.class)) { base64MockedStatic.when(() -> Base64.decode(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt())) .thenAnswer((Answer) invocation -> java.util.Base64.getDecoder().decode((String) invocation.getArguments()[0])); + // setup setConfigurationSharedState(SharedStateStatus.SET, new HashMap() { { @@ -1069,120 +962,56 @@ public void testHandleOptimizeRequestContent_HandleGetPropositions_notAllDecisio } }); - final Map testPropositionData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/PROPOSITION_VALID.json"), HashMap.class); - final Proposition testProposition = Proposition.fromEventData(testPropositionData); - Assert.assertNotNull(testProposition); - final Map cachedPropositions = new HashMap<>(); - cachedPropositions.put(new DecisionScope(testProposition.getScope()), testProposition); - extension.setCachedPropositions(cachedPropositions); - - final DecisionScope testScope1 = new DecisionScope("eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); - final DecisionScope testScope2 = new DecisionScope("myMbox"); + final DecisionScope testScope = new DecisionScope("eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); final Map testEventData = new HashMap<>(); - testEventData.put("requesttype", "getpropositions"); + testEventData.put("requesttype", "updatepropositions"); testEventData.put("decisionscopes", new ArrayList>() { { - add(testScope1.toEventData()); - add(testScope2.toEventData()); + add(testScope.toEventData()); } }); - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - final Event testEvent = new Event.Builder("Optimize Get Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") + final Event testEvent = new Event.Builder("Optimize Update Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") .setEventData(testEventData) .build(); - // test + + // simulate update extension.handleOptimizeRequestContent(testEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); - - final Event dispatchedEvent = eventCaptor.getValue(); - Assert.assertEquals("com.adobe.eventType.optimize", dispatchedEvent.getType()); - Assert.assertEquals("com.adobe.eventSource.responseContent", dispatchedEvent.getSource()); - Assert.assertEquals(testEvent.getUniqueIdentifier(), dispatchedEvent.getResponseID()); - - final List> propositionsList = (List>) dispatchedEvent.getEventData().get("propositions"); - Assert.assertNotNull(propositionsList); - Assert.assertEquals(1, propositionsList.size()); - - final Map propositionData = propositionsList.get(0); - Assert.assertNotNull(propositionData); - final Proposition proposition = Proposition.fromEventData(propositionData); - Assert.assertNotNull(proposition); - - Assert.assertEquals("de03ac85-802a-4331-a905-a57053164d35", proposition.getId()); - Assert.assertEquals("eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==", proposition.getScope()); - Assert.assertTrue(proposition.getScopeDetails().isEmpty()); - Assert.assertEquals(1, proposition.getOffers().size()); - - Offer offer = proposition.getOffers().get(0); - Assert.assertEquals("xcore:personalized-offer:1111111111111111", offer.getId()); - Assert.assertEquals("10", offer.getEtag()); - Assert.assertEquals("https://ns.adobe.com/experience/offer-management/content-component-html", offer.getSchema()); - Assert.assertEquals(OfferType.HTML, offer.getType()); - Assert.assertEquals("

This is a HTML content

", offer.getContent()); - Assert.assertNull(offer.getLanguage()); - Assert.assertNull(offer.getCharacteristics()); - } - } - - @Test - public void testHandleOptimizeRequestContent_HandleGetPropositions_noDecisionScopeInCache() throws Exception { - try (MockedStatic base64MockedStatic = Mockito.mockStatic(Base64.class)) { - base64MockedStatic.when(() -> Base64.decode(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt())) - .thenAnswer((Answer) invocation -> java.util.Base64.getDecoder().decode((String) invocation.getArguments()[0])); - - // setup - setConfigurationSharedState(SharedStateStatus.SET, new HashMap() { + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(1, updateEventIdsInProgress.size()); + Assert.assertTrue(updateEventIdsInProgress.containsValue(new ArrayList() { { - put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + add(testScope); } - }); - - final Map testPropositionData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/PROPOSITION_VALID.json"), HashMap.class); - final Proposition testProposition = Proposition.fromEventData(testPropositionData); - Assert.assertNotNull(testProposition); - final Map cachedPropositions = new HashMap<>(); - cachedPropositions.put(new DecisionScope(testProposition.getScope()), testProposition); - extension.setCachedPropositions(cachedPropositions); + })); - final DecisionScope testScope1 = new DecisionScope("myMbox1"); - final DecisionScope testScope2 = new DecisionScope("myMbox2"); - final Map testEventData = new HashMap<>(); - testEventData.put("requesttype", "getpropositions"); - testEventData.put("decisionscopes", new ArrayList>() { + // dispatch get event + final Map testGetEventData = new HashMap<>(); + testGetEventData.put("requesttype", "getpropositions"); + testGetEventData.put("decisionscopes", new ArrayList>() { { - add(testScope1.toEventData()); - add(testScope2.toEventData()); + add(testScope.toEventData()); } }); final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - final Event testEvent = new Event.Builder("Optimize Get Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") - .setEventData(testEventData) + final Event testGetEvent = new Event.Builder("Optimize Get Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") + .setEventData(testGetEventData) .build(); - // test - extension.handleOptimizeRequestContent(testEvent); + // simulate get + extension.handleOptimizeRequestContent(testGetEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); - - final Event dispatchedEvent = eventCaptor.getValue(); - Assert.assertEquals("com.adobe.eventType.optimize", dispatchedEvent.getType()); - Assert.assertEquals("com.adobe.eventSource.responseContent", dispatchedEvent.getSource()); - Assert.assertEquals(testEvent.getUniqueIdentifier(), dispatchedEvent.getResponseID()); - - final List> propositionsList = (List>) dispatchedEvent.getEventData().get("propositions"); - Assert.assertNotNull(propositionsList); - Assert.assertEquals(0, propositionsList.size()); + Mockito.verify(mockExtensionApi, Mockito.never()).dispatch(eventCaptor.capture()); } } @Test - public void testHandleOptimizeRequestContent_HandleGetPropositions_missingDecisionScopesList() throws Exception { + public void testHandleOptimizeRequestContent_GetPropositionsEvent_whenUpdateIsComplete() { try (MockedStatic base64MockedStatic = Mockito.mockStatic(Base64.class)) { base64MockedStatic.when(() -> Base64.decode(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt())) .thenAnswer((Answer) invocation -> java.util.Base64.getDecoder().decode((String) invocation.getArguments()[0])); @@ -1194,84 +1023,67 @@ public void testHandleOptimizeRequestContent_HandleGetPropositions_missingDecisi } }); - final Map testPropositionData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/PROPOSITION_VALID.json"), HashMap.class); - final Proposition testProposition = Proposition.fromEventData(testPropositionData); - Assert.assertNotNull(testProposition); - final Map cachedPropositions = new HashMap<>(); - cachedPropositions.put(new DecisionScope(testProposition.getScope()), testProposition); - extension.setCachedPropositions(cachedPropositions); - + final DecisionScope testScope = new DecisionScope("eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); final Map testEventData = new HashMap<>(); - testEventData.put("requesttype", "getpropositions"); - - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - final Event testEvent = new Event.Builder("Optimize Get Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") + testEventData.put("requesttype", "updatepropositions"); + testEventData.put("decisionscopes", new ArrayList>() { + { + add(testScope.toEventData()); + } + }); + final Event testEvent = new Event.Builder("Optimize Update Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") .setEventData(testEventData) .build(); - // test + + // simulate update extension.handleOptimizeRequestContent(testEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); - - final Event dispatchedEvent = eventCaptor.getValue(); - Assert.assertEquals("com.adobe.eventType.optimize", dispatchedEvent.getType()); - Assert.assertEquals("com.adobe.eventSource.responseContent", dispatchedEvent.getSource()); - Assert.assertEquals(testEvent.getUniqueIdentifier(), dispatchedEvent.getResponseID()); - - final List> propositionsList = (List>) dispatchedEvent.getEventData().get("propositions"); - Assert.assertNull(propositionsList); - - Assert.assertEquals(0, dispatchedEvent.getEventData().get("responseerror")); - } - } - - @Test - public void testHandleOptimizeRequestContent_HandleGetPropositions_emptyCachedPropositions(){ - try (MockedStatic base64MockedStatic = Mockito.mockStatic(Base64.class)) { - base64MockedStatic.when(() -> Base64.decode(ArgumentMatchers.anyString(), ArgumentMatchers.anyInt())) - .thenAnswer((Answer) invocation -> java.util.Base64.getDecoder().decode((String) invocation.getArguments()[0])); - - // setup - setConfigurationSharedState(SharedStateStatus.SET, new HashMap() { + final Map> updateEventIdsInProgress = extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(1, updateEventIdsInProgress.size()); + Assert.assertTrue(updateEventIdsInProgress.containsValue(new ArrayList() { { - put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + add(testScope); } - }); - - final Map cachedPropositions = new HashMap<>(); - extension.setCachedPropositions(cachedPropositions); + })); + Mockito.clearInvocations(mockExtensionApi); - final DecisionScope testScope = new DecisionScope("eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); - final Map testEventData = new HashMap<>(); - testEventData.put("requesttype", "getpropositions"); - testEventData.put("decisionscopes", new ArrayList>() { + // simulate get + final Map testGetEventData = new HashMap<>(); + testGetEventData.put("requesttype", "getpropositions"); + testGetEventData.put("decisionscopes", new ArrayList>() { { add(testScope.toEventData()); } }); - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - final Event testEvent = new Event.Builder("Optimize Get Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") - .setEventData(testEventData) + final Event testGetEvent = new Event.Builder("Optimize Get Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") + .setEventData(testGetEventData) + .build(); + + // simulate update complete + final Event testUpdateCompleteEvent = new Event.Builder("Optimize Update Propositions Complete", "com.adobe.eventType.optimize", "com.adobe.eventSource.contentComplete") + .setEventData(new HashMap() { + { + put("completedUpdateRequestForEventId", updateEventIdsInProgress.keySet().toArray()[0]); + } + }) .build(); + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + // test - extension.handleOptimizeRequestContent(testEvent); + extension.handleOptimizeRequestContent(testGetEvent); + extension.handleUpdatePropositionsCompleted(testUpdateCompleteEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.times(1)).dispatch(eventCaptor.capture()); - + Mockito.verify(mockExtensionApi, Mockito.after(2000L).times(1)).dispatch(eventCaptor.capture()); final Event dispatchedEvent = eventCaptor.getValue(); + Assert.assertEquals("Optimize Response", dispatchedEvent.getName()); Assert.assertEquals("com.adobe.eventType.optimize", dispatchedEvent.getType()); Assert.assertEquals("com.adobe.eventSource.responseContent", dispatchedEvent.getSource()); - Assert.assertEquals(testEvent.getUniqueIdentifier(), dispatchedEvent.getResponseID()); - - final List> propositionsList = (List>) dispatchedEvent.getEventData().get("propositions"); - Assert.assertNotNull(propositionsList); - Assert.assertEquals(0, propositionsList.size()); } } @@ -1556,6 +1368,92 @@ public void testHandleClearPropositions_coreResetIdentities() throws Exception{ Assert.assertTrue(actualCachedPropositions.isEmpty()); } + @Test + public void testHandleUpdatePropositionsComplete_updatesPropositionsCache() throws Exception{ + // setup + extension.setUpdateRequestEventIdsInProgress("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", new ArrayList() { + { + add(new DecisionScope("eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==")); + } + }); + final Map testPropositionData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/PROPOSITION_VALID.json"), HashMap.class); + final Proposition testProposition = Proposition.fromEventData(testPropositionData); + Assert.assertNotNull(testProposition); + final Map propositionsInProgress = new HashMap<>(); + propositionsInProgress.put(new DecisionScope(testProposition.getScope()), testProposition); + extension.setPropositionsInProgress(propositionsInProgress); + + final Event testEvent = new Event.Builder("Optimize Update Propositions Complete", "com.adobe.eventType.optimize", "com.adobe.eventSource.contentComplete") + .setEventData(new HashMap(){ + { + put("completedUpdateRequestForEventId", "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"); + } + }) + .build(); + + // test + extension.handleUpdatePropositionsCompleted(testEvent); + + // verify + Assert.assertEquals(1, extension.getCachedPropositions().size()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getUpdateRequestEventIdsInProgress().size()); + } + + @Test + public void testHandleUpdatePropositionsComplete_requestEventIdNotBeingTracked() throws Exception{ + // setup + final Map testPropositionData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/PROPOSITION_VALID.json"), HashMap.class); + final Proposition testProposition = Proposition.fromEventData(testPropositionData); + Assert.assertNotNull(testProposition); + final Map propositionsInProgress = new HashMap<>(); + propositionsInProgress.put(new DecisionScope(testProposition.getScope()), testProposition); + extension.setPropositionsInProgress(propositionsInProgress); + + final Event testEvent = new Event.Builder("Optimize Update Propositions Complete", "com.adobe.eventType.optimize", "com.adobe.eventSource.contentComplete") + .setEventData(new HashMap(){ + { + put("completedUpdateRequestForEventId", "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA"); + } + }) + .build(); + + // test + extension.handleUpdatePropositionsCompleted(testEvent); + + // verify + Assert.assertEquals(0, extension.getCachedPropositions().size()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(0, extension.getUpdateRequestEventIdsInProgress().size()); + } + + @Test + public void testHandleUpdatePropositionsComplete_missingRequestEventIdInData() throws Exception{ + // setup + extension.setUpdateRequestEventIdsInProgress("AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", new ArrayList() { + { + add(new DecisionScope("eydhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==")); + } + }); + final Map testPropositionData = new ObjectMapper().readValue(getClass().getClassLoader().getResource("json/PROPOSITION_VALID.json"), HashMap.class); + final Proposition testProposition = Proposition.fromEventData(testPropositionData); + Assert.assertNotNull(testProposition); + final Map propositionsInProgress = new HashMap<>(); + propositionsInProgress.put(new DecisionScope(testProposition.getScope()), testProposition); + extension.setPropositionsInProgress(propositionsInProgress); + + final Event testEvent = new Event.Builder("Optimize Update Propositions Complete", "com.adobe.eventType.optimize", "com.adobe.eventSource.contentComplete") + .setEventData(new HashMap()) + .build(); + + // test + extension.handleUpdatePropositionsCompleted(testEvent); + + // verify + Assert.assertEquals(0, extension.getCachedPropositions().size()); + Assert.assertEquals(0, extension.getPropositionsInProgress().size()); + Assert.assertEquals(1, extension.getUpdateRequestEventIdsInProgress().size()); + } // Helper methods private void setConfigurationSharedState(final SharedStateStatus status, final Map data) { diff --git a/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OptimizeTests.java b/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OptimizeTests.java index 64a4c63c..8ac1287b 100644 --- a/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OptimizeTests.java +++ b/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OptimizeTests.java @@ -55,7 +55,7 @@ public void teardown() { public void test_extensionVersion() { // test final String extensionVersion = Optimize.extensionVersion(); - Assert.assertEquals("extensionVersion API should return the correct version string.", "2.0.1", + Assert.assertEquals("extensionVersion API should return the correct version string.", "2.0.2", extensionVersion); }