Skip to content

Commit 2b0dc02

Browse files
authored
feat(ATS): update OptimizelyManager/OptimizelyClient for ODP integration (#444)
Extends top level (OptimizelyManager/OptimizelyClient) to provide public APIs and connect to the core ATS features provided by the integrated Java-SDK. Add new APIs: - OptimizelyClient.createUserContext() without userId - OptimizelyClient.sendOdpEvent() ODP Configuration (OptimizelyManager.Builder): - SegmentCache configuration - ODPClient timeout configuration - Disable ODP
1 parent ceab50f commit 2b0dc02

File tree

15 files changed

+684
-44
lines changed

15 files changed

+684
-44
lines changed

android-sdk/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2016-2020, Optimizely, Inc. and contributors *
2+
* Copyright 2016-2020, 2023, Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -55,6 +55,7 @@ dependencies {
5555
api project(':datafile-handler')
5656
api project(':event-handler')
5757
api project(':user-profile')
58+
api project(':odp')
5859
api ("com.optimizely.ab:core-api:$java_core_ver") {
5960
exclude group: 'com.google.code.findbugs'
6061
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/****************************************************************************
2+
* Copyright 2023, Optimizely, Inc. and contributors *
3+
* *
4+
* Licensed under the Apache License, Version 2.0 (the "License"); *
5+
* you may not use this file except in compliance with the License. *
6+
* You may obtain a copy of the License at *
7+
* *
8+
* http://www.apache.org/licenses/LICENSE-2.0 *
9+
* *
10+
* Unless required by applicable law or agreed to in writing, software *
11+
* distributed under the License is distributed on an "AS IS" BASIS, *
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
13+
* See the License for the specific language governing permissions and *
14+
* limitations under the License. *
15+
***************************************************************************/
16+
17+
package com.optimizely.ab.android.sdk;
18+
19+
import android.content.Context;
20+
import android.content.Intent;
21+
import android.content.pm.PackageInfo;
22+
import android.content.pm.PackageManager;
23+
import android.os.Build;
24+
25+
import androidx.test.ext.junit.runners.AndroidJUnit4;
26+
import androidx.test.filters.SdkSuppress;
27+
import androidx.test.platform.app.InstrumentationRegistry;
28+
29+
import com.optimizely.ab.Optimizely;
30+
import com.optimizely.ab.android.datafile_handler.DatafileHandler;
31+
import com.optimizely.ab.android.datafile_handler.DatafileLoadedListener;
32+
import com.optimizely.ab.android.datafile_handler.DefaultDatafileHandler;
33+
import com.optimizely.ab.android.event_handler.DefaultEventHandler;
34+
import com.optimizely.ab.android.shared.DatafileConfig;
35+
import com.optimizely.ab.android.user_profile.DefaultUserProfileService;
36+
import com.optimizely.ab.bucketing.UserProfileService;
37+
import com.optimizely.ab.config.DatafileProjectConfig;
38+
import com.optimizely.ab.config.ProjectConfig;
39+
import com.optimizely.ab.config.Variation;
40+
import com.optimizely.ab.config.parser.ConfigParseException;
41+
import com.optimizely.ab.event.EventHandler;
42+
import com.optimizely.ab.event.EventProcessor;
43+
import com.optimizely.ab.event.internal.UserEvent;
44+
import com.optimizely.ab.notification.NotificationCenter;
45+
import com.optimizely.ab.notification.UpdateConfigNotification;
46+
import com.optimizely.ab.odp.ODPEventManager;
47+
import com.optimizely.ab.odp.ODPManager;
48+
49+
import org.junit.Before;
50+
import org.junit.Test;
51+
import org.junit.runner.RunWith;
52+
import org.mockito.ArgumentCaptor;
53+
import org.mockito.invocation.InvocationOnMock;
54+
import org.mockito.stubbing.Answer;
55+
import org.slf4j.Logger;
56+
57+
import java.util.Collections;
58+
import java.util.Set;
59+
import java.util.concurrent.CountDownLatch;
60+
import java.util.concurrent.ExecutorService;
61+
import java.util.concurrent.Executors;
62+
import java.util.concurrent.TimeUnit;
63+
64+
import static junit.framework.Assert.assertEquals;
65+
import static junit.framework.Assert.assertFalse;
66+
import static junit.framework.Assert.assertNotNull;
67+
import static junit.framework.Assert.assertNull;
68+
import static junit.framework.Assert.assertTrue;
69+
import static junit.framework.Assert.fail;
70+
import static org.mockito.Matchers.any;
71+
import static org.mockito.Matchers.anyString;
72+
import static org.mockito.Matchers.eq;
73+
import static org.mockito.Mockito.doAnswer;
74+
import static org.mockito.Mockito.mock;
75+
import static org.mockito.Mockito.spy;
76+
import static org.mockito.Mockito.times;
77+
import static org.mockito.Mockito.verify;
78+
import static org.mockito.Mockito.when;
79+
80+
/**
81+
* Tests for Optimizely ODP Integration
82+
*/
83+
@RunWith(AndroidJUnit4.class)
84+
public class ODPIntegrationTest {
85+
86+
private OptimizelyManager optimizelyManager;
87+
private ODPManager odpManager;
88+
private DefaultDatafileHandler datafileHandler;
89+
private NotificationCenter notificationCenter;
90+
private Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
91+
private String testSdkKey = "12345";
92+
93+
private String emptyV4Core =
94+
"\"version\": \"4\"," +
95+
"\"rollouts\": []," +
96+
"\"anonymizeIP\": true," +
97+
"\"projectId\": \"10431130345\"," +
98+
"\"variables\": []," +
99+
"\"featureFlags\": []," +
100+
"\"experiments\": []," +
101+
"\"audiences\": []," +
102+
"\"groups\": []," +
103+
"\"attributes\": []," +
104+
"\"accountId\": \"10367498574\"," +
105+
"\"events\": []," +
106+
"\"revision\": \"100\",";
107+
108+
String integration1 = "\"integrations\":[{\"key\":\"odp\",\"host\":\"h-1\",\"publicKey\":\"p-1\"}]";
109+
String integration2 = "\"integrations\":[{\"key\":\"odp\",\"host\":\"h-2\",\"publicKey\":\"p-2\"}]";
110+
String odpDatafile1 = "{" + emptyV4Core + integration1 + "}";
111+
String odpDatafile2 = "{" + emptyV4Core + integration2 + "}";
112+
113+
@Before
114+
public void setup() throws Exception {
115+
odpManager = mock(ODPManager.class);
116+
when(odpManager.getEventManager()).thenReturn(mock(ODPEventManager.class));
117+
118+
datafileHandler = new DefaultDatafileHandler();
119+
notificationCenter = new NotificationCenter();
120+
121+
optimizelyManager = new OptimizelyManager(
122+
null,
123+
testSdkKey,
124+
null,
125+
mock(Logger.class),
126+
3600L,
127+
datafileHandler,
128+
null,
129+
3600L,
130+
mock(DefaultEventHandler.class),
131+
mock(EventProcessor.class),
132+
null,
133+
notificationCenter,
134+
null,
135+
odpManager,
136+
null);
137+
}
138+
139+
@Test
140+
public void initializeSynchronous_updateODPConfig() {
141+
// NOTE: odpConfig is updated when Optimizely.java (java-sdk core) is initialized.
142+
// Same for async-initialization, so need to repeat the same test (hard to test for async-init).
143+
144+
optimizelyManager.initialize(context, odpDatafile1);
145+
verify(odpManager, times(1)).updateSettings(
146+
eq("h-1"),
147+
eq("p-1"),
148+
eq(Collections.emptySet()));
149+
150+
// validate no other calls
151+
152+
verify(odpManager, times(1)).updateSettings(
153+
anyString(),
154+
anyString(),
155+
any(Set.class));
156+
}
157+
158+
@Test
159+
public void updateODPConfigWhenDatafileUpdatedByBackgroundPolling() throws InterruptedException {
160+
// NOTE: same logic for async-initialization, so no need to repeat for async
161+
162+
boolean updateConfigOnBackgroundDatafile = true;
163+
optimizelyManager.initialize(context, odpDatafile1, true, updateConfigOnBackgroundDatafile);
164+
165+
// datafile will be saved when a new datafile is downloaded by background polling
166+
datafileHandler.saveDatafile(context, new DatafileConfig(null, testSdkKey, null), odpDatafile2);
167+
Thread.sleep(1000); // need a delay for file-observer (update notification)
168+
169+
// odpConfig updated on initialization
170+
171+
verify(odpManager, times(1)).updateSettings(
172+
eq("h-1"),
173+
eq("p-1"),
174+
eq(Collections.emptySet()));
175+
176+
// odpConfig updated on background polling
177+
178+
verify(odpManager, times(1)).updateSettings(
179+
eq("h-2"),
180+
eq("p-2"),
181+
eq(Collections.emptySet()));
182+
183+
// no other calls
184+
185+
verify(odpManager, times(2)).updateSettings(
186+
anyString(),
187+
anyString(),
188+
any(Set.class));
189+
}
190+
191+
}

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyClientTest.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2017-2021, Optimizely, Inc. and contributors *
2+
* Copyright 2017-2021, 2023 Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -2197,6 +2197,40 @@ public void testCreateUserContext_withAttributes() {
21972197
assertEquals(userContext.getAttributes(), attributes);
21982198
}
21992199

2200+
@Test
2201+
public void testCreateUserContext_withVuid() {
2202+
String vuid = "test-vuid";
2203+
OptimizelyClient optimizelyClient = new OptimizelyClient(optimizely, logger, vuid);
2204+
OptimizelyUserContext userContext = optimizelyClient.createUserContext();
2205+
assertEquals(userContext.getUserId(), "test-vuid");
2206+
assert(userContext.getAttributes().isEmpty());
2207+
}
2208+
2209+
@Test
2210+
public void testCreateUserContext_withVuid_withAttributes() {
2211+
String vuid = "test-vuid";
2212+
Map<String, Object> attributes = Collections.singletonMap("house", "Gryffindor");
2213+
2214+
OptimizelyClient optimizelyClient = new OptimizelyClient(optimizely, logger, vuid);
2215+
OptimizelyUserContext userContext = optimizelyClient.createUserContext(attributes);
2216+
assertEquals(userContext.getUserId(), "test-vuid");
2217+
assertEquals(userContext.getAttributes(), attributes);
2218+
}
2219+
2220+
@Test
2221+
public void testVuidRegister() {
2222+
Optimizely mockOptimizely = mock(Optimizely.class);
2223+
when(mockOptimizely.isValid()).thenReturn(true);
2224+
2225+
OptimizelyClient optimizelyClient = new OptimizelyClient(mockOptimizely, logger, "any-vuid");
2226+
2227+
verify(mockOptimizely).sendODPEvent(
2228+
null,
2229+
"client_initialized",
2230+
null,
2231+
null);
2232+
}
2233+
22002234
@Test
22012235
// this should be enough to validate connection to the core java-sdk
22022236
public void testDecide() {

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyDefaultAttributesTest.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2017, Optimizely, Inc. and contributors *
2+
* Copyright 2017, 2023 Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -63,4 +63,18 @@ public void buildDefaultAttributesMap() throws Exception {
6363
assertEquals(defaultAttributes.size(), 4);
6464
}
6565

66-
}
66+
@Test
67+
public void buildODPCommonData() throws Exception {
68+
Context context = mock(Context.class);
69+
Map<String, Object> commonData = OptimizelyDefaultAttributes.buildODPCommonData(context, logger);
70+
71+
assertEquals(commonData.size(), 4);
72+
73+
assertEquals(commonData.get("os"), "Android");
74+
assertEquals(commonData.get("device_type"), "Phone");
75+
assertTrue(commonData.get("os_version").toString().length() >= 1);
76+
assertTrue(commonData.get("model").toString().length() > 2);
77+
}
78+
79+
}
80+

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerBuilderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2017, Optimizely, Inc. and contributors *
2+
* Copyright 2017, 2023 Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *

android-sdk/src/androidTest/java/com/optimizely/ab/android/sdk/OptimizelyManagerTest.java

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/****************************************************************************
2-
* Copyright 2017-2021, Optimizely, Inc. and contributors *
2+
* Copyright 2017-2021, 2023 Optimizely, Inc. and contributors *
33
* *
44
* Licensed under the Apache License, Version 2.0 (the "License"); *
55
* you may not use this file except in compliance with the License. *
@@ -163,7 +163,7 @@ public void initializeSyncWithEnvironment() {
163163
EventHandler eventHandler = mock(DefaultEventHandler.class);
164164
EventProcessor eventProcessor = mock(EventProcessor.class);
165165
OptimizelyManager optimizelyManager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, 3600L, datafileHandler, null, 3600L,
166-
eventHandler, eventProcessor, null, null, null);
166+
eventHandler, eventProcessor, null, null, null, null, null);
167167
/*
168168
* Scenario#1: when datafile is not Empty
169169
* Scenario#2: when datafile is Empty
@@ -222,7 +222,7 @@ public void initializeAsyncWithEnvironment() {
222222
EventHandler eventHandler = mock(DefaultEventHandler.class);
223223
EventProcessor eventProcessor = mock(EventProcessor.class);
224224
final OptimizelyManager optimizelyManager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, 3600L, datafileHandler, null, 3600L,
225-
eventHandler, eventProcessor, null, null, null);
225+
eventHandler, eventProcessor, null, null, null, null, null);
226226

227227
/*
228228
* Scenario#1: when datafile is not Empty
@@ -494,7 +494,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabled() {
494494
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
495495

496496
OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
497-
null, null, null, null, null);
497+
null, null, null, null, null, null, null);
498498

499499
doAnswer(
500500
new Answer<Object>() {
@@ -527,7 +527,7 @@ public void initializeSyncWithUpdateOnNewDatafileEnabled() {
527527
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
528528

529529
OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
530-
null, null, null, null, null);
530+
null, null, null, null, null, null, null);
531531

532532
doAnswer(
533533
new Answer<Object>() {
@@ -560,7 +560,7 @@ public void initializeSyncWithDownloadToCacheDisabled() {
560560
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
561561

562562
OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
563-
null, null, null, null, null);
563+
null, null, null, null, null, null, null);
564564

565565
doAnswer(
566566
new Answer<Object>() {
@@ -593,7 +593,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabledWithPeriodicPollingEnab
593593
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
594594

595595
OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
596-
null, null, null, null, null);
596+
null, null, null, null, null, null, null);
597597

598598
doAnswer(
599599
(Answer<Object>) invocation -> {
@@ -625,7 +625,7 @@ public void initializeSyncWithUpdateOnNewDatafileEnabledWithPeriodicPollingEnabl
625625
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
626626

627627
OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
628-
null, null, null, null, null);
628+
null, null, null, null, null, null, null);
629629

630630
doAnswer(
631631
new Answer<Object>() {
@@ -658,7 +658,7 @@ public void initializeSyncWithUpdateOnNewDatafileDisabledWithPeriodicPollingDisa
658658
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
659659

660660
OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
661-
null, null, null, null, null);
661+
null, null, null, null, null, null, null);
662662

663663
doAnswer(
664664
new Answer<Object>() {
@@ -692,7 +692,7 @@ public void initializeSyncWithUpdateOnNewDatafileEnabledWithPeriodicPollingDisab
692692
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
693693

694694
OptimizelyManager manager = new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
695-
null, null, null, null, null);
695+
null, null, null, null, null, null, null);
696696

697697
doAnswer(
698698
new Answer<Object>() {
@@ -725,7 +725,7 @@ public void initializeSyncWithResourceDatafileNoCache() {
725725
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
726726

727727
OptimizelyManager manager = spy(new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
728-
null, null, null, null, null));
728+
null, null, null, null, null, null, null));
729729

730730
datafileHandler.removeSavedDatafile(context, manager.getDatafileConfig());
731731
OptimizelyClient client = manager.initialize(context, R.raw.datafile, downloadToCache, updateConfigOnNewDatafile);
@@ -742,7 +742,7 @@ public void initializeSyncWithResourceDatafileNoCacheWithDefaultParams() {
742742
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
743743

744744
OptimizelyManager manager = spy(new OptimizelyManager(testProjectId, testSdkKey, null, logger, pollingInterval, datafileHandler, null, 0,
745-
null, null, null, null, null));
745+
null, null, null, null, null, null, null));
746746

747747
datafileHandler.removeSavedDatafile(context, manager.getDatafileConfig());
748748
OptimizelyClient client = manager.initialize(context, R.raw.datafile);

0 commit comments

Comments
 (0)