From 2c8d87f225532113e4fa46c8c9f747e651ab7e29 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 10 Sep 2024 11:54:42 +0530 Subject: [PATCH 01/37] Get proposition call will be dispatched immediately if all scopes are cached --- .../mobile/optimize/OptimizeExtension.java | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) 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 770cfe10..3ef36966 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 @@ -223,10 +223,66 @@ void handleOptimizeRequestContent(@NonNull final Event event) { handleUpdatePropositions(event); break; case OptimizeConstants.EventDataValues.REQUEST_TYPE_GET: - // Queue the get propositions event in the events dispatcher to ensure any prior - // update requests are completed - // before it is processed. - eventsDispatcher.offer(event); + try { + // Fetch decision scopes from the event + List> decisionScopesData = + DataReader.getTypedListOfMap( + Object.class, + eventData, + OptimizeConstants.EventDataKeys.DECISION_SCOPES); + List eventDecisionScopes = retrieveValidDecisionScopes(decisionScopesData); + + if (OptimizeUtils.isNullOrEmpty(eventDecisionScopes)) { + 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)); + return; + } + + // Fetch propositions for the decision scopes from the cache + Map fetchedPropositions = new HashMap<>(); + for (DecisionScope scope : eventDecisionScopes) { + if (cachedPropositions.containsKey(scope)) { + fetchedPropositions.put(scope, cachedPropositions.get(scope)); + } + } + + // Check if all scopes are cached and none are in progress + boolean anyScopeInProgress = false; + for (DecisionScope scope : eventDecisionScopes) { + if (propositionsInProgress.containsKey(scope)) { + anyScopeInProgress = true; + break; + } + } + + if ((fetchedPropositions.size() == eventDecisionScopes.size()) && !anyScopeInProgress) { + Log.trace( + OptimizeConstants.LOG_TAG, + SELF_TAG, + "handleOptimizeRequestContent - All scopes are cached and none are in progress, dispatching event directly."); + + // Dispatch the event directly + handleGetPropositions(event); + } else { + Log.trace( + OptimizeConstants.LOG_TAG, + SELF_TAG, + "handleOptimizeRequestContent - Scopes are not fully cached or are in progress, adding event to dispatcher."); + eventsDispatcher.offer(event); + } + break; + } 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()); + } break; case OptimizeConstants.EventDataValues.REQUEST_TYPE_TRACK: handleTrackPropositions(event); From 57a05da67e5382fdf4393fb606de1eb0ca3432f1 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 10 Sep 2024 12:37:00 +0530 Subject: [PATCH 02/37] Updated test cases with mock base64 support --- .../optimize/OptimizeExtensionTests.java | 113 ++++++++++-------- 1 file changed, 63 insertions(+), 50 deletions(-) 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 ea13e256..f21f9b77 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 @@ -1300,61 +1300,74 @@ public void testHandleEdgeErrorResponse_emptyEventData() { @Test public void testHandleOptimizeRequestContent_GetPropositionsEvent_shouldAddToSerialDispatcher() throws Exception { - extension.setEventsDispatcher(mockEventsDispatcher); - setConfigurationSharedState( - SharedStateStatus.SET, - new HashMap() { - { - put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); - } - }); + 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])); + extension.setEventsDispatcher(mockEventsDispatcher); + setConfigurationSharedState( + SharedStateStatus.SET, + new HashMap() { + { + put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + } + }); - 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); + 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); - final Event testEvent = - new Event.Builder( - "Optimize Get Propositions Request", - "com.adobe.eventType.optimize", - "com.adobe.eventSource.requestContent") - .setEventData(testEventData) - .build(); + final Event testEvent = + new Event.Builder( + "Optimize Get Propositions Request", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.requestContent") + .setEventData(testEventData) + .build(); - // test - extension.handleOptimizeRequestContent(testEvent); + // test + extension.handleOptimizeRequestContent(testEvent); - // 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); + // 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)); } - Assert.assertEquals(1, scopes.size()); - Assert.assertEquals(testScope, scopes.get(0)); } @Test From bbd5ca72a5695337682181f9404212c990874a91 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 24 Sep 2024 18:23:03 +0530 Subject: [PATCH 03/37] Added test cases for already cached proposition --- .../mobile/optimize/OptimizeExtension.java | 27 +-- .../optimize/OptimizeExtensionTests.java | 157 +++++++++++++++++- 2 files changed, 170 insertions(+), 14 deletions(-) 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 3ef36966..0da974ed 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 @@ -230,15 +230,19 @@ void handleOptimizeRequestContent(@NonNull final Event event) { Object.class, eventData, OptimizeConstants.EventDataKeys.DECISION_SCOPES); - List eventDecisionScopes = retrieveValidDecisionScopes(decisionScopesData); + List eventDecisionScopes = + retrieveValidDecisionScopes(decisionScopesData); if (OptimizeUtils.isNullOrEmpty(eventDecisionScopes)) { 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)); + "handleGetPropositions - Cannot process the get propositions" + + " request event, provided list of decision scopes has no" + + " valid scope."); + getApi().dispatch( + createResponseEventWithError( + event, AdobeError.UNEXPECTED_ERROR)); return; } @@ -259,11 +263,13 @@ void handleOptimizeRequestContent(@NonNull final Event event) { } } - if ((fetchedPropositions.size() == eventDecisionScopes.size()) && !anyScopeInProgress) { + if ((fetchedPropositions.size() == eventDecisionScopes.size()) + && !anyScopeInProgress) { Log.trace( OptimizeConstants.LOG_TAG, SELF_TAG, - "handleOptimizeRequestContent - All scopes are cached and none are in progress, dispatching event directly."); + "handleOptimizeRequestContent - All scopes are cached and none are" + + " in progress, dispatching event directly."); // Dispatch the event directly handleGetPropositions(event); @@ -271,16 +277,17 @@ void handleOptimizeRequestContent(@NonNull final Event event) { Log.trace( OptimizeConstants.LOG_TAG, SELF_TAG, - "handleOptimizeRequestContent - Scopes are not fully cached or are in progress, adding event to dispatcher."); + "handleOptimizeRequestContent - Scopes are not fully cached or are" + + " in progress, adding event to dispatcher."); eventsDispatcher.offer(event); } break; - } catch (final Exception e) { + } catch (final Exception e) { Log.warning( OptimizeConstants.LOG_TAG, SELF_TAG, - "handleUpdatePropositions - Failed to process update propositions request event" - + " due to an exception (%s)!", + "handleUpdatePropositions - Failed to process update propositions" + + " request event due to an exception (%s)!", e.getLocalizedMessage()); } break; 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 f21f9b77..814ce44b 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 @@ -1337,9 +1337,9 @@ public void testHandleOptimizeRequestContent_GetPropositionsEvent_shouldAddToSer final Event testEvent = new Event.Builder( - "Optimize Get Propositions Request", - "com.adobe.eventType.optimize", - "com.adobe.eventSource.requestContent") + "Optimize Get Propositions Request", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.requestContent") .setEventData(testEventData) .build(); @@ -1561,7 +1561,7 @@ public void testHandleOptimizeRequestContent_GetPropositionsEvent_whenUpdateIsCo extension.handleUpdatePropositionsCompleted(testUpdateCompleteEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.after(2000L).times(1)) + Mockito.verify(mockExtensionApi, Mockito.after(5000L).times(1)) .dispatch(eventCaptor.capture()); final Event dispatchedEvent = eventCaptor.getValue(); Assert.assertEquals("Optimize Response", dispatchedEvent.getName()); @@ -2165,4 +2165,153 @@ private void setConfigurationSharedState( ArgumentMatchers.eq(SharedStateResolution.ANY))) .thenReturn(new SharedStateResult(status, data)); } + + @Test + public void testGetPropositions_dispatchPropositionFromCacheBeforeNextUpdate() { + 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"); + } + }); + + extension.setEventsDispatcher(mockEventsDispatcher); + + // prepare update event + final DecisionScope updateScope = + new DecisionScope( + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); + final Map testEventData = new HashMap<>(); + testEventData.put("requesttype", "updatepropositions"); + testEventData.put( + "decisionscopes", + new ArrayList>() { + { + add(updateScope.toEventData()); + } + }); + final Event updateEvent = + new Event.Builder( + "Optimize Update Propositions Request", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.requestContent") + .setEventData(testEventData) + .build(); + + // prepare cache data + final Map testPropositionData = + new ObjectMapper() + .readValue( + getClass() + .getClassLoader() + .getResource("json/PROPOSITION_VALID.json"), + HashMap.class); + + final OptimizeProposition testOptimizeProposition = + OptimizeProposition.fromEventData(testPropositionData); + final DecisionScope getPropositionScope = + new DecisionScope(testOptimizeProposition.getScope()); + Assert.assertNotNull(testOptimizeProposition); + final Map cachedPropositions = new HashMap<>(); + cachedPropositions.put(getPropositionScope, testOptimizeProposition); + // update cache + extension.setCachedPropositions(cachedPropositions); + + // simulate update + extension.handleOptimizeRequestContent(updateEvent); + + // verify update in progress + final Map> updateEventIdsInProgress = + extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(1, updateEventIdsInProgress.size()); + Assert.assertTrue( + updateEventIdsInProgress.containsValue( + new ArrayList() { + { + add(updateScope); + } + })); + Mockito.clearInvocations(mockExtensionApi); + + // prepare get event + final Map testGetEventData = new HashMap<>(); + testGetEventData.put("requesttype", "getpropositions"); + testGetEventData.put( + "decisionscopes", + new ArrayList>() { + { + add(getPropositionScope.toEventData()); + } + }); + + final Event testGetEvent = + new Event.Builder( + "Optimize Get Propositions Request", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.requestContent") + .setEventData(testGetEventData) + .build(); + + // prepare update complete event + 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); + + // simulate test + extension.handleOptimizeRequestContent(testGetEvent); + extension.handleUpdatePropositionsCompleted(testUpdateCompleteEvent); + + // verify + 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()); + final List> propositionsList = + (List>) dispatchedEvent.getEventData().get("propositions"); + final Map cachedPropositionsAfter = + extension.getCachedPropositions(); + Assert.assertEquals(1, cachedPropositionsAfter.size()); + Assert.assertEquals( + getPropositionScope.getName(), propositionsList.get(0).get("scope")); + Assert.assertEquals( + "de03ac85-802a-4331-a905-a57053164d35", propositionsList.get(0).get("id")); + Assert.assertEquals( + cachedPropositionsAfter.get(getPropositionScope).getId(), + propositionsList.get(0).get("id")); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } From b4d62cf89aece8d19cca07501a6d182bfb2f52d4 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 24 Sep 2024 20:18:21 +0530 Subject: [PATCH 04/37] Fix: Checking inProgress Events --- .../marketing/mobile/optimize/OptimizeExtension.java | 9 ++++++++- .../mobile/optimize/OptimizeExtensionTests.java | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) 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 0da974ed..18d8e07c 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 @@ -29,8 +29,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; class OptimizeExtension extends Extension { @@ -256,8 +258,13 @@ void handleOptimizeRequestContent(@NonNull final Event event) { // Check if all scopes are cached and none are in progress boolean anyScopeInProgress = false; + Set scopesInProgress = new HashSet<>(); + for (List updatingScope : + updateRequestEventIdsInProgress.values()) { + scopesInProgress.addAll(updatingScope); + } for (DecisionScope scope : eventDecisionScopes) { - if (propositionsInProgress.containsKey(scope)) { + if (scopesInProgress.contains(scope)) { anyScopeInProgress = true; break; } 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 814ce44b..3ee3dbee 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 @@ -1561,7 +1561,7 @@ public void testHandleOptimizeRequestContent_GetPropositionsEvent_whenUpdateIsCo extension.handleUpdatePropositionsCompleted(testUpdateCompleteEvent); // verify - Mockito.verify(mockExtensionApi, Mockito.after(5000L).times(1)) + Mockito.verify(mockExtensionApi, Mockito.after(2000L).times(1)) .dispatch(eventCaptor.capture()); final Event dispatchedEvent = eventCaptor.getValue(); Assert.assertEquals("Optimize Response", dispatchedEvent.getName()); From 9af8803d54b5b727d46daa482a9b3495cb1d5deb Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 24 Sep 2024 21:08:43 +0530 Subject: [PATCH 05/37] Add: WIP Test for queuing get event after an update event with same decision scope --- .../mobile/optimize/OptimizeExtension.java | 3 +- .../optimize/OptimizeExtensionTests.java | 157 ++++++++++++++++++ 2 files changed, 158 insertions(+), 2 deletions(-) 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 e1688908..20a96e3a 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 @@ -32,7 +32,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; class OptimizeExtension extends Extension { @@ -267,7 +266,7 @@ void handleOptimizeRequestContent(@NonNull final Event event) { // Check if all scopes are cached and none are in progress boolean anyScopeInProgress = false; - Set scopesInProgress = new HashSet<>(); + HashSet scopesInProgress = new HashSet<>(); for (List updatingScope : updateRequestEventIdsInProgress.values()) { scopesInProgress.addAll(updatingScope); 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 6f9e514d..91505cb9 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 @@ -2371,4 +2371,161 @@ public void testGetPropositions_dispatchPropositionFromCacheBeforeNextUpdate() { throw new RuntimeException(e); } } + + /// WIP test, exception in getProposition + @Test + public void testGetPropositions_ScopesFromEventIsUpdateInProgress() { + 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"); + } + }); + + + // prepare cache data + final Map cachedPropositionData = + new ObjectMapper() + .readValue( + getClass() + .getClassLoader() + .getResource("json/PROPOSITION_VALID.json"), + HashMap.class); + + final Map updatedPropositionData = + new ObjectMapper() + .readValue( + getClass() + .getClassLoader() + .getResource("json/PROPOSITION_VALID_TARGET.json"), + HashMap.class); + + + final OptimizeProposition cachedProposition = + OptimizeProposition.fromEventData(cachedPropositionData); + final OptimizeProposition updatedProposition = + OptimizeProposition.fromEventData(updatedPropositionData); + + final DecisionScope decisionScope = + new DecisionScope(cachedProposition.getScope()); + Assert.assertNotNull(cachedProposition); + + // updating the cache + final Map cachedPropositions = new HashMap<>(); + cachedPropositions.put(decisionScope, cachedProposition); + extension.setCachedPropositions(cachedPropositions); + + // prepare update event data + final Map testEventData = new HashMap<>(); + testEventData.put("requesttype", "updatepropositions"); + testEventData.put( + "decisionscopes", + new ArrayList>() { + { + add(decisionScope.toEventData()); + } + }); + final Event updateEvent = + new Event.Builder( + "Optimize Update Propositions Request", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.requestContent") + .setEventData(testEventData) + .build(); + + + // simulate update + extension.handleOptimizeRequestContent(updateEvent); + + // verify update in progress + final Map> updateEventIdsInProgress = + extension.getUpdateRequestEventIdsInProgress(); + Assert.assertNotNull(updateEventIdsInProgress); + Assert.assertEquals(1, updateEventIdsInProgress.size()); + Assert.assertTrue( + updateEventIdsInProgress.containsValue( + new ArrayList() { + { + add(decisionScope); + } + })); + Mockito.clearInvocations(mockExtensionApi); + + // prepare get event + final Map testGetEventData = new HashMap<>(); + testGetEventData.put("requesttype", "getpropositions"); + testGetEventData.put( + "decisionscopes", + new ArrayList>() { + { + add(decisionScope.toEventData()); + } + }); + + final Event testGetEvent = + new Event.Builder( + "Optimize Get Propositions Request", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.requestContent") + .setEventData(testGetEventData) + .build(); + + // prepare update complete event + 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]); + put("propositions", updatedProposition.toEventData()); + } + }) + .build(); + + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + // simulate test + extension.handleOptimizeRequestContent(testGetEvent); + extension.handleUpdatePropositionsCompleted(testUpdateCompleteEvent); + + // verify +// Mockito.verify(mockEventsDispatcher, Mockito.after(2000L).times(1)) +// .offer(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()); + final List> propositionsList = + (List>) dispatchedEvent.getEventData().get("propositions"); + final Map cachedPropositionsAfter = + extension.getCachedPropositions(); + Assert.assertEquals(1, cachedPropositionsAfter.size()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } From eeae29ee7ac88ae98005cc78e28baff75f5733a7 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 24 Sep 2024 21:36:18 +0530 Subject: [PATCH 06/37] minor formatting --- .../optimize/OptimizeExtensionTests.java | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) 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 91505cb9..55d2079a 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 @@ -2397,7 +2397,6 @@ public void testGetPropositions_ScopesFromEventIsUpdateInProgress() { } }); - // prepare cache data final Map cachedPropositionData = new ObjectMapper() @@ -2415,14 +2414,12 @@ public void testGetPropositions_ScopesFromEventIsUpdateInProgress() { .getResource("json/PROPOSITION_VALID_TARGET.json"), HashMap.class); - final OptimizeProposition cachedProposition = OptimizeProposition.fromEventData(cachedPropositionData); final OptimizeProposition updatedProposition = OptimizeProposition.fromEventData(updatedPropositionData); - final DecisionScope decisionScope = - new DecisionScope(cachedProposition.getScope()); + final DecisionScope decisionScope = new DecisionScope(cachedProposition.getScope()); Assert.assertNotNull(cachedProposition); // updating the cache @@ -2442,13 +2439,12 @@ public void testGetPropositions_ScopesFromEventIsUpdateInProgress() { }); final Event updateEvent = new Event.Builder( - "Optimize Update Propositions Request", - "com.adobe.eventType.optimize", - "com.adobe.eventSource.requestContent") + "Optimize Update Propositions Request", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.requestContent") .setEventData(testEventData) .build(); - // simulate update extension.handleOptimizeRequestContent(updateEvent); @@ -2479,18 +2475,18 @@ public void testGetPropositions_ScopesFromEventIsUpdateInProgress() { final Event testGetEvent = new Event.Builder( - "Optimize Get Propositions Request", - "com.adobe.eventType.optimize", - "com.adobe.eventSource.requestContent") + "Optimize Get Propositions Request", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.requestContent") .setEventData(testGetEventData) .build(); // prepare update complete event final Event testUpdateCompleteEvent = new Event.Builder( - "Optimize Update Propositions Complete", - "com.adobe.eventType.optimize", - "com.adobe.eventSource.contentComplete") + "Optimize Update Propositions Complete", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.contentComplete") .setEventData( new HashMap() { { @@ -2509,8 +2505,8 @@ public void testGetPropositions_ScopesFromEventIsUpdateInProgress() { extension.handleUpdatePropositionsCompleted(testUpdateCompleteEvent); // verify -// Mockito.verify(mockEventsDispatcher, Mockito.after(2000L).times(1)) -// .offer(eventCaptor.capture()); + // Mockito.verify(mockEventsDispatcher, Mockito.after(2000L).times(1)) + // .offer(eventCaptor.capture()); Mockito.verify(mockExtensionApi, Mockito.after(2000L).times(1)) .dispatch(eventCaptor.capture()); From 8d515b3b3689cd29d1efb1b801d676b254a4f2e9 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 8 Oct 2024 22:01:00 +0530 Subject: [PATCH 07/37] Add: Added a test case for testGetPropositions_ScopesFromEventIsUpdateInProgress --- .../optimize/OptimizeFunctionalTests.java | 243 ++++++++++++++++++ .../optimize/OptimizeExtensionTests.java | 153 ----------- 2 files changed, 243 insertions(+), 153 deletions(-) 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 b41580d7..1edb54ec 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 @@ -2194,6 +2194,249 @@ public void testPropositionGenerateReferenceXdm() throws IOException { "de03ac85-802a-4331-a905-a57053164d35", decisioning.get("propositionID")); } + @Test + public void testGetPropositions_ScopesFromEventIsUpdateInProgress() + throws InterruptedException, IOException { + // setup + final Map configData = new HashMap<>(); + configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + updateConfiguration(configData); + + final String decisionScopeString = + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="; + + // Setting up the cache with a decision scope and a proposition. + Optimize.updatePropositions( + Collections.singletonList(new DecisionScope(decisionScopeString)), null, null); + List eventsListEdge = + TestHelper.getDispatchedEventsWith( + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.REQUEST_CONTENT, + 1000); + + Event edgeEvent = eventsListEdge.get(0); + final String requestEventId = edgeEvent.getUniqueIdentifier(); + final String edgeResponseData = + "{\n" + + " \"payload\": [\n" + + " {\n" + + " \"id\":" + + " \"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\",\n" + + " \"scope\": \"" + + decisionScopeString + + "\",\n" + + " \"activity\": {\n" + + " \"etag\": \"8\",\n" + + " \"id\":" + + " \"xcore:offer-activity:1111111111111111\"\n" + + " },\n" + + " \"placement\": {\n" + + " \"etag\": \"1\",\n" + + " \"id\":" + + " \"xcore:offer-placement:1111111111111111\"\n" + + " },\n" + + " \"items\": [\n" + + " {\n" + + " \"id\":" + + " \"xcore:personalized-offer:1111111111111111\",\n" + + " \"etag\": \"10\",\n" + + " \"score\": 1,\n" + + " \"schema\":" + + " \"https://ns.adobe.com/experience/offer-management/content-component-html\",\n" + + " \"data\": {\n" + + " \"id\":" + + " \"xcore:personalized-offer:1111111111111111\",\n" + + " \"format\":" + + " \"text/html\",\n" + + " \"content\":" + + " \"

This is HTML content

\",\n" + + " \"characteristics\":" + + " {\n" + + " \"testing\":" + + " \"true\"\n" + + " }\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"requestEventId\":\"" + + requestEventId + + "\",\n" + + " \"requestId\":" + + " \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" + + " \"type\":" + + " \"personalization:decisions\"\n" + + " }"; + + ObjectMapper objectMapper = new ObjectMapper(); + Map eventData = + objectMapper.readValue( + edgeResponseData, new TypeReference>() {}); + + Event event = + new Event.Builder( + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) + .setEventData(eventData) + .build(); + 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(); + // Cache is now updated with a proposition. + MobileCore.dispatchEvent(completionEvent); + + Thread.sleep(10000); + TestHelper.resetTestExpectations(); + + // Firing another update event with same decision scope but different proposition data. + Optimize.updatePropositions( + Collections.singletonList(new DecisionScope(decisionScopeString)), null, null); + + List secondEventsListEdge = + TestHelper.getDispatchedEventsWith( + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.REQUEST_CONTENT, + 1000); + + Event secondEdgeEvent = secondEventsListEdge.get(0); + + final String secondRequestEventId = secondEdgeEvent.getUniqueIdentifier(); + // Send Edge Response event + final String secondEdgeResponseData = + "{\n" + + " \"payload\": [\n" + + " {\n" + + " \"id\":" + + " \"cccccccc-cccc-cccc-cccc-cccccccc\",\n" + + " \"scope\": \"" + + decisionScopeString + + "\",\n" + + " \"activity\": {\n" + + " \"etag\": \"8\",\n" + + " \"id\":" + + " \"xcore:offer-activity:1111111111111111\"\n" + + " },\n" + + " \"placement\": {\n" + + " \"etag\": \"1\",\n" + + " \"id\":" + + " \"xcore:offer-placement:1111111111111111\"\n" + + " },\n" + + " \"items\": [\n" + + " {\n" + + " \"id\":" + + " \"xcore:personalized-offer:1111111111111111\",\n" + + " \"etag\": \"10\",\n" + + " \"score\": 1,\n" + + " \"schema\":" + + " \"https://ns.adobe.com/experience/offer-management/content-component-html\",\n" + + " \"data\": {\n" + + " \"id\":" + + " \"xcore:personalized-offer:1111111111111111\",\n" + + " \"format\":" + + " \"text/html\",\n" + + " \"content\":" + + " \"

This is HTML content

\",\n" + + " \"characteristics\":" + + " {\n" + + " \"testing\":" + + " \"true\"\n" + + " }\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ],\n" + + " \"requestEventId\":\"" + + secondRequestEventId + + "\",\n" + + " \"requestId\":" + + " \"CCCCCCCC-CCCC-CCCC-CCCC-CCCCCCCC\",\n" + + " \"type\":" + + " \"personalization:decisions\"\n" + + " }"; + + ObjectMapper secondObjectMapper = new ObjectMapper(); + Map secondEventData = + secondObjectMapper.readValue( + secondEdgeResponseData, new TypeReference>() {}); + + Event secondEvent = + new Event.Builder( + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) + .setEventData(secondEventData) + .build(); + + // Completing the second event with updated proposition. + MobileCore.dispatchEvent(secondEvent); + Thread.sleep(1000); + + // Executing Get proposition event before the update event is completed. + DecisionScope decisionScope = new DecisionScope(decisionScopeString); + final Map propositionMap = new HashMap<>(); + + Optimize.getPropositions( + Collections.singletonList(decisionScope), + new AdobeCallbackWithError>() { + @Override + public void fail(AdobeError adobeError) { + Assert.fail("Error in getting cached propositions"); + } + + @Override + public void call( + Map decisionScopePropositionMap) { + propositionMap.putAll(decisionScopePropositionMap); + + // Assertions + // Map should contain the updated proposition data. + OptimizeProposition optimizeProposition = propositionMap.get(decisionScope); + Assert.assertNotNull(optimizeProposition); + Assert.assertEquals( + "cccccccc-cccc-cccc-cccc-cccccccc", optimizeProposition.getId()); + Assert.assertEquals( + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==", + optimizeProposition.getScope()); + Assert.assertEquals(1, optimizeProposition.getOffers().size()); + } + }); + + // Send completion for second event + Map secondCompletionEventData = + new HashMap() { + { + put("completedUpdateRequestForEventId", secondRequestEventId); + } + }; + + Event secondCompletionEvent = + new Event.Builder( + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE) + .setEventData(secondCompletionEventData) + .build(); + + MobileCore.dispatchEvent(secondCompletionEvent); + } + private void updateConfiguration(final Map config) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); MonitorExtension.configurationAwareness(configurationState -> latch.countDown()); 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 55d2079a..6f9e514d 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 @@ -2371,157 +2371,4 @@ public void testGetPropositions_dispatchPropositionFromCacheBeforeNextUpdate() { throw new RuntimeException(e); } } - - /// WIP test, exception in getProposition - @Test - public void testGetPropositions_ScopesFromEventIsUpdateInProgress() { - 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"); - } - }); - - // prepare cache data - final Map cachedPropositionData = - new ObjectMapper() - .readValue( - getClass() - .getClassLoader() - .getResource("json/PROPOSITION_VALID.json"), - HashMap.class); - - final Map updatedPropositionData = - new ObjectMapper() - .readValue( - getClass() - .getClassLoader() - .getResource("json/PROPOSITION_VALID_TARGET.json"), - HashMap.class); - - final OptimizeProposition cachedProposition = - OptimizeProposition.fromEventData(cachedPropositionData); - final OptimizeProposition updatedProposition = - OptimizeProposition.fromEventData(updatedPropositionData); - - final DecisionScope decisionScope = new DecisionScope(cachedProposition.getScope()); - Assert.assertNotNull(cachedProposition); - - // updating the cache - final Map cachedPropositions = new HashMap<>(); - cachedPropositions.put(decisionScope, cachedProposition); - extension.setCachedPropositions(cachedPropositions); - - // prepare update event data - final Map testEventData = new HashMap<>(); - testEventData.put("requesttype", "updatepropositions"); - testEventData.put( - "decisionscopes", - new ArrayList>() { - { - add(decisionScope.toEventData()); - } - }); - final Event updateEvent = - new Event.Builder( - "Optimize Update Propositions Request", - "com.adobe.eventType.optimize", - "com.adobe.eventSource.requestContent") - .setEventData(testEventData) - .build(); - - // simulate update - extension.handleOptimizeRequestContent(updateEvent); - - // verify update in progress - final Map> updateEventIdsInProgress = - extension.getUpdateRequestEventIdsInProgress(); - Assert.assertNotNull(updateEventIdsInProgress); - Assert.assertEquals(1, updateEventIdsInProgress.size()); - Assert.assertTrue( - updateEventIdsInProgress.containsValue( - new ArrayList() { - { - add(decisionScope); - } - })); - Mockito.clearInvocations(mockExtensionApi); - - // prepare get event - final Map testGetEventData = new HashMap<>(); - testGetEventData.put("requesttype", "getpropositions"); - testGetEventData.put( - "decisionscopes", - new ArrayList>() { - { - add(decisionScope.toEventData()); - } - }); - - final Event testGetEvent = - new Event.Builder( - "Optimize Get Propositions Request", - "com.adobe.eventType.optimize", - "com.adobe.eventSource.requestContent") - .setEventData(testGetEventData) - .build(); - - // prepare update complete event - 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]); - put("propositions", updatedProposition.toEventData()); - } - }) - .build(); - - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - - // simulate test - extension.handleOptimizeRequestContent(testGetEvent); - extension.handleUpdatePropositionsCompleted(testUpdateCompleteEvent); - - // verify - // Mockito.verify(mockEventsDispatcher, Mockito.after(2000L).times(1)) - // .offer(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()); - final List> propositionsList = - (List>) dispatchedEvent.getEventData().get("propositions"); - final Map cachedPropositionsAfter = - extension.getCachedPropositions(); - Assert.assertEquals(1, cachedPropositionsAfter.size()); - } catch (Exception e) { - throw new RuntimeException(e); - } - } } From 96d4175401e0a3733369e047ffee7e3ff730d138 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 8 Oct 2024 22:25:35 +0530 Subject: [PATCH 08/37] Add: Added test case for testGetPropositions_FewDecisionScopesNotInCacheAndGetToBeQueued --- .../optimize/OptimizeFunctionalTests.java | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) 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 1edb54ec..0970a65f 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 @@ -2437,6 +2437,159 @@ public void call( MobileCore.dispatchEvent(secondCompletionEvent); } + @Test + public void testGetPropositions_FewDecisionScopesNotInCacheAndGetToBeQueued() + throws InterruptedException, IOException { + // setup + final Map configData = new HashMap<>(); + configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + updateConfiguration(configData); + + final String decisionScopeAString = + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="; + final String decisionScopeBString = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3Rpdml0eUlkIjoic2NvcGUtYiIsInBsYWNlbWVudElkIjoic2NvcGUtYl9wbGFjZW1lbnQifQ.QzNxT1dBZ1Z1M0Z5dW84SjdKak1nY2c1"; + + // Setting up the cache with decisionScopeA and a proposition. + Optimize.updatePropositions( + Collections.singletonList(new DecisionScope(decisionScopeAString)), null, null); + List eventsListEdge = + TestHelper.getDispatchedEventsWith( + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.REQUEST_CONTENT, + 1000); + + Event edgeEvent = eventsListEdge.get(0); + final String requestEventId = edgeEvent.getUniqueIdentifier(); + final String edgeResponseData = + "{\n" + + " \"payload\": [\n" + + " {\n" + + " \"id\": \"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\",\n" + + " \"scope\": \"" + decisionScopeAString + "\"\n" + + " }\n" + + " ],\n" + + " \"requestEventId\": \"" + requestEventId + "\",\n" + + " \"requestId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA\",\n" + + " \"type\": \"personalization:decisions\"\n" + + "}"; + + ObjectMapper objectMapper = new ObjectMapper(); + Map eventData = + objectMapper.readValue( + edgeResponseData, new TypeReference>() {}); + + Event event = + new Event.Builder( + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) + .setEventData(eventData) + .build(); + 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(); + + // Update event with decisionScopeB + Optimize.updatePropositions( + Collections.singletonList(new DecisionScope(decisionScopeBString)), null, null); + + List secondEventsListEdge = + TestHelper.getDispatchedEventsWith( + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.REQUEST_CONTENT, + 1000); + + Event secondEdgeEvent = secondEventsListEdge.get(0); + final String secondRequestEventId = secondEdgeEvent.getUniqueIdentifier(); + final String secondEdgeResponseData = + "{\n" + + " \"payload\": [\n" + + " {\n" + + " \"id\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" + + " \"scope\": \"" + decisionScopeBString + "\"\n" + + " }\n" + + " ],\n" + + " \"requestEventId\": \"" + secondRequestEventId + "\",\n" + + " \"requestId\": \"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbb\",\n" + + " \"type\": \"personalization:decisions\"\n" + + "}"; + + ObjectMapper secondObjectMapper = new ObjectMapper(); + Map secondEventData = + secondObjectMapper.readValue( + secondEdgeResponseData, new TypeReference>() {}); + + Event secondEvent = + new Event.Builder( + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) + .setEventData(secondEventData) + .build(); + MobileCore.dispatchEvent(secondEvent); + Thread.sleep(1000); + + // Execute get proposition event with both decisionScopeA and decisionScopeB + Optimize.getPropositions( + Arrays.asList(new DecisionScope(decisionScopeAString), new DecisionScope(decisionScopeBString)), + new AdobeCallbackWithError>() { + @Override + public void fail(AdobeError adobeError) { + Assert.fail("Error in getting cached propositions"); + } + + @Override + public void call(Map decisionScopePropositionMap) { + // Assertions + // Verify that the proposition for decisionScopeA is present and validate. + Assert.assertTrue(decisionScopePropositionMap.containsKey(new DecisionScope(decisionScopeAString))); + OptimizeProposition optimizePropositionA = decisionScopePropositionMap.get(new DecisionScope(decisionScopeAString)); + Assert.assertNotNull(optimizePropositionA); + Assert.assertEquals("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", optimizePropositionA.getId()); + + // Verify that the proposition for decisionScopeB is present and validate that the event GET event was queued. + Assert.assertTrue(decisionScopePropositionMap.containsKey(new DecisionScope(decisionScopeBString))); + OptimizeProposition optimizePropositionB = decisionScopePropositionMap.get(new DecisionScope(decisionScopeBString)); + Assert.assertNotNull(optimizePropositionB); + Assert.assertEquals("BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB", optimizePropositionB.getId()); + } + }); + + // Send completion for second update event after the Get event is fired + Map secondCompletionEventData = + new HashMap() {{ + put("completedUpdateRequestForEventId", secondRequestEventId); + }}; + Event secondCompletionEvent = + new Event.Builder( + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE) + .setEventData(secondCompletionEventData) + .build(); + MobileCore.dispatchEvent(secondCompletionEvent); + + Thread.sleep(1000); + TestHelper.resetTestExpectations(); + } + private void updateConfiguration(final Map config) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); MonitorExtension.configurationAwareness(configurationState -> latch.countDown()); From 170c45a2a126f96a261a8225d48cc57c6ffd114d Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 8 Oct 2024 22:32:08 +0530 Subject: [PATCH 09/37] Fix: Spotless applied --- .../optimize/OptimizeFunctionalTests.java | 92 ++++++++++++------- 1 file changed, 60 insertions(+), 32 deletions(-) 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 0970a65f..fc60dfd7 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 @@ -2466,10 +2466,14 @@ public void testGetPropositions_FewDecisionScopesNotInCacheAndGetToBeQueued() + " \"payload\": [\n" + " {\n" + " \"id\": \"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\",\n" - + " \"scope\": \"" + decisionScopeAString + "\"\n" + + " \"scope\": \"" + + decisionScopeAString + + "\"\n" + " }\n" + " ],\n" - + " \"requestEventId\": \"" + requestEventId + "\",\n" + + " \"requestEventId\": \"" + + requestEventId + + "\",\n" + " \"requestId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA\",\n" + " \"type\": \"personalization:decisions\"\n" + "}"; @@ -2481,9 +2485,9 @@ public void testGetPropositions_FewDecisionScopesNotInCacheAndGetToBeQueued() Event event = new Event.Builder( - "AEP Response Event Handle", - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.PERSONALIZATION) + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) .setEventData(eventData) .build(); MobileCore.dispatchEvent(event); @@ -2491,14 +2495,16 @@ public void testGetPropositions_FewDecisionScopesNotInCacheAndGetToBeQueued() // Send completion event Map completionEventData = - new HashMap() {{ - put("completedUpdateRequestForEventId", requestEventId); - }}; + new HashMap() { + { + put("completedUpdateRequestForEventId", requestEventId); + } + }; Event completionEvent = new Event.Builder( - "Optimize Update Propositions Complete", - OptimizeTestConstants.EventType.OPTIMIZE, - OptimizeTestConstants.EventSource.CONTENT_COMPLETE) + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE) .setEventData(completionEventData) .build(); MobileCore.dispatchEvent(completionEvent); @@ -2523,10 +2529,14 @@ public void testGetPropositions_FewDecisionScopesNotInCacheAndGetToBeQueued() + " \"payload\": [\n" + " {\n" + " \"id\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" - + " \"scope\": \"" + decisionScopeBString + "\"\n" + + " \"scope\": \"" + + decisionScopeBString + + "\"\n" + " }\n" + " ],\n" - + " \"requestEventId\": \"" + secondRequestEventId + "\",\n" + + " \"requestEventId\": \"" + + secondRequestEventId + + "\",\n" + " \"requestId\": \"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbb\",\n" + " \"type\": \"personalization:decisions\"\n" + "}"; @@ -2538,9 +2548,9 @@ public void testGetPropositions_FewDecisionScopesNotInCacheAndGetToBeQueued() Event secondEvent = new Event.Builder( - "AEP Response Event Handle", - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.PERSONALIZATION) + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) .setEventData(secondEventData) .build(); MobileCore.dispatchEvent(secondEvent); @@ -2548,7 +2558,9 @@ public void testGetPropositions_FewDecisionScopesNotInCacheAndGetToBeQueued() // Execute get proposition event with both decisionScopeA and decisionScopeB Optimize.getPropositions( - Arrays.asList(new DecisionScope(decisionScopeAString), new DecisionScope(decisionScopeBString)), + Arrays.asList( + new DecisionScope(decisionScopeAString), + new DecisionScope(decisionScopeBString)), new AdobeCallbackWithError>() { @Override public void fail(AdobeError adobeError) { @@ -2556,32 +2568,48 @@ public void fail(AdobeError adobeError) { } @Override - public void call(Map decisionScopePropositionMap) { + public void call( + Map decisionScopePropositionMap) { // Assertions // Verify that the proposition for decisionScopeA is present and validate. - Assert.assertTrue(decisionScopePropositionMap.containsKey(new DecisionScope(decisionScopeAString))); - OptimizeProposition optimizePropositionA = decisionScopePropositionMap.get(new DecisionScope(decisionScopeAString)); + Assert.assertTrue( + decisionScopePropositionMap.containsKey( + new DecisionScope(decisionScopeAString))); + OptimizeProposition optimizePropositionA = + decisionScopePropositionMap.get( + new DecisionScope(decisionScopeAString)); Assert.assertNotNull(optimizePropositionA); - Assert.assertEquals("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", optimizePropositionA.getId()); - - // Verify that the proposition for decisionScopeB is present and validate that the event GET event was queued. - Assert.assertTrue(decisionScopePropositionMap.containsKey(new DecisionScope(decisionScopeBString))); - OptimizeProposition optimizePropositionB = decisionScopePropositionMap.get(new DecisionScope(decisionScopeBString)); + Assert.assertEquals( + "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + optimizePropositionA.getId()); + + // Verify that the proposition for decisionScopeB is present and validate + // that the event GET event was queued. + Assert.assertTrue( + decisionScopePropositionMap.containsKey( + new DecisionScope(decisionScopeBString))); + OptimizeProposition optimizePropositionB = + decisionScopePropositionMap.get( + new DecisionScope(decisionScopeBString)); Assert.assertNotNull(optimizePropositionB); - Assert.assertEquals("BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB", optimizePropositionB.getId()); + Assert.assertEquals( + "BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB", + optimizePropositionB.getId()); } }); // Send completion for second update event after the Get event is fired Map secondCompletionEventData = - new HashMap() {{ - put("completedUpdateRequestForEventId", secondRequestEventId); - }}; + new HashMap() { + { + put("completedUpdateRequestForEventId", secondRequestEventId); + } + }; Event secondCompletionEvent = new Event.Builder( - "Optimize Update Propositions Complete", - OptimizeTestConstants.EventType.OPTIMIZE, - OptimizeTestConstants.EventSource.CONTENT_COMPLETE) + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE) .setEventData(secondCompletionEventData) .build(); MobileCore.dispatchEvent(secondCompletionEvent); From 3bb0c89eafd44ee42167aec1881a99546f8bf2c5 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 8 Oct 2024 23:08:09 +0530 Subject: [PATCH 10/37] Fix: Reduced the sleep time --- .../optimize/OptimizeFunctionalTests.java | 344 +++++++++++++++++- 1 file changed, 343 insertions(+), 1 deletion(-) 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 fc60dfd7..6eefd910 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 @@ -2194,6 +2194,7 @@ public void testPropositionGenerateReferenceXdm() throws IOException { "de03ac85-802a-4331-a905-a57053164d35", decisioning.get("propositionID")); } + // 19 @Test public void testGetPropositions_ScopesFromEventIsUpdateInProgress() throws InterruptedException, IOException { @@ -2301,7 +2302,7 @@ public void testGetPropositions_ScopesFromEventIsUpdateInProgress() // Cache is now updated with a proposition. MobileCore.dispatchEvent(completionEvent); - Thread.sleep(10000); + Thread.sleep(1000); TestHelper.resetTestExpectations(); // Firing another update event with same decision scope but different proposition data. @@ -2437,6 +2438,7 @@ public void call( MobileCore.dispatchEvent(secondCompletionEvent); } + // 20 @Test public void testGetPropositions_FewDecisionScopesNotInCacheAndGetToBeQueued() throws InterruptedException, IOException { @@ -2618,6 +2620,346 @@ public void call( TestHelper.resetTestExpectations(); } + @Test + public void testGetPropositions_DispatchPropositionFromCacheBeforeNextUpdate() + throws InterruptedException, IOException { + // Setup + final Map configData = new HashMap<>(); + configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + updateConfiguration(configData); + + final String decisionScopeAString = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3Rpdml0eUlkIjoic2NvcGUtYSIsInBsYWNlbWVudElkIjoic2NvcGUtYV9wbGFjZW1lbnQifQ.KW1HKVJHTTdmUkJZUmM5UEhNdURtOGdT"; + final String decisionScopeBString = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3Rpdml0eUlkIjoic2NvcGUtYiIsInBsYWNlbWVudElkIjoic2NvcGUtYl9wbGFjZW1lbnQifQ.QzNxT1dBZ1Z1M0Z5dW84SjdKak1nY2c1"; + + // Setting up the cache with decisionScopeA and a proposition. + Optimize.updatePropositions( + Collections.singletonList(new DecisionScope(decisionScopeAString)), null, null); + List eventsListEdge = + TestHelper.getDispatchedEventsWith( + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.REQUEST_CONTENT, + 1000); + + Event edgeEvent = eventsListEdge.get(0); + final String requestEventId = edgeEvent.getUniqueIdentifier(); + final String edgeResponseData = + "{\n" + + " \"payload\": [\n" + + " {\n" + + " \"id\": \"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\",\n" + + " \"scope\": \"" + + decisionScopeAString + + "\"\n" + + " }\n" + + " ],\n" + + " \"requestEventId\": \"" + + requestEventId + + "\",\n" + + " \"requestId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA\",\n" + + " \"type\": \"personalization:decisions\"\n" + + "}"; + + ObjectMapper objectMapper = new ObjectMapper(); + Map eventData = + objectMapper.readValue( + edgeResponseData, new TypeReference>() {}); + + Event event = + new Event.Builder( + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) + .setEventData(eventData) + .build(); + 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(); + + // Create update event with decisionScopeB + Optimize.updatePropositions( + Collections.singletonList(new DecisionScope(decisionScopeBString)), null, null); + + List secondEventsListEdge = + TestHelper.getDispatchedEventsWith( + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.REQUEST_CONTENT, + 1000); + + Event secondEdgeEvent = secondEventsListEdge.get(0); + final String secondRequestEventId = secondEdgeEvent.getUniqueIdentifier(); + final String secondEdgeResponseData = + "{\n" + + " \"payload\": [\n" + + " {\n" + + " \"id\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" + + " \"scope\": \"" + + decisionScopeBString + + "\"\n" + + " }\n" + + " ],\n" + + " \"requestEventId\": \"" + + secondRequestEventId + + "\",\n" + + " \"requestId\": \"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbb\",\n" + + " \"type\": \"personalization:decisions\"\n" + + "}"; + + ObjectMapper secondObjectMapper = new ObjectMapper(); + Map secondEventData = + secondObjectMapper.readValue( + secondEdgeResponseData, new TypeReference>() {}); + + Event secondEvent = + new Event.Builder( + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) + .setEventData(secondEventData) + .build(); + MobileCore.dispatchEvent(secondEvent); + Thread.sleep(1000); + + // Execute get proposition event with decisionScopeA before the update event is completed. + DecisionScope decisionScopeA = new DecisionScope(decisionScopeAString); + final Map propositionMap = new HashMap<>(); + + Optimize.getPropositions( + Collections.singletonList(decisionScopeA), + new AdobeCallbackWithError>() { + @Override + public void fail(AdobeError adobeError) { + Assert.fail("Error in getting cached propositions"); + } + + @Override + public void call( + Map decisionScopePropositionMap) { + propositionMap.putAll(decisionScopePropositionMap); + + // Assertions + // Verify that the proposition for decisionScopeA is dispatched from cache + // before the update completes. + Assert.assertTrue(propositionMap.containsKey(decisionScopeA)); + OptimizeProposition optimizePropositionA = + propositionMap.get(decisionScopeA); + Assert.assertNotNull(optimizePropositionA); + Assert.assertEquals( + "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + optimizePropositionA.getId()); + Assert.assertEquals(decisionScopeAString, optimizePropositionA.getScope()); + } + }); + + // Send completion for second update event after the Get event is fired + Map secondCompletionEventData = + new HashMap() { + { + put("completedUpdateRequestForEventId", secondRequestEventId); + } + }; + Event secondCompletionEvent = + new Event.Builder( + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE) + .setEventData(secondCompletionEventData) + .build(); + MobileCore.dispatchEvent(secondCompletionEvent); + + Thread.sleep(1000); + TestHelper.resetTestExpectations(); + } + + @Test + public void testGetPropositions_DispatchPropositionFrosmCacheBeforeNextUpdate() + throws InterruptedException, IOException { + // Setup + final Map configData = new HashMap<>(); + configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + updateConfiguration(configData); + + final String decisionScopeAString = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3Rpdml0eUlkIjoic2NvcGUtYSIsInBsYWNlbWVudElkIjoic2NvcGUtYV9wbGFjZW1lbnQifQ.KW1HKVJHTTdmUkJZUmM5UEhNdURtOGdT"; + final String decisionScopeBString = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3Rpdml0eUlkIjoic2NvcGUtYiIsInBsYWNlbWVudElkIjoic2NvcGUtYl9wbGFjZW1lbnQifQ.QzNxT1dBZ1Z1M0Z5dW84SjdKak1nY2c1"; + + // Setting up the cache with decisionScopeA and a proposition. + Optimize.updatePropositions( + Collections.singletonList(new DecisionScope(decisionScopeAString)), null, null); + List eventsListEdge = + TestHelper.getDispatchedEventsWith( + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.REQUEST_CONTENT, + 1000); + + Event edgeEvent = eventsListEdge.get(0); + final String requestEventId = edgeEvent.getUniqueIdentifier(); + final String edgeResponseData = + "{\n" + + " \"payload\": [\n" + + " {\n" + + " \"id\": \"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\",\n" + + " \"scope\": \"" + + decisionScopeAString + + "\"\n" + + " }\n" + + " ],\n" + + " \"requestEventId\": \"" + + requestEventId + + "\",\n" + + " \"requestId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA\",\n" + + " \"type\": \"personalization:decisions\"\n" + + "}"; + + ObjectMapper objectMapper = new ObjectMapper(); + Map eventData = + objectMapper.readValue( + edgeResponseData, new TypeReference>() {}); + + Event event = + new Event.Builder( + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) + .setEventData(eventData) + .build(); + 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(); + + // Create update event with decisionScopeB + Optimize.updatePropositions( + Collections.singletonList(new DecisionScope(decisionScopeBString)), null, null); + + List secondEventsListEdge = + TestHelper.getDispatchedEventsWith( + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.REQUEST_CONTENT, + 1000); + + Event secondEdgeEvent = secondEventsListEdge.get(0); + final String secondRequestEventId = secondEdgeEvent.getUniqueIdentifier(); + final String secondEdgeResponseData = + "{\n" + + " \"payload\": [\n" + + " {\n" + + " \"id\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" + + " \"scope\": \"" + + decisionScopeBString + + "\"\n" + + " }\n" + + " ],\n" + + " \"requestEventId\": \"" + + secondRequestEventId + + "\",\n" + + " \"requestId\": \"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbb\",\n" + + " \"type\": \"personalization:decisions\"\n" + + "}"; + + ObjectMapper secondObjectMapper = new ObjectMapper(); + Map secondEventData = + secondObjectMapper.readValue( + secondEdgeResponseData, new TypeReference>() {}); + + Event secondEvent = + new Event.Builder( + "AEP Response Event Handle", + OptimizeTestConstants.EventType.EDGE, + OptimizeTestConstants.EventSource.PERSONALIZATION) + .setEventData(secondEventData) + .build(); + MobileCore.dispatchEvent(secondEvent); + Thread.sleep(1000); + + // Execute get proposition event with decisionScopeA before the update event is completed. + DecisionScope decisionScopeA = new DecisionScope(decisionScopeAString); + final Map propositionMap = new HashMap<>(); + + Optimize.getPropositions( + Collections.singletonList(decisionScopeA), + new AdobeCallbackWithError>() { + @Override + public void fail(AdobeError adobeError) { + Assert.fail("Error in getting cached propositions"); + } + + @Override + public void call( + Map decisionScopePropositionMap) { + propositionMap.putAll(decisionScopePropositionMap); + + // Assertions + // Verify that the proposition for decisionScopeA is dispatched from cache + // before the update completes. + Assert.assertTrue(propositionMap.containsKey(decisionScopeA)); + OptimizeProposition optimizePropositionA = + propositionMap.get(decisionScopeA); + Assert.assertNotNull(optimizePropositionA); + Assert.assertEquals( + "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + optimizePropositionA.getId()); + Assert.assertEquals(decisionScopeAString, optimizePropositionA.getScope()); + } + }); + + // Send completion for second update event after the Get event is fired + Map secondCompletionEventData = + new HashMap() { + { + put("completedUpdateRequestForEventId", secondRequestEventId); + } + }; + Event secondCompletionEvent = + new Event.Builder( + "Optimize Update Propositions Complete", + OptimizeTestConstants.EventType.OPTIMIZE, + OptimizeTestConstants.EventSource.CONTENT_COMPLETE) + .setEventData(secondCompletionEventData) + .build(); + MobileCore.dispatchEvent(secondCompletionEvent); + + Thread.sleep(1000); + TestHelper.resetTestExpectations(); + } + private void updateConfiguration(final Map config) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); MonitorExtension.configurationAwareness(configurationState -> latch.countDown()); From 564ff6060a0b21199d4d46f9f0e78b4bf6ce0f68 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Tue, 8 Oct 2024 23:16:32 +0530 Subject: [PATCH 11/37] Fix: removed duplicate tests --- .../optimize/OptimizeFunctionalTests.java | 340 ------------------ 1 file changed, 340 deletions(-) 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 6eefd910..b37b2a27 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 @@ -2620,346 +2620,6 @@ public void call( TestHelper.resetTestExpectations(); } - @Test - public void testGetPropositions_DispatchPropositionFromCacheBeforeNextUpdate() - throws InterruptedException, IOException { - // Setup - final Map configData = new HashMap<>(); - configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); - updateConfiguration(configData); - - final String decisionScopeAString = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3Rpdml0eUlkIjoic2NvcGUtYSIsInBsYWNlbWVudElkIjoic2NvcGUtYV9wbGFjZW1lbnQifQ.KW1HKVJHTTdmUkJZUmM5UEhNdURtOGdT"; - final String decisionScopeBString = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3Rpdml0eUlkIjoic2NvcGUtYiIsInBsYWNlbWVudElkIjoic2NvcGUtYl9wbGFjZW1lbnQifQ.QzNxT1dBZ1Z1M0Z5dW84SjdKak1nY2c1"; - - // Setting up the cache with decisionScopeA and a proposition. - Optimize.updatePropositions( - Collections.singletonList(new DecisionScope(decisionScopeAString)), null, null); - List eventsListEdge = - TestHelper.getDispatchedEventsWith( - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.REQUEST_CONTENT, - 1000); - - Event edgeEvent = eventsListEdge.get(0); - final String requestEventId = edgeEvent.getUniqueIdentifier(); - final String edgeResponseData = - "{\n" - + " \"payload\": [\n" - + " {\n" - + " \"id\": \"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\",\n" - + " \"scope\": \"" - + decisionScopeAString - + "\"\n" - + " }\n" - + " ],\n" - + " \"requestEventId\": \"" - + requestEventId - + "\",\n" - + " \"requestId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA\",\n" - + " \"type\": \"personalization:decisions\"\n" - + "}"; - - ObjectMapper objectMapper = new ObjectMapper(); - Map eventData = - objectMapper.readValue( - edgeResponseData, new TypeReference>() {}); - - Event event = - new Event.Builder( - "AEP Response Event Handle", - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.PERSONALIZATION) - .setEventData(eventData) - .build(); - 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(); - - // Create update event with decisionScopeB - Optimize.updatePropositions( - Collections.singletonList(new DecisionScope(decisionScopeBString)), null, null); - - List secondEventsListEdge = - TestHelper.getDispatchedEventsWith( - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.REQUEST_CONTENT, - 1000); - - Event secondEdgeEvent = secondEventsListEdge.get(0); - final String secondRequestEventId = secondEdgeEvent.getUniqueIdentifier(); - final String secondEdgeResponseData = - "{\n" - + " \"payload\": [\n" - + " {\n" - + " \"id\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" - + " \"scope\": \"" - + decisionScopeBString - + "\"\n" - + " }\n" - + " ],\n" - + " \"requestEventId\": \"" - + secondRequestEventId - + "\",\n" - + " \"requestId\": \"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbb\",\n" - + " \"type\": \"personalization:decisions\"\n" - + "}"; - - ObjectMapper secondObjectMapper = new ObjectMapper(); - Map secondEventData = - secondObjectMapper.readValue( - secondEdgeResponseData, new TypeReference>() {}); - - Event secondEvent = - new Event.Builder( - "AEP Response Event Handle", - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.PERSONALIZATION) - .setEventData(secondEventData) - .build(); - MobileCore.dispatchEvent(secondEvent); - Thread.sleep(1000); - - // Execute get proposition event with decisionScopeA before the update event is completed. - DecisionScope decisionScopeA = new DecisionScope(decisionScopeAString); - final Map propositionMap = new HashMap<>(); - - Optimize.getPropositions( - Collections.singletonList(decisionScopeA), - new AdobeCallbackWithError>() { - @Override - public void fail(AdobeError adobeError) { - Assert.fail("Error in getting cached propositions"); - } - - @Override - public void call( - Map decisionScopePropositionMap) { - propositionMap.putAll(decisionScopePropositionMap); - - // Assertions - // Verify that the proposition for decisionScopeA is dispatched from cache - // before the update completes. - Assert.assertTrue(propositionMap.containsKey(decisionScopeA)); - OptimizeProposition optimizePropositionA = - propositionMap.get(decisionScopeA); - Assert.assertNotNull(optimizePropositionA); - Assert.assertEquals( - "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", - optimizePropositionA.getId()); - Assert.assertEquals(decisionScopeAString, optimizePropositionA.getScope()); - } - }); - - // Send completion for second update event after the Get event is fired - Map secondCompletionEventData = - new HashMap() { - { - put("completedUpdateRequestForEventId", secondRequestEventId); - } - }; - Event secondCompletionEvent = - new Event.Builder( - "Optimize Update Propositions Complete", - OptimizeTestConstants.EventType.OPTIMIZE, - OptimizeTestConstants.EventSource.CONTENT_COMPLETE) - .setEventData(secondCompletionEventData) - .build(); - MobileCore.dispatchEvent(secondCompletionEvent); - - Thread.sleep(1000); - TestHelper.resetTestExpectations(); - } - - @Test - public void testGetPropositions_DispatchPropositionFrosmCacheBeforeNextUpdate() - throws InterruptedException, IOException { - // Setup - final Map configData = new HashMap<>(); - configData.put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); - updateConfiguration(configData); - - final String decisionScopeAString = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3Rpdml0eUlkIjoic2NvcGUtYSIsInBsYWNlbWVudElkIjoic2NvcGUtYV9wbGFjZW1lbnQifQ.KW1HKVJHTTdmUkJZUmM5UEhNdURtOGdT"; - final String decisionScopeBString = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY3Rpdml0eUlkIjoic2NvcGUtYiIsInBsYWNlbWVudElkIjoic2NvcGUtYl9wbGFjZW1lbnQifQ.QzNxT1dBZ1Z1M0Z5dW84SjdKak1nY2c1"; - - // Setting up the cache with decisionScopeA and a proposition. - Optimize.updatePropositions( - Collections.singletonList(new DecisionScope(decisionScopeAString)), null, null); - List eventsListEdge = - TestHelper.getDispatchedEventsWith( - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.REQUEST_CONTENT, - 1000); - - Event edgeEvent = eventsListEdge.get(0); - final String requestEventId = edgeEvent.getUniqueIdentifier(); - final String edgeResponseData = - "{\n" - + " \"payload\": [\n" - + " {\n" - + " \"id\": \"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa\",\n" - + " \"scope\": \"" - + decisionScopeAString - + "\"\n" - + " }\n" - + " ],\n" - + " \"requestEventId\": \"" - + requestEventId - + "\",\n" - + " \"requestId\": \"AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAA\",\n" - + " \"type\": \"personalization:decisions\"\n" - + "}"; - - ObjectMapper objectMapper = new ObjectMapper(); - Map eventData = - objectMapper.readValue( - edgeResponseData, new TypeReference>() {}); - - Event event = - new Event.Builder( - "AEP Response Event Handle", - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.PERSONALIZATION) - .setEventData(eventData) - .build(); - 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(); - - // Create update event with decisionScopeB - Optimize.updatePropositions( - Collections.singletonList(new DecisionScope(decisionScopeBString)), null, null); - - List secondEventsListEdge = - TestHelper.getDispatchedEventsWith( - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.REQUEST_CONTENT, - 1000); - - Event secondEdgeEvent = secondEventsListEdge.get(0); - final String secondRequestEventId = secondEdgeEvent.getUniqueIdentifier(); - final String secondEdgeResponseData = - "{\n" - + " \"payload\": [\n" - + " {\n" - + " \"id\": \"BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB\",\n" - + " \"scope\": \"" - + decisionScopeBString - + "\"\n" - + " }\n" - + " ],\n" - + " \"requestEventId\": \"" - + secondRequestEventId - + "\",\n" - + " \"requestId\": \"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbb\",\n" - + " \"type\": \"personalization:decisions\"\n" - + "}"; - - ObjectMapper secondObjectMapper = new ObjectMapper(); - Map secondEventData = - secondObjectMapper.readValue( - secondEdgeResponseData, new TypeReference>() {}); - - Event secondEvent = - new Event.Builder( - "AEP Response Event Handle", - OptimizeTestConstants.EventType.EDGE, - OptimizeTestConstants.EventSource.PERSONALIZATION) - .setEventData(secondEventData) - .build(); - MobileCore.dispatchEvent(secondEvent); - Thread.sleep(1000); - - // Execute get proposition event with decisionScopeA before the update event is completed. - DecisionScope decisionScopeA = new DecisionScope(decisionScopeAString); - final Map propositionMap = new HashMap<>(); - - Optimize.getPropositions( - Collections.singletonList(decisionScopeA), - new AdobeCallbackWithError>() { - @Override - public void fail(AdobeError adobeError) { - Assert.fail("Error in getting cached propositions"); - } - - @Override - public void call( - Map decisionScopePropositionMap) { - propositionMap.putAll(decisionScopePropositionMap); - - // Assertions - // Verify that the proposition for decisionScopeA is dispatched from cache - // before the update completes. - Assert.assertTrue(propositionMap.containsKey(decisionScopeA)); - OptimizeProposition optimizePropositionA = - propositionMap.get(decisionScopeA); - Assert.assertNotNull(optimizePropositionA); - Assert.assertEquals( - "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", - optimizePropositionA.getId()); - Assert.assertEquals(decisionScopeAString, optimizePropositionA.getScope()); - } - }); - - // Send completion for second update event after the Get event is fired - Map secondCompletionEventData = - new HashMap() { - { - put("completedUpdateRequestForEventId", secondRequestEventId); - } - }; - Event secondCompletionEvent = - new Event.Builder( - "Optimize Update Propositions Complete", - OptimizeTestConstants.EventType.OPTIMIZE, - OptimizeTestConstants.EventSource.CONTENT_COMPLETE) - .setEventData(secondCompletionEventData) - .build(); - MobileCore.dispatchEvent(secondCompletionEvent); - - Thread.sleep(1000); - TestHelper.resetTestExpectations(); - } - private void updateConfiguration(final Map config) throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); MonitorExtension.configurationAwareness(configurationState -> latch.countDown()); From 548e5125b996317afc0dd06ecc194b33bb97ccf5 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Wed, 9 Oct 2024 00:52:05 +0530 Subject: [PATCH 12/37] Fix: test naming updated --- .../marketing/mobile/optimize/OptimizeFunctionalTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b37b2a27..c01598dd 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 @@ -2196,7 +2196,7 @@ public void testPropositionGenerateReferenceXdm() throws IOException { // 19 @Test - public void testGetPropositions_ScopesFromEventIsUpdateInProgress() + public void testGetPropositions_multipleUpdatePropositonsCallsBeforeGetPropositions() throws InterruptedException, IOException { // setup final Map configData = new HashMap<>(); From 203622546eb9ab8ab6666ee7f8482bdadba6411a Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Wed, 9 Oct 2024 01:04:41 +0530 Subject: [PATCH 13/37] Fix: updated log messages --- .../adobe/marketing/mobile/optimize/OptimizeExtension.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 20a96e3a..2ab43d4d 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 @@ -247,7 +247,7 @@ void handleOptimizeRequestContent(@NonNull final Event event) { Log.debug( OptimizeConstants.LOG_TAG, SELF_TAG, - "handleGetPropositions - Cannot process the get propositions" + "handleOptimizeRequestContent - Cannot process the get propositions" + " request event, provided list of decision scopes has no" + " valid scope."); getApi().dispatch( @@ -301,7 +301,7 @@ void handleOptimizeRequestContent(@NonNull final Event event) { Log.warning( OptimizeConstants.LOG_TAG, SELF_TAG, - "handleUpdatePropositions - Failed to process update propositions" + "handleOptimizeRequestContent - Failed to process get propositions" + " request event due to an exception (%s)!", e.getLocalizedMessage()); } From 745121b5d4b90647790bb4dcaeeeaec4cd07c735 Mon Sep 17 00:00:00 2001 From: Spoorthi Pujari <63024083+spoorthipujariadobe@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:21:08 -0700 Subject: [PATCH 14/37] Update config.yml Downgrade codecov to 3.2.4 --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fe139715..b1ef70ab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,7 +13,7 @@ version: 2.1 orbs: android: circleci/android@2.4.0 - codecov: codecov/codecov@4.0.1 + codecov: codecov/codecov@3.2.4 # Workflows orchestrate a set of jobs to be run; workflows: From 6126722b6a87d108f28a64acd5a2207f49ce67ea Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Wed, 9 Oct 2024 22:20:23 +0530 Subject: [PATCH 15/37] Add: Added two test cases - handleGetPropositions_invalidDecisionScope & handleGetPropositions_withException --- .../optimize/OptimizeExtensionTests.java | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) 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 6f9e514d..50295337 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 @@ -2371,4 +2371,110 @@ public void testGetPropositions_dispatchPropositionFromCacheBeforeNextUpdate() { throw new RuntimeException(e); } } + + @Test + public void testHandleOptimizeRequestContent_HandleGetPropositions_invalidDecisionScope() { + try (MockedStatic base64MockedStatic = Mockito.mockStatic(Base64.class); + MockedStatic logMockedStatic = Mockito.mockStatic(Log.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 DecisionScope testScope = + new DecisionScope( + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoiIn0="); + final Map testEventData = new HashMap<>(); + testEventData.put("requesttype", "getpropositions"); + 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 + extension.handleOptimizeRequestContent(testEvent); + + // verify + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + 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.assertNotNull(dispatchedEvent.getEventData().get("responseerror")); + } + } + + @Test + public void testHandleOptimizeRequestContent_HandleGetPropositions_withException() { + try (MockedStatic base64MockedStatic = Mockito.mockStatic(Base64.class); + MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { + + // setup + setConfigurationSharedState( + SharedStateStatus.SET, + new HashMap() { + { + put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + } + }); + + final DecisionScope testScope = + new DecisionScope( + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); + final Map testEventData = new HashMap<>(); + testEventData.put("requesttype", "getpropositions"); + 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 + extension.handleOptimizeRequestContent(testEvent); + + // verify + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Mockito.verify(mockExtensionApi, Mockito.never()).dispatch(eventCaptor.capture()); + } + } } From c938f9fc59adb15f918dab92daf8435cf2682631 Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Wed, 9 Oct 2024 22:22:20 +0530 Subject: [PATCH 16/37] Fix: Code formatting --- .../optimize/OptimizeExtensionTests.java | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) 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 50295337..2eb66938 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 @@ -2436,45 +2436,41 @@ public void testHandleOptimizeRequestContent_HandleGetPropositions_invalidDecisi @Test public void testHandleOptimizeRequestContent_HandleGetPropositions_withException() { - try (MockedStatic base64MockedStatic = Mockito.mockStatic(Base64.class); - MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { - - // setup - setConfigurationSharedState( - SharedStateStatus.SET, - new HashMap() { - { - put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); - } - }); + // setup + setConfigurationSharedState( + SharedStateStatus.SET, + new HashMap() { + { + put("edge.configId", "ffffffff-ffff-ffff-ffff-ffffffffffff"); + } + }); - final DecisionScope testScope = - new DecisionScope( - "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); - final Map testEventData = new HashMap<>(); - testEventData.put("requesttype", "getpropositions"); - testEventData.put( - "decisionscopes", - new ArrayList>() { - { - add(testScope.toEventData()); - } - }); + final DecisionScope testScope = + new DecisionScope( + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="); + final Map testEventData = new HashMap<>(); + testEventData.put("requesttype", "getpropositions"); + 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(); + final Event testEvent = + new Event.Builder( + "Optimize Get Propositions Request", + "com.adobe.eventType.optimize", + "com.adobe.eventSource.requestContent") + .setEventData(testEventData) + .build(); - // test - extension.handleOptimizeRequestContent(testEvent); + // test + extension.handleOptimizeRequestContent(testEvent); - // verify - final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); - Mockito.verify(mockExtensionApi, Mockito.never()).dispatch(eventCaptor.capture()); - } + // verify + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + Mockito.verify(mockExtensionApi, Mockito.never()).dispatch(eventCaptor.capture()); } } From 63e8eb5fac6852f5d16c0722b4f0d31efb0b053b Mon Sep 17 00:00:00 2001 From: Shwetansh Srivastava Date: Wed, 9 Oct 2024 22:24:13 +0530 Subject: [PATCH 17/37] Nit: event naming --- .../adobe/marketing/mobile/optimize/OptimizeExtensionTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2eb66938..922c3539 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 @@ -2412,7 +2412,7 @@ public void testHandleOptimizeRequestContent_HandleGetPropositions_invalidDecisi final Event testEvent = new Event.Builder( - "Optimize Update Propositions Request", + "Optimize Get Propositions Request", "com.adobe.eventType.optimize", "com.adobe.eventSource.requestContent") .setEventData(testEventData) From acfd77e7873953d98016418995264dac2b2d9041 Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Thu, 10 Oct 2024 11:13:11 +0530 Subject: [PATCH 18/37] Change the type of offer score from int to double (#102) * Change the type of offer score from int to double * fix failing unit test * fix failing functional tests --- .../optimize/OptimizeFunctionalTests.java | 6 ++- .../marketing/mobile/optimize/Offer.java | 18 ++++---- .../marketing/mobile/optimize/OfferTests.java | 45 ++++++++++++++++--- .../json/OFFER_VALID_WITH_DOUBLE_SCORE.json | 17 +++++++ 4 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 code/optimize/src/test/resources/json/OFFER_VALID_WITH_DOUBLE_SCORE.json 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 f8fdf9af..063d401f 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 @@ -43,6 +43,8 @@ @RunWith(AndroidJUnit4.class) public class OptimizeFunctionalTests { + double doubleAccuracy = 0.001; + @Rule public RuleChain ruleChain = RuleChain.outerRule(new TestHelper.SetupCoreRule()) @@ -697,7 +699,7 @@ public void call( Offer offer = optimizeProposition.getOffers().get(0); Assert.assertEquals("xcore:personalized-offer:1111111111111111", offer.getId()); Assert.assertEquals("10", offer.getEtag()); - Assert.assertEquals(1, offer.getScore()); + Assert.assertEquals(1, offer.getScore(), doubleAccuracy); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-html", offer.getSchema()); @@ -867,7 +869,7 @@ public void call( Offer offer = optimizeProposition.getOffers().get(0); Assert.assertEquals("0", offer.getId()); Assert.assertEquals(null, offer.getEtag()); - Assert.assertEquals(0, offer.getScore()); + Assert.assertEquals(0, offer.getScore(), doubleAccuracy); Assert.assertEquals( "https://ns.adobe.com/personalization/default-content-item", offer.getSchema()); Assert.assertEquals(OfferType.UNKNOWN, offer.getType()); diff --git a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Offer.java b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Offer.java index 1a8ec726..bd6cbef3 100644 --- a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Offer.java +++ b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Offer.java @@ -29,7 +29,7 @@ public class Offer { private static final String SELF_TAG = "Offer"; private String id; private String etag; - private int score; + private double score; private String schema; private Map meta; private OfferType type; @@ -66,7 +66,7 @@ public Builder(final String id, final OfferType type, final String content) { offer.type = type != null ? type : OfferType.UNKNOWN; offer.content = content != null ? content : ""; offer.etag = ""; - offer.score = 0; + offer.score = 0.0; offer.schema = ""; offer.meta = new HashMap<>(); offer.language = new ArrayList<>(); @@ -92,12 +92,12 @@ public Builder setEtag(final String etag) { /** * Sets the score for this {@code Offer}. * - * @param score {@code int} containing {@link Offer} score. + * @param score {@code double} containing {@link Offer} score. * @return this Offer {@link Builder} * @throws UnsupportedOperationException if this method is invoked after {@link * Builder#build()}. */ - public Builder setScore(final int score) { + public Builder setScore(final double score) { throwIfAlreadyBuilt(); offer.score = score; @@ -206,9 +206,9 @@ public String getEtag() { /** * Gets the {@code Offer} score. * - * @return {@code int} containing the {@link Offer} score. + * @return {@code double} containing the {@link Offer} score. */ - public int getScore() { + public double getScore() { return score; } @@ -446,8 +446,8 @@ static Offer fromEventData(final Map data) { DataReader.getString(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_ID); final String etag = DataReader.getString(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_ETAG); - final int score = - DataReader.optInt(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_SCORE, 0); + final double score = + DataReader.optDouble(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_SCORE, 0.0); final String schema = DataReader.getString(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_SCHEMA); @@ -534,7 +534,7 @@ static Offer fromEventData(final Map data) { + " empty string."); return new Builder(id, OfferType.UNKNOWN, "") .setEtag(null) - .setScore(0) + .setScore(0.0) .setSchema(schema) .setMeta(meta) .setLanguage(null) diff --git a/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OfferTests.java b/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OfferTests.java index d3b81535..e33c8a61 100644 --- a/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OfferTests.java +++ b/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OfferTests.java @@ -32,6 +32,8 @@ @SuppressWarnings("unchecked") public class OfferTests { + double doubleAccuracy = 0.001; + @Test public void testBuilder_validOffer() { final Offer offer = @@ -59,7 +61,7 @@ public void testBuilder_validOffer() { Assert.assertEquals("xcore:personalized-offer:2222222222222222", offer.getId()); Assert.assertEquals("7", offer.getEtag()); - Assert.assertEquals(2, offer.getScore()); + Assert.assertEquals(2, offer.getScore(), doubleAccuracy); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-text", offer.getSchema()); @@ -85,7 +87,7 @@ public void testFromEventData_validJsonOffer() throws Exception { Assert.assertEquals("xcore:personalized-offer:1111111111111111", offer.getId()); Assert.assertEquals("8", offer.getEtag()); - Assert.assertEquals(0, offer.getScore()); + Assert.assertEquals(0, offer.getScore(), doubleAccuracy); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-json", offer.getSchema()); @@ -111,7 +113,7 @@ public void testFromEventData_validTextOffer() throws Exception { Assert.assertEquals("xcore:personalized-offer:2222222222222222", offer.getId()); Assert.assertEquals("7", offer.getEtag()); - Assert.assertEquals(0, offer.getScore()); + Assert.assertEquals(0, offer.getScore(), doubleAccuracy); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-text", offer.getSchema()); @@ -137,7 +139,7 @@ public void testFromEventData_validHtmlOffer() throws Exception { Assert.assertEquals("xcore:personalized-offer:3333333333333333", offer.getId()); Assert.assertEquals("8", offer.getEtag()); - Assert.assertEquals(0, offer.getScore()); + Assert.assertEquals(0, offer.getScore(), doubleAccuracy); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-html", offer.getSchema()); @@ -163,7 +165,7 @@ public void testFromEventData_validImageOffer() throws Exception { Assert.assertEquals("xcore:personalized-offer:4444444444444444", offer.getId()); Assert.assertEquals("8", offer.getEtag()); - Assert.assertEquals(0, offer.getScore()); + Assert.assertEquals(0, offer.getScore(), doubleAccuracy); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-imagelink", offer.getSchema()); @@ -189,7 +191,38 @@ public void testFromEventData_validOfferWithScore() throws Exception { Assert.assertEquals("xcore:personalized-offer:2222222222222222", offer.getId()); Assert.assertEquals("7", offer.getEtag()); - Assert.assertEquals(1, offer.getScore()); + Assert.assertEquals(1, offer.getScore(), doubleAccuracy); + Assert.assertEquals( + "https://ns.adobe.com/experience/offer-management/content-component-text", + offer.getSchema()); + Assert.assertEquals(OfferType.TEXT, offer.getType()); + Assert.assertEquals(1, offer.getLanguage().size()); + Assert.assertEquals("en-us", offer.getLanguage().get(0)); + Assert.assertEquals("This is a plain text content!", offer.getContent()); + Assert.assertEquals(1, offer.getCharacteristics().size()); + Assert.assertEquals("true", offer.getCharacteristics().get("mobile")); + } + + @Test + public void testFromEventData_withDoubleScore() throws Exception { + Map offerData = + new ObjectMapper() + .readValue( + getClass() + .getClassLoader() + .getResource("json/OFFER_VALID_WITH_DOUBLE_SCORE.json"), + HashMap.class); + final Offer offer = Offer.fromEventData(offerData); + Assert.assertNotNull(offer); + + Assert.assertEquals("xcore:personalized-offer:2222222222222222", offer.getId()); + Assert.assertEquals("7", offer.getEtag()); + + // validate that the score is of type double and has the correct value + Object offerScore = offer.getScore(); + Assert.assertTrue(offerScore instanceof Double); + Assert.assertEquals(6.43, offer.getScore(), doubleAccuracy); + Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-text", offer.getSchema()); diff --git a/code/optimize/src/test/resources/json/OFFER_VALID_WITH_DOUBLE_SCORE.json b/code/optimize/src/test/resources/json/OFFER_VALID_WITH_DOUBLE_SCORE.json new file mode 100644 index 00000000..8ef0f9c0 --- /dev/null +++ b/code/optimize/src/test/resources/json/OFFER_VALID_WITH_DOUBLE_SCORE.json @@ -0,0 +1,17 @@ +{ + "id": "xcore:personalized-offer:2222222222222222", + "etag": "7", + "score": 6.43, + "schema": "https://ns.adobe.com/experience/offer-management/content-component-text", + "data": { + "id": "xcore:personalized-offer:2222222222222222", + "format": "text/plain", + "content": "This is a plain text content!", + "language": [ + "en-us" + ], + "characteristics": { + "mobile": "true" + } + } +} \ No newline at end of file From a3203c3ae83a080fe7f47964e1ce631e8c31afee Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Thu, 10 Oct 2024 15:51:18 +0530 Subject: [PATCH 19/37] documentation update for Offer Score datatype --- Documentation/api-reference.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/api-reference.md b/Documentation/api-reference.md index 468e11a1..048734bf 100644 --- a/Documentation/api-reference.md +++ b/Documentation/api-reference.md @@ -328,11 +328,11 @@ public class Offer { /** * Sets the score for this {@code Offer}. * - * @param score {@code int} containing {@link Offer} score. + * @param score {@code double} containing {@link Offer} score. * @return this Offer {@link Builder} * @throws UnsupportedOperationException if this method is invoked after {@link Builder#build()}. */ - public Builder setScore(final int score) {...} + public Builder setScore(final double score) {...} /** * Sets the schema for this {@code Offer}. @@ -396,9 +396,9 @@ public class Offer { /** * Gets the {@code Offer} score. * - * @return {@code int} containing the {@link Offer} score. + * @return {@code double} containing the {@link Offer} score. */ - public int getScore() {...} + public double getScore() {...} /** * Gets the {@code Offer} schema. From 9a84df992ff274beb9921ef5f15465800aae6ebc Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Tue, 15 Oct 2024 12:46:16 +0530 Subject: [PATCH 20/37] update documentation for updatePropositionsWithCompletionHandler (#104) --- Documentation/api-reference.md | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/Documentation/api-reference.md b/Documentation/api-reference.md index 468e11a1..37772282 100644 --- a/Documentation/api-reference.md +++ b/Documentation/api-reference.md @@ -12,6 +12,7 @@ Refer to the [Getting Started Guide](getting-started.md). - [onPropositionsUpdate](#onPropositionsUpdate) - [resetIdentities](#resetIdentities) - [updatePropositions](#updatePropositions) +- [updatePropositionsWithCompletionHandler](#updatePropositionsWithCompletionHandler) ## Public classes @@ -184,6 +185,63 @@ Optimize.updatePropositions(decisionScopes, }); ``` +## updatePropositionsWithCompletionHandler + +This API dispatches an event for the Edge network extension to fetch decision propositions, for the provided decision scopes array, from the decisioning services enabled in the Experience Edge. The returned decision propositions are cached in-memory in the Optimize SDK extension and can be retrieved using `getPropositions` API. + +> [!TIP] +> Completion callback passed to `updatePropositions` supports network timeout and fatal errors returned by edge network along with fetched propositions data. The SDK's internal retry mechanism handles the recoverable HTTP errors. As a result, recoverable HTTP errors are not returned through this callback. + +### Java + +#### Syntax + +```java +public static void updatePropositions(final List decisionScopes, + final Map xdm, + final Map data, + final AdobeCallback> callback) +``` + +* _decisionScopes_ is a list of decision scopes for which propositions need updating. +* _xdm_ is a map containing additional xdm formatted data to be attached to the Experience Event. +* _data_ is a map containing additional freeform data to be attached to the Experience Event. +* _callback_ is an optional completion handler that is invoked at the completion of the edge request. `call` method is invoked with propositions map of type `Map`. If the callback is an instance of `AdobeCallbackWithOptimizeError`, and if the operation times out or an error occurs in retrieving propositions, the `fail` method is invoked with the appropriate [AEPOptimizeError](https://developer.adobe.com/client-sdks/edge/adobe-journey-optimizer-decisioning/api-reference/#aepoptimizeerror). _Note:_ In certain cases, both the success and failure callbacks may be triggered. To handle these cases, ensure that your implementation checks for both successful propositions and errors within the callback, as both may be present simultaneously. + +#### Example + +```java +final DecisionScope decisionScope1 = DecisionScope("xcore:offer-activity:1111111111111111", "xcore:offer-placement:1111111111111111", 2); +final DecisionScope decisionScope2 = new DecisionScope("myScope"); + +final List decisionScopes = new ArrayList<>(); +decisionScopes.add(decisionScope1); +decisionScopes.add(decisionScope2); + +Optimize.updatePropositions(decisionScopes, + new HashMap() { + { + put("xdmKey", "xdmValue"); + } + }, + new HashMap() { + { + put("dataKey", "dataValue"); + } + }, + new AdobeCallbackWithOptimizeError>() { + @Override + public void fail(AEPOptimizeError optimizeError) { + responseError = optimizeError; + } + + @Override + public void call(Map propositionsMap) { + responseMap = propositionsMap; + } + }); +``` + ## Public classes ### DecisionScope From 45651a88e8344c161a544dc0ddfc3f1ee5c83c68 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Thu, 7 Nov 2024 19:59:27 +0530 Subject: [PATCH 21/37] Overloaded getPropositions and updatePropositions with new timeout argument in Optimize.java --- .../marketing/mobile/optimize/Optimize.java | 75 +++++++++++++++++-- 1 file changed, 69 insertions(+), 6 deletions(-) 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 5033e55c..db88e867 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 @@ -49,7 +49,7 @@ private Optimize() {} * Experience Edge network. * *

The returned decision propositions are cached in-memory in the Optimize SDK extension and - * can be retrieved using {@link #getPropositions(List, AdobeCallback)} API. + * can be retrieved using {@link #getPropositions(List, long, AdobeCallback)} API. * * @param decisionScopes {@code List} containing scopes for which offers need to * be updated. @@ -61,9 +61,10 @@ private Optimize() {} public static void updatePropositions( @NonNull final List decisionScopes, @Nullable final Map xdm, - @Nullable final Map data) { + @Nullable final Map data, + final long timeout) { - updatePropositions(decisionScopes, xdm, data, null); + updatePropositions(decisionScopes, xdm, data, timeout, null); } /** @@ -88,6 +89,44 @@ public static void updatePropositions( @Nullable final Map xdm, @Nullable final Map data, @Nullable final AdobeCallback> callback) { + final long defaultTimeout = OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT; + updatePropositionsInternal(decisionScopes, xdm, data, defaultTimeout, callback); + } + + /** + * This API dispatches an Event for the Edge network extension to fetch decision propositions, + * for the provided decision scopes list, from the decisioning services enabled in the + * Experience Edge network. + * + *

The returned decision propositions are cached in-memory in the Optimize SDK extension and + * can be retrieved using {@link #getPropositions(List, long, AdobeCallback)} API. + * + * @param decisionScopes {@code List} containing scopes for which offers need to + * be updated. + * @param xdm {@code Map} containing additional XDM-formatted data to be sent in + * the personalization query request. + * @param data {@code Map} containing additional free-form data to be sent in + * the personalization query request. + * @param timeoutMillis {@code Long} containing additional configurable timeout to be sent in the + * personalization query request. + * @param callback {@code AdobeCallback>} which will be + * invoked when decision propositions are received from the Edge network. + */ + public static void updatePropositions( + @NonNull final List decisionScopes, + @Nullable final Map xdm, + @Nullable final Map data, + final long timeoutMillis, + @Nullable final AdobeCallback> callback) { + updatePropositionsInternal(decisionScopes, xdm, data, timeoutMillis, callback); + } + + private static void updatePropositionsInternal( + @NonNull final List decisionScopes, + @Nullable final Map xdm, + @Nullable final Map data, + final long timeoutMillis, + @Nullable final AdobeCallback> callback) { if (OptimizeUtils.isNullOrEmpty(decisionScopes)) { Log.warning( @@ -148,7 +187,7 @@ public static void updatePropositions( MobileCore.dispatchEventWithResponseCallback( event, - OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT, + timeoutMillis, new AdobeCallbackWithError() { @Override public void fail(final AdobeError adobeError) { @@ -236,6 +275,30 @@ public void call(final Event event) { public static void getPropositions( @NonNull final List decisionScopes, @NonNull final AdobeCallback> callback) { + long defaultTimeout = OptimizeConstants.GET_RESPONSE_CALLBACK_TIMEOUT; + getPropositionsInternal(decisionScopes, defaultTimeout, callback); + } + + /** + * This API retrieves the previously fetched propositions, for the provided decision scopes, + * from the in-memory extension propositions cache. + * + * @param decisionScopes {@code List} containing scopes for which offers need to + * be requested. + * @param callback {@code AdobeCallbackWithError>} which + * will be invoked when decision propositions are retrieved from the local cache. + */ + public static void getPropositions( + @NonNull final List decisionScopes, + final long timeoutMillis, + @NonNull final AdobeCallback> callback) { + getPropositionsInternal(decisionScopes, timeoutMillis, callback); + } + + private static void getPropositionsInternal( + @NonNull final List decisionScopes, + final long timeoutMillis, + @NonNull final AdobeCallback> callback) { if (OptimizeUtils.isNullOrEmpty(decisionScopes)) { Log.warning( OptimizeConstants.LOG_TAG, @@ -286,7 +349,7 @@ public static void getPropositions( // requests have enough time to complete. MobileCore.dispatchEventWithResponseCallback( event, - OptimizeConstants.GET_RESPONSE_CALLBACK_TIMEOUT, + timeoutMillis, new AdobeCallbackWithError() { @Override public void fail(final AdobeError adobeError) { @@ -348,7 +411,7 @@ public void call(final Event event) { * query. * *

The personalization query requests can be triggered by the {@link - * Optimize#updatePropositions(List, Map, Map)} API, Edge extension {@code + * Optimize#updatePropositions(List, Map, Map, long)} API, Edge extension {@code * sendEvent(ExperienceEvent, EdgeCallback)} API or launch consequence rules. * * @param callback {@code AdobeCallbackWithError>} which From 2b948f10f51f584c3c6396cae74b9fadb23af68b Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Thu, 7 Nov 2024 20:00:34 +0530 Subject: [PATCH 22/37] Propagated the changes to test app --- .../marketing/mobile/optimize/Optimize.java | 4 +- .../marketing/optimizeapp/OffersScreen.kt | 8 +++- .../optimizeapp/viewmodels/MainViewModel.kt | 41 ++++++++++++++----- 3 files changed, 39 insertions(+), 14 deletions(-) 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 db88e867..7af9d94d 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 @@ -107,8 +107,8 @@ public static void updatePropositions( * the personalization query request. * @param data {@code Map} containing additional free-form data to be sent in * the personalization query request. - * @param timeoutMillis {@code Long} containing additional configurable timeout to be sent in the - * personalization query request. + * @param timeoutMillis {@code Long} containing additional configurable timeout to be sent in + * the personalization query request. * @param callback {@code AdobeCallback>} which will be * invoked when decision propositions are received from the Edge network. */ diff --git a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt index 37c6d060..173bf29c 100644 --- a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt +++ b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt @@ -181,7 +181,8 @@ fun OffersView(viewModel: MainViewModel) { viewModel.updatePropositions( decisionScopes = decisionScopeList, xdm = mapOf(Pair("xdmKey", "1234")), - data = data + data = data, + timeout = 200 ) }) { Text( @@ -200,7 +201,10 @@ fun OffersView(viewModel: MainViewModel) { viewModel.jsonDecisionScope?.also { decisionScopeList.add(it) } viewModel.targetMboxDecisionScope?.also { decisionScopeList.add(it) } - viewModel.getPropositions(decisionScopes = decisionScopeList) + viewModel.getPropositions( + decisionScopes = decisionScopeList, + timeout = 200 + ) }) { Text( text = "Get \n Propositions", diff --git a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt index a7832f5e..3768678f 100644 --- a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt +++ b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt @@ -71,10 +71,11 @@ class MainViewModel: ViewModel() { * Calls the Optimize SDK API to get the Propositions see [Optimize.getPropositions] * * @param [decisionScopes] a [List] of [DecisionScope] + * @param [timeout] a [Long] in milliseconds */ - fun getPropositions(decisionScopes: List) { + fun getPropositions(decisionScopes: List, timeout: Long? = null) { optimizePropositionStateMap.clear() - Optimize.getPropositions(decisionScopes, object: AdobeCallbackWithError>{ + val callback = object : AdobeCallbackWithError> { override fun call(propositions: Map?) { propositions?.forEach { optimizePropositionStateMap[it.key.name] = it.value @@ -84,8 +85,10 @@ class MainViewModel: ViewModel() { override fun fail(error: AdobeError?) { print("Error in getting Propositions.") } - - }) + } + timeout?.let { milliseconds -> + Optimize.getPropositions(decisionScopes, milliseconds, callback) + } ?: Optimize.getPropositions(decisionScopes, callback) } /** @@ -94,19 +97,37 @@ class MainViewModel: ViewModel() { * @param decisionScopes a [List] of [DecisionScope] * @param xdm a [Map] of xdm params * @param data a [Map] of data + * @param timeout a [Long] in milliseconds */ - fun updatePropositions(decisionScopes: List , xdm: Map , data: Map) { - optimizePropositionStateMap.clear() - Optimize.updatePropositions(decisionScopes, xdm, data, object: AdobeCallbackWithOptimizeError>{ + fun updatePropositions( + decisionScopes: List, + xdm: Map, + data: Map, + timeout: Long? = null + ) { + val callback = object : AdobeCallbackWithOptimizeError> { override fun call(propositions: Map?) { - Log.i("Optimize Test App","Propositions updated successfully.") + Log.i("Optimize Test App", "Propositions updated successfully.") } override fun fail(error: AEPOptimizeError?) { - Log.i("Optimize Test App","Error in updating Propositions:: ${error?.title ?: "Undefined"}.") + Log.i( + "Optimize Test App", + "Error in updating Propositions:: ${error?.title ?: "Undefined"}." + ) } - }) + } + optimizePropositionStateMap.clear() + timeout?.let { milliseconds -> + Optimize.updatePropositions( + decisionScopes, + xdm, + data, + milliseconds, + callback + ) + } ?: Optimize.updatePropositions(decisionScopes, xdm, data, callback) } /** From 8d79c5ac2e582fbf90d2136e36f82509865c989e Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Mon, 11 Nov 2024 20:42:09 +0530 Subject: [PATCH 23/37] Adding another overloaded method with no callback and no timeout --- .../marketing/mobile/optimize/Optimize.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) 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 7af9d94d..84507af2 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 @@ -43,6 +43,29 @@ private Optimize() {} return OptimizeConstants.EXTENSION_VERSION; } + /** + * This API dispatches an Event for the Edge network extension to fetch decision propositions, + * for the provided decision scopes list, from the decisioning services enabled in the + * Experience Edge network. + * + *

The returned decision propositions are cached in-memory in the Optimize SDK extension and + * can be retrieved using {@link #getPropositions(List, long, AdobeCallback)} API. + * + * @param decisionScopes {@code List} containing scopes for which offers need to + * be updated. + * @param xdm {@code Map} containing additional XDM-formatted data to be sent in + * the personalization query request. + * @param data {@code Map} containing additional free-form data to be sent in + * the personalization query request. + */ + public static void updatePropositions( + @NonNull final List decisionScopes, + @Nullable final Map xdm, + @Nullable final Map data) { + final long defaultTimeout = OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT; + updatePropositions(decisionScopes, xdm, data, defaultTimeout, null); + } + /** * This API dispatches an Event for the Edge network extension to fetch decision propositions, * for the provided decision scopes list, from the decisioning services enabled in the From 715e898a0482775672c5e3919d8d3e2fdb75cf85 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Wed, 13 Nov 2024 16:11:59 +0530 Subject: [PATCH 24/37] Timeout propagated to handleUpdatePropositions from updatePropositions --- .../java/com/adobe/marketing/mobile/optimize/Optimize.java | 1 + .../adobe/marketing/mobile/optimize/OptimizeConstants.java | 1 + .../adobe/marketing/mobile/optimize/OptimizeExtension.java | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) 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 84507af2..0d93f781 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 @@ -199,6 +199,7 @@ private static void updatePropositionsInternal( if (!OptimizeUtils.isNullOrEmpty(data)) { eventData.put(OptimizeConstants.EventDataKeys.DATA, data); } + eventData.put(OptimizeConstants.EventDataKeys.TIMEOUT, timeoutMillis); final Event event = new Event.Builder( 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 4633cf71..ff27229b 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 @@ -74,6 +74,7 @@ static final class EventDataKeys { static final String DECISION_SCOPE_NAME = "name"; static final String XDM = "xdm"; static final String DATA = "data"; + static final String TIMEOUT = "timeout"; static final String PROPOSITIONS = "propositions"; static final String RESPONSE_ERROR = "responseerror"; static final String PROPOSITION_INTERACTIONS = "propositioninteractions"; 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 4a9f168d..8147e125 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 @@ -449,10 +449,11 @@ void handleUpdatePropositions(@NonNull final Event event) { // add the Edge event to update propositions in the events queue. eventsDispatcher.offer(edgeEvent); - + long timeoutMillis = + DataReader.getLong(eventData, OptimizeConstants.EventDataKeys.TIMEOUT); MobileCore.dispatchEventWithResponseCallback( edgeEvent, - OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT, + timeoutMillis, new AdobeCallbackWithError() { @Override public void fail(final AdobeError error) { From 3ed034b7a4398ed6e3e8fa63f3c2b666a780c1d3 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Thu, 14 Nov 2024 20:01:10 +0530 Subject: [PATCH 25/37] Removing the updatePropositions overload with timeout and without callback --- .../marketing/mobile/optimize/Optimize.java | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) 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 0d93f781..a0c58bc0 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 @@ -66,30 +66,6 @@ public static void updatePropositions( updatePropositions(decisionScopes, xdm, data, defaultTimeout, null); } - /** - * This API dispatches an Event for the Edge network extension to fetch decision propositions, - * for the provided decision scopes list, from the decisioning services enabled in the - * Experience Edge network. - * - *

The returned decision propositions are cached in-memory in the Optimize SDK extension and - * can be retrieved using {@link #getPropositions(List, long, AdobeCallback)} API. - * - * @param decisionScopes {@code List} containing scopes for which offers need to - * be updated. - * @param xdm {@code Map} containing additional XDM-formatted data to be sent in - * the personalization query request. - * @param data {@code Map} containing additional free-form data to be sent in - * the personalization query request. - */ - public static void updatePropositions( - @NonNull final List decisionScopes, - @Nullable final Map xdm, - @Nullable final Map data, - final long timeout) { - - updatePropositions(decisionScopes, xdm, data, timeout, null); - } - /** * This API dispatches an Event for the Edge network extension to fetch decision propositions, * for the provided decision scopes list, from the decisioning services enabled in the @@ -435,7 +411,7 @@ public void call(final Event event) { * query. * *

The personalization query requests can be triggered by the {@link - * Optimize#updatePropositions(List, Map, Map, long)} API, Edge extension {@code + * Optimize#updatePropositions(List, Map, Map)} API, Edge extension {@code * sendEvent(ExperienceEvent, EdgeCallback)} API or launch consequence rules. * * @param callback {@code AdobeCallbackWithError>} which From 34f1183191f27b121cdd2ff6fb3946a9639e939d Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Mon, 18 Nov 2024 15:13:18 +0530 Subject: [PATCH 26/37] Adding the unit test for update proposition with timeout --- .../mobile/optimize/AEPOptimizeError.kt | 1 + .../mobile/optimize/OptimizeTests.java | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/AEPOptimizeError.kt b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/AEPOptimizeError.kt index 5a092da4..63a60355 100644 --- a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/AEPOptimizeError.kt +++ b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/AEPOptimizeError.kt @@ -84,6 +84,7 @@ data class AEPOptimizeError( return getAdobeErrorFromStatus(data[STATUS] as Int?) } + @JvmStatic fun getTimeoutError(): AEPOptimizeError { return AEPOptimizeError( null, 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 a880b206..cb2161a4 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 @@ -11,6 +11,11 @@ package com.adobe.marketing.mobile.optimize; +import static com.adobe.marketing.mobile.optimize.Optimize.failWithOptimizeError; +import static com.adobe.marketing.mobile.optimize.Optimize.updatePropositions; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + import android.util.Base64; import com.adobe.marketing.mobile.AdobeCallbackWithError; import com.adobe.marketing.mobile.AdobeError; @@ -896,4 +901,63 @@ public void test_clearCachedPropositions() { Assert.assertNull(event.getEventData()); } } + + @Test + public void testUpdatePropositions_timeoutError() { + + long timeoutMillis = 100; // Short timeout for testing + Map xdm = new HashMap<>(); + Map data = new HashMap<>(); + final List scopes = new ArrayList<>(); + scopes.add( + new DecisionScope( + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==")); + + // Mock the callback + AdobeCallbackWithError> callbackMock = + Mockito.mock(AdobeCallbackWithError.class); + + AdobeCallbackWithOptimizeError callbackMockEvent = + Mockito.mock(AdobeCallbackWithOptimizeError.class); + + try (MockedStatic mobileCoreMockedStatic = + Mockito.mockStatic(MobileCore.class); + 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])); + + mobileCoreMockedStatic + .when( + () -> + MobileCore.dispatchEventWithResponseCallback( + ArgumentMatchers.any(Event.class), + ArgumentMatchers.anyLong(), + ArgumentMatchers.any(AdobeCallbackWithError.class))) + .thenAnswer( + (Answer) + invocation -> { + failWithOptimizeError( + callbackMockEvent, + AEPOptimizeError.Companion.getTimeoutError()); + return null; + }); + + updatePropositions(scopes, xdm, data, timeoutMillis, callbackMock); + ArgumentCaptor errorCaptor = + ArgumentCaptor.forClass(AEPOptimizeError.class); + verify(callbackMockEvent, times(1)).fail(errorCaptor.capture()); + Assert.assertEquals( + AEPOptimizeError.Companion.getTimeoutError(), errorCaptor.getValue()); + } + } } From b9eff05423799949b9966666df636c4d83a5041c Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Mon, 18 Nov 2024 15:34:24 +0530 Subject: [PATCH 27/37] Adding the unit test for get proposition with timeout --- .../mobile/optimize/OptimizeTests.java | 70 +++++++++++++++++-- 1 file changed, 64 insertions(+), 6 deletions(-) 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 cb2161a4..92544132 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 @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile.optimize; import static com.adobe.marketing.mobile.optimize.Optimize.failWithOptimizeError; +import static com.adobe.marketing.mobile.optimize.Optimize.getPropositions; import static com.adobe.marketing.mobile.optimize.Optimize.updatePropositions; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -459,7 +460,7 @@ public void testGetPropositions_validDecisionScope() throws Exception { new DecisionScope( "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==")); - Optimize.getPropositions( + getPropositions( scopes, new AdobeCallbackWithError>() { @Override @@ -566,7 +567,7 @@ public void testGetPropositions_multipleValidDecisionScopes() { "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==")); scopes.add(new DecisionScope("myMbox")); - Optimize.getPropositions( + getPropositions( scopes, new AdobeCallbackWithError>() { @Override @@ -636,7 +637,7 @@ public void testGetPropositions_invalidDecisionScopeInList() { new DecisionScope( "eyJhY3Rpdml0eUlkIjoiIiwicGxhY2VtZW50SWQiOiJ4Y29yZTpvZmZlci1wbGFjZW1lbnQ6MTExMTExMTExMTExMTExMSJ9")); - Optimize.getPropositions( + getPropositions( scopes, new AdobeCallbackWithError>() { @Override @@ -665,7 +666,7 @@ public void call(Map propositionsMap) { public void testGetPropositions_emptyDecisionScopesList() { try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { // test - Optimize.getPropositions( + getPropositions( new ArrayList(), new AdobeCallbackWithError>() { @Override @@ -694,7 +695,7 @@ public void call(Map propositionsMap) { public void testGetPropositions_nullDecisionScopesList() { try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { // test - Optimize.getPropositions( + getPropositions( null, new AdobeCallbackWithError>() { @Override @@ -905,7 +906,7 @@ public void test_clearCachedPropositions() { @Test public void testUpdatePropositions_timeoutError() { - long timeoutMillis = 100; // Short timeout for testing + long timeoutMillis = 100; Map xdm = new HashMap<>(); Map data = new HashMap<>(); final List scopes = new ArrayList<>(); @@ -960,4 +961,61 @@ public void testUpdatePropositions_timeoutError() { AEPOptimizeError.Companion.getTimeoutError(), errorCaptor.getValue()); } } + + @Test + public void testGetPropositions_timeoutError() { + + long timeoutMillis = 100; + final List scopes = new ArrayList<>(); + scopes.add( + new DecisionScope( + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==")); + + // Mock the callback + AdobeCallbackWithError> callbackMock = + Mockito.mock(AdobeCallbackWithError.class); + + AdobeCallbackWithOptimizeError callbackMockEvent = + Mockito.mock(AdobeCallbackWithOptimizeError.class); + + try (MockedStatic mobileCoreMockedStatic = + Mockito.mockStatic(MobileCore.class); + 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])); + + mobileCoreMockedStatic + .when( + () -> + MobileCore.dispatchEventWithResponseCallback( + ArgumentMatchers.any(Event.class), + ArgumentMatchers.anyLong(), + ArgumentMatchers.any(AdobeCallbackWithError.class))) + .thenAnswer( + (Answer) + invocation -> { + failWithOptimizeError( + callbackMockEvent, + AEPOptimizeError.Companion.getTimeoutError()); + return null; + }); + + getPropositions(scopes, timeoutMillis, callbackMock); + ArgumentCaptor errorCaptor = + ArgumentCaptor.forClass(AEPOptimizeError.class); + verify(callbackMockEvent, times(1)).fail(errorCaptor.capture()); + Assert.assertEquals( + AEPOptimizeError.Companion.getTimeoutError(), errorCaptor.getValue()); + } + } } From 6e7c78f4d4393c7d2733adc3a0b5eef1babd032f Mon Sep 17 00:00:00 2001 From: navratan-soni Date: Wed, 20 Nov 2024 10:30:35 +0000 Subject: [PATCH 28/37] Updating version to 3.2.0. --- code/gradle.properties | 2 +- .../adobe/marketing/mobile/optimize/OptimizeTestConstants.java | 2 +- .../com/adobe/marketing/mobile/optimize/OptimizeConstants.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/gradle.properties b/code/gradle.properties index 5cdddcfb..f3545f0f 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -16,7 +16,7 @@ org.gradle.caching=true android.useAndroidX=true moduleName=optimize -moduleVersion=3.1.0 +moduleVersion=3.2.0 #Maven artifact mavenRepoName=AdobeMobileOptimizeSdk 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 b3a5ca74..1720574f 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 @@ -13,7 +13,7 @@ public class OptimizeTestConstants { - static final String EXTENSION_VERSION = "3.1.0"; + static final String EXTENSION_VERSION = "3.2.0"; public static final String LOG_TAG = "OptimizeTest"; static final String CONFIG_DATA_STORE = "AdobeMobile_ConfigState"; 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 ff27229b..b410331f 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 @@ -13,7 +13,7 @@ class OptimizeConstants { static final String LOG_TAG = "Optimize"; - static final String EXTENSION_VERSION = "3.1.0"; + static final String EXTENSION_VERSION = "3.2.0"; static final String EXTENSION_NAME = "com.adobe.optimize"; static final String FRIENDLY_NAME = "Optimize"; static final long DEFAULT_RESPONSE_CALLBACK_TIMEOUT = 500L; From f5090603b5d6c4387257e0526b49c2cf2071f527 Mon Sep 17 00:00:00 2001 From: siddique-adobe Date: Wed, 27 Nov 2024 16:31:29 +0530 Subject: [PATCH 29/37] Revert "Change the type of offer score from int to double (#102)" This reverts commit acfd77e7873953d98016418995264dac2b2d9041. --- .../optimize/OptimizeFunctionalTests.java | 6 +-- .../marketing/mobile/optimize/Offer.java | 18 ++++---- .../marketing/mobile/optimize/OfferTests.java | 45 +++---------------- .../json/OFFER_VALID_WITH_DOUBLE_SCORE.json | 17 ------- 4 files changed, 17 insertions(+), 69 deletions(-) delete mode 100644 code/optimize/src/test/resources/json/OFFER_VALID_WITH_DOUBLE_SCORE.json 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 063d401f..f8fdf9af 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 @@ -43,8 +43,6 @@ @RunWith(AndroidJUnit4.class) public class OptimizeFunctionalTests { - double doubleAccuracy = 0.001; - @Rule public RuleChain ruleChain = RuleChain.outerRule(new TestHelper.SetupCoreRule()) @@ -699,7 +697,7 @@ public void call( Offer offer = optimizeProposition.getOffers().get(0); Assert.assertEquals("xcore:personalized-offer:1111111111111111", offer.getId()); Assert.assertEquals("10", offer.getEtag()); - Assert.assertEquals(1, offer.getScore(), doubleAccuracy); + Assert.assertEquals(1, offer.getScore()); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-html", offer.getSchema()); @@ -869,7 +867,7 @@ public void call( Offer offer = optimizeProposition.getOffers().get(0); Assert.assertEquals("0", offer.getId()); Assert.assertEquals(null, offer.getEtag()); - Assert.assertEquals(0, offer.getScore(), doubleAccuracy); + Assert.assertEquals(0, offer.getScore()); Assert.assertEquals( "https://ns.adobe.com/personalization/default-content-item", offer.getSchema()); Assert.assertEquals(OfferType.UNKNOWN, offer.getType()); diff --git a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Offer.java b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Offer.java index bd6cbef3..1a8ec726 100644 --- a/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Offer.java +++ b/code/optimize/src/main/java/com/adobe/marketing/mobile/optimize/Offer.java @@ -29,7 +29,7 @@ public class Offer { private static final String SELF_TAG = "Offer"; private String id; private String etag; - private double score; + private int score; private String schema; private Map meta; private OfferType type; @@ -66,7 +66,7 @@ public Builder(final String id, final OfferType type, final String content) { offer.type = type != null ? type : OfferType.UNKNOWN; offer.content = content != null ? content : ""; offer.etag = ""; - offer.score = 0.0; + offer.score = 0; offer.schema = ""; offer.meta = new HashMap<>(); offer.language = new ArrayList<>(); @@ -92,12 +92,12 @@ public Builder setEtag(final String etag) { /** * Sets the score for this {@code Offer}. * - * @param score {@code double} containing {@link Offer} score. + * @param score {@code int} containing {@link Offer} score. * @return this Offer {@link Builder} * @throws UnsupportedOperationException if this method is invoked after {@link * Builder#build()}. */ - public Builder setScore(final double score) { + public Builder setScore(final int score) { throwIfAlreadyBuilt(); offer.score = score; @@ -206,9 +206,9 @@ public String getEtag() { /** * Gets the {@code Offer} score. * - * @return {@code double} containing the {@link Offer} score. + * @return {@code int} containing the {@link Offer} score. */ - public double getScore() { + public int getScore() { return score; } @@ -446,8 +446,8 @@ static Offer fromEventData(final Map data) { DataReader.getString(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_ID); final String etag = DataReader.getString(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_ETAG); - final double score = - DataReader.optDouble(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_SCORE, 0.0); + final int score = + DataReader.optInt(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_SCORE, 0); final String schema = DataReader.getString(data, OptimizeConstants.JsonKeys.PAYLOAD_ITEM_SCHEMA); @@ -534,7 +534,7 @@ static Offer fromEventData(final Map data) { + " empty string."); return new Builder(id, OfferType.UNKNOWN, "") .setEtag(null) - .setScore(0.0) + .setScore(0) .setSchema(schema) .setMeta(meta) .setLanguage(null) diff --git a/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OfferTests.java b/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OfferTests.java index e33c8a61..d3b81535 100644 --- a/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OfferTests.java +++ b/code/optimize/src/test/java/com/adobe/marketing/mobile/optimize/OfferTests.java @@ -32,8 +32,6 @@ @SuppressWarnings("unchecked") public class OfferTests { - double doubleAccuracy = 0.001; - @Test public void testBuilder_validOffer() { final Offer offer = @@ -61,7 +59,7 @@ public void testBuilder_validOffer() { Assert.assertEquals("xcore:personalized-offer:2222222222222222", offer.getId()); Assert.assertEquals("7", offer.getEtag()); - Assert.assertEquals(2, offer.getScore(), doubleAccuracy); + Assert.assertEquals(2, offer.getScore()); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-text", offer.getSchema()); @@ -87,7 +85,7 @@ public void testFromEventData_validJsonOffer() throws Exception { Assert.assertEquals("xcore:personalized-offer:1111111111111111", offer.getId()); Assert.assertEquals("8", offer.getEtag()); - Assert.assertEquals(0, offer.getScore(), doubleAccuracy); + Assert.assertEquals(0, offer.getScore()); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-json", offer.getSchema()); @@ -113,7 +111,7 @@ public void testFromEventData_validTextOffer() throws Exception { Assert.assertEquals("xcore:personalized-offer:2222222222222222", offer.getId()); Assert.assertEquals("7", offer.getEtag()); - Assert.assertEquals(0, offer.getScore(), doubleAccuracy); + Assert.assertEquals(0, offer.getScore()); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-text", offer.getSchema()); @@ -139,7 +137,7 @@ public void testFromEventData_validHtmlOffer() throws Exception { Assert.assertEquals("xcore:personalized-offer:3333333333333333", offer.getId()); Assert.assertEquals("8", offer.getEtag()); - Assert.assertEquals(0, offer.getScore(), doubleAccuracy); + Assert.assertEquals(0, offer.getScore()); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-html", offer.getSchema()); @@ -165,7 +163,7 @@ public void testFromEventData_validImageOffer() throws Exception { Assert.assertEquals("xcore:personalized-offer:4444444444444444", offer.getId()); Assert.assertEquals("8", offer.getEtag()); - Assert.assertEquals(0, offer.getScore(), doubleAccuracy); + Assert.assertEquals(0, offer.getScore()); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-imagelink", offer.getSchema()); @@ -191,38 +189,7 @@ public void testFromEventData_validOfferWithScore() throws Exception { Assert.assertEquals("xcore:personalized-offer:2222222222222222", offer.getId()); Assert.assertEquals("7", offer.getEtag()); - Assert.assertEquals(1, offer.getScore(), doubleAccuracy); - Assert.assertEquals( - "https://ns.adobe.com/experience/offer-management/content-component-text", - offer.getSchema()); - Assert.assertEquals(OfferType.TEXT, offer.getType()); - Assert.assertEquals(1, offer.getLanguage().size()); - Assert.assertEquals("en-us", offer.getLanguage().get(0)); - Assert.assertEquals("This is a plain text content!", offer.getContent()); - Assert.assertEquals(1, offer.getCharacteristics().size()); - Assert.assertEquals("true", offer.getCharacteristics().get("mobile")); - } - - @Test - public void testFromEventData_withDoubleScore() throws Exception { - Map offerData = - new ObjectMapper() - .readValue( - getClass() - .getClassLoader() - .getResource("json/OFFER_VALID_WITH_DOUBLE_SCORE.json"), - HashMap.class); - final Offer offer = Offer.fromEventData(offerData); - Assert.assertNotNull(offer); - - Assert.assertEquals("xcore:personalized-offer:2222222222222222", offer.getId()); - Assert.assertEquals("7", offer.getEtag()); - - // validate that the score is of type double and has the correct value - Object offerScore = offer.getScore(); - Assert.assertTrue(offerScore instanceof Double); - Assert.assertEquals(6.43, offer.getScore(), doubleAccuracy); - + Assert.assertEquals(1, offer.getScore()); Assert.assertEquals( "https://ns.adobe.com/experience/offer-management/content-component-text", offer.getSchema()); diff --git a/code/optimize/src/test/resources/json/OFFER_VALID_WITH_DOUBLE_SCORE.json b/code/optimize/src/test/resources/json/OFFER_VALID_WITH_DOUBLE_SCORE.json deleted file mode 100644 index 8ef0f9c0..00000000 --- a/code/optimize/src/test/resources/json/OFFER_VALID_WITH_DOUBLE_SCORE.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "id": "xcore:personalized-offer:2222222222222222", - "etag": "7", - "score": 6.43, - "schema": "https://ns.adobe.com/experience/offer-management/content-component-text", - "data": { - "id": "xcore:personalized-offer:2222222222222222", - "format": "text/plain", - "content": "This is a plain text content!", - "language": [ - "en-us" - ], - "characteristics": { - "mobile": "true" - } - } -} \ No newline at end of file From 2c5fdce99e6dd2cd95f12cdd7acc1bae4901f0b5 Mon Sep 17 00:00:00 2001 From: siddique-adobe Date: Wed, 27 Nov 2024 16:33:13 +0530 Subject: [PATCH 30/37] Revert "documentation update for Offer Score datatype" --- Documentation/api-reference.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Documentation/api-reference.md b/Documentation/api-reference.md index bcad511d..37772282 100644 --- a/Documentation/api-reference.md +++ b/Documentation/api-reference.md @@ -386,11 +386,11 @@ public class Offer { /** * Sets the score for this {@code Offer}. * - * @param score {@code double} containing {@link Offer} score. + * @param score {@code int} containing {@link Offer} score. * @return this Offer {@link Builder} * @throws UnsupportedOperationException if this method is invoked after {@link Builder#build()}. */ - public Builder setScore(final double score) {...} + public Builder setScore(final int score) {...} /** * Sets the schema for this {@code Offer}. @@ -454,9 +454,9 @@ public class Offer { /** * Gets the {@code Offer} score. * - * @return {@code double} containing the {@link Offer} score. + * @return {@code int} containing the {@link Offer} score. */ - public double getScore() {...} + public int getScore() {...} /** * Gets the {@code Offer} schema. From 660dd3d7738e0cd5d093209647ac7625e5b95d0b Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Thu, 28 Nov 2024 12:56:50 +0530 Subject: [PATCH 31/37] Marking the updateProposition API without callback deprecated --- .../java/com/adobe/marketing/mobile/optimize/Optimize.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 a0c58bc0..8dc4f1d7 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 @@ -58,12 +58,13 @@ private Optimize() {} * @param data {@code Map} containing additional free-form data to be sent in * the personalization query request. */ + @Deprecated public static void updatePropositions( @NonNull final List decisionScopes, @Nullable final Map xdm, @Nullable final Map data) { - final long defaultTimeout = OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT; - updatePropositions(decisionScopes, xdm, data, defaultTimeout, null); + + updatePropositions(decisionScopes, xdm, data, null); } /** From 2fb16232be4a529002e4e52da5e28378d820a3b4 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Thu, 28 Nov 2024 14:57:54 +0530 Subject: [PATCH 32/37] Changing the timeout scale of update proposition from milliseconds to seconds --- .../marketing/mobile/optimize/Optimize.java | 16 +++++++++------- .../mobile/optimize/OptimizeConstants.java | 2 +- .../adobe/marketing/optimizeapp/OffersScreen.kt | 2 +- .../optimizeapp/viewmodels/MainViewModel.kt | 8 ++++---- 4 files changed, 15 insertions(+), 13 deletions(-) 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 8dc4f1d7..40f22040 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 @@ -89,8 +89,8 @@ public static void updatePropositions( @Nullable final Map xdm, @Nullable final Map data, @Nullable final AdobeCallback> callback) { - final long defaultTimeout = OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT; - updatePropositionsInternal(decisionScopes, xdm, data, defaultTimeout, callback); + final double defaultTimeoutSeconds = OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT; + updatePropositionsInternal(decisionScopes, xdm, data, defaultTimeoutSeconds, callback); } /** @@ -107,8 +107,8 @@ public static void updatePropositions( * the personalization query request. * @param data {@code Map} containing additional free-form data to be sent in * the personalization query request. - * @param timeoutMillis {@code Long} containing additional configurable timeout to be sent in - * the personalization query request. + * @param timeoutSeconds {@code Double} containing additional configurable timeout(seconds) to + * be sent in the personalization query request. * @param callback {@code AdobeCallback>} which will be * invoked when decision propositions are received from the Edge network. */ @@ -116,16 +116,16 @@ public static void updatePropositions( @NonNull final List decisionScopes, @Nullable final Map xdm, @Nullable final Map data, - final long timeoutMillis, + final double timeoutSeconds, @Nullable final AdobeCallback> callback) { - updatePropositionsInternal(decisionScopes, xdm, data, timeoutMillis, callback); + updatePropositionsInternal(decisionScopes, xdm, data, timeoutSeconds, callback); } private static void updatePropositionsInternal( @NonNull final List decisionScopes, @Nullable final Map xdm, @Nullable final Map data, - final long timeoutMillis, + final double timeoutSeconds, @Nullable final AdobeCallback> callback) { if (OptimizeUtils.isNullOrEmpty(decisionScopes)) { @@ -176,6 +176,8 @@ private static void updatePropositionsInternal( if (!OptimizeUtils.isNullOrEmpty(data)) { eventData.put(OptimizeConstants.EventDataKeys.DATA, data); } + + long timeoutMillis = (long) (timeoutSeconds * 1000); eventData.put(OptimizeConstants.EventDataKeys.TIMEOUT, timeoutMillis); final Event event = 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 b410331f..6d7aaeee 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 @@ -18,7 +18,7 @@ class OptimizeConstants { 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 = 10000L; + static final double EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT = 10; static final String ACTIVITY_ID = "activityId"; static final String XDM_ACTIVITY_ID = "xdm:activityId"; diff --git a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt index 173bf29c..2fb686a4 100644 --- a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt +++ b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt @@ -182,7 +182,7 @@ fun OffersView(viewModel: MainViewModel) { decisionScopes = decisionScopeList, xdm = mapOf(Pair("xdmKey", "1234")), data = data, - timeout = 200 + timeoutSeconds = 0.2 ) }) { Text( diff --git a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt index 3768678f..cb907052 100644 --- a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt +++ b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt @@ -97,13 +97,13 @@ class MainViewModel: ViewModel() { * @param decisionScopes a [List] of [DecisionScope] * @param xdm a [Map] of xdm params * @param data a [Map] of data - * @param timeout a [Long] in milliseconds + * @param timeoutSeconds a [Double] in seconds */ fun updatePropositions( decisionScopes: List, xdm: Map, data: Map, - timeout: Long? = null + timeoutSeconds: Double? = null ) { val callback = object : AdobeCallbackWithOptimizeError> { override fun call(propositions: Map?) { @@ -119,12 +119,12 @@ class MainViewModel: ViewModel() { } optimizePropositionStateMap.clear() - timeout?.let { milliseconds -> + timeoutSeconds?.let { seconds -> Optimize.updatePropositions( decisionScopes, xdm, data, - milliseconds, + seconds, callback ) } ?: Optimize.updatePropositions(decisionScopes, xdm, data, callback) From eb6b34de0c9a5d3155b62fe6798c6307410430e2 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Thu, 28 Nov 2024 15:26:00 +0530 Subject: [PATCH 33/37] Changing the timeout scale of get proposition from milliseconds to seconds --- .../marketing/mobile/optimize/Optimize.java | 18 +++++++++--------- .../mobile/optimize/OptimizeConstants.java | 3 +-- .../marketing/optimizeapp/OffersScreen.kt | 2 +- .../optimizeapp/viewmodels/MainViewModel.kt | 8 ++++---- 4 files changed, 15 insertions(+), 16 deletions(-) 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 40f22040..f9e28dbd 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 @@ -49,7 +49,7 @@ private Optimize() {} * Experience Edge network. * *

The returned decision propositions are cached in-memory in the Optimize SDK extension and - * can be retrieved using {@link #getPropositions(List, long, AdobeCallback)} API. + * can be retrieved using {@link #getPropositions(List, AdobeCallback)} API. * * @param decisionScopes {@code List} containing scopes for which offers need to * be updated. @@ -99,7 +99,7 @@ public static void updatePropositions( * Experience Edge network. * *

The returned decision propositions are cached in-memory in the Optimize SDK extension and - * can be retrieved using {@link #getPropositions(List, long, AdobeCallback)} API. + * can be retrieved using {@link #getPropositions(List, double, AdobeCallback)} API. * * @param decisionScopes {@code List} containing scopes for which offers need to * be updated. @@ -278,8 +278,8 @@ public void call(final Event event) { public static void getPropositions( @NonNull final List decisionScopes, @NonNull final AdobeCallback> callback) { - long defaultTimeout = OptimizeConstants.GET_RESPONSE_CALLBACK_TIMEOUT; - getPropositionsInternal(decisionScopes, defaultTimeout, callback); + final double defaultTimeoutSeconds = OptimizeConstants.GET_RESPONSE_CALLBACK_TIMEOUT; + getPropositionsInternal(decisionScopes, defaultTimeoutSeconds, callback); } /** @@ -293,14 +293,14 @@ public static void getPropositions( */ public static void getPropositions( @NonNull final List decisionScopes, - final long timeoutMillis, + final double timeoutSeconds, @NonNull final AdobeCallback> callback) { - getPropositionsInternal(decisionScopes, timeoutMillis, callback); + getPropositionsInternal(decisionScopes, timeoutSeconds, callback); } private static void getPropositionsInternal( @NonNull final List decisionScopes, - final long timeoutMillis, + final double timeoutSeconds, @NonNull final AdobeCallback> callback) { if (OptimizeUtils.isNullOrEmpty(decisionScopes)) { Log.warning( @@ -348,8 +348,8 @@ private static void getPropositionsInternal( .setEventData(eventData) .build(); - // Increased default response callback timeout to 10s to ensure prior update propositions - // requests have enough time to complete. + long timeoutMillis = (long) (timeoutSeconds * 1000); + MobileCore.dispatchEventWithResponseCallback( event, timeoutMillis, 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 6d7aaeee..c9933f62 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 @@ -16,8 +16,7 @@ class OptimizeConstants { static final String EXTENSION_VERSION = "3.2.0"; 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 double GET_RESPONSE_CALLBACK_TIMEOUT = 10; static final double EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT = 10; static final String ACTIVITY_ID = "activityId"; diff --git a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt index 2fb686a4..19a9ddc9 100644 --- a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt +++ b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/OffersScreen.kt @@ -203,7 +203,7 @@ fun OffersView(viewModel: MainViewModel) { viewModel.getPropositions( decisionScopes = decisionScopeList, - timeout = 200 + timeoutSeconds = 0.2 ) }) { Text( diff --git a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt index cb907052..2240944d 100644 --- a/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt +++ b/code/testapp/src/main/java/com/adobe/marketing/optimizeapp/viewmodels/MainViewModel.kt @@ -71,9 +71,9 @@ class MainViewModel: ViewModel() { * Calls the Optimize SDK API to get the Propositions see [Optimize.getPropositions] * * @param [decisionScopes] a [List] of [DecisionScope] - * @param [timeout] a [Long] in milliseconds + * @param [timeoutSeconds] a [Double] in seconds */ - fun getPropositions(decisionScopes: List, timeout: Long? = null) { + fun getPropositions(decisionScopes: List, timeoutSeconds: Double? = null) { optimizePropositionStateMap.clear() val callback = object : AdobeCallbackWithError> { override fun call(propositions: Map?) { @@ -86,8 +86,8 @@ class MainViewModel: ViewModel() { print("Error in getting Propositions.") } } - timeout?.let { milliseconds -> - Optimize.getPropositions(decisionScopes, milliseconds, callback) + timeoutSeconds?.let { seconds -> + Optimize.getPropositions(decisionScopes, seconds, callback) } ?: Optimize.getPropositions(decisionScopes, callback) } From ab24d23458ef9074279d3ef7ce237a13103c521d Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Thu, 28 Nov 2024 15:30:02 +0530 Subject: [PATCH 34/37] Updating the unit tests for the get and update timeout case accordingly --- .../mobile/optimize/OptimizeTests.java | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) 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 92544132..90fc3476 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 @@ -10,10 +10,6 @@ */ package com.adobe.marketing.mobile.optimize; - -import static com.adobe.marketing.mobile.optimize.Optimize.failWithOptimizeError; -import static com.adobe.marketing.mobile.optimize.Optimize.getPropositions; -import static com.adobe.marketing.mobile.optimize.Optimize.updatePropositions; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -460,7 +456,7 @@ public void testGetPropositions_validDecisionScope() throws Exception { new DecisionScope( "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==")); - getPropositions( + Optimize.getPropositions( scopes, new AdobeCallbackWithError>() { @Override @@ -567,7 +563,7 @@ public void testGetPropositions_multipleValidDecisionScopes() { "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==")); scopes.add(new DecisionScope("myMbox")); - getPropositions( + Optimize.getPropositions( scopes, new AdobeCallbackWithError>() { @Override @@ -637,7 +633,7 @@ public void testGetPropositions_invalidDecisionScopeInList() { new DecisionScope( "eyJhY3Rpdml0eUlkIjoiIiwicGxhY2VtZW50SWQiOiJ4Y29yZTpvZmZlci1wbGFjZW1lbnQ6MTExMTExMTExMTExMTExMSJ9")); - getPropositions( + Optimize.getPropositions( scopes, new AdobeCallbackWithError>() { @Override @@ -666,7 +662,7 @@ public void call(Map propositionsMap) { public void testGetPropositions_emptyDecisionScopesList() { try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { // test - getPropositions( + Optimize.getPropositions( new ArrayList(), new AdobeCallbackWithError>() { @Override @@ -695,7 +691,7 @@ public void call(Map propositionsMap) { public void testGetPropositions_nullDecisionScopesList() { try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { // test - getPropositions( + Optimize.getPropositions( null, new AdobeCallbackWithError>() { @Override @@ -906,7 +902,7 @@ public void test_clearCachedPropositions() { @Test public void testUpdatePropositions_timeoutError() { - long timeoutMillis = 100; + double timeoutSeconds = 0.1; Map xdm = new HashMap<>(); Map data = new HashMap<>(); final List scopes = new ArrayList<>(); @@ -947,13 +943,13 @@ public void testUpdatePropositions_timeoutError() { .thenAnswer( (Answer) invocation -> { - failWithOptimizeError( + Optimize.failWithOptimizeError( callbackMockEvent, AEPOptimizeError.Companion.getTimeoutError()); return null; }); - updatePropositions(scopes, xdm, data, timeoutMillis, callbackMock); + Optimize.updatePropositions(scopes, xdm, data, timeoutSeconds, callbackMock); ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(AEPOptimizeError.class); verify(callbackMockEvent, times(1)).fail(errorCaptor.capture()); @@ -965,7 +961,7 @@ public void testUpdatePropositions_timeoutError() { @Test public void testGetPropositions_timeoutError() { - long timeoutMillis = 100; + double timeoutSeconds = 0.1; final List scopes = new ArrayList<>(); scopes.add( new DecisionScope( @@ -1004,13 +1000,13 @@ public void testGetPropositions_timeoutError() { .thenAnswer( (Answer) invocation -> { - failWithOptimizeError( + Optimize.failWithOptimizeError( callbackMockEvent, AEPOptimizeError.Companion.getTimeoutError()); return null; }); - getPropositions(scopes, timeoutMillis, callbackMock); + Optimize.getPropositions(scopes, timeoutSeconds, callbackMock); ArgumentCaptor errorCaptor = ArgumentCaptor.forClass(AEPOptimizeError.class); verify(callbackMockEvent, times(1)).fail(errorCaptor.capture()); From 208ec6c1b8a003bb3ab701eb7e6248095d9b94a3 Mon Sep 17 00:00:00 2001 From: saquib-adobe Date: Thu, 28 Nov 2024 15:31:11 +0530 Subject: [PATCH 35/37] spotless and checkStyle check failure fixes --- .../com/adobe/marketing/mobile/optimize/Optimize.java | 8 +++++--- .../marketing/mobile/optimize/OptimizeConstants.java | 1 + .../adobe/marketing/mobile/optimize/OptimizeTests.java | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) 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 f9e28dbd..ae536b6c 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 @@ -89,7 +89,8 @@ public static void updatePropositions( @Nullable final Map xdm, @Nullable final Map data, @Nullable final AdobeCallback> callback) { - final double defaultTimeoutSeconds = OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT; + final double defaultTimeoutSeconds = + OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT; updatePropositionsInternal(decisionScopes, xdm, data, defaultTimeoutSeconds, callback); } @@ -177,7 +178,8 @@ private static void updatePropositionsInternal( eventData.put(OptimizeConstants.EventDataKeys.DATA, data); } - long timeoutMillis = (long) (timeoutSeconds * 1000); + long timeoutMillis = (long) (timeoutSeconds * OptimizeConstants.TIMEOUT_CONVERSION_FACTOR); + eventData.put(OptimizeConstants.EventDataKeys.TIMEOUT, timeoutMillis); final Event event = @@ -348,7 +350,7 @@ private static void getPropositionsInternal( .setEventData(eventData) .build(); - long timeoutMillis = (long) (timeoutSeconds * 1000); + long timeoutMillis = (long) (timeoutSeconds * OptimizeConstants.TIMEOUT_CONVERSION_FACTOR); MobileCore.dispatchEventWithResponseCallback( event, 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 c9933f62..d58f4f37 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 @@ -18,6 +18,7 @@ class OptimizeConstants { static final String FRIENDLY_NAME = "Optimize"; static final double GET_RESPONSE_CALLBACK_TIMEOUT = 10; static final double EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT = 10; + static final long TIMEOUT_CONVERSION_FACTOR = 1000; static final String ACTIVITY_ID = "activityId"; static final String XDM_ACTIVITY_ID = "xdm:activityId"; 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 90fc3476..c26255c1 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 @@ -10,6 +10,7 @@ */ package com.adobe.marketing.mobile.optimize; + import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; From 27517bdb4a1fb3bb340d1ee609dab6cd7a27116c Mon Sep 17 00:00:00 2001 From: Ishita Gambhir Date: Mon, 2 Dec 2024 11:32:18 +0530 Subject: [PATCH 36/37] add separate cache for preview propositions (#113) * add separate cache for preview propositions * add tests * run spotlessApply * add functionality to clear preview cache * address PR comments --- .../mobile/optimize/OptimizeConstants.java | 1 + .../mobile/optimize/OptimizeExtension.java | 135 ++++++- .../mobile/optimize/OptimizeUtils.java | 12 + .../optimize/OptimizeExtensionTests.java | 360 ++++++++++++++++++ ...RESPONSE_PROPOSITION_WITH_EMPTY_OFFER.json | 20 + .../resources/json/PROPOSITION_VALID_B.json | 27 ++ 6 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 code/optimize/src/test/resources/json/EVENT_DATA_EDGE_RESPONSE_PROPOSITION_WITH_EMPTY_OFFER.json create mode 100644 code/optimize/src/test/resources/json/PROPOSITION_VALID_B.json 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 b410331f..bb5a598a 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 @@ -64,6 +64,7 @@ static final class EventSource { 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"; + static final String DEBUG = "com.adobe.eventSource.debug"; private EventSource() {} } 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 8147e125..174a6345 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 @@ -16,6 +16,7 @@ import com.adobe.marketing.mobile.AdobeCallbackWithError; import com.adobe.marketing.mobile.AdobeError; import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.EventType; import com.adobe.marketing.mobile.Extension; import com.adobe.marketing.mobile.ExtensionApi; import com.adobe.marketing.mobile.MobileCore; @@ -44,6 +45,10 @@ class OptimizeExtension extends Extension { // This is accessed from multiple threads. private Map cachedPropositions = new ConcurrentHashMap<>(); + // Concurrent Map containing propositions simulated for preview and cached in-memory in the SDK + private Map previewCachedPropositions = + new ConcurrentHashMap<>(); + // 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 @@ -125,7 +130,8 @@ public boolean doWork(final Event event) { * 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} + * OptimizeConstants.EventSource#CONTENT_COMPLETE} Listener for {@code Event} type {@value + * EventType#SYSTEM} and source {@value OptimizeConstants.EventSource#DEBUG} * * * @param extensionApi {@link ExtensionApi} instance. @@ -168,6 +174,11 @@ protected void onRegistered() { OptimizeConstants.EventSource.CONTENT_COMPLETE, this::handleUpdatePropositionsCompleted); + getApi().registerEventListener( + EventType.SYSTEM, + OptimizeConstants.EventSource.DEBUG, + this::handleDebugEvent); + eventsDispatcher.start(); } @@ -854,8 +865,25 @@ void handleGetPropositions(@NonNull final Event event) { } } + final List> previewPropositionsList = new ArrayList<>(); + for (final DecisionScope scope : validScopes) { + if (previewCachedPropositions.containsKey(scope)) { + final OptimizeProposition optimizeProposition = + previewCachedPropositions.get(scope); + previewPropositionsList.add(optimizeProposition.toEventData()); + } + } + final Map responseEventData = new HashMap<>(); - responseEventData.put(OptimizeConstants.EventDataKeys.PROPOSITIONS, propositionsList); + + if (!previewPropositionsList.isEmpty()) { + Log.debug(OptimizeConstants.LOG_TAG, SELF_TAG, "Preview Mode is enabled."); + responseEventData.put( + OptimizeConstants.EventDataKeys.PROPOSITIONS, previewPropositionsList); + } else { + responseEventData.put( + OptimizeConstants.EventDataKeys.PROPOSITIONS, propositionsList); + } final Event responseEvent = new Event.Builder( @@ -964,6 +992,98 @@ void handleTrackPropositions(@NonNull final Event event) { */ void handleClearPropositions(@NonNull final Event event) { cachedPropositions.clear(); + previewCachedPropositions.clear(); + } + + /** + * Handles the event with type {@value EventType#SYSTEM} and source {@value + * OptimizeConstants.EventSource#DEBUG}. + * + *

A debug event allows the optimize extension to processes non-production workflows. + * + * @param event the debug {@link Event} to be handled. + */ + void handleDebugEvent(@NonNull final Event event) { + try { + if (OptimizeUtils.isNullOrEmpty(event.getEventData())) { + Log.debug( + OptimizeConstants.LOG_TAG, + SELF_TAG, + "handleDebugEvent - Ignoring the Optimize Debug event, either event is null" + + " or event data is null/ empty."); + return; + } + + if (!OptimizeUtils.isDebugEvent(event)) { + Log.debug( + OptimizeConstants.LOG_TAG, + SELF_TAG, + "handleDebugEvent - Ignoring Optimize Debug event, either handle type is" + + " not com.adobe.eventType.system or source is not" + + " com.adobe.eventSource.debug"); + return; + } + + final Map eventData = event.getEventData(); + + final List> payload = + DataReader.getTypedListOfMap( + Object.class, eventData, OptimizeConstants.Edge.PAYLOAD); + if (OptimizeUtils.isNullOrEmpty(payload)) { + Log.debug( + OptimizeConstants.LOG_TAG, + SELF_TAG, + "handleDebugEvent - Cannot process the Debug event, propositions list is" + + " either null or empty in the response."); + return; + } + + final Map propositionsMap = new HashMap<>(); + for (final Map propositionData : payload) { + final OptimizeProposition optimizeProposition = + OptimizeProposition.fromEventData(propositionData); + if (optimizeProposition != null + && !OptimizeUtils.isNullOrEmpty(optimizeProposition.getOffers())) { + final DecisionScope scope = new DecisionScope(optimizeProposition.getScope()); + propositionsMap.put(scope, optimizeProposition); + } + } + + if (OptimizeUtils.isNullOrEmpty(propositionsMap)) { + Log.debug( + OptimizeConstants.LOG_TAG, + SELF_TAG, + "handleDebugEvent - Cannot process the Debug event, no propositions with" + + " valid offers are present in the response."); + return; + } + + previewCachedPropositions.putAll(propositionsMap); + + final List> propositionsList = new ArrayList<>(); + for (final OptimizeProposition optimizeProposition : propositionsMap.values()) { + propositionsList.add(optimizeProposition.toEventData()); + } + final Map notificationData = new HashMap<>(); + notificationData.put(OptimizeConstants.EventDataKeys.PROPOSITIONS, propositionsList); + + final Event notificationEvent = + new Event.Builder( + OptimizeConstants.EventNames.OPTIMIZE_NOTIFICATION, + OptimizeConstants.EventType.OPTIMIZE, + OptimizeConstants.EventSource.NOTIFICATION) + .setEventData(notificationData) + .build(); + + // Dispatch notification event + getApi().dispatch(notificationEvent); + } catch (final Exception e) { + Log.warning( + OptimizeConstants.LOG_TAG, + SELF_TAG, + "handleDebugEvent - Cannot process the Debug event due to an exception (%s)!", + e.getLocalizedMessage()); + } } /** @@ -1067,6 +1187,17 @@ void setCachedPropositions(final Map cachedP this.cachedPropositions = cachedPropositions; } + @VisibleForTesting + Map getPreviewCachedPropositions() { + return previewCachedPropositions; + } + + @VisibleForTesting + void setPreviewCachedPropositions( + final Map previewCachedPropositions) { + this.previewCachedPropositions = previewCachedPropositions; + } + @VisibleForTesting Map getPropositionsInProgress() { return propositionsInProgress; 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 505e0214..a24e0d66 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 @@ -14,6 +14,7 @@ import android.util.Base64; import com.adobe.marketing.mobile.AdobeError; import com.adobe.marketing.mobile.Event; +import com.adobe.marketing.mobile.EventType; import com.adobe.marketing.mobile.services.Log; import com.adobe.marketing.mobile.util.DataReader; import java.util.Collection; @@ -153,6 +154,17 @@ static boolean isEdgeErrorResponseContent(final Event event) { event.getSource()); } + /** + * Checks whether the given event is a Debug Event returned from the Edge network. + * + * @param event instance of {@link Event} + * @return {@code boolean} return true if event is a debug event, false otherwise. + */ + static boolean isDebugEvent(final Event event) { + return EventType.SYSTEM.equalsIgnoreCase(event.getType()) + && OptimizeConstants.EventSource.DEBUG.equalsIgnoreCase(event.getSource()); + } + /** * Checks whether the given event is an Optimize request content event for retrieving cached * propositions. 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 11b8a65c..9ef0a8b9 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 @@ -12,6 +12,7 @@ package com.adobe.marketing.mobile.optimize; 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; @@ -27,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -43,6 +45,8 @@ @SuppressWarnings("unchecked") public class OptimizeExtensionTests { private OptimizeExtension extension; + private Map responseMap; + private AdobeError responseError; // Mocks @Mock ExtensionApi mockExtensionApi; @@ -57,6 +61,12 @@ public void setup() { Mockito.clearInvocations(mockExtensionApi); } + @After + public void teardown() { + responseMap = null; + responseError = null; + } + @Test public void test_getName() { // test @@ -2222,6 +2232,356 @@ public void testHandleUpdatePropositionsComplete_missingRequestEventIdInData() Assert.assertEquals(1, extension.getUpdateRequestEventIdsInProgress().size()); } + @Test + public void testHandleDebugEvent_validProposition() throws Exception { + // 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 (Spoof)", + "com.adobe.eventType.system", + "com.adobe.eventSource.debug") + .setEventData(edgeResponseData) + .build(); + + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + // test + extension.handleDebugEvent(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.notification", dispatchedEvent.getSource()); + + final List> propositionsList = + (List>) dispatchedEvent.getEventData().get("propositions"); + Assert.assertNotNull(propositionsList); + Assert.assertEquals(1, propositionsList.size()); + + final Map propositionsData = propositionsList.get(0); + Assert.assertNotNull(propositionsData); + final OptimizeProposition optimizeProposition = + OptimizeProposition.fromEventData(propositionsData); + Assert.assertNotNull(optimizeProposition); + + // for debug events propositions should be cached + // in the preview cache and not in the main cache + Assert.assertEquals(0, extension.getCachedPropositions().size()); + Assert.assertEquals(1, extension.getPreviewCachedPropositions().size()); + + Assert.assertEquals("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", optimizeProposition.getId()); + + Map.Entry cachedPropositionEntry = + extension.getPreviewCachedPropositions().entrySet().iterator().next(); + Assert.assertEquals(optimizeProposition.getId(), cachedPropositionEntry.getValue().getId()); + + Assert.assertEquals( + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==", + optimizeProposition.getScope()); + Assert.assertTrue(optimizeProposition.getScopeDetails().isEmpty()); + Assert.assertEquals(1, optimizeProposition.getOffers().size()); + + final Offer offer = optimizeProposition.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.assertEquals(1, offer.getCharacteristics().size()); + Assert.assertEquals("true", offer.getCharacteristics().get("testing")); + Assert.assertNull(offer.getLanguage()); + } + + @Test + public void testHandleDebugEvent_getPropositionsForMultipleScopes() throws Exception { + // setup + final Map testPropositionDataA = + new ObjectMapper() + .readValue( + getClass() + .getClassLoader() + .getResource("json/PROPOSITION_VALID.json"), + HashMap.class); + final OptimizeProposition testOptimizePropositionA = + OptimizeProposition.fromEventData(testPropositionDataA); + Assert.assertNotNull(testOptimizePropositionA); + final Map cachedPropositions = new HashMap<>(); + cachedPropositions.put( + new DecisionScope(testOptimizePropositionA.getScope()), testOptimizePropositionA); + + final Map testPropositionDataB = + new ObjectMapper() + .readValue( + getClass() + .getClassLoader() + .getResource("json/PROPOSITION_VALID_B.json"), + HashMap.class); + final OptimizeProposition testOptimizePropositionB = + OptimizeProposition.fromEventData(testPropositionDataB); + Assert.assertNotNull(testOptimizePropositionB); + cachedPropositions.put( + new DecisionScope(testOptimizePropositionB.getScope()), testOptimizePropositionB); + + extension.setCachedPropositions(cachedPropositions); + + // send a debug event with data of Decision Scope B + 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 (Spoof)", + "com.adobe.eventType.system", + "com.adobe.eventSource.debug") + .setEventData(edgeResponseData) + .build(); + + final ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(Event.class); + + // test + extension.handleDebugEvent(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.notification", dispatchedEvent.getSource()); + + final List> propositionsList = + (List>) dispatchedEvent.getEventData().get("propositions"); + Assert.assertNotNull(propositionsList); + Assert.assertEquals(1, propositionsList.size()); + + final Map propositionsData = propositionsList.get(0); + Assert.assertNotNull(propositionsData); + final OptimizeProposition optimizeProposition = + OptimizeProposition.fromEventData(propositionsData); + Assert.assertNotNull(optimizeProposition); + + // decision scopes A and B should be cached in normal cache + Assert.assertEquals(2, extension.getCachedPropositions().size()); + + // decision scope B should be cached in preview cache + Assert.assertEquals(1, extension.getPreviewCachedPropositions().size()); + Map.Entry previewCachedPropositionEntry = + extension.getPreviewCachedPropositions().entrySet().iterator().next(); + Assert.assertEquals( + "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + previewCachedPropositionEntry.getValue().getId()); + Assert.assertEquals( + "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==", + previewCachedPropositionEntry.getValue().getScope()); + } + + @Test + public void testHandleDebugEvent_InvalidEventType() 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 (Spoof)", + "com.adobe.eventType.edge", + "com.adobe.eventSource.debug") + .setEventData(edgeResponseData) + .build(); + + // test + extension.handleDebugEvent(testEvent); + + // verify + logMockedStatic.verify( + () -> + Log.debug( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.any())); + } + } + + @Test + public void testHandleDebugEvent_InvalidEventSource() 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 (Spoof)", + "com.adobe.eventType.system", + "com.adobe.eventSource.personalization:decisions") + .setEventData(edgeResponseData) + .build(); + + // test + extension.handleDebugEvent(testEvent); + + // verify + logMockedStatic.verify( + () -> + Log.debug( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.any())); + } + } + + @Test + public void testHandleDebugEvent_emptyPayload() throws Exception { + 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); + + final Event testEvent = + new Event.Builder( + "AEP Response Event Handle (Spoof)", + "com.adobe.eventType.system", + "com.adobe.eventSource.debug") + .setEventData(edgeResponseData) + .build(); + + // test + extension.handleDebugEvent(testEvent); + + // verify + logMockedStatic.verify( + () -> + Log.debug( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString())); + } + } + + @Test + public void testHandleDebugEvent_emptyProposition() throws Exception { + try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { + + final Map edgeResponseData = + new ObjectMapper() + .readValue( + getClass() + .getClassLoader() + .getResource( + "json/EVENT_DATA_EDGE_RESPONSE_PROPOSITION_WITH_EMPTY_OFFER.json"), + HashMap.class); + + final Event testEvent = + new Event.Builder( + "Test Event", + "com.adobe.eventType.edge", + "com.adobe.eventSource.responseContent") + .setEventData(edgeResponseData) + .build(); + + // test + extension.handleDebugEvent(testEvent); + + // verify + logMockedStatic.verify( + () -> + Log.debug( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.any())); + } + } + + @Test + public void testDebugEvent_nullEventData() { + try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { + + // setup + final Event testEvent = + new Event.Builder( + "AEP Response Event Handle (Spoof)", + "com.adobe.eventType.system", + "com.adobe.eventSource.debug") + .setEventData(null) + .build(); + + // test + extension.handleDebugEvent(testEvent); + + // verify + logMockedStatic.verify( + () -> + Log.debug( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.any())); + } + } + + @Test + public void testDebugEvent_emptyEventData() { + try (MockedStatic logMockedStatic = Mockito.mockStatic(Log.class)) { + + // setup + final Event testEvent = + new Event.Builder( + "AEP Response Event Handle (Spoof)", + "com.adobe.eventType.system", + "com.adobe.eventSource.debug") + .setEventData(new HashMap<>()) + .build(); + + // test + extension.handleDebugEvent(testEvent); + + // verify + logMockedStatic.verify( + () -> + Log.debug( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.any())); + } + } + // Helper methods private void setConfigurationSharedState( final SharedStateStatus status, final Map data) { diff --git a/code/optimize/src/test/resources/json/EVENT_DATA_EDGE_RESPONSE_PROPOSITION_WITH_EMPTY_OFFER.json b/code/optimize/src/test/resources/json/EVENT_DATA_EDGE_RESPONSE_PROPOSITION_WITH_EMPTY_OFFER.json new file mode 100644 index 00000000..23c3197d --- /dev/null +++ b/code/optimize/src/test/resources/json/EVENT_DATA_EDGE_RESPONSE_PROPOSITION_WITH_EMPTY_OFFER.json @@ -0,0 +1,20 @@ +{ + "payload": [ + { + "id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "scope": "eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==", + "activity": { + "etag": "8", + "id": "xcore:offer-activity:1111111111111111" + }, + "placement": { + "etag": "1", + "id": "xcore:offer-placement:1111111111111111" + }, + "items": [] + } + ], + "requestEventId": "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA", + "requestId": "BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB", + "type": "personalization:decisions" +} \ No newline at end of file diff --git a/code/optimize/src/test/resources/json/PROPOSITION_VALID_B.json b/code/optimize/src/test/resources/json/PROPOSITION_VALID_B.json new file mode 100644 index 00000000..ef83c097 --- /dev/null +++ b/code/optimize/src/test/resources/json/PROPOSITION_VALID_B.json @@ -0,0 +1,27 @@ +{ + "id":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + "items":[ + { + "id":"xcore:personalized-offer:1111111111111111", + "etag":"10", + "schema":"https://ns.adobe.com/experience/offer-management/content-component-html", + "data":{ + "id":"xcore:personalized-offer:1111111111111111", + "format":"text/html", + "content":"

This is a HTML content

", + "characteristics": { + "testing": "true" + } + } + } + ], + "placement":{ + "etag":"1", + "id":"xcore:offer-placement:1111111111111111" + }, + "activity":{ + "etag":"8", + "id":"xcore:offer-activity:1111111111111111" + }, + "scope":"eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ==" +} \ No newline at end of file From 86e3d0fd279dcb9283abd7e5f8f4ae039e52068d Mon Sep 17 00:00:00 2001 From: siddique-adobe Date: Mon, 2 Dec 2024 07:43:27 +0000 Subject: [PATCH 37/37] Updating version to 3.2.2. --- code/gradle.properties | 2 +- .../adobe/marketing/mobile/optimize/OptimizeTestConstants.java | 2 +- .../com/adobe/marketing/mobile/optimize/OptimizeConstants.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/gradle.properties b/code/gradle.properties index f3545f0f..0825d0fc 100644 --- a/code/gradle.properties +++ b/code/gradle.properties @@ -16,7 +16,7 @@ org.gradle.caching=true android.useAndroidX=true moduleName=optimize -moduleVersion=3.2.0 +moduleVersion=3.2.2 #Maven artifact mavenRepoName=AdobeMobileOptimizeSdk 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 1720574f..534ed82a 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 @@ -13,7 +13,7 @@ public class OptimizeTestConstants { - static final String EXTENSION_VERSION = "3.2.0"; + static final String EXTENSION_VERSION = "3.2.2"; public static final String LOG_TAG = "OptimizeTest"; static final String CONFIG_DATA_STORE = "AdobeMobile_ConfigState"; 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 71571c49..449e2e3a 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 @@ -13,7 +13,7 @@ class OptimizeConstants { static final String LOG_TAG = "Optimize"; - static final String EXTENSION_VERSION = "3.2.0"; + static final String EXTENSION_VERSION = "3.2.2"; static final String EXTENSION_NAME = "com.adobe.optimize"; static final String FRIENDLY_NAME = "Optimize"; static final double GET_RESPONSE_CALLBACK_TIMEOUT = 10;