Skip to content

Commit

Permalink
Merge pull request #107 from siddique-adobe/MOB-22286
Browse files Browse the repository at this point in the history
[MOB-22286] Updating `updatePropositions` and `getPropositions` public APIs for dynamic/configurable timeouts
  • Loading branch information
siddique-adobe authored Nov 20, 2024
2 parents 334b393 + b9eff05 commit 24722b8
Show file tree
Hide file tree
Showing 7 changed files with 237 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ data class AEPOptimizeError(
return getAdobeErrorFromStatus(data[STATUS] as Int?)
}

@JvmStatic
fun getTimeoutError(): AEPOptimizeError {
return AEPOptimizeError(
null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private Optimize() {}
* Experience Edge network.
*
* <p>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<DecisionScope>} containing scopes for which offers need to
* be updated.
Expand All @@ -62,8 +62,8 @@ public static void updatePropositions(
@NonNull final List<DecisionScope> decisionScopes,
@Nullable final Map<String, Object> xdm,
@Nullable final Map<String, Object> data) {

updatePropositions(decisionScopes, xdm, data, null);
final long defaultTimeout = OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT;
updatePropositions(decisionScopes, xdm, data, defaultTimeout, null);
}

/**
Expand All @@ -88,6 +88,44 @@ public static void updatePropositions(
@Nullable final Map<String, Object> xdm,
@Nullable final Map<String, Object> data,
@Nullable final AdobeCallback<Map<DecisionScope, OptimizeProposition>> 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.
*
* <p>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<DecisionScope>} containing scopes for which offers need to
* be updated.
* @param xdm {@code Map<String, Object>} containing additional XDM-formatted data to be sent in
* the personalization query request.
* @param data {@code Map<String, Object>} 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<Map<DecisionScope, OptimizeProposition>>} which will be
* invoked when decision propositions are received from the Edge network.
*/
public static void updatePropositions(
@NonNull final List<DecisionScope> decisionScopes,
@Nullable final Map<String, Object> xdm,
@Nullable final Map<String, Object> data,
final long timeoutMillis,
@Nullable final AdobeCallback<Map<DecisionScope, OptimizeProposition>> callback) {
updatePropositionsInternal(decisionScopes, xdm, data, timeoutMillis, callback);
}

private static void updatePropositionsInternal(
@NonNull final List<DecisionScope> decisionScopes,
@Nullable final Map<String, Object> xdm,
@Nullable final Map<String, Object> data,
final long timeoutMillis,
@Nullable final AdobeCallback<Map<DecisionScope, OptimizeProposition>> callback) {

if (OptimizeUtils.isNullOrEmpty(decisionScopes)) {
Log.warning(
Expand Down Expand Up @@ -137,6 +175,7 @@ public static void updatePropositions(
if (!OptimizeUtils.isNullOrEmpty(data)) {
eventData.put(OptimizeConstants.EventDataKeys.DATA, data);
}
eventData.put(OptimizeConstants.EventDataKeys.TIMEOUT, timeoutMillis);

final Event event =
new Event.Builder(
Expand All @@ -148,7 +187,7 @@ public static void updatePropositions(

MobileCore.dispatchEventWithResponseCallback(
event,
OptimizeConstants.EDGE_CONTENT_COMPLETE_RESPONSE_TIMEOUT,
timeoutMillis,
new AdobeCallbackWithError<Event>() {
@Override
public void fail(final AdobeError adobeError) {
Expand Down Expand Up @@ -236,6 +275,30 @@ public void call(final Event event) {
public static void getPropositions(
@NonNull final List<DecisionScope> decisionScopes,
@NonNull final AdobeCallback<Map<DecisionScope, OptimizeProposition>> 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<DecisionScope>} containing scopes for which offers need to
* be requested.
* @param callback {@code AdobeCallbackWithError<Map<DecisionScope, OptimizeProposition>>} which
* will be invoked when decision propositions are retrieved from the local cache.
*/
public static void getPropositions(
@NonNull final List<DecisionScope> decisionScopes,
final long timeoutMillis,
@NonNull final AdobeCallback<Map<DecisionScope, OptimizeProposition>> callback) {
getPropositionsInternal(decisionScopes, timeoutMillis, callback);
}

private static void getPropositionsInternal(
@NonNull final List<DecisionScope> decisionScopes,
final long timeoutMillis,
@NonNull final AdobeCallback<Map<DecisionScope, OptimizeProposition>> callback) {
if (OptimizeUtils.isNullOrEmpty(decisionScopes)) {
Log.warning(
OptimizeConstants.LOG_TAG,
Expand Down Expand Up @@ -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<Event>() {
@Override
public void fail(final AdobeError adobeError) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Event>() {
@Override
public void fail(final AdobeError error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@

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;

import android.util.Base64;
import com.adobe.marketing.mobile.AdobeCallbackWithError;
import com.adobe.marketing.mobile.AdobeError;
Expand Down Expand Up @@ -454,7 +460,7 @@ public void testGetPropositions_validDecisionScope() throws Exception {
new DecisionScope(
"eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="));

Optimize.getPropositions(
getPropositions(
scopes,
new AdobeCallbackWithError<Map<DecisionScope, OptimizeProposition>>() {
@Override
Expand Down Expand Up @@ -561,7 +567,7 @@ public void testGetPropositions_multipleValidDecisionScopes() {
"eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="));
scopes.add(new DecisionScope("myMbox"));

Optimize.getPropositions(
getPropositions(
scopes,
new AdobeCallbackWithError<Map<DecisionScope, OptimizeProposition>>() {
@Override
Expand Down Expand Up @@ -631,7 +637,7 @@ public void testGetPropositions_invalidDecisionScopeInList() {
new DecisionScope(
"eyJhY3Rpdml0eUlkIjoiIiwicGxhY2VtZW50SWQiOiJ4Y29yZTpvZmZlci1wbGFjZW1lbnQ6MTExMTExMTExMTExMTExMSJ9"));

Optimize.getPropositions(
getPropositions(
scopes,
new AdobeCallbackWithError<Map<DecisionScope, OptimizeProposition>>() {
@Override
Expand Down Expand Up @@ -660,7 +666,7 @@ public void call(Map<DecisionScope, OptimizeProposition> propositionsMap) {
public void testGetPropositions_emptyDecisionScopesList() {
try (MockedStatic<Log> logMockedStatic = Mockito.mockStatic(Log.class)) {
// test
Optimize.getPropositions(
getPropositions(
new ArrayList<DecisionScope>(),
new AdobeCallbackWithError<Map<DecisionScope, OptimizeProposition>>() {
@Override
Expand Down Expand Up @@ -689,7 +695,7 @@ public void call(Map<DecisionScope, OptimizeProposition> propositionsMap) {
public void testGetPropositions_nullDecisionScopesList() {
try (MockedStatic<Log> logMockedStatic = Mockito.mockStatic(Log.class)) {
// test
Optimize.getPropositions(
getPropositions(
null,
new AdobeCallbackWithError<Map<DecisionScope, OptimizeProposition>>() {
@Override
Expand Down Expand Up @@ -896,4 +902,120 @@ public void test_clearCachedPropositions() {
Assert.assertNull(event.getEventData());
}
}

@Test
public void testUpdatePropositions_timeoutError() {

long timeoutMillis = 100;
Map<String, Object> xdm = new HashMap<>();
Map<String, Object> data = new HashMap<>();
final List<DecisionScope> scopes = new ArrayList<>();
scopes.add(
new DecisionScope(
"eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="));

// Mock the callback
AdobeCallbackWithError<Map<DecisionScope, OptimizeProposition>> callbackMock =
Mockito.mock(AdobeCallbackWithError.class);

AdobeCallbackWithOptimizeError<Event> callbackMockEvent =
Mockito.mock(AdobeCallbackWithOptimizeError.class);

try (MockedStatic<MobileCore> mobileCoreMockedStatic =
Mockito.mockStatic(MobileCore.class);
MockedStatic<Base64> base64MockedStatic = Mockito.mockStatic(Base64.class)) {

base64MockedStatic
.when(
() ->
Base64.decode(
ArgumentMatchers.anyString(),
ArgumentMatchers.anyInt()))
.thenAnswer(
(Answer<byte[]>)
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<Void>)
invocation -> {
failWithOptimizeError(
callbackMockEvent,
AEPOptimizeError.Companion.getTimeoutError());
return null;
});

updatePropositions(scopes, xdm, data, timeoutMillis, callbackMock);
ArgumentCaptor<AEPOptimizeError> errorCaptor =
ArgumentCaptor.forClass(AEPOptimizeError.class);
verify(callbackMockEvent, times(1)).fail(errorCaptor.capture());
Assert.assertEquals(
AEPOptimizeError.Companion.getTimeoutError(), errorCaptor.getValue());
}
}

@Test
public void testGetPropositions_timeoutError() {

long timeoutMillis = 100;
final List<DecisionScope> scopes = new ArrayList<>();
scopes.add(
new DecisionScope(
"eyJhY3Rpdml0eUlkIjoieGNvcmU6b2ZmZXItYWN0aXZpdHk6MTExMTExMTExMTExMTExMSIsInBsYWNlbWVudElkIjoieGNvcmU6b2ZmZXItcGxhY2VtZW50OjExMTExMTExMTExMTExMTEifQ=="));

// Mock the callback
AdobeCallbackWithError<Map<DecisionScope, OptimizeProposition>> callbackMock =
Mockito.mock(AdobeCallbackWithError.class);

AdobeCallbackWithOptimizeError<Event> callbackMockEvent =
Mockito.mock(AdobeCallbackWithOptimizeError.class);

try (MockedStatic<MobileCore> mobileCoreMockedStatic =
Mockito.mockStatic(MobileCore.class);
MockedStatic<Base64> base64MockedStatic = Mockito.mockStatic(Base64.class)) {

base64MockedStatic
.when(
() ->
Base64.decode(
ArgumentMatchers.anyString(),
ArgumentMatchers.anyInt()))
.thenAnswer(
(Answer<byte[]>)
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<Void>)
invocation -> {
failWithOptimizeError(
callbackMockEvent,
AEPOptimizeError.Companion.getTimeoutError());
return null;
});

getPropositions(scopes, timeoutMillis, callbackMock);
ArgumentCaptor<AEPOptimizeError> errorCaptor =
ArgumentCaptor.forClass(AEPOptimizeError.class);
verify(callbackMockEvent, times(1)).fail(errorCaptor.capture());
Assert.assertEquals(
AEPOptimizeError.Companion.getTimeoutError(), errorCaptor.getValue());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ fun OffersView(viewModel: MainViewModel) {
viewModel.updatePropositions(
decisionScopes = decisionScopeList,
xdm = mapOf(Pair("xdmKey", "1234")),
data = data
data = data,
timeout = 200
)
}) {
Text(
Expand All @@ -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",
Expand Down
Loading

0 comments on commit 24722b8

Please sign in to comment.