Skip to content

Commit 6d5b7d1

Browse files
committed
Add FeedbackSender and tests
1 parent 792e775 commit 6d5b7d1

File tree

9 files changed

+256
-25
lines changed

9 files changed

+256
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.google.firebase.appdistribution.impl;
2+
3+
import static androidx.test.espresso.Espresso.onView;
4+
import static androidx.test.espresso.matcher.ViewMatchers.withId;
5+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
6+
7+
import android.content.res.Resources;
8+
import androidx.test.core.app.ApplicationProvider;
9+
import androidx.test.espresso.action.ViewActions;
10+
import androidx.test.ext.junit.rules.ActivityScenarioRule;
11+
import androidx.test.runner.AndroidJUnit4;
12+
import com.google.firebase.FirebaseApp;
13+
import com.google.firebase.FirebaseOptions;
14+
import org.junit.Before;
15+
import org.junit.Rule;
16+
import org.junit.Test;
17+
import org.junit.runner.RunWith;
18+
19+
@RunWith(AndroidJUnit4.class)
20+
public class FeedbackActivityTest {
21+
private static final String TEST_API_KEY = "AIzaSyabcdefghijklmnopqrstuvwxyz1234567";
22+
private static final String TEST_APP_ID_1 = "1:123456789:android:abcdef";
23+
private static final String TEST_PROJECT_ID = "test-project-id";
24+
25+
@Rule
26+
public ActivityScenarioRule<FeedbackActivity> activityRule =
27+
new ActivityScenarioRule<>(FeedbackActivity.class);
28+
29+
@Before
30+
public void setup() {
31+
FirebaseApp.initializeApp(
32+
ApplicationProvider.getApplicationContext(),
33+
new FirebaseOptions.Builder()
34+
.setApplicationId(TEST_APP_ID_1)
35+
.setProjectId(TEST_PROJECT_ID)
36+
.setApiKey(TEST_API_KEY)
37+
.build());
38+
}
39+
40+
@Test
41+
public void testEvent() {
42+
// ActivityScenario<FeedbackActivity> scenario = activityRule.getScenario();
43+
onView(withId(getResourceId("feedbackText"))).perform(ViewActions.typeText("The feedback"));
44+
onView(withId(getResourceId("submitButton"))).perform(ViewActions.click());
45+
}
46+
47+
private static int getResourceId(String xmlResourceName) {
48+
Resources r = getInstrumentation().getTargetContext().getResources();
49+
return r.getIdentifier(
50+
xmlResourceName, "xml", getInstrumentation().getTargetContext().getPackageName());
51+
}
52+
}

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FeedbackActivity.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
package com.google.firebase.appdistribution.impl;
216

317
import android.graphics.Bitmap;
@@ -18,7 +32,7 @@ public class FeedbackActivity extends AppCompatActivity {
1832
public static final String SCREENSHOT_EXTRA_KEY =
1933
"com.google.firebase.appdistribution.FeedbackActivity.SCREENSHOT";
2034

21-
private FirebaseAppDistributionTesterApiClient testerApiClient;
35+
private FeedbackSender feedbackSender;
2236
private String releaseName;
2337
private Bitmap screenshot;
2438

@@ -27,17 +41,15 @@ protected void onCreate(Bundle savedInstanceState) {
2741
super.onCreate(savedInstanceState);
2842
releaseName = getIntent().getStringExtra(RELEASE_NAME_EXTRA_KEY);
2943
screenshot = getIntent().getParcelableExtra(SCREENSHOT_EXTRA_KEY);
30-
testerApiClient = FirebaseApp.getInstance().get(FirebaseAppDistributionTesterApiClient.class);
44+
feedbackSender = FirebaseApp.getInstance().get(FeedbackSender.class);
3145
setContentView(R.layout.activity_feedback);
3246
}
3347

3448
public void submitFeedback(View view) {
3549
setSubmittingStateEnabled(true);
3650
EditText feedbackText = (EditText) findViewById(R.id.feedbackText);
37-
testerApiClient
38-
.createFeedback(releaseName, feedbackText.getText().toString())
39-
.onSuccessTask(feedbackName -> testerApiClient.attachScreenshot(feedbackName, screenshot))
40-
.onSuccessTask(testerApiClient::commitFeedback)
51+
feedbackSender
52+
.sendFeedback(releaseName, feedbackText.getText().toString(), screenshot)
4153
.addOnSuccessListener(
4254
unused -> {
4355
LogWrapper.getInstance().i(TAG, "Feedback submitted");
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.appdistribution.impl;
16+
17+
import android.graphics.Bitmap;
18+
import com.google.android.gms.tasks.Task;
19+
20+
/** Sends tester feedback to the Tester API. */
21+
class FeedbackSender {
22+
23+
private final FirebaseAppDistributionTesterApiClient testerApiClient;
24+
25+
FeedbackSender(FirebaseAppDistributionTesterApiClient testerApiClient) {
26+
this.testerApiClient = testerApiClient;
27+
}
28+
29+
/** Send feedback text and screenshot to the Tester API for the given release. */
30+
Task<Void> sendFeedback(String releaseName, String feedbackText, Bitmap screenshot) {
31+
return testerApiClient
32+
.createFeedback(releaseName, feedbackText)
33+
.onSuccessTask(feedbackName -> testerApiClient.attachScreenshot(feedbackName, screenshot))
34+
.onSuccessTask(testerApiClient::commitFeedback);
35+
}
36+
}

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionImpl.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,8 @@ public void collectAndSendFeedback() {
313313

314314
@VisibleForTesting
315315
public void collectAndSendFeedback(Executor taskExecutor) {
316-
screenshotTaker.takeScreenshot()
316+
screenshotTaker
317+
.takeScreenshot()
317318
.onSuccessTask(
318319
taskExecutor,
319320
screenshot ->
@@ -325,8 +326,11 @@ public void collectAndSendFeedback(Executor taskExecutor) {
325326
LogWrapper.getInstance()
326327
.e("Failed to sign in tester. Could not collect feedback.", e))
327328
.onSuccessTask(taskExecutor, unused -> releaseIdentifier.identifyRelease())
328-
.onSuccessTask(taskExecutor, releaseName -> launchFeedbackActivity(releaseName, screenshot)))
329-
.addOnFailureListener(taskExecutor, e -> LogWrapper.getInstance().e("Failed to launch feedback flow", e));
329+
.onSuccessTask(
330+
taskExecutor,
331+
releaseName -> launchFeedbackActivity(releaseName, screenshot)))
332+
.addOnFailureListener(
333+
taskExecutor, e -> LogWrapper.getInstance().e("Failed to launch feedback flow", e));
330334
}
331335

332336
private Task<Void> launchFeedbackActivity(String releaseName, Bitmap screenshot) {

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionRegistrar.java

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,36 +46,38 @@ public class FirebaseAppDistributionRegistrar implements ComponentRegistrar {
4646
Component.builder(FirebaseAppDistribution.class)
4747
.add(Dependency.required(FirebaseApp.class))
4848
.add(Dependency.requiredProvider(FirebaseInstallationsApi.class))
49-
.add(Dependency.required(FirebaseAppDistributionTesterApiClient.class))
49+
.add(Dependency.required(FeedbackSender.class))
5050
.factory(this::buildFirebaseAppDistribution)
5151
// construct FirebaseAppDistribution instance on startup so we can register for
5252
// activity lifecycle callbacks before the API is called
5353
.alwaysEager()
5454
.build(),
55-
Component.builder(FirebaseAppDistributionTesterApiClient.class)
55+
Component.builder(FeedbackSender.class)
5656
.add(Dependency.required(FirebaseApp.class))
5757
.add(Dependency.requiredProvider(FirebaseInstallationsApi.class))
58-
.factory(this::buildFirebaseAppDistributionTesterApiClient)
58+
.factory(this::buildFeedbackSender)
5959
.build(),
6060
LibraryVersionComponent.create("fire-appdistribution", BuildConfig.VERSION_NAME));
6161
}
6262

63-
private FirebaseAppDistributionTesterApiClient buildFirebaseAppDistributionTesterApiClient(
64-
ComponentContainer container) {
63+
private FeedbackSender buildFeedbackSender(ComponentContainer container) {
6564
FirebaseApp firebaseApp = container.get(FirebaseApp.class);
6665
Provider<FirebaseInstallationsApi> firebaseInstallationsApiProvider =
6766
container.getProvider(FirebaseInstallationsApi.class);
68-
return new FirebaseAppDistributionTesterApiClient(
69-
firebaseApp, firebaseInstallationsApiProvider, new TesterApiHttpClient(firebaseApp));
67+
FirebaseAppDistributionTesterApiClient testerApiClient =
68+
new FirebaseAppDistributionTesterApiClient(
69+
firebaseApp, firebaseInstallationsApiProvider, new TesterApiHttpClient(firebaseApp));
70+
return new FeedbackSender(testerApiClient);
7071
}
7172

7273
private FirebaseAppDistribution buildFirebaseAppDistribution(ComponentContainer container) {
7374
FirebaseApp firebaseApp = container.get(FirebaseApp.class);
74-
FirebaseAppDistributionTesterApiClient testerApiClient =
75-
container.get(FirebaseAppDistributionTesterApiClient.class);
7675
Context context = firebaseApp.getApplicationContext();
7776
Provider<FirebaseInstallationsApi> firebaseInstallationsApiProvider =
7877
container.getProvider(FirebaseInstallationsApi.class);
78+
FirebaseAppDistributionTesterApiClient testerApiClient =
79+
new FirebaseAppDistributionTesterApiClient(
80+
firebaseApp, firebaseInstallationsApiProvider, new TesterApiHttpClient(firebaseApp));
7981
SignInStorage signInStorage = new SignInStorage(context);
8082
FirebaseAppDistributionLifecycleNotifier lifecycleNotifier =
8183
FirebaseAppDistributionLifecycleNotifier.getInstance();

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionTesterApiClient.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.google.firebase.inject.Provider;
2929
import com.google.firebase.installations.FirebaseInstallationsApi;
3030
import com.google.firebase.installations.InstallationTokenResult;
31-
import java.io.ByteArrayOutputStream;
3231
import java.util.concurrent.Executor;
3332
import java.util.concurrent.Executors;
3433
import org.json.JSONArray;

firebase-appdistribution/src/main/java/com/google/firebase/appdistribution/impl/ScreenshotTaker.java

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
package com.google.firebase.appdistribution.impl;
216

317
import android.graphics.Bitmap;
@@ -8,8 +22,7 @@
822
/** A class that takes screenshots of the host app. */
923
class ScreenshotTaker {
1024

11-
private static final Bitmap TEMP_FIXED_BITMAP =
12-
Bitmap.createBitmap(400, 400, Config.RGB_565);
25+
private static final Bitmap TEMP_FIXED_BITMAP = Bitmap.createBitmap(400, 400, Config.RGB_565);
1326

1427
/** Take a screenshot of the running host app. */
1528
Task<Bitmap> takeScreenshot() {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.appdistribution.impl;
16+
17+
import static org.mockito.Mockito.verify;
18+
import static org.mockito.Mockito.when;
19+
20+
import android.graphics.Bitmap;
21+
import android.graphics.Bitmap.Config;
22+
import com.google.android.gms.tasks.Task;
23+
import com.google.android.gms.tasks.Tasks;
24+
import com.google.firebase.appdistribution.FirebaseAppDistributionException;
25+
import com.google.firebase.appdistribution.FirebaseAppDistributionException.Status;
26+
import org.junit.Before;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
import org.mockito.Mock;
30+
import org.mockito.MockitoAnnotations;
31+
import org.robolectric.RobolectricTestRunner;
32+
33+
@RunWith(RobolectricTestRunner.class)
34+
public class FeedbackSenderTest {
35+
private static final String TEST_RELEASE_NAME = "release-name";
36+
private static final String TEST_FEEDBACK_NAME = "feedback-name";
37+
private static final String TEST_FEEDBACK_TEXT = "Feedback text";
38+
private static final Bitmap TEST_SCREENSHOT = Bitmap.createBitmap(400, 400, Config.RGB_565);
39+
40+
@Mock private FirebaseAppDistributionTesterApiClient mockTesterApiClient;
41+
42+
private FeedbackSender feedbackSender;
43+
44+
@Before
45+
public void setup() {
46+
MockitoAnnotations.initMocks(this);
47+
feedbackSender = new FeedbackSender(mockTesterApiClient);
48+
}
49+
50+
@Test
51+
public void sendFeedback_success() throws Exception {
52+
when(mockTesterApiClient.createFeedback(TEST_RELEASE_NAME, TEST_FEEDBACK_TEXT))
53+
.thenReturn(Tasks.forResult(TEST_FEEDBACK_NAME));
54+
when(mockTesterApiClient.attachScreenshot(TEST_FEEDBACK_NAME, TEST_SCREENSHOT))
55+
.thenReturn(Tasks.forResult(TEST_FEEDBACK_NAME));
56+
when(mockTesterApiClient.commitFeedback(TEST_FEEDBACK_NAME)).thenReturn(Tasks.forResult(null));
57+
58+
Task<Void> task =
59+
feedbackSender.sendFeedback(TEST_RELEASE_NAME, TEST_FEEDBACK_TEXT, TEST_SCREENSHOT);
60+
TestUtils.awaitTask(task);
61+
62+
verify(mockTesterApiClient).createFeedback(TEST_RELEASE_NAME, TEST_FEEDBACK_TEXT);
63+
verify(mockTesterApiClient).attachScreenshot(TEST_FEEDBACK_NAME, TEST_SCREENSHOT);
64+
verify(mockTesterApiClient).commitFeedback(TEST_FEEDBACK_NAME);
65+
}
66+
67+
@Test
68+
public void sendFeedback_createFeedbackFails_failsTask() {
69+
FirebaseAppDistributionException cause =
70+
new FirebaseAppDistributionException("test ex", Status.AUTHENTICATION_FAILURE);
71+
when(mockTesterApiClient.createFeedback(TEST_RELEASE_NAME, TEST_FEEDBACK_TEXT))
72+
.thenReturn(Tasks.forException(cause));
73+
74+
Task<Void> task =
75+
feedbackSender.sendFeedback(TEST_RELEASE_NAME, TEST_FEEDBACK_TEXT, TEST_SCREENSHOT);
76+
77+
TestUtils.awaitTaskFailure(task, Status.AUTHENTICATION_FAILURE, "test ex");
78+
}
79+
80+
@Test
81+
public void sendFeedback_attachScreenshotFails_failsTask() {
82+
when(mockTesterApiClient.createFeedback(TEST_RELEASE_NAME, TEST_FEEDBACK_TEXT))
83+
.thenReturn(Tasks.forResult(TEST_FEEDBACK_NAME));
84+
FirebaseAppDistributionException cause =
85+
new FirebaseAppDistributionException("test ex", Status.AUTHENTICATION_FAILURE);
86+
when(mockTesterApiClient.attachScreenshot(TEST_FEEDBACK_NAME, TEST_SCREENSHOT))
87+
.thenReturn(Tasks.forException(cause));
88+
89+
Task<Void> task =
90+
feedbackSender.sendFeedback(TEST_RELEASE_NAME, TEST_FEEDBACK_TEXT, TEST_SCREENSHOT);
91+
92+
TestUtils.awaitTaskFailure(task, Status.AUTHENTICATION_FAILURE, "test ex");
93+
}
94+
95+
@Test
96+
public void sendFeedback_commitFeedbackFails_failsTask() {
97+
when(mockTesterApiClient.createFeedback(TEST_RELEASE_NAME, TEST_FEEDBACK_TEXT))
98+
.thenReturn(Tasks.forResult(TEST_FEEDBACK_NAME));
99+
when(mockTesterApiClient.attachScreenshot(TEST_FEEDBACK_NAME, TEST_SCREENSHOT))
100+
.thenReturn(Tasks.forResult(TEST_FEEDBACK_NAME));
101+
FirebaseAppDistributionException cause =
102+
new FirebaseAppDistributionException("test ex", Status.AUTHENTICATION_FAILURE);
103+
when(mockTesterApiClient.commitFeedback(TEST_FEEDBACK_NAME))
104+
.thenReturn(Tasks.forException(cause));
105+
106+
Task<Void> task =
107+
feedbackSender.sendFeedback(TEST_RELEASE_NAME, TEST_FEEDBACK_TEXT, TEST_SCREENSHOT);
108+
109+
TestUtils.awaitTaskFailure(task, Status.AUTHENTICATION_FAILURE, "test ex");
110+
}
111+
}

firebase-appdistribution/src/test/java/com/google/firebase/appdistribution/impl/FirebaseAppDistributionServiceImplTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -605,17 +605,19 @@ public void updateApp_withApkReleaseAvailable_returnsSameApkTask() {
605605
}
606606

607607
@Test
608-
public void collectAndSendFeedback_startsFeedbackActivity() throws InterruptedException {
608+
public void collectAndSendFeedback_signsInTesterAndStartsActivity() throws InterruptedException {
609609
ExecutorService testExecutor = Executors.newSingleThreadExecutor();
610-
when(mockSignInStorage.getSignInStatus()).thenReturn(true);
611610
when(mockReleaseIdentifier.identifyRelease()).thenReturn(Tasks.forResult("release-name"));
612611

613612
firebaseAppDistribution.collectAndSendFeedback(testExecutor);
614613
TestUtils.awaitAsyncOperations(testExecutor);
615614

616615
ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
617616
verify(activity).startActivity(argument.capture());
618-
assertThat(argument.getValue().getStringExtra(RELEASE_NAME_EXTRA_KEY)).isEqualTo("release-name");
619-
assertThat(argument.getValue().<Bitmap>getParcelableExtra(SCREENSHOT_EXTRA_KEY)).isEqualTo(TEST_SCREENSHOT);
617+
verify(mockTesterSignInManager).signInTester();
618+
assertThat(argument.getValue().getStringExtra(RELEASE_NAME_EXTRA_KEY))
619+
.isEqualTo("release-name");
620+
assertThat(argument.getValue().<Bitmap>getParcelableExtra(SCREENSHOT_EXTRA_KEY))
621+
.isEqualTo(TEST_SCREENSHOT);
620622
}
621623
}

0 commit comments

Comments
 (0)