Skip to content

Add upload request to TesterApiHttpClient #3810

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 4 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
import javax.net.ssl.HttpsURLConnection;
import org.json.JSONException;
Expand All @@ -35,7 +38,7 @@
/** Client that makes FIS-authenticated GET and POST requests to the App Distribution Tester API. */
class TesterApiHttpClient {

@VisibleForTesting static final String APP_TESTERS_HOST = "firebaseapptesters.googleapis.com";
private static final String APP_TESTERS_HOST = "firebaseapptesters.googleapis.com";
private static final String REQUEST_METHOD_GET = "GET";
private static final String REQUEST_METHOD_POST = "POST";
private static final String CONTENT_TYPE_HEADER_KEY = "Content-Type";
Expand All @@ -48,6 +51,12 @@ class TesterApiHttpClient {
private static final String X_ANDROID_CERT_HEADER_KEY = "X-Android-Cert";
// Format of "X-Client-Version": "{ClientId}/{ClientVersion}"
private static final String X_CLIENT_VERSION_HEADER_KEY = "X-Client-Version";
private static final String X_GOOG_UPLOAD_PROTOCOL_HEADER = "X-Goog-Upload-Protocol";
private static final String X_GOOG_UPLOAD_PROTOCOL_RAW = "raw";
private static final String X_GOOG_UPLOAD_FILE_NAME_HEADER = "X-Goog-Upload-File-Name";
private static final String X_GOOG_UPLOAD_FILE_NAME = "screenshot.png";
// StandardCharsets.UTF_8 requires API level 19
private static final String UTF_8 = "UTF-8";

private static final String TAG = "TesterApiClient:";
private static final int DEFAULT_BUFFER_SIZE = 8192;
Expand Down Expand Up @@ -94,17 +103,47 @@ JSONObject makeGetRequest(String tag, String path, String token)
*/
JSONObject makePostRequest(String tag, String path, String token, String requestBody)
throws FirebaseAppDistributionException {
byte[] bytes;
try {
bytes = requestBody.getBytes(UTF_8);
} catch (UnsupportedEncodingException e) {
throw new FirebaseAppDistributionException(
"Unsupported encoding: " + UTF_8, Status.UNKNOWN, e);
}
return makePostRequest(tag, path, token, bytes, new HashMap<>());
}

/**
* Make a raw file upload request to the tester API at the given path using a FIS token for auth.
*
* <p>Uploads the file with gzip encoding.
*
* @return the response body
*/
JSONObject makeUploadRequest(String tag, String path, String token, byte[] requestBody)
throws FirebaseAppDistributionException {
Map<String, String> extraHeaders = new HashMap<>();
extraHeaders.put(X_GOOG_UPLOAD_PROTOCOL_HEADER, X_GOOG_UPLOAD_PROTOCOL_RAW);
extraHeaders.put(X_GOOG_UPLOAD_FILE_NAME_HEADER, X_GOOG_UPLOAD_FILE_NAME);
return makePostRequest(tag, path, token, requestBody, extraHeaders);
}

private JSONObject makePostRequest(
String tag, String path, String token, byte[] requestBody, Map<String, String> extraHeaders)
throws FirebaseAppDistributionException {
HttpsURLConnection connection = null;
try {
connection = openHttpsUrlConnection(getTesterApiUrl(path), token);
connection.setDoOutput(true);
connection.setRequestMethod(REQUEST_METHOD_POST);
connection.addRequestProperty(CONTENT_TYPE_HEADER_KEY, JSON_CONTENT_TYPE);
connection.addRequestProperty(CONTENT_ENCODING_HEADER_KEY, GZIP_CONTENT_ENCODING);
connection.getOutputStream();
for (Map.Entry<String, String> e : extraHeaders.entrySet()) {
connection.addRequestProperty(e.getKey(), e.getValue());
}
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(connection.getOutputStream());
try {
gzipOutputStream.write(requestBody.getBytes("UTF-8"));
gzipOutputStream.write(requestBody);
} catch (IOException e) {
throw getException(tag, "Error compressing network request body", Status.UNKNOWN, e);
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@

import static com.google.common.truth.Truth.assertThat;
import static com.google.firebase.appdistribution.impl.TestUtils.readTestFile;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import androidx.test.core.app.ApplicationProvider;
import com.google.common.collect.Iterators;
import com.google.common.io.ByteStreams;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.appdistribution.FirebaseAppDistributionException;
import com.google.firebase.appdistribution.FirebaseAppDistributionException.Status;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.zip.GZIPInputStream;
import javax.net.ssl.HttpsURLConnection;
import org.json.JSONObject;
import org.junit.Before;
Expand All @@ -51,6 +54,7 @@ public class TesterApiHttpClientTest {
private static final String TEST_URL =
String.format("https://firebaseapptesters.googleapis.com/%s", TEST_PATH);
private static final String TAG = "Test Tag";
private static final String TEST_POST_BODY = "Post body";

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

when(mockHttpsURLConnectionFactory.openConnection(TEST_URL)).thenReturn(mockHttpsURLConnection);

FirebaseApp firebaseApp =
FirebaseApp.initializeApp(
ApplicationProvider.getApplicationContext(),
Expand All @@ -72,14 +74,15 @@ public void setup() throws Exception {
.setApiKey(TEST_API_KEY)
.build());

when(mockHttpsURLConnectionFactory.openConnection(TEST_URL)).thenReturn(mockHttpsURLConnection);

testerApiHttpClient = new TesterApiHttpClient(firebaseApp, mockHttpsURLConnectionFactory);
}

@Test
public void makeGetRequest_whenResponseSuccessful_returnsJsonResponse() throws Exception {
String responseJson = readTestFile("testSimpleResponse.json");
InputStream responseInputStream =
new ByteArrayInputStream(responseJson.getBytes(StandardCharsets.UTF_8));
InputStream responseInputStream = new ByteArrayInputStream(responseJson.getBytes(UTF_8));
when(mockHttpsURLConnection.getResponseCode()).thenReturn(200);
when(mockHttpsURLConnection.getInputStream()).thenReturn(responseInputStream);

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

@Test
public void makeGetRequest_whenInvalidJson_throwsError() throws Exception {
InputStream response =
new ByteArrayInputStream(INVALID_RESPONSE.getBytes(StandardCharsets.UTF_8));
InputStream response = new ByteArrayInputStream(INVALID_RESPONSE.getBytes(UTF_8));
when(mockHttpsURLConnection.getInputStream()).thenReturn(response);
when(mockHttpsURLConnection.getResponseCode()).thenReturn(200);

Expand Down Expand Up @@ -186,4 +188,68 @@ public void makeGetRequest_whenResponseFailsWithUnknownCode_throwsError() throws
assertThat(e.getMessage()).contains("409");
verify(mockHttpsURLConnection).disconnect();
}

@Test
public void makePostRequest_zipsRequestBodyAndSetsCorrectHeaders() throws Exception {
String responseJson = readTestFile("testSimpleResponse.json");
InputStream responseInputStream = new ByteArrayInputStream(responseJson.getBytes(UTF_8));
when(mockHttpsURLConnection.getResponseCode()).thenReturn(200);
when(mockHttpsURLConnection.getInputStream()).thenReturn(responseInputStream);
ByteArrayOutputStream requestBodyOutputStream = new ByteArrayOutputStream();
when(mockHttpsURLConnection.getOutputStream()).thenReturn(requestBodyOutputStream);

testerApiHttpClient.makePostRequest(TAG, TEST_PATH, TEST_AUTH_TOKEN, TEST_POST_BODY);

byte[] unzippedPostBody =
ByteStreams.toByteArray(
new GZIPInputStream(new ByteArrayInputStream(requestBodyOutputStream.toByteArray())));
assertThat(new String(unzippedPostBody, UTF_8)).isEqualTo(TEST_POST_BODY);
verify(mockHttpsURLConnection).setDoOutput(true);
verify(mockHttpsURLConnection).setRequestMethod("POST");
verify(mockHttpsURLConnection).addRequestProperty("Content-Type", "application/json");
verify(mockHttpsURLConnection).addRequestProperty("Content-Encoding", "gzip");
verify(mockHttpsURLConnection).disconnect();
}

@Test
public void makePostRequest_whenConnectionFails_throwsError() throws Exception {
IOException caughtException = new IOException("error");
when(mockHttpsURLConnectionFactory.openConnection(TEST_URL)).thenThrow(caughtException);

FirebaseAppDistributionException e =
assertThrows(
FirebaseAppDistributionException.class,
() ->
testerApiHttpClient.makePostRequest(
TAG, TEST_PATH, TEST_AUTH_TOKEN, TEST_POST_BODY));

assertThat(e.getErrorCode()).isEqualTo(Status.NETWORK_FAILURE);
assertThat(e.getMessage()).contains(TAG);
assertThat(e.getMessage()).contains(ErrorMessages.NETWORK_ERROR);
}

@Test
public void makeUploadRequest_zipsRequestBodyAndSetsCorrectHeaders() throws Exception {
String responseJson = readTestFile("testSimpleResponse.json");
InputStream responseInputStream = new ByteArrayInputStream(responseJson.getBytes(UTF_8));
when(mockHttpsURLConnection.getResponseCode()).thenReturn(200);
when(mockHttpsURLConnection.getInputStream()).thenReturn(responseInputStream);
ByteArrayOutputStream requestBodyOutputStream = new ByteArrayOutputStream();
when(mockHttpsURLConnection.getOutputStream()).thenReturn(requestBodyOutputStream);

testerApiHttpClient.makeUploadRequest(
TAG, TEST_PATH, TEST_AUTH_TOKEN, TEST_POST_BODY.getBytes(UTF_8));

byte[] unzippedPostBody =
ByteStreams.toByteArray(
new GZIPInputStream(new ByteArrayInputStream(requestBodyOutputStream.toByteArray())));
assertThat(new String(unzippedPostBody, UTF_8)).isEqualTo(TEST_POST_BODY);
verify(mockHttpsURLConnection).setDoOutput(true);
verify(mockHttpsURLConnection).setRequestMethod("POST");
verify(mockHttpsURLConnection).addRequestProperty("Content-Type", "application/json");
verify(mockHttpsURLConnection).addRequestProperty("Content-Encoding", "gzip");
verify(mockHttpsURLConnection).addRequestProperty("X-Goog-Upload-Protocol", "raw");
verify(mockHttpsURLConnection).addRequestProperty("X-Goog-Upload-File-Name", "screenshot.png");
verify(mockHttpsURLConnection).disconnect();
}
}