Skip to content

Commit da89e74

Browse files
authored
Add upload request to TesterApiHttpClient (#3810)
* Add upload request to TesterApiHttpClient * Address Kai's feedback * Java formatting * Formatting
1 parent 943abd8 commit da89e74

File tree

2 files changed

+115
-10
lines changed

2 files changed

+115
-10
lines changed

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

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import java.io.ByteArrayOutputStream;
2828
import java.io.IOException;
2929
import java.io.InputStream;
30+
import java.io.UnsupportedEncodingException;
31+
import java.util.HashMap;
32+
import java.util.Map;
3033
import java.util.zip.GZIPOutputStream;
3134
import javax.net.ssl.HttpsURLConnection;
3235
import org.json.JSONException;
@@ -35,7 +38,7 @@
3538
/** Client that makes FIS-authenticated GET and POST requests to the App Distribution Tester API. */
3639
class TesterApiHttpClient {
3740

38-
@VisibleForTesting static final String APP_TESTERS_HOST = "firebaseapptesters.googleapis.com";
41+
private static final String APP_TESTERS_HOST = "firebaseapptesters.googleapis.com";
3942
private static final String REQUEST_METHOD_GET = "GET";
4043
private static final String REQUEST_METHOD_POST = "POST";
4144
private static final String CONTENT_TYPE_HEADER_KEY = "Content-Type";
@@ -48,6 +51,12 @@ class TesterApiHttpClient {
4851
private static final String X_ANDROID_CERT_HEADER_KEY = "X-Android-Cert";
4952
// Format of "X-Client-Version": "{ClientId}/{ClientVersion}"
5053
private static final String X_CLIENT_VERSION_HEADER_KEY = "X-Client-Version";
54+
private static final String X_GOOG_UPLOAD_PROTOCOL_HEADER = "X-Goog-Upload-Protocol";
55+
private static final String X_GOOG_UPLOAD_PROTOCOL_RAW = "raw";
56+
private static final String X_GOOG_UPLOAD_FILE_NAME_HEADER = "X-Goog-Upload-File-Name";
57+
private static final String X_GOOG_UPLOAD_FILE_NAME = "screenshot.png";
58+
// StandardCharsets.UTF_8 requires API level 19
59+
private static final String UTF_8 = "UTF-8";
5160

5261
private static final String TAG = "TesterApiClient:";
5362
private static final int DEFAULT_BUFFER_SIZE = 8192;
@@ -94,17 +103,47 @@ JSONObject makeGetRequest(String tag, String path, String token)
94103
*/
95104
JSONObject makePostRequest(String tag, String path, String token, String requestBody)
96105
throws FirebaseAppDistributionException {
106+
byte[] bytes;
107+
try {
108+
bytes = requestBody.getBytes(UTF_8);
109+
} catch (UnsupportedEncodingException e) {
110+
throw new FirebaseAppDistributionException(
111+
"Unsupported encoding: " + UTF_8, Status.UNKNOWN, e);
112+
}
113+
return makePostRequest(tag, path, token, bytes, new HashMap<>());
114+
}
115+
116+
/**
117+
* Make a raw file upload request to the tester API at the given path using a FIS token for auth.
118+
*
119+
* <p>Uploads the file with gzip encoding.
120+
*
121+
* @return the response body
122+
*/
123+
JSONObject makeUploadRequest(String tag, String path, String token, byte[] requestBody)
124+
throws FirebaseAppDistributionException {
125+
Map<String, String> extraHeaders = new HashMap<>();
126+
extraHeaders.put(X_GOOG_UPLOAD_PROTOCOL_HEADER, X_GOOG_UPLOAD_PROTOCOL_RAW);
127+
extraHeaders.put(X_GOOG_UPLOAD_FILE_NAME_HEADER, X_GOOG_UPLOAD_FILE_NAME);
128+
return makePostRequest(tag, path, token, requestBody, extraHeaders);
129+
}
130+
131+
private JSONObject makePostRequest(
132+
String tag, String path, String token, byte[] requestBody, Map<String, String> extraHeaders)
133+
throws FirebaseAppDistributionException {
97134
HttpsURLConnection connection = null;
98135
try {
99136
connection = openHttpsUrlConnection(getTesterApiUrl(path), token);
100137
connection.setDoOutput(true);
101138
connection.setRequestMethod(REQUEST_METHOD_POST);
102139
connection.addRequestProperty(CONTENT_TYPE_HEADER_KEY, JSON_CONTENT_TYPE);
103140
connection.addRequestProperty(CONTENT_ENCODING_HEADER_KEY, GZIP_CONTENT_ENCODING);
104-
connection.getOutputStream();
141+
for (Map.Entry<String, String> e : extraHeaders.entrySet()) {
142+
connection.addRequestProperty(e.getKey(), e.getValue());
143+
}
105144
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(connection.getOutputStream());
106145
try {
107-
gzipOutputStream.write(requestBody.getBytes("UTF-8"));
146+
gzipOutputStream.write(requestBody);
108147
} catch (IOException e) {
109148
throw getException(tag, "Error compressing network request body", Status.UNKNOWN, e);
110149
} finally {

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

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,23 @@
1616

1717
import static com.google.common.truth.Truth.assertThat;
1818
import static com.google.firebase.appdistribution.impl.TestUtils.readTestFile;
19+
import static java.nio.charset.StandardCharsets.UTF_8;
1920
import static org.junit.Assert.assertThrows;
2021
import static org.mockito.Mockito.verify;
2122
import static org.mockito.Mockito.when;
2223

2324
import androidx.test.core.app.ApplicationProvider;
2425
import com.google.common.collect.Iterators;
26+
import com.google.common.io.ByteStreams;
2527
import com.google.firebase.FirebaseApp;
2628
import com.google.firebase.FirebaseOptions;
2729
import com.google.firebase.appdistribution.FirebaseAppDistributionException;
2830
import com.google.firebase.appdistribution.FirebaseAppDistributionException.Status;
2931
import java.io.ByteArrayInputStream;
32+
import java.io.ByteArrayOutputStream;
3033
import java.io.IOException;
3134
import java.io.InputStream;
32-
import java.nio.charset.StandardCharsets;
35+
import java.util.zip.GZIPInputStream;
3336
import javax.net.ssl.HttpsURLConnection;
3437
import org.json.JSONObject;
3538
import org.junit.Before;
@@ -51,6 +54,7 @@ public class TesterApiHttpClientTest {
5154
private static final String TEST_URL =
5255
String.format("https://firebaseapptesters.googleapis.com/%s", TEST_PATH);
5356
private static final String TAG = "Test Tag";
57+
private static final String TEST_POST_BODY = "Post body";
5458

5559
private TesterApiHttpClient testerApiHttpClient;
5660
@Mock private HttpsURLConnection mockHttpsURLConnection;
@@ -61,8 +65,6 @@ public void setup() throws Exception {
6165
MockitoAnnotations.initMocks(this);
6266
FirebaseApp.clearInstancesForTest();
6367

64-
when(mockHttpsURLConnectionFactory.openConnection(TEST_URL)).thenReturn(mockHttpsURLConnection);
65-
6668
FirebaseApp firebaseApp =
6769
FirebaseApp.initializeApp(
6870
ApplicationProvider.getApplicationContext(),
@@ -72,14 +74,15 @@ public void setup() throws Exception {
7274
.setApiKey(TEST_API_KEY)
7375
.build());
7476

77+
when(mockHttpsURLConnectionFactory.openConnection(TEST_URL)).thenReturn(mockHttpsURLConnection);
78+
7579
testerApiHttpClient = new TesterApiHttpClient(firebaseApp, mockHttpsURLConnectionFactory);
7680
}
7781

7882
@Test
7983
public void makeGetRequest_whenResponseSuccessful_returnsJsonResponse() throws Exception {
8084
String responseJson = readTestFile("testSimpleResponse.json");
81-
InputStream responseInputStream =
82-
new ByteArrayInputStream(responseJson.getBytes(StandardCharsets.UTF_8));
85+
InputStream responseInputStream = new ByteArrayInputStream(responseJson.getBytes(UTF_8));
8386
when(mockHttpsURLConnection.getResponseCode()).thenReturn(200);
8487
when(mockHttpsURLConnection.getInputStream()).thenReturn(responseInputStream);
8588

@@ -107,8 +110,7 @@ public void makeGetRequest_whenConnectionFails_throwsError() throws Exception {
107110

108111
@Test
109112
public void makeGetRequest_whenInvalidJson_throwsError() throws Exception {
110-
InputStream response =
111-
new ByteArrayInputStream(INVALID_RESPONSE.getBytes(StandardCharsets.UTF_8));
113+
InputStream response = new ByteArrayInputStream(INVALID_RESPONSE.getBytes(UTF_8));
112114
when(mockHttpsURLConnection.getInputStream()).thenReturn(response);
113115
when(mockHttpsURLConnection.getResponseCode()).thenReturn(200);
114116

@@ -186,4 +188,68 @@ public void makeGetRequest_whenResponseFailsWithUnknownCode_throwsError() throws
186188
assertThat(e.getMessage()).contains("409");
187189
verify(mockHttpsURLConnection).disconnect();
188190
}
191+
192+
@Test
193+
public void makePostRequest_zipsRequestBodyAndSetsCorrectHeaders() throws Exception {
194+
String responseJson = readTestFile("testSimpleResponse.json");
195+
InputStream responseInputStream = new ByteArrayInputStream(responseJson.getBytes(UTF_8));
196+
when(mockHttpsURLConnection.getResponseCode()).thenReturn(200);
197+
when(mockHttpsURLConnection.getInputStream()).thenReturn(responseInputStream);
198+
ByteArrayOutputStream requestBodyOutputStream = new ByteArrayOutputStream();
199+
when(mockHttpsURLConnection.getOutputStream()).thenReturn(requestBodyOutputStream);
200+
201+
testerApiHttpClient.makePostRequest(TAG, TEST_PATH, TEST_AUTH_TOKEN, TEST_POST_BODY);
202+
203+
byte[] unzippedPostBody =
204+
ByteStreams.toByteArray(
205+
new GZIPInputStream(new ByteArrayInputStream(requestBodyOutputStream.toByteArray())));
206+
assertThat(new String(unzippedPostBody, UTF_8)).isEqualTo(TEST_POST_BODY);
207+
verify(mockHttpsURLConnection).setDoOutput(true);
208+
verify(mockHttpsURLConnection).setRequestMethod("POST");
209+
verify(mockHttpsURLConnection).addRequestProperty("Content-Type", "application/json");
210+
verify(mockHttpsURLConnection).addRequestProperty("Content-Encoding", "gzip");
211+
verify(mockHttpsURLConnection).disconnect();
212+
}
213+
214+
@Test
215+
public void makePostRequest_whenConnectionFails_throwsError() throws Exception {
216+
IOException caughtException = new IOException("error");
217+
when(mockHttpsURLConnectionFactory.openConnection(TEST_URL)).thenThrow(caughtException);
218+
219+
FirebaseAppDistributionException e =
220+
assertThrows(
221+
FirebaseAppDistributionException.class,
222+
() ->
223+
testerApiHttpClient.makePostRequest(
224+
TAG, TEST_PATH, TEST_AUTH_TOKEN, TEST_POST_BODY));
225+
226+
assertThat(e.getErrorCode()).isEqualTo(Status.NETWORK_FAILURE);
227+
assertThat(e.getMessage()).contains(TAG);
228+
assertThat(e.getMessage()).contains(ErrorMessages.NETWORK_ERROR);
229+
}
230+
231+
@Test
232+
public void makeUploadRequest_zipsRequestBodyAndSetsCorrectHeaders() throws Exception {
233+
String responseJson = readTestFile("testSimpleResponse.json");
234+
InputStream responseInputStream = new ByteArrayInputStream(responseJson.getBytes(UTF_8));
235+
when(mockHttpsURLConnection.getResponseCode()).thenReturn(200);
236+
when(mockHttpsURLConnection.getInputStream()).thenReturn(responseInputStream);
237+
ByteArrayOutputStream requestBodyOutputStream = new ByteArrayOutputStream();
238+
when(mockHttpsURLConnection.getOutputStream()).thenReturn(requestBodyOutputStream);
239+
240+
testerApiHttpClient.makeUploadRequest(
241+
TAG, TEST_PATH, TEST_AUTH_TOKEN, TEST_POST_BODY.getBytes(UTF_8));
242+
243+
byte[] unzippedPostBody =
244+
ByteStreams.toByteArray(
245+
new GZIPInputStream(new ByteArrayInputStream(requestBodyOutputStream.toByteArray())));
246+
assertThat(new String(unzippedPostBody, UTF_8)).isEqualTo(TEST_POST_BODY);
247+
verify(mockHttpsURLConnection).setDoOutput(true);
248+
verify(mockHttpsURLConnection).setRequestMethod("POST");
249+
verify(mockHttpsURLConnection).addRequestProperty("Content-Type", "application/json");
250+
verify(mockHttpsURLConnection).addRequestProperty("Content-Encoding", "gzip");
251+
verify(mockHttpsURLConnection).addRequestProperty("X-Goog-Upload-Protocol", "raw");
252+
verify(mockHttpsURLConnection).addRequestProperty("X-Goog-Upload-File-Name", "screenshot.png");
253+
verify(mockHttpsURLConnection).disconnect();
254+
}
189255
}

0 commit comments

Comments
 (0)