-
Notifications
You must be signed in to change notification settings - Fork 615
getId() implementation with instrumented tests. #703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
30cf6ed
d034bbb
f8b468c
8db7967
b507a3c
f5a7c05
90cdbaa
0dfa987
a71a1f3
0a92e2d
4cc0cee
f16db3f
f8e75fd
227bf3b
4be3847
68acde5
16544a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,10 +14,31 @@ | |
|
||
package com.google.firebase.installations; | ||
|
||
import static com.google.common.truth.Truth.assertThat; | ||
import static org.junit.Assert.fail; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.ArgumentMatchers.anyString; | ||
import static org.mockito.Mockito.when; | ||
|
||
import androidx.test.core.app.ApplicationProvider; | ||
import androidx.test.runner.AndroidJUnit4; | ||
import com.google.android.gms.tasks.Tasks; | ||
import com.google.firebase.FirebaseApp; | ||
import com.google.firebase.FirebaseOptions; | ||
import com.google.firebase.installations.local.FiidCache; | ||
import com.google.firebase.installations.local.FiidCacheEntryValue; | ||
import com.google.firebase.installations.remote.FirebaseInstallationServiceClient; | ||
import com.google.firebase.installations.remote.FirebaseInstallationServiceException; | ||
import com.google.firebase.installations.remote.InstallationResponse; | ||
import java.util.concurrent.ExecutionException; | ||
import org.junit.After; | ||
import org.junit.Before; | ||
import org.junit.FixMethodOrder; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.junit.runners.MethodSorters; | ||
import org.mockito.Mock; | ||
import org.mockito.MockitoAnnotations; | ||
|
||
/** | ||
* Instrumented test, which will execute on an Android device. | ||
|
@@ -26,4 +47,99 @@ | |
*/ | ||
@RunWith(AndroidJUnit4.class) | ||
@FixMethodOrder(MethodSorters.NAME_ASCENDING) | ||
public class FirebaseInstallationsInstrumentedTest {} | ||
public class FirebaseInstallationsInstrumentedTest { | ||
private FirebaseApp firebaseApp; | ||
@Mock private FirebaseInstallationServiceClient backendClientReturnsOk; | ||
@Mock private FirebaseInstallationServiceClient backendClientReturnsError; | ||
private FiidCache actualCache; | ||
@Mock private FiidCache cacheReturnsError; | ||
|
||
@Before | ||
public void setUp() throws FirebaseInstallationServiceException { | ||
MockitoAnnotations.initMocks(this); | ||
FirebaseApp.clearInstancesForTest(); | ||
firebaseApp = | ||
FirebaseApp.initializeApp( | ||
ApplicationProvider.getApplicationContext(), | ||
new FirebaseOptions.Builder() | ||
.setApplicationId("1:123456789:android:abcdef") | ||
.setProjectId("project-id") | ||
.setApiKey("api_key") | ||
.build()); | ||
actualCache = new FiidCache(firebaseApp); | ||
when(backendClientReturnsOk.createFirebaseInstallation( | ||
anyString(), anyString(), anyString(), anyString())) | ||
.thenReturn( | ||
InstallationResponse.builder() | ||
.setName("/projects/123456789/installations/fid") | ||
.setRefreshToken("refresh-token") | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.setAuthToken( | ||
InstallationTokenResult.builder() | ||
.setToken("auth-token") | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.setTokenExpirationTimestampMillis(1000L) | ||
.build()) | ||
.build()); | ||
when(backendClientReturnsError.createFirebaseInstallation( | ||
anyString(), anyString(), anyString(), anyString())) | ||
.thenThrow( | ||
new FirebaseInstallationServiceException( | ||
"SDK Error", FirebaseInstallationServiceException.Status.SERVER_ERROR)); | ||
when(cacheReturnsError.insertOrUpdateCacheEntry(any())).thenReturn(false); | ||
when(cacheReturnsError.readCacheEntryValue()).thenReturn(null); | ||
} | ||
|
||
@After | ||
public void cleanUp() throws Exception { | ||
actualCache.clear(); | ||
} | ||
|
||
@Test | ||
public void testCreateFirebaseInstallation_CacheOk_BackendOk() throws Exception { | ||
FirebaseInstallations firebaseInstallations = | ||
new FirebaseInstallations(firebaseApp, actualCache, backendClientReturnsOk); | ||
|
||
// No exception, means success. | ||
assertThat(Tasks.await(firebaseInstallations.getId())).isNotEmpty(); | ||
FiidCacheEntryValue entryValue = actualCache.readCacheEntryValue(); | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assertThat(entryValue.getFirebaseInstallationId()).isNotEmpty(); | ||
assertThat(entryValue.getCacheStatus()).isEqualTo(FiidCache.CacheStatus.REGISTERED); | ||
} | ||
|
||
@Test | ||
public void testCreateFirebaseInstallation_CacheOk_BackendError() throws Exception { | ||
FirebaseInstallations firebaseInstallations = | ||
new FirebaseInstallations(firebaseApp, actualCache, backendClientReturnsError); | ||
|
||
// Expect exception | ||
try { | ||
Tasks.await(firebaseInstallations.getId()); | ||
fail(); | ||
} catch (ExecutionException expected) { | ||
Throwable cause = expected.getCause(); | ||
assertThat(cause).isInstanceOf(FirebaseInstallationsException.class); | ||
assertThat(((FirebaseInstallationsException) cause).getStatus()) | ||
.isEqualTo(FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR); | ||
} | ||
|
||
FiidCacheEntryValue entryValue = actualCache.readCacheEntryValue(); | ||
assertThat(entryValue.getFirebaseInstallationId()).isNotEmpty(); | ||
assertThat(entryValue.getCacheStatus()).isEqualTo(FiidCache.CacheStatus.REGISTER_ERROR); | ||
} | ||
|
||
@Test | ||
public void testCreateFirebaseInstallation_CacheError_BackendOk() throws InterruptedException { | ||
FirebaseInstallations firebaseInstallations = | ||
new FirebaseInstallations(firebaseApp, cacheReturnsError, backendClientReturnsOk); | ||
|
||
// Expect exception | ||
try { | ||
Tasks.await(firebaseInstallations.getId()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. out of curiosity: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There isnt one. However, I found RC using one by adding Asssert in Test Utils. Rest of the firebase-android-sdk teams have handled the same way as me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it was only added in junit5, and this build is using junit4. https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/Assertions.html#assertThrows-java.lang.Class-org.junit.jupiter.api.function.Executable- |
||
fail(); | ||
} catch (ExecutionException expected) { | ||
Throwable cause = expected.getCause(); | ||
assertThat(cause).isInstanceOf(FirebaseInstallationsException.class); | ||
assertThat(((FirebaseInstallationsException) cause).getStatus()) | ||
.isEqualTo(FirebaseInstallationsException.Status.CLIENT_ERROR); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,10 +16,18 @@ | |
|
||
import androidx.annotation.NonNull; | ||
import androidx.annotation.VisibleForTesting; | ||
import androidx.annotation.WorkerThread; | ||
import com.google.android.gms.common.internal.Preconditions; | ||
import com.google.android.gms.tasks.Task; | ||
import com.google.android.gms.tasks.Tasks; | ||
import com.google.firebase.FirebaseApp; | ||
import com.google.firebase.installations.local.FiidCache; | ||
import com.google.firebase.installations.local.FiidCacheEntryValue; | ||
import com.google.firebase.installations.remote.FirebaseInstallationServiceClient; | ||
import com.google.firebase.installations.remote.FirebaseInstallationServiceException; | ||
import com.google.firebase.installations.remote.InstallationResponse; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.Executors; | ||
|
||
/** | ||
* Entry point for Firebase Installations. | ||
|
@@ -35,10 +43,23 @@ | |
public class FirebaseInstallations implements FirebaseInstallationsApi { | ||
|
||
private final FirebaseApp firebaseApp; | ||
private final FirebaseInstallationServiceClient serviceClient; | ||
private final FiidCache localCache; | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private final Executor executor; | ||
|
||
/** package private constructor. */ | ||
FirebaseInstallations(FirebaseApp firebaseApp) { | ||
this(firebaseApp, new FiidCache(firebaseApp), new FirebaseInstallationServiceClient()); | ||
} | ||
|
||
FirebaseInstallations( | ||
FirebaseApp firebaseApp, | ||
FiidCache localCache, | ||
FirebaseInstallationServiceClient serviceClient) { | ||
this.firebaseApp = firebaseApp; | ||
this.serviceClient = serviceClient; | ||
this.executor = Executors.newFixedThreadPool(6); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this not a constant? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed it. Its a random number, based on 3 APIs I chose double the number of available threads. |
||
this.localCache = localCache; | ||
} | ||
|
||
/** | ||
|
@@ -71,7 +92,7 @@ public static FirebaseInstallations getInstance(@NonNull FirebaseApp app) { | |
@NonNull | ||
@Override | ||
public Task<String> getId() { | ||
return Tasks.forResult("fid-is-better-than-iid"); | ||
return Tasks.call(executor, () -> createFirebaseInstallationId()); | ||
} | ||
|
||
/** Returns a auth token(public key) of this Firebase app installation. */ | ||
|
@@ -103,4 +124,105 @@ String getApplicationId() { | |
String getName() { | ||
return firebaseApp.getName(); | ||
} | ||
|
||
/** | ||
* Create firebase installation id for the {@link FirebaseApp} on FIS Servers and client side | ||
* cache. | ||
* | ||
* <pre> | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* The workflow is: | ||
* check if cache empty or cache status is REGISTER_ERROR | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This description is somewhat confusing to me: What happens if the status is REGISTER_ERROR or not? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also what happens if status is UNREGISTERED? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated the description. Let me know, if you want me to change further. |
||
* | | ||
* create random fid | ||
* | | ||
* update cache with cache status UNREGISTERED | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there a status "IN PROGRESS" or something to prevent multiple requests in parallel? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We currently don't have any IN PROGRESS state. I am looking at ios-sdk to see how its handled. I ll discuss with you in person about this. |
||
* | | ||
* send http request to backend | ||
* | | | ||
* on success: set cache entry status to REGISTERED | | ||
* | | | ||
* | on failure: set cache entry status to REGISTER_ERROR | ||
* | | | ||
* return throw exception | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* | ||
* | ||
* else if cached FID exists | ||
* | | | ||
* if cache status is UNREGISTERED | | ||
* | return cached FID | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* send http request to backend | ||
* | | | ||
* on success: set cache \ | ||
* entry status to REGISTERED \ | ||
* | \ | ||
* | on failure: set cache entry status to REGISTER_ERROR | ||
* | | | ||
* return throw exception | ||
* </pre> | ||
*/ | ||
@WorkerThread | ||
private String createFirebaseInstallationId() throws FirebaseInstallationsException { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
FiidCacheEntryValue cacheEntryValue = localCache.readCacheEntryValue(); | ||
|
||
if (cacheEntryValue == null | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|| cacheEntryValue.getCacheStatus() == FiidCache.CacheStatus.REGISTER_ERROR) { | ||
String fid = Utils.createRandomFid(); | ||
|
||
boolean firstUpdateCacheResult = | ||
localCache.insertOrUpdateCacheEntry( | ||
FiidCacheEntryValue.create(fid, FiidCache.CacheStatus.UNREGISTERED, "", "", 0, 0)); | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (!firstUpdateCacheResult) { | ||
throw new FirebaseInstallationsException( | ||
"Failed to update client side cache.", | ||
FirebaseInstallationsException.Status.CLIENT_ERROR); | ||
} | ||
|
||
registerAndSaveFID(fid); | ||
|
||
return fid; | ||
} else if (!cacheEntryValue.getFirebaseInstallationId().isEmpty()) { | ||
|
||
if (cacheEntryValue.getCacheStatus() == FiidCache.CacheStatus.UNREGISTERED) { | ||
registerAndSaveFID(cacheEntryValue.getFirebaseInstallationId()); | ||
} | ||
|
||
return cacheEntryValue.getFirebaseInstallationId(); | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Registers the created FID with FIS Servers if the Network is available and update the cache. | ||
*/ | ||
private void registerAndSaveFID(String fid) throws FirebaseInstallationsException { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
try { | ||
if (Utils.isNetworkAvailable(firebaseApp.getApplicationContext())) { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
long creationTime = Utils.getCurrentTimeInSeconds(); | ||
|
||
InstallationResponse installationResponse = | ||
serviceClient.createFirebaseInstallation( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think, I mentioned this before (I know it's pretty nitty): There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed it in the parent PR. 34848c6 |
||
firebaseApp.getOptions().getApiKey(), | ||
firebaseApp.getOptions().getProjectId(), | ||
fid, | ||
getApplicationId()); | ||
|
||
localCache.insertOrUpdateCacheEntry( | ||
FiidCacheEntryValue.create( | ||
fid, | ||
FiidCache.CacheStatus.REGISTERED, | ||
installationResponse.getAuthToken().getToken(), | ||
installationResponse.getRefreshToken(), | ||
creationTime, | ||
installationResponse.getAuthToken().getTokenExpirationTimestampMillis())); | ||
} | ||
|
||
} catch (FirebaseInstallationServiceException exception) { | ||
localCache.insertOrUpdateCacheEntry( | ||
FiidCacheEntryValue.create(fid, FiidCache.CacheStatus.REGISTER_ERROR, "", "", 0, 0)); | ||
throw new FirebaseInstallationsException( | ||
exception.getMessage(), FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,9 @@ | |
|
||
package com.google.firebase.installations; | ||
|
||
import android.content.Context; | ||
import android.net.ConnectivityManager; | ||
import android.net.NetworkInfo; | ||
import androidx.annotation.NonNull; | ||
import java.nio.ByteBuffer; | ||
import java.nio.charset.Charset; | ||
|
@@ -92,4 +95,15 @@ private static byte[] getBytesFromUUID(UUID uuid) { | |
|
||
return bb.array(); | ||
} | ||
|
||
public static long getCurrentTimeInSeconds() { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return System.currentTimeMillis() / 1000; | ||
} | ||
|
||
public static boolean isNetworkAvailable(Context context) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as above, I'd inject a "NetworkChecker" or similar so that you're not forced to muck around with a real ConnectivityManager in tests There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Skipped the connectivity check here. Fred was of the opinion - the developer should check and handle the cases of network connectivity instead of us doing it. |
||
ConnectivityManager connectivityManager = | ||
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); | ||
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); | ||
return activeNetworkInfo != null && activeNetworkInfo.isConnected(); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.