DataSourceContractTest: Add expected response headers

PiperOrigin-RevId: 688556440
This commit is contained in:
ibaker 2024-10-22 08:17:03 -07:00 committed by Copybara-Service
parent 8260bb3d2e
commit 219565c15e
3 changed files with 148 additions and 8 deletions

View File

@ -43,12 +43,15 @@ import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.TransferListener;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.ForOverride;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.junit.Ignore;
import org.junit.Rule;
@ -569,6 +572,31 @@ public abstract class DataSourceContractTest {
});
}
@Test
public void getResponseHeaders_returnsExpectedValues() throws Exception {
forAllTestResourcesAndDataSources(
(resource, dataSource) -> {
try {
dataSource.open(new DataSpec(resource.getUri()));
// Iterate over the expected headers (instead of using Truth's batch
// containsAtLeastEntriesIn() method) in order to leverage the case-insensitivity of
// the DataSource.getResponseHeaders() implementation.
Map<String, List<String>> actualHeaders = dataSource.getResponseHeaders();
for (Map.Entry<String, List<String>> expectedHeaders :
resource.getResponseHeaders().entrySet()) {
assertWithMessage("Header values for key=%s", expectedHeaders.getKey())
.that(actualHeaders.get(expectedHeaders.getKey()))
.isEqualTo(expectedHeaders.getValue());
}
for (String unexpectedKey : resource.getUnexpectedResponseHeaderKeys()) {
assertThat(actualHeaders).doesNotContainKey(unexpectedKey);
}
} finally {
dataSource.close();
}
});
}
@Test
public void getResponseHeaders_noNullKeysOrValues() throws Exception {
forAllTestResourcesAndDataSources(
@ -741,12 +769,22 @@ public abstract class DataSourceContractTest {
@Nullable private final String name;
private final Uri uri;
private final Uri resolvedUri;
private final Map<String, List<String>> responseHeaders;
private final Set<String> unexpectedResponseHeaderKeys;
private final byte[] expectedBytes;
private TestResource(@Nullable String name, Uri uri, Uri resolvedUri, byte[] expectedBytes) {
private TestResource(
@Nullable String name,
Uri uri,
Uri resolvedUri,
Map<String, List<String>> responseHeaders,
Set<String> unexpectedResponseHeaderKeys,
byte[] expectedBytes) {
this.name = name;
this.uri = uri;
this.resolvedUri = resolvedUri;
this.responseHeaders = responseHeaders;
this.unexpectedResponseHeaderKeys = unexpectedResponseHeaderKeys;
this.expectedBytes = expectedBytes;
}
@ -769,6 +807,25 @@ public abstract class DataSourceContractTest {
return resolvedUri;
}
/**
* Returns the headers associated with this resource that are expected to be present in {@link
* DataSource#getResponseHeaders()}.
*
* <p>This doesn't have to be an exhaustive list, extra headers in {@link
* DataSource#getResponseHeaders()} are ignored.
*/
public Map<String, List<String>> getResponseHeaders() {
return responseHeaders;
}
/**
* Returns the keys that must <b>not</b> be present in {@link DataSource#getResponseHeaders()}
* when reading this resource.
*/
public Set<String> getUnexpectedResponseHeaderKeys() {
return unexpectedResponseHeaderKeys;
}
/** Returns the expected contents of this resource. */
public byte[] getExpectedBytes() {
return expectedBytes;
@ -779,8 +836,15 @@ public abstract class DataSourceContractTest {
private @MonotonicNonNull String name;
private @MonotonicNonNull Uri uri;
private @MonotonicNonNull Uri resolvedUri;
private Map<String, List<String>> responseHeaders;
private Set<String> unexpectedResponseHeaderKeys;
private byte @MonotonicNonNull [] expectedBytes;
public Builder() {
responseHeaders = ImmutableMap.of();
unexpectedResponseHeaderKeys = ImmutableSet.of();
}
/**
* Sets a human-readable name for this resource which will be shown in test failure messages.
*/
@ -822,6 +886,29 @@ public abstract class DataSourceContractTest {
return this;
}
/**
* Sets the headers associated with this resource that are expected to be present in {@link
* DataSource#getResponseHeaders()}.
*
* <p>This doesn't have to be an exhaustive list, extra headers in {@link
* DataSource#getResponseHeaders()} are ignored. Defaults to an empty map.
*/
@CanIgnoreReturnValue
public Builder setResponseHeaders(Map<String, List<String>> responseHeaders) {
this.responseHeaders = responseHeaders;
return this;
}
/**
* Sets the keys that must <b>not</b> be present in {@link DataSource#getResponseHeaders()}
* when reading this resource. Defaults to an empty set.
*/
@CanIgnoreReturnValue
public Builder setUnexpectedResponseHeaderKeys(Set<String> unexpectedResponseHeaderKeys) {
this.unexpectedResponseHeaderKeys = unexpectedResponseHeaderKeys;
return this;
}
/**
* Sets the expected contents of this resource.
*
@ -839,6 +926,8 @@ public abstract class DataSourceContractTest {
name,
checkNotNull(uri),
resolvedUri != null ? resolvedUri : uri,
ImmutableMap.copyOf(responseHeaders),
ImmutableSet.copyOf(unexpectedResponseHeaderKeys),
checkNotNull(expectedBytes));
}
}

View File

@ -22,7 +22,12 @@ import android.net.Uri;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.datasource.HttpDataSource;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.net.HttpHeaders;
import java.io.IOException;
import java.util.List;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
@ -33,12 +38,19 @@ import org.junit.rules.ExternalResource;
/** A JUnit {@link Rule} that creates test resources for {@link HttpDataSource} contract tests. */
@UnstableApi
public class HttpDataSourceTestEnv extends ExternalResource {
private static final ImmutableListMultimap<String, String> EXTRA_HEADERS =
ImmutableListMultimap.<String, String>builder()
.putAll("X-Test-Header", "test value1", "test value2")
.build();
private static int seed = 0;
private static final WebServerDispatcher.Resource RANGE_SUPPORTED =
new WebServerDispatcher.Resource.Builder()
.setPath("/supports/range-requests")
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
.supportsRangeRequests(true)
.setExtraResponseHeaders(EXTRA_HEADERS)
.build();
private static final WebServerDispatcher.Resource RANGE_SUPPORTED_LENGTH_UNKNOWN =
@ -47,6 +59,7 @@ public class HttpDataSourceTestEnv extends ExternalResource {
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
.supportsRangeRequests(true)
.resolvesToUnknownLength(true)
.setExtraResponseHeaders(EXTRA_HEADERS)
.build();
private static final WebServerDispatcher.Resource RANGE_NOT_SUPPORTED =
@ -54,6 +67,7 @@ public class HttpDataSourceTestEnv extends ExternalResource {
.setPath("/doesnt/support/range-requests")
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
.supportsRangeRequests(false)
.setExtraResponseHeaders(EXTRA_HEADERS)
.build();
private static final WebServerDispatcher.Resource RANGE_NOT_SUPPORTED_LENGTH_UNKNOWN =
@ -62,6 +76,7 @@ public class HttpDataSourceTestEnv extends ExternalResource {
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
.supportsRangeRequests(false)
.resolvesToUnknownLength(true)
.setExtraResponseHeaders(EXTRA_HEADERS)
.build();
private static final WebServerDispatcher.Resource GZIP_ENABLED =
@ -69,6 +84,7 @@ public class HttpDataSourceTestEnv extends ExternalResource {
.setPath("/gzip/enabled")
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
.setGzipSupport(WebServerDispatcher.Resource.GZIP_SUPPORT_ENABLED)
.setExtraResponseHeaders(EXTRA_HEADERS)
.build();
private static final WebServerDispatcher.Resource GZIP_FORCED =
@ -76,6 +92,7 @@ public class HttpDataSourceTestEnv extends ExternalResource {
.setPath("/gzip/forced")
.setData(TestUtil.buildTestData(/* length= */ 20, seed++))
.setGzipSupport(WebServerDispatcher.Resource.GZIP_SUPPORT_FORCED)
.setExtraResponseHeaders(EXTRA_HEADERS)
.build();
private static final WebServerDispatcher.Resource REDIRECTS_TO_RANGE_SUPPORTED =
@ -147,10 +164,15 @@ public class HttpDataSourceTestEnv extends ExternalResource {
private DataSourceContractTest.TestResource createTestResource(
String name, WebServerDispatcher.Resource resource) {
return new DataSourceContractTest.TestResource.Builder()
.setName(name)
.setUri(Uri.parse(originServer.url(resource.getPath()).toString()))
.setExpectedBytes(resource.getData())
.build();
DataSourceContractTest.TestResource.Builder testResource =
new DataSourceContractTest.TestResource.Builder()
.setName(name)
.setUri(Uri.parse(originServer.url(resource.getPath()).toString()))
.setResponseHeaders(Maps.transformValues(EXTRA_HEADERS.asMap(), v -> (List<String>) v))
.setExpectedBytes(resource.getData());
if (resource.resolvesToUnknownLength()) {
testResource.setUnexpectedResponseHeaderKeys(ImmutableSet.of(HttpHeaders.CONTENT_LENGTH));
}
return testResource.build();
}
}

View File

@ -32,8 +32,10 @@ import androidx.media3.common.util.Util;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@ -41,6 +43,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import okhttp3.mockwebserver.Dispatcher;
@ -102,10 +105,12 @@ public class WebServerDispatcher extends Dispatcher {
private boolean supportsRangeRequests;
private boolean resolvesToUnknownLength;
private @GzipSupport int gzipSupport;
private ImmutableListMultimap<String, String> extraResponseHeaders;
/** Constructs an instance. */
public Builder() {
this.gzipSupport = GZIP_SUPPORT_DISABLED;
this.extraResponseHeaders = ImmutableListMultimap.of();
}
private Builder(Resource resource) {
@ -114,6 +119,7 @@ public class WebServerDispatcher extends Dispatcher {
this.supportsRangeRequests = resource.supportsRangeRequests();
this.resolvesToUnknownLength = resource.resolvesToUnknownLength();
this.gzipSupport = resource.getGzipSupport();
this.extraResponseHeaders = resource.getExtraResponseHeaders();
}
/**
@ -175,6 +181,17 @@ public class WebServerDispatcher extends Dispatcher {
return this;
}
/**
* Sets the extra response headers that should be attached.
*
* @return this builder, for convenience.
*/
@CanIgnoreReturnValue
public Builder setExtraResponseHeaders(Multimap<String, String> extraResponseHeaders) {
this.extraResponseHeaders = ImmutableListMultimap.copyOf(extraResponseHeaders);
return this;
}
/** Builds the {@link Resource}. */
public Resource build() {
if (gzipSupport != GZIP_SUPPORT_DISABLED) {
@ -186,7 +203,8 @@ public class WebServerDispatcher extends Dispatcher {
checkNotNull(data),
supportsRangeRequests,
resolvesToUnknownLength,
gzipSupport);
gzipSupport,
extraResponseHeaders);
}
}
@ -195,18 +213,21 @@ public class WebServerDispatcher extends Dispatcher {
private final boolean supportsRangeRequests;
private final boolean resolvesToUnknownLength;
private final @GzipSupport int gzipSupport;
ImmutableListMultimap<String, String> extraResponseHeaders;
private Resource(
String path,
byte[] data,
boolean supportsRangeRequests,
boolean resolvesToUnknownLength,
@GzipSupport int gzipSupport) {
@GzipSupport int gzipSupport,
ImmutableListMultimap<String, String> extraResponseHeaders) {
this.path = path;
this.data = data;
this.supportsRangeRequests = supportsRangeRequests;
this.resolvesToUnknownLength = resolvesToUnknownLength;
this.gzipSupport = gzipSupport;
this.extraResponseHeaders = extraResponseHeaders;
}
/** Returns the path this resource is available at. */
@ -234,6 +255,11 @@ public class WebServerDispatcher extends Dispatcher {
return gzipSupport;
}
/** Returns the extra response headers that should be attached. */
public ImmutableListMultimap<String, String> getExtraResponseHeaders() {
return extraResponseHeaders;
}
/** Returns a new {@link Builder} initialized with the values from this instance. */
public Builder buildUpon() {
return new Builder(this);
@ -270,6 +296,9 @@ public class WebServerDispatcher extends Dispatcher {
return response.setResponseCode(404);
}
Resource resource = checkNotNull(resourcesByPath.get(requestPath));
for (Map.Entry<String, String> extraHeader : resource.getExtraResponseHeaders().entries()) {
response.addHeader(extraHeader.getKey(), extraHeader.getValue());
}
byte[] resourceData = resource.getData();
if (resource.supportsRangeRequests()) {
response.setHeader("Accept-Ranges", "bytes");