servo/tests/wpt/web-platform-tests/fetch/api/request/request-cache.html

626 lines
27 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Request cache</title>
<meta name="help" href="https://fetch.spec.whatwg.org/#request">
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="/common/get-host-info.sub.js"></script>
</head>
<body>
<script>
var now = new Date();
/**
* Each test is run twice: once using etag/If-None-Match and once with
* date/If-Modified-Since. Each test run gets its own URL and randomized
* content and operates independently.
*
* The test steps are run with request_cache.length fetch requests issued
* and their immediate results sanity-checked. The cache.py server script
* stashes an entry containing any If-None-Match, If-Modified-Since, Pragma,
* and Cache-Control observed headers for each request it receives. When
* the test fetches have run, this state is retrieved from cache.py and the
* expected_* lists are checked, including their length.
*
* This means that if a request_* fetch is expected to hit the cache and not
* touch the network, then there will be no entry for it in the expect_*
* lists. AKA (request_cache.length - expected_validation_headers.length)
* should equal the number of cache hits that didn't touch the network.
*
* Test dictionary keys:
* - state: required string that determines whether the Expires response for
* the fetched document should be set in the future ("fresh") or past
* ("stale").
* - vary: optional string to be passed to the server for it to quote back
* in a Vary header on the response to us.
* - cache_control: optional string to be passed to the server for it to
* quote back in a Cache-Control header on the response to us.
* - redirect: optional string "same-origin" or "cross-origin". If
* provided, the server will issue an absolute redirect to the script on
* the same or a different origin, as appropriate. The redirected
* location is the script with the redirect parameter removed, so the
* content/state/etc. will be as if you hadn't specified a redirect.
* - request_cache: required array of cache modes to use (via `cache`).
* - request_headers: optional array of explicit fetch `headers` arguments.
* If provided, the server will log an empty dictionary for each request
* instead of the request headers it would normally log.
* - response: optional array of specialized response handling. Right now,
* "error" array entries indicate a network error response is expected
* which will reject with a TypeError.
* - expected_validation_headers: required boolean array indicating whether
* the server should have seen an If-None-Match/If-Modified-Since header
* in the request.
* - expected_no_cache_headers: required boolean array indicating whether
* the server should have seen Pragma/Cache-control:no-cache headers in
* the request.
* - expected_max_age_headers: optional boolean array indicating whether
* the server should have seen a Cache-Control:max-age=0 header in the
* request.
*/
var tests = [
{
name: 'RequestCache "default" mode checks the cache for previously cached content and goes to the network for stale responses',
state: "stale",
request_cache: ["default", "default"],
expected_validation_headers: [false, true],
expected_no_cache_headers: [false, false],
},
{
name: 'RequestCache "default" mode checks the cache for previously cached content and avoids going to the network if a fresh response exists',
state: "fresh",
request_cache: ["default", "default"],
expected_validation_headers: [false],
expected_no_cache_headers: [false],
},
{
name: 'RequestCache "no-cache" mode revalidates stale responses found in the cache',
state: "stale",
request_cache: ["default", "no-cache"],
expected_validation_headers: [false, true],
expected_no_cache_headers: [false, false],
expected_max_age_headers: [false, true],
},
{
name: 'RequestCache "no-cache" mode revalidates fresh responses found in the cache',
state: "fresh",
request_cache: ["default", "no-cache"],
expected_validation_headers: [false, true],
expected_no_cache_headers: [false, false],
expected_max_age_headers: [false, true],
},
{
name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for stale responses',
state: "stale",
request_cache: ["default", "force-cache"],
expected_validation_headers: [false],
expected_no_cache_headers: [false],
},
{
name: 'RequestCache "force-cache" mode checks the cache for previously cached content and avoid revalidation for fresh responses',
state: "fresh",
request_cache: ["default", "force-cache"],
expected_validation_headers: [false],
expected_no_cache_headers: [false],
},
{
name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found',
state: "stale",
request_cache: ["force-cache"],
expected_validation_headers: [false],
expected_no_cache_headers: [false],
},
{
name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response is not found',
state: "fresh",
request_cache: ["force-cache"],
expected_validation_headers: [false],
expected_no_cache_headers: [false],
},
{
name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary',
state: "stale",
vary: "*",
request_cache: ["default", "force-cache"],
expected_validation_headers: [false, true],
expected_no_cache_headers: [false, false],
},
{
name: 'RequestCache "force-cache" mode checks the cache for previously cached content and goes to the network if a cached response would vary',
state: "fresh",
vary: "*",
request_cache: ["default", "force-cache"],
expected_validation_headers: [false, true],
expected_no_cache_headers: [false, false],
},
{
name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network',
state: "stale",
request_cache: ["force-cache", "default"],
expected_validation_headers: [false, true],
expected_no_cache_headers: [false, false],
},
{
name: 'RequestCache "force-cache" stores the response in the cache if it goes to the network',
state: "fresh",
request_cache: ["force-cache", "default"],
expected_validation_headers: [false],
expected_no_cache_headers: [false],
},
{
name: 'RequestCache "only-if-cached" mode checks the cache for previously cached content and avoids revalidation for stale responses',
state: "stale",
request_cache: ["default", "only-if-cached"],
expected_validation_headers: [false],
expected_no_cache_headers: [false]
},
{
name: 'RequestCache "only-if-cached" mode checks the cache for previously cached content and avoids revalidation for fresh responses',
state: "fresh",
request_cache: ["default", "only-if-cached"],
expected_validation_headers: [false],
expected_no_cache_headers: [false]
},
{
name: 'RequestCache "only-if-cached" mode checks the cache for previously cached content and does not go to the network if a cached response is not found',
state: "fresh",
request_cache: ["only-if-cached"],
response: ["error"],
expected_validation_headers: [],
expected_no_cache_headers: []
},
{
name: 'RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content',
state: "fresh",
request_cache: ["default", "only-if-cached"],
redirect: "same-origin",
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, false],
},
{
name: 'RequestCache "only-if-cached" (with "same-origin") uses cached same-origin redirects to same-origin content',
state: "stale",
request_cache: ["default", "only-if-cached"],
redirect: "same-origin",
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, false],
},
{
name: 'RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects',
state: "fresh",
request_cache: ["default", "only-if-cached"],
redirect: "cross-origin",
response: [null, "error"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, false],
},
{
name: 'RequestCache "only-if-cached" (with "same-origin") does not follow redirects across origins and rejects',
state: "stale",
request_cache: ["default", "only-if-cached"],
redirect: "cross-origin",
response: [null, "error"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, false],
},
{
name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless',
state: "stale",
request_cache: ["default", "no-store"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "no-store" mode does not check the cache for previously cached content and goes to the network regardless',
state: "fresh",
request_cache: ["default", "no-store"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "no-store" mode does not store the response in the cache',
state: "stale",
request_cache: ["no-store", "default"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "no-store" mode does not store the response in the cache',
state: "fresh",
request_cache: ["no-store", "default"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-Modified-Since header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{}, {"If-Modified-Since": now.toGMTString()}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-Modified-Since header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{}, {"If-Modified-Since": now.toGMTString()}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-Modified-Since header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{"If-Modified-Since": now.toGMTString()}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-Modified-Since header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{"If-Modified-Since": now.toGMTString()}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-None-Match header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{}, {"If-None-Match": '"foo"'}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-None-Match header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{}, {"If-None-Match": '"foo"'}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-None-Match header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{"If-None-Match": '"foo"'}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-None-Match header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{"If-None-Match": '"foo"'}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-Unmodified-Since header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{}, {"If-Unmodified-Since": now.toGMTString()}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-Unmodified-Since header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{}, {"If-Unmodified-Since": now.toGMTString()}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-Unmodified-Since header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{"If-Unmodified-Since": now.toGMTString()}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-Unmodified-Since header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{"If-Unmodified-Since": now.toGMTString()}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-Match header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{}, {"If-Match": '"foo"'}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-Match header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{}, {"If-Match": '"foo"'}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-Match header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{"If-Match": '"foo"'}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-Match header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{"If-Match": '"foo"'}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-Range header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{}, {"If-Range": '"foo"'}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-Range header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{}, {"If-Range": '"foo"'}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "default" mode with an If-Range header is treated similarly to "no-store"',
state: "stale",
request_cache: ["default", "default"],
request_headers: [{"If-Range": '"foo"'}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "default" mode with an If-Range header is treated similarly to "no-store"',
state: "fresh",
request_cache: ["default", "default"],
request_headers: [{"If-Range": '"foo"'}, {}],
expected_validation_headers: [false, false],
expected_no_cache_headers: [true, false],
},
{
name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache',
state: "stale",
cache_control: "no-store",
request_cache: ["default", "default"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, false],
},
{
name: 'Responses with the "Cache-Control: no-store" header are not stored in the cache',
state: "fresh",
cache_control: "no-store",
request_cache: ["default", "default"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, false],
},
{
name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless',
state: "stale",
request_cache: ["default", "reload"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "reload" mode does not check the cache for previously cached content and goes to the network regardless',
state: "fresh",
request_cache: ["default", "reload"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
{
name: 'RequestCache "reload" mode does store the response in the cache',
state: "stale",
request_cache: ["reload", "default"],
expected_validation_headers: [false, true],
expected_no_cache_headers: [true, false],
},
{
name: 'RequestCache "reload" mode does store the response in the cache',
state: "fresh",
request_cache: ["reload", "default"],
expected_validation_headers: [false],
expected_no_cache_headers: [true],
},
{
name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored',
state: "stale",
request_cache: ["default", "reload", "default"],
expected_validation_headers: [false, false, true],
expected_no_cache_headers: [false, true, false],
},
{
name: 'RequestCache "reload" mode does store the response in the cache even if a previous response is already stored',
state: "fresh",
request_cache: ["default", "reload", "default"],
expected_validation_headers: [false, false],
expected_no_cache_headers: [false, true],
},
];
function base_path() {
return location.pathname.replace(/\/[^\/]*$/, '/');
}
function make_url(uuid, id, value, content, info) {
var dates = {
fresh: new Date(now.getFullYear() + 1, now.getMonth(), now.getDay()).toGMTString(),
stale: new Date(now.getFullYear() - 1, now.getMonth(), now.getDay()).toGMTString(),
};
var vary = "";
if ("vary" in info) {
vary = "&vary=" + info.vary;
}
var cache_control = "";
if ("cache_control" in info) {
cache_control = "&cache_control=" + info.cache_control;
}
var redirect = "";
var ignore_request_headers = "";
if ("request_headers" in info) {
// Ignore the request headers that we send since they may be synthesized by the test.
ignore_request_headers = "&ignore";
}
var url_sans_redirect = "resources/cache.py?token=" + uuid +
"&content=" + content +
"&" + id + "=" + value +
"&expires=" + dates[info.state] +
vary + cache_control + ignore_request_headers;
// If there's a redirect, the target is the script without any redirect at
// either the same domain or a different domain.
if ("redirect" in info) {
var host_info = get_host_info();
var origin;
switch (info.redirect) {
case "same-origin":
origin = host_info['HTTP_ORIGIN'];
break;
case "cross-origin":
origin = host_info['HTTP_REMOTE_ORIGIN'];
break;
}
var redirected_url = origin + base_path() + url_sans_redirect;
return url_sans_redirect + "&redirect=" + encodeURIComponent(redirected_url);
} else {
return url_sans_redirect;
}
}
function expected_status(type, identifier, init) {
if (type == "date" &&
init.headers &&
init.headers["If-Modified-Since"] == identifier) {
// The server will respond with a 304 in this case.
return [304, "Not Modified"];
}
return [200, "OK"];
}
function expected_response_text(type, identifier, init, content) {
if (type == "date" &&
init.headers &&
init.headers["If-Modified-Since"] == identifier) {
// The server will respond with a 304 in this case.
return "";
}
return content;
}
function server_state(uuid) {
return fetch("resources/cache.py?querystate&token=" + uuid)
.then(function(response) {
return response.text();
}).then(function(text) {
// null will be returned if the server never received any requests
// for the given uuid. Normalize that to an empty list consistent
// with our representation.
return JSON.parse(text) || [];
});
}
function make_test(type, info) {
return function(test) {
var uuid = token();
var identifier = (type == "tag" ? Math.random() : now.toGMTString());
var content = Math.random().toString();
var url = make_url(uuid, type, identifier, content, info);
var fetch_functions = [];
for (var i = 0; i < info.request_cache.length; ++i) {
fetch_functions.push(function(idx) {
var init = {cache: info.request_cache[idx]};
if ("request_headers" in info) {
init.headers = info.request_headers[idx];
}
if (init.cache === "only-if-cached") {
// only-if-cached requires we use same-origin mode.
init.mode = "same-origin";
}
return fetch(url, init)
.then(function(response) {
if ("response" in info && info.response[idx] === "error") {
assert_true(false, "fetch should have been an error");
return;
}
assert_array_equals([response.status, response.statusText],
expected_status(type, identifier, init));
return response.text();
}).then(function(text) {
assert_equals(text, expected_response_text(type, identifier, init, content));
}, function(reason) {
if ("response" in info && info.response[idx] === "error") {
assert_throws(new TypeError(), function() { throw reason; });
} else {
throw reason;
}
});
});
}
var i = 0;
function run_next_step() {
if (fetch_functions.length) {
return fetch_functions.shift()(i++)
.then(run_next_step);
} else {
return Promise.resolve();
}
}
return run_next_step()
.then(function() {
// Now, query the server state
return server_state(uuid);
}).then(function(state) {
var expectedState = [];
info.expected_validation_headers.forEach(function (validate) {
if (validate) {
if (type == "tag") {
expectedState.push({"If-None-Match": '"' + identifier + '"'});
} else {
expectedState.push({"If-Modified-Since": identifier});
}
} else {
expectedState.push({});
}
});
for (var i = 0; i < info.expected_no_cache_headers.length; ++i) {
if (info.expected_no_cache_headers[i]) {
expectedState[i]["Pragma"] = "no-cache";
expectedState[i]["Cache-Control"] = "no-cache";
}
}
if ("expected_max_age_headers" in info) {
for (var i = 0; i < info.expected_max_age_headers.length; ++i) {
if (info.expected_max_age_headers[i]) {
expectedState[i]["Cache-Control"] = "max-age=0";
}
}
}
assert_equals(state.length, expectedState.length);
for (var i = 0; i < state.length; ++i) {
for (var header in state[i]) {
assert_equals(state[i][header], expectedState[i][header]);
delete expectedState[i][header];
}
for (var header in expectedState[i]) {
assert_false(header in state[i]);
}
}
});
};
}
tests.forEach(function(info) {
promise_test(make_test("tag", info), info.name + " with Etag and " + info.state + " response");
promise_test(make_test("date", info), info.name + " with date and " + info.state + " response");
});
</script>
</body>
</html>