request-cache.js (8812B)
1 /** 2 * Each test is run twice: once using etag/If-None-Match and once with 3 * date/If-Modified-Since. Each test run gets its own URL and randomized 4 * content and operates independently. 5 * 6 * The test steps are run with request_cache.length fetch requests issued 7 * and their immediate results sanity-checked. The cache.py server script 8 * stashes an entry containing any If-None-Match, If-Modified-Since, Pragma, 9 * and Cache-Control observed headers for each request it receives. When 10 * the test fetches have run, this state is retrieved from cache.py and the 11 * expected_* lists are checked, including their length. 12 * 13 * This means that if a request_* fetch is expected to hit the cache and not 14 * touch the network, then there will be no entry for it in the expect_* 15 * lists. AKA (request_cache.length - expected_validation_headers.length) 16 * should equal the number of cache hits that didn't touch the network. 17 * 18 * Test dictionary keys: 19 * - state: required string that determines whether the Expires response for 20 * the fetched document should be set in the future ("fresh") or past 21 * ("stale"). 22 * - vary: optional string to be passed to the server for it to quote back 23 * in a Vary header on the response to us. 24 * - cache_control: optional string to be passed to the server for it to 25 * quote back in a Cache-Control header on the response to us. 26 * - redirect: optional string "same-origin" or "cross-origin". If 27 * provided, the server will issue an absolute redirect to the script on 28 * the same or a different origin, as appropriate. The redirected 29 * location is the script with the redirect parameter removed, so the 30 * content/state/etc. will be as if you hadn't specified a redirect. 31 * - request_cache: required array of cache modes to use (via `cache`). 32 * - request_headers: optional array of explicit fetch `headers` arguments. 33 * If provided, the server will log an empty dictionary for each request 34 * instead of the request headers it would normally log. 35 * - response: optional array of specialized response handling. Right now, 36 * "error" array entries indicate a network error response is expected 37 * which will reject with a TypeError. 38 * - expected_validation_headers: required boolean array indicating whether 39 * the server should have seen an If-None-Match/If-Modified-Since header 40 * in the request. 41 * - expected_no_cache_headers: required boolean array indicating whether 42 * the server should have seen Pragma/Cache-control:no-cache headers in 43 * the request. 44 * - expected_max_age_headers: optional boolean array indicating whether 45 * the server should have seen a Cache-Control:max-age=0 header in the 46 * request. 47 */ 48 49 var now = new Date(); 50 51 function base_path() { 52 return location.pathname.replace(/\/[^\/]*$/, '/'); 53 } 54 function make_url(uuid, id, value, content, info) { 55 var dates = { 56 fresh: new Date(now.getFullYear() + 1, now.getMonth(), now.getDay()).toGMTString(), 57 stale: new Date(now.getFullYear() - 1, now.getMonth(), now.getDay()).toGMTString(), 58 }; 59 var vary = ""; 60 if ("vary" in info) { 61 vary = "&vary=" + info.vary; 62 } 63 var cache_control = ""; 64 if ("cache_control" in info) { 65 cache_control = "&cache_control=" + info.cache_control; 66 } 67 var redirect = ""; 68 69 var ignore_request_headers = ""; 70 if ("request_headers" in info) { 71 // Ignore the request headers that we send since they may be synthesized by the test. 72 ignore_request_headers = "&ignore"; 73 } 74 var url_sans_redirect = "resources/cache.py?token=" + uuid + 75 "&content=" + content + 76 "&" + id + "=" + value + 77 "&expires=" + dates[info.state] + 78 vary + cache_control + ignore_request_headers; 79 // If there's a redirect, the target is the script without any redirect at 80 // either the same domain or a different domain. 81 if ("redirect" in info) { 82 var host_info = get_host_info(); 83 var origin; 84 switch (info.redirect) { 85 case "same-origin": 86 origin = host_info['HTTP_ORIGIN']; 87 break; 88 case "cross-origin": 89 origin = host_info['HTTP_REMOTE_ORIGIN']; 90 break; 91 } 92 var redirected_url = origin + base_path() + url_sans_redirect; 93 return url_sans_redirect + "&redirect=" + encodeURIComponent(redirected_url); 94 } else { 95 return url_sans_redirect; 96 } 97 } 98 function expected_status(type, identifier, init) { 99 if (type == "date" && 100 init.headers && 101 init.headers["If-Modified-Since"] == identifier) { 102 // The server will respond with a 304 in this case. 103 return [304, "Not Modified"]; 104 } 105 return [200, "OK"]; 106 } 107 function expected_response_text(type, identifier, init, content) { 108 if (type == "date" && 109 init.headers && 110 init.headers["If-Modified-Since"] == identifier) { 111 // The server will respond with a 304 in this case. 112 return ""; 113 } 114 return content; 115 } 116 function server_state(uuid) { 117 return fetch("resources/cache.py?querystate&token=" + uuid) 118 .then(function(response) { 119 return response.text(); 120 }).then(function(text) { 121 // null will be returned if the server never received any requests 122 // for the given uuid. Normalize that to an empty list consistent 123 // with our representation. 124 return JSON.parse(text) || []; 125 }); 126 } 127 function make_test(type, info) { 128 return function(test) { 129 var uuid = token(); 130 var identifier = (type == "tag" ? Math.random() : now.toGMTString()); 131 var content = Math.random().toString(); 132 var url = make_url(uuid, type, identifier, content, info); 133 var fetch_functions = []; 134 for (var i = 0; i < info.request_cache.length; ++i) { 135 fetch_functions.push(function(idx) { 136 var init = {cache: info.request_cache[idx]}; 137 if ("request_headers" in info) { 138 init.headers = info.request_headers[idx]; 139 } 140 if (init.cache === "only-if-cached") { 141 // only-if-cached requires we use same-origin mode. 142 init.mode = "same-origin"; 143 } 144 return fetch(url, init) 145 .then(function(response) { 146 if ("response" in info && info.response[idx] === "error") { 147 assert_true(false, "fetch should have been an error"); 148 return; 149 } 150 assert_array_equals([response.status, response.statusText], 151 expected_status(type, identifier, init)); 152 return response.text(); 153 }).then(function(text) { 154 assert_equals(text, expected_response_text(type, identifier, init, content)); 155 }, function(reason) { 156 if ("response" in info && info.response[idx] === "error") { 157 assert_throws_js(TypeError, function() { throw reason; }); 158 } else { 159 throw reason; 160 } 161 }); 162 }); 163 } 164 var i = 0; 165 function run_next_step() { 166 if (fetch_functions.length) { 167 return fetch_functions.shift()(i++) 168 .then(run_next_step); 169 } else { 170 return Promise.resolve(); 171 } 172 } 173 return run_next_step() 174 .then(function() { 175 // Now, query the server state 176 return server_state(uuid); 177 }).then(function(state) { 178 var expectedState = []; 179 info.expected_validation_headers.forEach(function (validate) { 180 if (validate) { 181 if (type == "tag") { 182 expectedState.push({"If-None-Match": '"' + identifier + '"'}); 183 } else { 184 expectedState.push({"If-Modified-Since": identifier}); 185 } 186 } else { 187 expectedState.push({}); 188 } 189 }); 190 for (var i = 0; i < info.expected_no_cache_headers.length; ++i) { 191 if (info.expected_no_cache_headers[i]) { 192 expectedState[i]["Pragma"] = "no-cache"; 193 expectedState[i]["Cache-Control"] = "no-cache"; 194 } 195 } 196 if ("expected_max_age_headers" in info) { 197 for (var i = 0; i < info.expected_max_age_headers.length; ++i) { 198 if (info.expected_max_age_headers[i]) { 199 expectedState[i]["Cache-Control"] = "max-age=0"; 200 } 201 } 202 } 203 assert_equals(state.length, expectedState.length); 204 for (var i = 0; i < state.length; ++i) { 205 for (var header in state[i]) { 206 assert_equals(state[i][header], expectedState[i][header]); 207 delete expectedState[i][header]; 208 } 209 for (var header in expectedState[i]) { 210 assert_false(header in state[i]); 211 } 212 } 213 }); 214 }; 215 } 216 217 function run_tests(tests) 218 { 219 tests.forEach(function(info) { 220 promise_test(make_test("tag", info), info.name + " with Etag and " + info.state + " response"); 221 promise_test(make_test("date", info), info.name + " with Last-Modified and " + info.state + " response"); 222 }); 223 }