Skip to content

Commit e7fb811

Browse files
authored
Add the state machine of updating custom installation id in the local cache and update to Firebase Segmentation backend. (#545)
* Implement Firebase segmentation SDK device local cache * [Firebase Segmentation] Add custom installation id cache layer and tests for it. * Add test for updating cache * Switch to use SQLiteOpenHelper * Switch to use SharedPreferences from SQLite. * Change the cache class to be singleton * Wrap shared pref commit in a async task. * Address comments * Google format fix * Replace some deprecated code. * Package refactor * nit * nit * Add the state machine of updating custom installation id in the local cache and update to Firebase Segmentation backend. CL also contains unit tests. (The http client is not implemented yet.) * minor format fix * Address comments #1
1 parent fdbbdee commit e7fb811

File tree

10 files changed

+586
-85
lines changed

10 files changed

+586
-85
lines changed

firebase-segmentation/firebase-segmentation.gradle

+7
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,18 @@ android {
8383

8484
dependencies {
8585
implementation project(':firebase-common')
86+
implementation project(':protolite-well-known-types')
8687

8788
implementation('com.google.firebase:firebase-iid:17.0.3') {
8889
exclude group: "com.google.firebase", module: "firebase-common"
8990
}
91+
implementation 'io.grpc:grpc-stub:1.21.0'
92+
implementation 'io.grpc:grpc-protobuf-lite:1.21.0'
93+
implementation 'io.grpc:grpc-okhttp:1.21.0'
9094
implementation 'androidx.appcompat:appcompat:1.0.2'
9195
implementation 'androidx.multidex:multidex:2.0.0'
9296
implementation 'com.google.android.gms:play-services-tasks:16.0.1'
97+
implementation 'com.squareup.okhttp:okhttp:2.7.5'
9398

9499
compileOnly "com.google.auto.value:auto-value-annotations:1.6.5"
95100
annotationProcessor "com.google.auto.value:auto-value:1.6.2"
@@ -104,4 +109,6 @@ dependencies {
104109
androidTestImplementation 'androidx.test:runner:1.2.0'
105110
androidTestImplementation "com.google.truth:truth:$googleTruthVersion"
106111
androidTestImplementation 'junit:junit:4.12'
112+
androidTestImplementation 'org.mockito:mockito-core:2.25.0'
113+
androidTestImplementation 'org.mockito:mockito-android:2.25.0'
107114
}

firebase-segmentation/src/androidTest/java/com/google/firebase/segmentation/FirebaseSegmentationInstrumentedTest.java

+183-2
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,218 @@
1414

1515
package com.google.firebase.segmentation;
1616

17+
import static com.google.common.truth.Truth.assertThat;
1718
import static org.junit.Assert.assertNull;
19+
import static org.junit.Assert.fail;
20+
import static org.mockito.ArgumentMatchers.anyLong;
21+
import static org.mockito.ArgumentMatchers.anyString;
22+
import static org.mockito.Mockito.any;
23+
import static org.mockito.Mockito.when;
1824

25+
import androidx.annotation.NonNull;
1926
import androidx.test.core.app.ApplicationProvider;
2027
import androidx.test.ext.junit.runners.AndroidJUnit4;
28+
import com.google.android.gms.tasks.Tasks;
2129
import com.google.firebase.FirebaseApp;
2230
import com.google.firebase.FirebaseOptions;
31+
import com.google.firebase.iid.FirebaseInstanceId;
32+
import com.google.firebase.iid.InstanceIdResult;
33+
import com.google.firebase.segmentation.local.CustomInstallationIdCache;
34+
import com.google.firebase.segmentation.local.CustomInstallationIdCacheEntryValue;
35+
import com.google.firebase.segmentation.remote.SegmentationServiceClient;
36+
import java.util.concurrent.ExecutionException;
37+
import org.junit.After;
2338
import org.junit.Before;
39+
import org.junit.FixMethodOrder;
2440
import org.junit.Test;
2541
import org.junit.runner.RunWith;
42+
import org.junit.runners.MethodSorters;
43+
import org.mockito.Mock;
44+
import org.mockito.MockitoAnnotations;
2645

2746
/**
2847
* Instrumented test, which will execute on an Android device.
2948
*
3049
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
3150
*/
3251
@RunWith(AndroidJUnit4.class)
52+
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
3353
public class FirebaseSegmentationInstrumentedTest {
3454

55+
private static final String CUSTOM_INSTALLATION_ID = "123";
56+
private static final String FIREBASE_INSTANCE_ID = "cAAAAAAAAAA";
57+
3558
private FirebaseApp firebaseApp;
59+
@Mock private FirebaseInstanceId firebaseInstanceId;
60+
@Mock private SegmentationServiceClient backendClientReturnsOk;
61+
@Mock private SegmentationServiceClient backendClientReturnsError;
62+
private CustomInstallationIdCache actualCache;
63+
@Mock private CustomInstallationIdCache cacheReturnsError;
3664

3765
@Before
3866
public void setUp() {
67+
MockitoAnnotations.initMocks(this);
3968
FirebaseApp.clearInstancesForTest();
4069
firebaseApp =
4170
FirebaseApp.initializeApp(
4271
ApplicationProvider.getApplicationContext(),
4372
new FirebaseOptions.Builder().setApplicationId("1:123456789:android:abcdef").build());
73+
actualCache = new CustomInstallationIdCache(firebaseApp);
74+
75+
when(backendClientReturnsOk.updateCustomInstallationId(
76+
anyLong(), anyString(), anyString(), anyString()))
77+
.thenReturn(Tasks.forResult(SegmentationServiceClient.Code.OK));
78+
when(backendClientReturnsOk.clearCustomInstallationId(anyLong(), anyString(), anyString()))
79+
.thenReturn(Tasks.forResult(SegmentationServiceClient.Code.OK));
80+
when(backendClientReturnsError.updateCustomInstallationId(
81+
anyLong(), anyString(), anyString(), anyString()))
82+
.thenReturn(Tasks.forResult(SegmentationServiceClient.Code.SERVER_INTERNAL_ERROR));
83+
when(backendClientReturnsError.clearCustomInstallationId(anyLong(), anyString(), anyString()))
84+
.thenReturn(Tasks.forResult(SegmentationServiceClient.Code.SERVER_INTERNAL_ERROR));
85+
when(firebaseInstanceId.getInstanceId())
86+
.thenReturn(
87+
Tasks.forResult(
88+
new InstanceIdResult() {
89+
@NonNull
90+
@Override
91+
public String getId() {
92+
return FIREBASE_INSTANCE_ID;
93+
}
94+
95+
@NonNull
96+
@Override
97+
public String getToken() {
98+
return "iid_token";
99+
}
100+
}));
101+
when(cacheReturnsError.insertOrUpdateCacheEntry(any())).thenReturn(Tasks.forResult(false));
102+
when(cacheReturnsError.readCacheEntryValue()).thenReturn(null);
103+
}
104+
105+
@After
106+
public void cleanUp() throws Exception {
107+
Tasks.await(actualCache.clear());
108+
}
109+
110+
@Test
111+
public void testUpdateCustomInstallationId_CacheOk_BackendOk() throws Exception {
112+
FirebaseSegmentation firebaseSegmentation =
113+
new FirebaseSegmentation(
114+
firebaseApp, firebaseInstanceId, actualCache, backendClientReturnsOk);
115+
116+
// No exception, means success.
117+
assertNull(Tasks.await(firebaseSegmentation.setCustomInstallationId(CUSTOM_INSTALLATION_ID)));
118+
CustomInstallationIdCacheEntryValue entryValue = actualCache.readCacheEntryValue();
119+
assertThat(entryValue.getCustomInstallationId()).isEqualTo(CUSTOM_INSTALLATION_ID);
120+
assertThat(entryValue.getFirebaseInstanceId()).isEqualTo(FIREBASE_INSTANCE_ID);
121+
assertThat(entryValue.getCacheStatus()).isEqualTo(CustomInstallationIdCache.CacheStatus.SYNCED);
122+
}
123+
124+
@Test
125+
public void testUpdateCustomInstallationId_CacheOk_BackendError() throws InterruptedException {
126+
FirebaseSegmentation firebaseSegmentation =
127+
new FirebaseSegmentation(
128+
firebaseApp, firebaseInstanceId, actualCache, backendClientReturnsError);
129+
130+
// Expect exception
131+
try {
132+
Tasks.await(firebaseSegmentation.setCustomInstallationId(CUSTOM_INSTALLATION_ID));
133+
fail();
134+
} catch (ExecutionException expected) {
135+
Throwable cause = expected.getCause();
136+
assertThat(cause).isInstanceOf(SetCustomInstallationIdException.class);
137+
assertThat(((SetCustomInstallationIdException) cause).getStatus())
138+
.isEqualTo(SetCustomInstallationIdException.Status.BACKEND_ERROR);
139+
}
140+
141+
CustomInstallationIdCacheEntryValue entryValue = actualCache.readCacheEntryValue();
142+
assertThat(entryValue.getCustomInstallationId()).isEqualTo(CUSTOM_INSTALLATION_ID);
143+
assertThat(entryValue.getFirebaseInstanceId()).isEqualTo(FIREBASE_INSTANCE_ID);
144+
assertThat(entryValue.getCacheStatus())
145+
.isEqualTo(CustomInstallationIdCache.CacheStatus.PENDING_UPDATE);
44146
}
45147

46148
@Test
47-
public void useAppContext() {
48-
assertNull(FirebaseSegmentation.getInstance().setCustomInstallationId("123123").getResult());
149+
public void testUpdateCustomInstallationId_CacheError_BackendOk() throws InterruptedException {
150+
FirebaseSegmentation firebaseSegmentation =
151+
new FirebaseSegmentation(
152+
firebaseApp, firebaseInstanceId, cacheReturnsError, backendClientReturnsOk);
153+
154+
// Expect exception
155+
try {
156+
Tasks.await(firebaseSegmentation.setCustomInstallationId(CUSTOM_INSTALLATION_ID));
157+
fail();
158+
} catch (ExecutionException expected) {
159+
Throwable cause = expected.getCause();
160+
assertThat(cause).isInstanceOf(SetCustomInstallationIdException.class);
161+
assertThat(((SetCustomInstallationIdException) cause).getStatus())
162+
.isEqualTo(SetCustomInstallationIdException.Status.CLIENT_ERROR);
163+
}
164+
}
165+
166+
@Test
167+
public void testClearCustomInstallationId_CacheOk_BackendOk() throws Exception {
168+
Tasks.await(
169+
actualCache.insertOrUpdateCacheEntry(
170+
CustomInstallationIdCacheEntryValue.create(
171+
CUSTOM_INSTALLATION_ID,
172+
FIREBASE_INSTANCE_ID,
173+
CustomInstallationIdCache.CacheStatus.SYNCED)));
174+
FirebaseSegmentation firebaseSegmentation =
175+
new FirebaseSegmentation(
176+
firebaseApp, firebaseInstanceId, actualCache, backendClientReturnsOk);
177+
178+
// No exception, means success.
179+
assertNull(Tasks.await(firebaseSegmentation.setCustomInstallationId(null)));
180+
CustomInstallationIdCacheEntryValue entryValue = actualCache.readCacheEntryValue();
181+
assertNull(entryValue);
182+
}
183+
184+
@Test
185+
public void testClearCustomInstallationId_CacheOk_BackendError() throws Exception {
186+
Tasks.await(
187+
actualCache.insertOrUpdateCacheEntry(
188+
CustomInstallationIdCacheEntryValue.create(
189+
CUSTOM_INSTALLATION_ID,
190+
FIREBASE_INSTANCE_ID,
191+
CustomInstallationIdCache.CacheStatus.SYNCED)));
192+
FirebaseSegmentation firebaseSegmentation =
193+
new FirebaseSegmentation(
194+
firebaseApp, firebaseInstanceId, actualCache, backendClientReturnsError);
195+
196+
// Expect exception
197+
try {
198+
Tasks.await(firebaseSegmentation.setCustomInstallationId(null));
199+
fail();
200+
} catch (ExecutionException expected) {
201+
Throwable cause = expected.getCause();
202+
assertThat(cause).isInstanceOf(SetCustomInstallationIdException.class);
203+
assertThat(((SetCustomInstallationIdException) cause).getStatus())
204+
.isEqualTo(SetCustomInstallationIdException.Status.BACKEND_ERROR);
205+
}
206+
207+
CustomInstallationIdCacheEntryValue entryValue = actualCache.readCacheEntryValue();
208+
assertThat(entryValue.getCustomInstallationId().isEmpty()).isTrue();
209+
assertThat(entryValue.getFirebaseInstanceId()).isEqualTo(FIREBASE_INSTANCE_ID);
210+
assertThat(entryValue.getCacheStatus())
211+
.isEqualTo(CustomInstallationIdCache.CacheStatus.PENDING_CLEAR);
212+
}
213+
214+
@Test
215+
public void testClearCustomInstallationId_CacheError_BackendOk() throws InterruptedException {
216+
FirebaseSegmentation firebaseSegmentation =
217+
new FirebaseSegmentation(
218+
firebaseApp, firebaseInstanceId, cacheReturnsError, backendClientReturnsOk);
219+
220+
// Expect exception
221+
try {
222+
Tasks.await(firebaseSegmentation.setCustomInstallationId(CUSTOM_INSTALLATION_ID));
223+
fail();
224+
} catch (ExecutionException expected) {
225+
Throwable cause = expected.getCause();
226+
assertThat(cause).isInstanceOf(SetCustomInstallationIdException.class);
227+
assertThat(((SetCustomInstallationIdException) cause).getStatus())
228+
.isEqualTo(SetCustomInstallationIdException.Status.CLIENT_ERROR);
229+
}
49230
}
50231
}

firebase-segmentation/src/androidTest/java/com/google/firebase/segmentation/local/CustomInstallationIdCacheTest.java

+17-14
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ public class CustomInstallationIdCacheTest {
3434

3535
private FirebaseApp firebaseApp0;
3636
private FirebaseApp firebaseApp1;
37-
private CustomInstallationIdCache cache;
37+
private CustomInstallationIdCache cache0;
38+
private CustomInstallationIdCache cache1;
3839

3940
@Before
4041
public void setUp() {
@@ -48,42 +49,44 @@ public void setUp() {
4849
ApplicationProvider.getApplicationContext(),
4950
new FirebaseOptions.Builder().setApplicationId("1:987654321:android:abcdef").build(),
5051
"firebase_app_1");
51-
cache = CustomInstallationIdCache.getInstance();
52+
cache0 = new CustomInstallationIdCache(firebaseApp0);
53+
cache1 = new CustomInstallationIdCache(firebaseApp1);
5254
}
5355

5456
@After
5557
public void cleanUp() throws Exception {
56-
Tasks.await(cache.clearAll());
58+
Tasks.await(cache0.clear());
59+
Tasks.await(cache1.clear());
5760
}
5861

5962
@Test
6063
public void testReadCacheEntry_Null() {
61-
assertNull(cache.readCacheEntryValue(firebaseApp0));
62-
assertNull(cache.readCacheEntryValue(firebaseApp1));
64+
assertNull(cache0.readCacheEntryValue());
65+
assertNull(cache1.readCacheEntryValue());
6366
}
6467

6568
@Test
6669
public void testUpdateAndReadCacheEntry() throws Exception {
6770
assertTrue(
6871
Tasks.await(
69-
cache.insertOrUpdateCacheEntry(
70-
firebaseApp0,
72+
cache0.insertOrUpdateCacheEntry(
7173
CustomInstallationIdCacheEntryValue.create(
72-
"123456", "cAAAAAAAAAA", CustomInstallationIdCache.CacheStatus.PENDING))));
73-
CustomInstallationIdCacheEntryValue entryValue = cache.readCacheEntryValue(firebaseApp0);
74+
"123456",
75+
"cAAAAAAAAAA",
76+
CustomInstallationIdCache.CacheStatus.PENDING_UPDATE))));
77+
CustomInstallationIdCacheEntryValue entryValue = cache0.readCacheEntryValue();
7478
assertThat(entryValue.getCustomInstallationId()).isEqualTo("123456");
7579
assertThat(entryValue.getFirebaseInstanceId()).isEqualTo("cAAAAAAAAAA");
7680
assertThat(entryValue.getCacheStatus())
77-
.isEqualTo(CustomInstallationIdCache.CacheStatus.PENDING);
78-
assertNull(cache.readCacheEntryValue(firebaseApp1));
81+
.isEqualTo(CustomInstallationIdCache.CacheStatus.PENDING_UPDATE);
82+
assertNull(cache1.readCacheEntryValue());
7983

8084
assertTrue(
8185
Tasks.await(
82-
cache.insertOrUpdateCacheEntry(
83-
firebaseApp0,
86+
cache0.insertOrUpdateCacheEntry(
8487
CustomInstallationIdCacheEntryValue.create(
8588
"123456", "cAAAAAAAAAA", CustomInstallationIdCache.CacheStatus.SYNCED))));
86-
entryValue = cache.readCacheEntryValue(firebaseApp0);
89+
entryValue = cache0.readCacheEntryValue();
8790
assertThat(entryValue.getCustomInstallationId()).isEqualTo("123456");
8891
assertThat(entryValue.getFirebaseInstanceId()).isEqualTo("cAAAAAAAAAA");
8992
assertThat(entryValue.getCacheStatus()).isEqualTo(CustomInstallationIdCache.CacheStatus.SYNCED);

0 commit comments

Comments
 (0)