Commit 8b89a5ed by Ian Baker

Merge pull request #6861 from…

Merge pull request #6861 from chrisfillmore:feature/responseBodyForInvalidResponseCodeException_6853

PiperOrigin-RevId: 314105612
parents c13be44f f288b3e6
...@@ -88,6 +88,8 @@ ...@@ -88,6 +88,8 @@
* Extend `EventTime` with more details about the current player state for * Extend `EventTime` with more details about the current player state for
easier access easier access
([#7332](https://github.com/google/ExoPlayer/issues/7332)). ([#7332](https://github.com/google/ExoPlayer/issues/7332)).
* Add `HttpDataSource.InvalidResponseCodeException#responseBody` field
([#6853](https://github.com/google/ExoPlayer/issues/6853)).
* Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices. * Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices.
* Text: * Text:
* Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming * Parse `<ruby>` and `<rt>` tags in WebVTT subtitles (rendering is coming
......
...@@ -31,11 +31,13 @@ import com.google.android.exoplayer2.util.Clock; ...@@ -31,11 +31,13 @@ import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ConditionVariable; import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Predicate; import com.google.android.exoplayer2.util.Predicate;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
...@@ -440,12 +442,28 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { ...@@ -440,12 +442,28 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
UrlResponseInfo responseInfo = Assertions.checkNotNull(this.responseInfo); UrlResponseInfo responseInfo = Assertions.checkNotNull(this.responseInfo);
int responseCode = responseInfo.getHttpStatusCode(); int responseCode = responseInfo.getHttpStatusCode();
if (responseCode < 200 || responseCode > 299) { if (responseCode < 200 || responseCode > 299) {
byte[] responseBody = Util.EMPTY_BYTE_ARRAY;
ByteBuffer readBuffer = getOrCreateReadBuffer();
while (!readBuffer.hasRemaining()) {
operation.close();
readBuffer.clear();
readInternal(readBuffer);
if (finished) {
break;
}
readBuffer.flip();
int existingResponseBodyEnd = responseBody.length;
responseBody = Arrays.copyOf(responseBody, responseBody.length + readBuffer.remaining());
readBuffer.get(responseBody, existingResponseBodyEnd, readBuffer.remaining());
}
InvalidResponseCodeException exception = InvalidResponseCodeException exception =
new InvalidResponseCodeException( new InvalidResponseCodeException(
responseCode, responseCode,
responseInfo.getHttpStatusText(), responseInfo.getHttpStatusText(),
responseInfo.getAllHeaders(), responseInfo.getAllHeaders(),
dataSpec); dataSpec,
responseBody);
if (responseCode == 416) { if (responseCode == 416) {
exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE));
} }
...@@ -496,17 +514,12 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { ...@@ -496,17 +514,12 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
} }
ByteBuffer readBuffer = this.readBuffer; ByteBuffer readBuffer = getOrCreateReadBuffer();
if (readBuffer == null) {
readBuffer = ByteBuffer.allocateDirect(READ_BUFFER_SIZE_BYTES);
readBuffer.limit(0);
this.readBuffer = readBuffer;
}
while (!readBuffer.hasRemaining()) { while (!readBuffer.hasRemaining()) {
// Fill readBuffer with more data from Cronet. // Fill readBuffer with more data from Cronet.
operation.close(); operation.close();
readBuffer.clear(); readBuffer.clear();
readInternal(castNonNull(readBuffer)); readInternal(readBuffer);
if (finished) { if (finished) {
bytesRemaining = 0; bytesRemaining = 0;
...@@ -603,11 +616,8 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { ...@@ -603,11 +616,8 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
operation.close(); operation.close();
if (!useCallerBuffer) { if (!useCallerBuffer) {
if (readBuffer == null) { ByteBuffer readBuffer = getOrCreateReadBuffer();
readBuffer = ByteBuffer.allocateDirect(READ_BUFFER_SIZE_BYTES); readBuffer.clear();
} else {
readBuffer.clear();
}
if (bytesToSkip < READ_BUFFER_SIZE_BYTES) { if (bytesToSkip < READ_BUFFER_SIZE_BYTES) {
readBuffer.limit((int) bytesToSkip); readBuffer.limit((int) bytesToSkip);
} }
...@@ -781,6 +791,14 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { ...@@ -781,6 +791,14 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
} }
} }
private ByteBuffer getOrCreateReadBuffer() {
if (readBuffer == null) {
readBuffer = ByteBuffer.allocateDirect(READ_BUFFER_SIZE_BYTES);
readBuffer.limit(0);
}
return readBuffer;
}
private static boolean isCompressed(UrlResponseInfo info) { private static boolean isCompressed(UrlResponseInfo info) {
for (Map.Entry<String, String> entry : info.getAllHeadersAsList()) { for (Map.Entry<String, String> entry : info.getAllHeadersAsList()) {
if (entry.getKey().equalsIgnoreCase("Content-Encoding")) { if (entry.getKey().equalsIgnoreCase("Content-Encoding")) {
...@@ -893,7 +911,11 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource { ...@@ -893,7 +911,11 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
if (responseCode == 307 || responseCode == 308) { if (responseCode == 307 || responseCode == 308) {
exception = exception =
new InvalidResponseCodeException( new InvalidResponseCodeException(
responseCode, info.getHttpStatusText(), info.getAllHeaders(), dataSpec); responseCode,
info.getHttpStatusText(),
info.getAllHeaders(),
dataSpec,
/* responseBody= */ Util.EMPTY_BYTE_ARRAY);
operation.open(); operation.open();
return; return;
} }
......
...@@ -378,15 +378,18 @@ public final class CronetDataSourceTest { ...@@ -378,15 +378,18 @@ public final class CronetDataSourceTest {
} }
@Test @Test
public void requestOpenValidatesStatusCode() { public void requestOpenPropagatesFailureResponseBody() throws Exception {
mockResponseStartSuccess(); mockResponseStartSuccess();
testUrlResponseInfo = createUrlResponseInfo(500); // statusCode // Use a size larger than CronetDataSource.READ_BUFFER_SIZE_BYTES
int responseLength = 40 * 1024;
mockReadSuccess(/* position= */ 0, /* length= */ responseLength);
testUrlResponseInfo = createUrlResponseInfo(/* statusCode= */ 500);
try { try {
dataSourceUnderTest.open(testDataSpec); dataSourceUnderTest.open(testDataSpec);
fail("HttpDataSource.HttpDataSourceException expected"); fail("HttpDataSource.InvalidResponseCodeException expected");
} catch (HttpDataSourceException e) { } catch (HttpDataSource.InvalidResponseCodeException e) {
assertThat(e).isInstanceOf(HttpDataSource.InvalidResponseCodeException.class); assertThat(e.responseBody).isEqualTo(buildTestDataArray(0, responseLength));
// Check for connection not automatically closed. // Check for connection not automatically closed.
verify(mockUrlRequest, never()).cancel(); verify(mockUrlRequest, never()).cancel();
verify(mockTransferListener, never()) verify(mockTransferListener, never())
......
...@@ -230,10 +230,18 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource { ...@@ -230,10 +230,18 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
// Check for a valid response code. // Check for a valid response code.
if (!response.isSuccessful()) { if (!response.isSuccessful()) {
byte[] errorResponseBody;
try {
errorResponseBody = Util.toByteArray(Assertions.checkNotNull(responseByteStream));
} catch (IOException e) {
throw new HttpDataSourceException(
"Error reading non-2xx response body", e, dataSpec, HttpDataSourceException.TYPE_OPEN);
}
Map<String, List<String>> headers = response.headers().toMultimap(); Map<String, List<String>> headers = response.headers().toMultimap();
closeConnectionQuietly(); closeConnectionQuietly();
InvalidResponseCodeException exception = InvalidResponseCodeException exception =
new InvalidResponseCodeException(responseCode, response.message(), headers, dataSpec); new InvalidResponseCodeException(
responseCode, response.message(), headers, dataSpec, errorResponseBody);
if (responseCode == 416) { if (responseCode == 416) {
exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE));
} }
......
...@@ -17,10 +17,16 @@ ...@@ -17,10 +17,16 @@
package com.google.android.exoplayer2.ext.okhttp; package com.google.android.exoplayer2.ext.okhttp;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.HttpDataSource;
import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import okhttp3.Call; import okhttp3.Call;
...@@ -38,24 +44,25 @@ import org.mockito.Mockito; ...@@ -38,24 +44,25 @@ import org.mockito.Mockito;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class OkHttpDataSourceTest { public class OkHttpDataSourceTest {
/**
* This test will set HTTP default request parameters (1) in the OkHttpDataSource, (2) via
* OkHttpDataSource.setRequestProperty() and (3) in the DataSpec instance according to the table
* below. Values wrapped in '*' are the ones that should be set in the connection request.
*
* <pre>{@code
* +-----------------------+---+-----+-----+-----+-----+-----+
* | | Header Key |
* +-----------------------+---+-----+-----+-----+-----+-----+
* | Location | 0 | 1 | 2 | 3 | 4 | 5 |
* +-----------------------+---+-----+-----+-----+-----+-----+
* | Default |*Y*| Y | Y | | | |
* | OkHttpDataSource | | *Y* | Y | Y | *Y* | |
* | DataSpec | | | *Y* | *Y* | | *Y* |
* +-----------------------+---+-----+-----+-----+-----+-----+
* }</pre>
*/
@Test @Test
public void open_setsCorrectHeaders() throws HttpDataSource.HttpDataSourceException { public void open_setsCorrectHeaders() throws HttpDataSource.HttpDataSourceException {
/*
* This test will set HTTP default request parameters (1) in the OkHttpDataSource, (2) via
* OkHttpDataSource.setRequestProperty() and (3) in the DataSpec instance according to the table
* below. Values wrapped in '*' are the ones that should be set in the connection request.
*
* +-----------------------+---+-----+-----+-----+-----+-----+
* | | Header Key |
* +-----------------------+---+-----+-----+-----+-----+-----+
* | Location | 0 | 1 | 2 | 3 | 4 | 5 |
* +-----------------------+---+-----+-----+-----+-----+-----+
* | Default |*Y*| Y | Y | | | |
* | OkHttpDataSource | | *Y* | Y | Y | *Y* | |
* | DataSpec | | | *Y* | *Y* | | *Y* |
* +-----------------------+---+-----+-----+-----+-----+-----+
*/
String defaultValue = "Default"; String defaultValue = "Default";
String okHttpDataSourceValue = "OkHttpDataSource"; String okHttpDataSourceValue = "OkHttpDataSource";
String dataSpecValue = "DataSpec"; String dataSpecValue = "DataSpec";
...@@ -67,7 +74,7 @@ public class OkHttpDataSourceTest { ...@@ -67,7 +74,7 @@ public class OkHttpDataSourceTest {
defaultRequestProperties.set("1", defaultValue); defaultRequestProperties.set("1", defaultValue);
defaultRequestProperties.set("2", defaultValue); defaultRequestProperties.set("2", defaultValue);
Call.Factory mockCallFactory = Mockito.mock(Call.Factory.class); Call.Factory mockCallFactory = mock(Call.Factory.class);
OkHttpDataSource okHttpDataSource = OkHttpDataSource okHttpDataSource =
new OkHttpDataSource( new OkHttpDataSource(
mockCallFactory, "testAgent", /* cacheControl= */ null, defaultRequestProperties); mockCallFactory, "testAgent", /* cacheControl= */ null, defaultRequestProperties);
...@@ -103,8 +110,8 @@ public class OkHttpDataSourceTest { ...@@ -103,8 +110,8 @@ public class OkHttpDataSourceTest {
assertThat(request.header("5")).isEqualTo(dataSpecValue); assertThat(request.header("5")).isEqualTo(dataSpecValue);
// return a Call whose .execute() will return a mock Response // return a Call whose .execute() will return a mock Response
Call returnValue = Mockito.mock(Call.class); Call returnValue = mock(Call.class);
Mockito.doReturn( doReturn(
new Response.Builder() new Response.Builder()
.request(request) .request(request)
.protocol(Protocol.HTTP_1_1) .protocol(Protocol.HTTP_1_1)
...@@ -120,4 +127,44 @@ public class OkHttpDataSourceTest { ...@@ -120,4 +127,44 @@ public class OkHttpDataSourceTest {
.newCall(ArgumentMatchers.any()); .newCall(ArgumentMatchers.any());
okHttpDataSource.open(dataSpec); okHttpDataSource.open(dataSpec);
} }
@Test
public void open_invalidResponseCode() throws Exception {
Call.Factory callFactory =
request -> {
Call mockCall = mock(Call.class);
try {
when(mockCall.execute())
.thenReturn(
new Response.Builder()
.request(request)
.protocol(Protocol.HTTP_1_1)
.code(404)
.message("NOT FOUND")
.body(ResponseBody.create(MediaType.parse("text/plain"), "failure msg"))
.build());
} catch (IOException e) {
throw new AssertionError(e);
}
return mockCall;
};
OkHttpDataSource okHttpDataSource =
new OkHttpDataSource(
callFactory,
"testAgent",
/* cacheControl= */ null,
/* defaultRequestProperties= */ null);
DataSpec dataSpec = new DataSpec.Builder().setUri("http://www.google.com").build();
HttpDataSource.InvalidResponseCodeException exception =
assertThrows(
HttpDataSource.InvalidResponseCodeException.class,
() -> okHttpDataSource.open(dataSpec));
assertThat(exception.responseCode).isEqualTo(404);
assertThat(exception.responseBody).isEqualTo("failure msg".getBytes(C.UTF8_NAME));
}
} }
...@@ -306,22 +306,51 @@ public interface HttpDataSource extends DataSource { ...@@ -306,22 +306,51 @@ public interface HttpDataSource extends DataSource {
*/ */
public final Map<String, List<String>> headerFields; public final Map<String, List<String>> headerFields;
/** @deprecated Use {@link #InvalidResponseCodeException(int, String, Map, DataSpec)}. */ /** The response body. */
public final byte[] responseBody;
/**
* @deprecated Use {@link #InvalidResponseCodeException(int, String, Map, DataSpec, byte[])}.
*/
@Deprecated @Deprecated
public InvalidResponseCodeException( public InvalidResponseCodeException(
int responseCode, Map<String, List<String>> headerFields, DataSpec dataSpec) { int responseCode, Map<String, List<String>> headerFields, DataSpec dataSpec) {
this(responseCode, /* responseMessage= */ null, headerFields, dataSpec); this(
responseCode,
/* responseMessage= */ null,
headerFields,
dataSpec,
/* responseBody= */ Util.EMPTY_BYTE_ARRAY);
} }
/**
* @deprecated Use {@link #InvalidResponseCodeException(int, String, Map, DataSpec, byte[])}.
*/
@Deprecated
public InvalidResponseCodeException( public InvalidResponseCodeException(
int responseCode, int responseCode,
@Nullable String responseMessage, @Nullable String responseMessage,
Map<String, List<String>> headerFields, Map<String, List<String>> headerFields,
DataSpec dataSpec) { DataSpec dataSpec) {
this(
responseCode,
responseMessage,
headerFields,
dataSpec,
/* responseBody= */ Util.EMPTY_BYTE_ARRAY);
}
public InvalidResponseCodeException(
int responseCode,
@Nullable String responseMessage,
Map<String, List<String>> headerFields,
DataSpec dataSpec,
byte[] responseBody) {
super("Response code: " + responseCode, dataSpec, TYPE_OPEN); super("Response code: " + responseCode, dataSpec, TYPE_OPEN);
this.responseCode = responseCode; this.responseCode = responseCode;
this.responseMessage = responseMessage; this.responseMessage = responseMessage;
this.headerFields = headerFields; this.headerFields = headerFields;
this.responseBody = responseBody;
} }
} }
......
...@@ -296,9 +296,20 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou ...@@ -296,9 +296,20 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
// Check for a valid response code. // Check for a valid response code.
if (responseCode < 200 || responseCode > 299) { if (responseCode < 200 || responseCode > 299) {
Map<String, List<String>> headers = connection.getHeaderFields(); Map<String, List<String>> headers = connection.getHeaderFields();
@Nullable InputStream errorStream = connection.getErrorStream();
byte[] errorResponseBody;
try {
errorResponseBody =
errorStream != null ? Util.toByteArray(errorStream) : Util.EMPTY_BYTE_ARRAY;
} catch (IOException e) {
throw new HttpDataSourceException(
"Error reading non-2xx response body", e, dataSpec, HttpDataSourceException.TYPE_OPEN);
}
closeConnectionQuietly(); closeConnectionQuietly();
InvalidResponseCodeException exception = InvalidResponseCodeException exception =
new InvalidResponseCodeException(responseCode, responseMessage, headers, dataSpec); new InvalidResponseCodeException(
responseCode, responseMessage, headers, dataSpec, errorResponseBody);
if (responseCode == 416) { if (responseCode == 416) {
exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE)); exception.initCause(new DataSourceException(DataSourceException.POSITION_OUT_OF_RANGE));
} }
...@@ -540,7 +551,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou ...@@ -540,7 +551,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
connection.setInstanceFollowRedirects(followRedirects); connection.setInstanceFollowRedirects(followRedirects);
connection.setDoOutput(httpBody != null); connection.setDoOutput(httpBody != null);
connection.setRequestMethod(DataSpec.getStringForHttpMethod(httpMethod)); connection.setRequestMethod(DataSpec.getStringForHttpMethod(httpMethod));
if (httpBody != null) { if (httpBody != null) {
connection.setFixedLengthStreamingMode(httpBody.length); connection.setFixedLengthStreamingMode(httpBody.length);
connection.connect(); connection.connect();
......
...@@ -17,8 +17,13 @@ ...@@ -17,8 +17,13 @@
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
...@@ -34,25 +39,25 @@ import org.mockito.Mockito; ...@@ -34,25 +39,25 @@ import org.mockito.Mockito;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class DefaultHttpDataSourceTest { public class DefaultHttpDataSourceTest {
/**
* This test will set HTTP default request parameters (1) in the DefaultHttpDataSource, (2) via
* DefaultHttpDataSource.setRequestProperty() and (3) in the DataSpec instance according to the
* table below. Values wrapped in '*' are the ones that should be set in the connection request.
*
* <pre>{@code
* +-----------------------+---+-----+-----+-----+-----+-----+
* | | Header Key |
* +-----------------------+---+-----+-----+-----+-----+-----+
* | Location | 0 | 1 | 2 | 3 | 4 | 5 |
* +-----------------------+---+-----+-----+-----+-----+-----+
* | Default |*Y*| Y | Y | | | |
* | DefaultHttpDataSource | | *Y* | Y | Y | *Y* | |
* | DataSpec | | | *Y* | *Y* | | *Y* |
* +-----------------------+---+-----+-----+-----+-----+-----+
* }</pre>
*/
@Test @Test
public void open_withSpecifiedRequestParameters_usesCorrectParameters() throws IOException { public void open_withSpecifiedRequestParameters_usesCorrectParameters() throws IOException {
/*
* This test will set HTTP default request parameters (1) in the DefaultHttpDataSource, (2) via
* DefaultHttpDataSource.setRequestProperty() and (3) in the DataSpec instance according to the
* table below. Values wrapped in '*' are the ones that should be set in the connection request.
*
* +-----------------------+---+-----+-----+-----+-----+-----+
* | | Header Key |
* +-----------------------+---+-----+-----+-----+-----+-----+
* | Location | 0 | 1 | 2 | 3 | 4 | 5 |
* +-----------------------+---+-----+-----+-----+-----+-----+
* | Default |*Y*| Y | Y | | | |
* | DefaultHttpDataSource | | *Y* | Y | Y | *Y* | |
* | DataSpec | | | *Y* | *Y* | | *Y* |
* +-----------------------+---+-----+-----+-----+-----+-----+
*/
String defaultParameter = "Default"; String defaultParameter = "Default";
String dataSourceInstanceParameter = "DefaultHttpDataSource"; String dataSourceInstanceParameter = "DefaultHttpDataSource";
String dataSpecParameter = "Dataspec"; String dataSpecParameter = "Dataspec";
...@@ -72,10 +77,8 @@ public class DefaultHttpDataSourceTest { ...@@ -72,10 +77,8 @@ public class DefaultHttpDataSourceTest {
defaultParameters)); defaultParameters));
Map<String, String> sentRequestProperties = new HashMap<>(); Map<String, String> sentRequestProperties = new HashMap<>();
HttpURLConnection mockHttpUrlConnection = makeMockHttpUrlConnection(sentRequestProperties); HttpURLConnection mockHttpUrlConnection = make200MockHttpUrlConnection(sentRequestProperties);
Mockito.doReturn(mockHttpUrlConnection) doReturn(mockHttpUrlConnection).when(defaultHttpDataSource).openConnection(any());
.when(defaultHttpDataSource)
.openConnection(ArgumentMatchers.any());
defaultHttpDataSource.setRequestProperty("1", dataSourceInstanceParameter); defaultHttpDataSource.setRequestProperty("1", dataSourceInstanceParameter);
defaultHttpDataSource.setRequestProperty("2", dataSourceInstanceParameter); defaultHttpDataSource.setRequestProperty("2", dataSourceInstanceParameter);
...@@ -106,22 +109,48 @@ public class DefaultHttpDataSourceTest { ...@@ -106,22 +109,48 @@ public class DefaultHttpDataSourceTest {
assertThat(sentRequestProperties.get("5")).isEqualTo(dataSpecParameter); assertThat(sentRequestProperties.get("5")).isEqualTo(dataSpecParameter);
} }
@Test
public void open_invalidResponseCode() throws Exception {
DefaultHttpDataSource defaultHttpDataSource =
Mockito.spy(
new DefaultHttpDataSource(
/* userAgent= */ "testAgent",
/* connectTimeoutMillis= */ 1000,
/* readTimeoutMillis= */ 1000,
/* allowCrossProtocolRedirects= */ false,
/* defaultRequestProperties= */ null));
HttpURLConnection mockHttpUrlConnection =
make404MockHttpUrlConnection(/* responseData= */ TestUtil.createByteArray(1, 2, 3));
doReturn(mockHttpUrlConnection).when(defaultHttpDataSource).openConnection(any());
DataSpec dataSpec = new DataSpec.Builder().setUri("http://www.google.com").build();
HttpDataSource.InvalidResponseCodeException exception =
assertThrows(
HttpDataSource.InvalidResponseCodeException.class,
() -> defaultHttpDataSource.open(dataSpec));
assertThat(exception.responseCode).isEqualTo(404);
assertThat(exception.responseBody).isEqualTo(TestUtil.createByteArray(1, 2, 3));
}
/** /**
* Creates a mock {@link HttpURLConnection} that stores all request parameters inside {@code * Creates a mock {@link HttpURLConnection} that stores all request parameters inside {@code
* requestProperties}. * requestProperties}.
*/ */
private static HttpURLConnection makeMockHttpUrlConnection(Map<String, String> requestProperties) private static HttpURLConnection make200MockHttpUrlConnection(
throws IOException { Map<String, String> requestProperties) throws IOException {
HttpURLConnection mockHttpUrlConnection = Mockito.mock(HttpURLConnection.class); HttpURLConnection mockHttpUrlConnection = Mockito.mock(HttpURLConnection.class);
Mockito.when(mockHttpUrlConnection.usingProxy()).thenReturn(false); when(mockHttpUrlConnection.usingProxy()).thenReturn(false);
Mockito.when(mockHttpUrlConnection.getInputStream()) when(mockHttpUrlConnection.getInputStream())
.thenReturn(new ByteArrayInputStream(new byte[128])); .thenReturn(new ByteArrayInputStream(new byte[128]));
Mockito.when(mockHttpUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream()); when(mockHttpUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream());
Mockito.when(mockHttpUrlConnection.getResponseCode()).thenReturn(200); when(mockHttpUrlConnection.getResponseCode()).thenReturn(200);
Mockito.when(mockHttpUrlConnection.getResponseMessage()).thenReturn("OK"); when(mockHttpUrlConnection.getResponseMessage()).thenReturn("OK");
Mockito.doAnswer( Mockito.doAnswer(
(invocation) -> { (invocation) -> {
...@@ -135,4 +164,15 @@ public class DefaultHttpDataSourceTest { ...@@ -135,4 +164,15 @@ public class DefaultHttpDataSourceTest {
return mockHttpUrlConnection; return mockHttpUrlConnection;
} }
/** Creates a mock {@link HttpURLConnection} that returns a 404 response code. */
private static HttpURLConnection make404MockHttpUrlConnection(byte[] responseData)
throws IOException {
HttpURLConnection mockHttpUrlConnection = Mockito.mock(HttpURLConnection.class);
when(mockHttpUrlConnection.getOutputStream()).thenReturn(new ByteArrayOutputStream());
when(mockHttpUrlConnection.getErrorStream()).thenReturn(new ByteArrayInputStream(responseData));
when(mockHttpUrlConnection.getResponseCode()).thenReturn(404);
when(mockHttpUrlConnection.getResponseMessage()).thenReturn("NOT FOUND");
return mockHttpUrlConnection;
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment