Skip to content

Commit 184f426

Browse files
authored
fix(ATS): send an internal notification when project config is updated (#448)
- For ODPConfig update, send an internal notification when project config is updated (in addition to public NotificationCenter). - A few more unit test cases are added for OPD integration tests.
1 parent b724e40 commit 184f426

File tree

6 files changed

+328
-108
lines changed

6 files changed

+328
-108
lines changed

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

Lines changed: 118 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -17,49 +17,24 @@
1717
package com.optimizely.ab.android.sdk;
1818

1919
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;
2420

2521
import androidx.test.ext.junit.runners.AndroidJUnit4;
26-
import androidx.test.filters.SdkSuppress;
2722
import androidx.test.platform.app.InstrumentationRegistry;
2823

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;
24+
import com.google.gson.JsonArray;
25+
import com.google.gson.JsonObject;
26+
import com.google.gson.JsonParser;
27+
import com.optimizely.ab.OptimizelyUserContext;
28+
import com.optimizely.ab.android.odp.DefaultODPApiManager;
29+
import com.optimizely.ab.odp.ODPApiManager;
4630
import com.optimizely.ab.odp.ODPEventManager;
4731
import com.optimizely.ab.odp.ODPManager;
32+
import com.optimizely.ab.odp.ODPSegmentManager;
4833

4934
import org.junit.Before;
5035
import org.junit.Test;
5136
import org.junit.runner.RunWith;
5237
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;
6338

6439
import static junit.framework.Assert.assertEquals;
6540
import static junit.framework.Assert.assertFalse;
@@ -70,27 +45,34 @@
7045
import static org.mockito.Matchers.any;
7146
import static org.mockito.Matchers.anyString;
7247
import static org.mockito.Matchers.eq;
73-
import static org.mockito.Mockito.doAnswer;
7448
import static org.mockito.Mockito.mock;
7549
import static org.mockito.Mockito.spy;
7650
import static org.mockito.Mockito.times;
7751
import static org.mockito.Mockito.verify;
7852
import static org.mockito.Mockito.when;
7953

54+
import java.util.Arrays;
55+
import java.util.HashSet;
56+
import java.util.Set;
57+
8058
/**
8159
* Tests for Optimizely ODP Integration
8260
*/
8361
@RunWith(AndroidJUnit4.class)
8462
public class ODPIntegrationTest {
8563

8664
private OptimizelyManager optimizelyManager;
65+
private OptimizelyClient optimizelyClient;
8766
private ODPManager odpManager;
88-
private DefaultDatafileHandler datafileHandler;
89-
private NotificationCenter notificationCenter;
67+
private ODPEventManager odpEventManager;
68+
private ODPSegmentManager odpSegmentManager;
69+
private ODPApiManager odpApiManager;
9070
private Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
9171
private String testSdkKey = "12345";
72+
private String testUser = "test-user";
73+
private String testVuid = "vuid_123"; // must start with "vuid_" to be parsed properly in java-sdk core
9274

93-
private String emptyV4Core =
75+
private String odpDatafile = "{" +
9476
"\"version\": \"4\"," +
9577
"\"rollouts\": []," +
9678
"\"anonymizeIP\": true," +
@@ -103,89 +85,126 @@ public class ODPIntegrationTest {
10385
"\"attributes\": []," +
10486
"\"accountId\": \"10367498574\"," +
10587
"\"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 + "}";
88+
"\"revision\": \"100\"," +
89+
"\"typedAudiences\":[{\"id\": \"12\",\"conditions\": [\"or\",{\"value\": \"segment-1\",\"type\": \"third_party_dimension\",\"name\": \"odp.audiences\",\"match\": \"qualified\"}],\"name\": \"audience-1\"}]," +
90+
"\"integrations\":[{\"key\":\"odp\",\"host\":\"h-1\",\"publicKey\":\"p-1\"}]" +
91+
"}";
11292

11393
@Before
11494
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);
95+
odpApiManager = mock(DefaultODPApiManager.class);
96+
when(odpApiManager.sendEvents(anyString(), anyString(), anyString())).thenReturn(200); // return success, otherwise retried 3 times.
97+
98+
odpEventManager = new ODPEventManager(odpApiManager);
99+
odpSegmentManager = new ODPSegmentManager(odpApiManager);
100+
101+
optimizelyManager = OptimizelyManager.builder()
102+
.withSDKKey(testSdkKey)
103+
.withVuid(testVuid)
104+
.withODPEventManager(odpEventManager)
105+
.withODPSegmentManager(odpSegmentManager)
106+
.build(context);
107+
108+
optimizelyManager.initialize(context, odpDatafile);
109+
optimizelyClient = optimizelyManager.getOptimizely();
137110
}
138111

139112
@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).
113+
public void identifyOdpEventSentWhenUserContextCreated() throws InterruptedException {
114+
optimizelyClient.createUserContext(testUser);
143115

144-
optimizelyManager.initialize(context, odpDatafile1);
145-
verify(odpManager, times(1)).updateSettings(
146-
eq("h-1"),
147-
eq("p-1"),
148-
eq(Collections.emptySet()));
116+
Thread.sleep(2000); // wait for batch timeout (1sec)
117+
118+
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
119+
verify(odpApiManager, times(1)).sendEvents(eq("p-1"), eq("h-1/v3/events"), captor.capture());
120+
String eventStr = captor.getValue();
121+
122+
// 2 events (client_initialized, identified) will be batched in a single sendEvents() call.
123+
JsonArray jsonArray = JsonParser.parseString(eventStr).getAsJsonArray();
124+
assertEquals(jsonArray.size(), 2);
125+
126+
// "client_initialized" event (vuid only)
127+
JsonObject firstEvt = jsonArray.get(0).getAsJsonObject();
128+
JsonObject firstIdentifiers = firstEvt.get("identifiers").getAsJsonObject();
129+
JsonObject firstData = firstEvt.get("data").getAsJsonObject();
130+
131+
// "identified" event (vuid + fs_user_id)
132+
JsonObject secondEvt = jsonArray.get(1).getAsJsonObject();
133+
JsonObject secondIdentifiers = secondEvt.get("identifiers").getAsJsonObject();
149134

150-
// validate no other calls
135+
assertEquals(firstEvt.get("action").getAsString(), "client_initialized");
136+
assertEquals(firstIdentifiers.size(), 1);
137+
assertEquals(firstIdentifiers.get("vuid").getAsString(), testVuid);
151138

152-
verify(odpManager, times(1)).updateSettings(
153-
anyString(),
154-
anyString(),
155-
any(Set.class));
139+
assertEquals(secondEvt.get("action").getAsString(), "identified");
140+
assertEquals(secondIdentifiers.size(), 2);
141+
assertEquals(secondIdentifiers.get("vuid").getAsString(), testVuid);
142+
assertEquals(secondIdentifiers.get("fs_user_id").getAsString(), testUser);
143+
144+
// validate that ODP event data includes correct values.
145+
assertEquals(firstData.size(), 8); // {idempotence_id, os, os_version, data_source_type, data_source_version, device_type, model, data_source}
146+
assertEquals(firstData.get("data_source").getAsString(), "android-sdk");
156147
}
157148

158149
@Test
159-
public void updateODPConfigWhenDatafileUpdatedByBackgroundPolling() throws InterruptedException {
160-
// NOTE: same logic for async-initialization, so no need to repeat for async
150+
public void identifyOdpEventSentWhenVuidUserContextCreated() throws InterruptedException {
151+
optimizelyClient.createUserContext(); // empty userId. vuid will be used.
161152

162-
boolean updateConfigOnBackgroundDatafile = true;
163-
optimizelyManager.initialize(context, odpDatafile1, true, updateConfigOnBackgroundDatafile);
153+
Thread.sleep(2000); // wait for batch timeout (1sec)
164154

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)
155+
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
156+
verify(odpApiManager, times(1)).sendEvents(eq("p-1"), eq("h-1/v3/events"), captor.capture());
157+
String eventStr = captor.getValue();
168158

169-
// odpConfig updated on initialization
159+
// 2 events (client_initialized, identified) will be batched in a single sendEvents() call.
160+
JsonArray jsonArray = JsonParser.parseString(eventStr).getAsJsonArray();
161+
assertEquals(jsonArray.size(), 2);
170162

171-
verify(odpManager, times(1)).updateSettings(
172-
eq("h-1"),
173-
eq("p-1"),
174-
eq(Collections.emptySet()));
163+
// "client_initialized" event (vuid only)
164+
JsonObject firstEvt = jsonArray.get(0).getAsJsonObject();
165+
JsonObject firstIdentifiers = firstEvt.get("identifiers").getAsJsonObject();
175166

176-
// odpConfig updated on background polling
167+
// "identified" event (vuid only)
168+
JsonObject secondEvt = jsonArray.get(1).getAsJsonObject();
169+
JsonObject secondIdentifiers = secondEvt.get("identifiers").getAsJsonObject();
177170

178-
verify(odpManager, times(1)).updateSettings(
179-
eq("h-2"),
180-
eq("p-2"),
181-
eq(Collections.emptySet()));
171+
assertEquals(firstEvt.get("action").getAsString(), "client_initialized");
172+
assertEquals(firstIdentifiers.size(), 1);
173+
assertEquals(firstIdentifiers.get("vuid").getAsString(), testVuid);
182174

183-
// no other calls
175+
assertEquals(secondEvt.get("action").getAsString(), "identified");
176+
assertEquals(secondIdentifiers.size(), 1);
177+
assertEquals(secondIdentifiers.get("vuid").getAsString(), testVuid);
178+
}
184179

185-
verify(odpManager, times(2)).updateSettings(
186-
anyString(),
187-
anyString(),
188-
any(Set.class));
180+
@Test
181+
public void fetchQualifiedSegmentsWithUserContext() throws InterruptedException {
182+
OptimizelyUserContext user = optimizelyClient.createUserContext(testUser);
183+
184+
Boolean status = user.fetchQualifiedSegments();
185+
186+
verify(odpApiManager, times(1)).fetchQualifiedSegments(
187+
eq("p-1"),
188+
eq("h-1/v3/graphql"),
189+
eq("fs_user_id"),
190+
eq(testUser),
191+
eq(new HashSet<>(Arrays.asList("segment-1")))
192+
);
193+
}
194+
195+
@Test
196+
public void fetchQualifiedSegmentsWithVuidUserContext() throws InterruptedException {
197+
OptimizelyUserContext user = optimizelyClient.createUserContext(); // empty userId. vuid will be used.
198+
199+
Boolean status = user.fetchQualifiedSegments();
200+
201+
verify(odpApiManager, times(1)).fetchQualifiedSegments(
202+
eq("p-1"),
203+
eq("h-1/v3/graphql"),
204+
eq("vuid"),
205+
eq(testVuid),
206+
eq(new HashSet<>(Arrays.asList("segment-1")))
207+
);
189208
}
190209

191210
}

0 commit comments

Comments
 (0)