Skip to content

FIS getAuthToken implementation. #769

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

Merged
merged 31 commits into from
Sep 16, 2019
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c54292e
FIS getAuthToken implementation.
ankitaj224 Sep 5, 2019
b4b6ba8
FIS getAuthToken implementation.
ankitaj224 Sep 5, 2019
b8bfc1a
Merge branch 'fis_auth' of github.com:firebase/firebase-android-sdk i…
ankitaj224 Sep 5, 2019
7a2a112
Merge branch 'fis_auth' of github.com:firebase/firebase-android-sdk i…
ankitaj224 Sep 5, 2019
42c20f6
Merge branch 'fis_auth' of github.com:firebase/firebase-android-sdk i…
ankitaj224 Sep 5, 2019
46d936c
Merge branch 'fis_sdk' of github.com:firebase/firebase-android-sdk in…
ankitaj224 Sep 5, 2019
48fd7fe
Merge branch 'fis_sdk' of github.com:firebase/firebase-android-sdk in…
ankitaj224 Sep 5, 2019
82fb69e
1. Addressing Ciaran's comments
ankitaj224 Sep 6, 2019
fe04e15
1. Addressing Ciaran's comments
ankitaj224 Sep 6, 2019
e91cb7e
Merge branch 'fis_auth' of github.com:firebase/firebase-android-sdk i…
ankitaj224 Sep 6, 2019
8569952
Merge branch 'fis_auth' of github.com:firebase/firebase-android-sdk i…
ankitaj224 Sep 6, 2019
b74b163
Merge branch 'fis_auth' of github.com:firebase/firebase-android-sdk i…
ankitaj224 Sep 6, 2019
58a30e3
Updated IntDef usage in firebase-installations
ankitaj224 Sep 6, 2019
96e30c2
Using CountDownLatch to await instead of executor.awaitTermination
ankitaj224 Sep 7, 2019
9e3e34d
Addressing Di's comments.
ankitaj224 Sep 10, 2019
824cb63
Addressing Di's comments.
ankitaj224 Sep 10, 2019
90673c3
Merge branch 'fis_auth' of github.com:firebase/firebase-android-sdk i…
ankitaj224 Sep 10, 2019
f5b4f8c
Addressing Ciaran's comments to replace latch with a custom listener.
ankitaj224 Sep 10, 2019
3e29941
Adding onSuccess to AwaitListener.
ankitaj224 Sep 10, 2019
75f2581
Cleaning up the code as per the ciaran's comments.
ankitaj224 Sep 11, 2019
91e49bc
Addressing Di's comments.
ankitaj224 Sep 11, 2019
4578880
1. Handling multiple calls to getAUthToken()
ankitaj224 Sep 13, 2019
7d19b50
Fixing api-information presubmit check.
ankitaj224 Sep 13, 2019
fe04884
Merge branch 'fis_sdk' of github.com:firebase/firebase-android-sdk in…
ankitaj224 Sep 13, 2019
f1d1d37
Merge branch 'fis_sdk' of github.com:firebase/firebase-android-sdk in…
ankitaj224 Sep 13, 2019
ec65040
updating api.txt for firbase-installations-interop
ankitaj224 Sep 13, 2019
396d273
Merge branch 'fis_sdk' of github.com:firebase/firebase-android-sdk in…
ankitaj224 Sep 13, 2019
81b4aeb
Addressing Ciaran's comments.
ankitaj224 Sep 13, 2019
3932d3b
Fixing check-changed - GoogleJavaFormat error.
ankitaj224 Sep 16, 2019
8d4d811
Addressing ciaran`s comments
ankitaj224 Sep 16, 2019
3f44b77
nit fixes to executor : use unblocking queue.
ankitaj224 Sep 16, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions firebase-installations-interop/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ package com.google.firebase.installations {
ctor public InstallationTokenResult();
method @NonNull public static com.google.firebase.installations.InstallationTokenResult.Builder builder();
method @NonNull public abstract String getToken();
method @NonNull public abstract long getTokenExpirationTimestampMillis();
method @NonNull public abstract long getTokenExpirationInSecs();
method @NonNull public abstract com.google.firebase.installations.InstallationTokenResult.Builder toBuilder();
}

public abstract static class InstallationTokenResult.Builder {
ctor public InstallationTokenResult.Builder();
method @NonNull public abstract com.google.firebase.installations.InstallationTokenResult build();
method @NonNull public abstract com.google.firebase.installations.InstallationTokenResult.Builder setToken(@NonNull String);
method @NonNull public abstract com.google.firebase.installations.InstallationTokenResult.Builder setTokenExpirationTimestampMillis(@NonNull long);
method @NonNull public abstract com.google.firebase.installations.InstallationTokenResult.Builder setTokenExpirationInSecs(@NonNull long);
}

}
Expand Down
2 changes: 1 addition & 1 deletion firebase-installations/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.google.firebase.installations {

public class FirebaseInstallations {
method @NonNull public Task<Void> delete();
method @NonNull public Task<InstallationTokenResult> getAuthToken(boolean);
method @NonNull public Task<InstallationTokenResult> getAuthToken(int);
method @NonNull public Task<String> getId();
method @NonNull public static com.google.firebase.installations.FirebaseInstallations getInstance();
method @NonNull public static com.google.firebase.installations.FirebaseInstallations getInstance(@NonNull FirebaseApp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package com.google.firebase.installations;

import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_API_KEY;
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_APP_ID_1;
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_AUTH_TOKEN;
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_AUTH_TOKEN_2;
Expand All @@ -30,12 +31,11 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.util.Log;
import androidx.annotation.NonNull;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
import com.google.android.gms.common.util.Clock;
Expand All @@ -61,6 +61,8 @@
import org.junit.runners.MethodSorters;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.internal.stubbing.answers.AnswersWithDelay;
import org.mockito.internal.stubbing.answers.Returns;

/**
* Instrumented test, which will execute on an Android device.
Expand Down Expand Up @@ -131,7 +133,7 @@ public void setUp() throws FirebaseInstallationServiceException {
new FirebaseOptions.Builder()
.setApplicationId(TEST_APP_ID_1)
.setProjectId(TEST_PROJECT_ID)
.setApiKey("api_key")
.setApiKey(TEST_API_KEY)
.build());
persistedFid = new PersistedFid(firebaseApp);
when(backendClientReturnsOk.createFirebaseInstallation(
Expand Down Expand Up @@ -264,11 +266,12 @@ public void testGetId_PersistedFidError_BackendOk() throws InterruptedException
// Expect exception
try {
Tasks.await(firebaseInstallations.getId());
fail();
fail("Could not update local storage.");
} catch (ExecutionException expected) {
Throwable cause = expected.getCause();
assertWithMessage("Exception class doesn't match")
.that(cause)
.that(expected)
.hasCauseThat()
.isInstanceOf(FirebaseInstallationsException.class);
assertWithMessage("Exception status doesn't match")
.that(((FirebaseInstallationsException) cause).getStatus())
Expand Down Expand Up @@ -305,11 +308,12 @@ public void testGetAuthToken_PersistedFidError_failure() throws Exception {
try {
Tasks.await(
firebaseInstallations.getAuthToken(FirebaseInstallationsApi.DO_NOT_FORCE_REFRESH));
fail();
fail("Could not update local storage.");
} catch (ExecutionException expected) {
Throwable cause = expected.getCause();
assertWithMessage("Exception class doesn't match")
.that(cause)
.that(expected)
.hasCauseThat()
.isInstanceOf(FirebaseInstallationsException.class);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Truth has built-in support for unpacking exceptions

assertWithMessage("w/e").that(expected).hasCauseThat().isInstanceOf(FirebaseInstallationsException.class)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Did you also want the status to be changed? I tried but dint find a easy way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'd have to create a custom truth subject for that and tbh I don't think it's worth it atm

assertWithMessage("Exception status doesn't match")
.that(((FirebaseInstallationsException) cause).getStatus())
Expand Down Expand Up @@ -351,6 +355,9 @@ public void testGetAuthToken_expiredAuthToken_fetchedNewTokenFromFIS() throws Ex

@Test
public void testGetAuthToken_unregisteredFid_fetchedNewTokenFromFIS() throws Exception {
// Using mockPersistedFid to ensure the order of returning persistedFidEntry. This test
// validates that getAuthToken calls getId to ensure FID registration and returns a valid auth
// token.
when(mockPersistedFid.readPersistedFidEntryValue())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo this test could do with some comments explaining what you're testing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. PTAL.

.thenReturn(UNREGISTERED_FID_ENTRY, REGISTERED_FID_ENTRY);
FirebaseInstallations firebaseInstallations =
Expand Down Expand Up @@ -386,11 +393,12 @@ public void testGetAuthToken_serverError_failure() throws Exception {
// Expect exception
try {
Tasks.await(firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH));
fail();
fail("getAuthToken() failed due to Server Error.");
} catch (ExecutionException expected) {
Throwable cause = expected.getCause();
assertWithMessage("Exception class doesn't match")
.that(cause)
.that(expected)
.hasCauseThat()
.isInstanceOf(FirebaseInstallationsException.class);
assertWithMessage("Exception status doesn't match")
.that(((FirebaseInstallationsException) cause).getStatus())
Expand All @@ -399,7 +407,11 @@ public void testGetAuthToken_serverError_failure() throws Exception {
}

@Test
public void testGetAuthToken_multipleCalls_fetchedNewTokenOnce() throws Exception {
public void testGetAuthToken_multipleCallsDoNotForceRefresh_fetchedNewTokenOnce()
throws Exception {
// Using mockPersistedFid to ensure the order of returning persistedFidEntry to 2 tasks
// triggered simultaneously. Task2 waits for Task1 to complete.On Task1 completion, task2 reads
// the UPDATED_AUTH_TOKEN_FID_ENTRY by Task1 on execution.
when(mockPersistedFid.readPersistedFidEntryValue())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment explaining what this means

.thenReturn(
EXPIRED_AUTH_TOKEN_ENTRY,
Expand All @@ -425,67 +437,54 @@ public void testGetAuthToken_multipleCalls_fetchedNewTokenOnce() throws Exceptio
.that(task2.getResult().getToken())
.isEqualTo(TEST_AUTH_TOKEN_2);
verify(backendClientReturnsOk, times(1))
.generateAuthToken(anyString(), anyString(), anyString(), anyString());
.generateAuthToken(TEST_API_KEY, TEST_FID_1, TEST_PROJECT_ID, TEST_REFRESH_TOKEN);
}

@Test
public void testGetAuthToken_multipleCallsForceRefresh_fetchedNewTokenTwice() throws Exception {
when(mockPersistedFid.readPersistedFidEntryValue()).thenReturn(REGISTERED_FID_ENTRY);
// Use a custom ServiceClient to mock the network calls ensuring task1 is not completed
// before task2. Hence, we can test multiple calls to getAUthToken() and verify task2 waits for
// task1 to complete.
ServiceClient serviceClient = new ServiceClient();
// Use a mock ServiceClient for network calls with delay(1000ms) to ensure first task is not
// completed before the second task starts. Hence, we can test multiple calls to getAUthToken()
// and verify one task waits for another task to complete.

doAnswer(
new AnswersWithDelay(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure here, but I think we're supposed to use the AdditionalAnswers class to create these instead of reaching into the mockito internal package: https://static.javadoc.io/org.mockito/mockito-core/2.9.0/org/mockito/AdditionalAnswers.html

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the link. I dint realize it was internal package. Updated.

1000,
new Returns(
InstallationTokenResult.builder()
.setToken(TEST_AUTH_TOKEN_3)
.setTokenExpirationInSecs(TEST_TOKEN_EXPIRATION_TIMESTAMP)
.build())))
.doAnswer(
new AnswersWithDelay(
1000,
new Returns(
InstallationTokenResult.builder()
.setToken(TEST_AUTH_TOKEN_4)
.setTokenExpirationInSecs(TEST_TOKEN_EXPIRATION_TIMESTAMP)
.build())))
.when(backendClientReturnsOk)
.generateAuthToken(anyString(), anyString(), anyString(), anyString());

FirebaseInstallations firebaseInstallations =
new FirebaseInstallations(
mockClock, executor, firebaseApp, serviceClient, mockPersistedFid, mockUtils);
mockClock, executor, firebaseApp, backendClientReturnsOk, mockPersistedFid, mockUtils);

// Call getAuthToken multiple times with FORCE_REFRESH option. Also, sleep for 500ms in between
// the calls to ensure tasks are called in order.
// Call getAuthToken multiple times with FORCE_REFRESH option.
Task<InstallationTokenResult> task1 =
firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH);
Thread.sleep(500);
Task<InstallationTokenResult> task2 =
firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't have to sleep in tests. Can we somehow use the injected to ensure these tasks are run?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted the sleep to ensure task executed in order. But I removed it, instead of validating on both expected values

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't they execute in order at the moment? Is it because the executor has 4 threads? Could we use a single threaded executor to make this deterministic?

Tasks.await(Tasks.whenAllComplete(task1, task2));

// As we cannot ensure which task got executed first, verifying with both expected values
assertWithMessage("Persisted Auth Token doesn't match")
.that(task1.getResult().getToken())
.isEqualTo(TEST_AUTH_TOKEN_3);
.isAnyOf(TEST_AUTH_TOKEN_3, TEST_AUTH_TOKEN_4);
assertWithMessage("Persisted Auth Token doesn't match")
.that(task2.getResult().getToken())
.isEqualTo(TEST_AUTH_TOKEN_4);
}

class ServiceClient extends FirebaseInstallationServiceClient {

private boolean secondCall;

@Override
@NonNull
public InstallationTokenResult generateAuthToken(
@NonNull String apiKey,
@NonNull String fid,
@NonNull String projectID,
@NonNull String refreshToken)
throws FirebaseInstallationServiceException {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Log.e("InterruptedException", e.getMessage());
}

if (!secondCall) {
secondCall = true;
return InstallationTokenResult.builder()
.setToken(TEST_AUTH_TOKEN_3)
.setTokenExpirationInSecs(TEST_TOKEN_EXPIRATION_TIMESTAMP)
.build();
}

return InstallationTokenResult.builder()
.setToken(TEST_AUTH_TOKEN_4)
.setTokenExpirationInSecs(TEST_TOKEN_EXPIRATION_TIMESTAMP)
.build();
}
.isAnyOf(TEST_AUTH_TOKEN_4, TEST_AUTH_TOKEN_3);
verify(backendClientReturnsOk, times(2))
.generateAuthToken(TEST_API_KEY, TEST_FID_1, TEST_PROJECT_ID, TEST_REFRESH_TOKEN);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public final class FisAndroidTestConstants {
public static final String TEST_AUTH_TOKEN_3 = "fis.auth.token3";
public static final String TEST_AUTH_TOKEN_4 = "fis.auth.token4";

public static final String TEST_API_KEY = "apiKey";

public static final String TEST_REFRESH_TOKEN = "1:test-refresh-token";

public static final String TEST_APP_ID_1 = "1:123456789:android:abcdef";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@
import java.util.concurrent.TimeUnit;

final class AwaitListener implements OnCompleteListener<Void> {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final CountDownLatch latch = new CountDownLatch(1);

public void onSuccess() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't actually used, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its used. If the status is REGISTERED, instead of waiting for 10s, I am using this method to update the awaitlistener.

FirebaseInstalllations.java line 261

mLatch.countDown();
latch.countDown();
}

public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
return mLatch.await(timeout, unit);
return latch.await(timeout, unit);
}

@Override
public void onComplete(@NonNull Task<Void> task) {
mLatch.countDown();
latch.countDown();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class FirebaseInstallations implements FirebaseInstallationsApi {
private final Utils utils;

private static final long AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS = 3600L; // 1 hour
private static final long AWAIT_TIMEOUT_IN_SECS = 10L;

/** package private constructor. */
FirebaseInstallations(FirebaseApp firebaseApp) {
Expand Down Expand Up @@ -205,7 +206,7 @@ private static <F, T> Continuation<F, T> awaitFidRegistration(
@NonNull Supplier<T> supplier, AwaitListener listener) {
return t -> {
// Waiting for Task that registers FID on the FIS Servers
listener.await(10, TimeUnit.SECONDS);
listener.await(AWAIT_TIMEOUT_IN_SECS, TimeUnit.SECONDS);
return supplier.get();
};
}
Expand Down