http-cache.js (9369B)
1 /* global btoa fetch token promise_test step_timeout */ 2 /* global assert_equals assert_true assert_own_property assert_throws_js assert_less_than */ 3 4 const templates = { 5 'fresh': { 6 'response_headers': [ 7 ['Expires', 100000], 8 ['Last-Modified', 0] 9 ] 10 }, 11 'stale': { 12 'response_headers': [ 13 ['Expires', -5000], 14 ['Last-Modified', -100000] 15 ] 16 }, 17 'lcl_response': { 18 'response_headers': [ 19 ['Location', 'location_target'], 20 ['Content-Location', 'content_location_target'] 21 ] 22 }, 23 'location': { 24 'query_arg': 'location_target', 25 'response_headers': [ 26 ['Expires', 100000], 27 ['Last-Modified', 0] 28 ] 29 }, 30 'content_location': { 31 'query_arg': 'content_location_target', 32 'response_headers': [ 33 ['Expires', 100000], 34 ['Last-Modified', 0] 35 ] 36 } 37 } 38 39 const noBodyStatus = new Set([204, 304]) 40 41 function makeTest (test) { 42 return function () { 43 var uuid = token() 44 var requests = expandTemplates(test) 45 var fetchFunctions = makeFetchFunctions(requests, uuid) 46 return runTest(fetchFunctions, test, requests, uuid) 47 } 48 } 49 50 function makeFetchFunctions(requests, uuid) { 51 var fetchFunctions = [] 52 for (let i = 0; i < requests.length; ++i) { 53 var config = requests[i]; 54 if (config.skip) { 55 // Skip request are ones that we expect the browser to make in 56 // response to a redirect. We don't fetch them again, but 57 // the server needs them in the config to be able to respond to 58 // them. 59 continue; 60 } 61 fetchFunctions.push({ 62 code: function (idx) { 63 var config = requests[idx] 64 var url = makeTestUrl(uuid, config); 65 var init = fetchInit(requests, config) 66 return fetch(url, init) 67 .then(makeCheckResponse(idx, config)) 68 .then(makeCheckResponseBody(config, uuid), function (reason) { 69 if ('expected_type' in config && config.expected_type === 'error') { 70 assert_throws_js(TypeError, function () { throw reason }) 71 } else { 72 throw reason 73 } 74 }) 75 }, 76 pauseAfter: 'pause_after' in requests[i] 77 }) 78 } 79 return fetchFunctions 80 } 81 82 function runTest(fetchFunctions, test, requests, uuid) { 83 var idx = 0 84 function runNextStep () { 85 if (fetchFunctions.length) { 86 var nextFetchFunction = fetchFunctions.shift() 87 if (nextFetchFunction.pauseAfter === true) { 88 return nextFetchFunction.code(idx++) 89 .then(pause) 90 .then(runNextStep) 91 } else { 92 return nextFetchFunction.code(idx++) 93 .then(runNextStep) 94 } 95 } else { 96 return Promise.resolve() 97 } 98 } 99 100 return runNextStep() 101 .then(function () { 102 return getServerState(uuid) 103 }).then(function (testState) { 104 checkRequests(test, requests, testState) 105 return Promise.resolve() 106 }) 107 } 108 109 function expandTemplates (test) { 110 var rawRequests = test.requests 111 var requests = [] 112 for (let i = 0; i < rawRequests.length; i++) { 113 var request = rawRequests[i] 114 request.name = test.name 115 if ('template' in request) { 116 var template = templates[request['template']] 117 for (let member in template) { 118 if (!request.hasOwnProperty(member)) { 119 request[member] = template[member] 120 } 121 } 122 } 123 requests.push(request) 124 } 125 return requests 126 } 127 128 function fetchInit (requests, config) { 129 var init = { 130 'headers': [] 131 } 132 if ('request_method' in config) init.method = config['request_method'] 133 // Note: init.headers must be a copy of config['request_headers'] array, 134 // because new elements are added later. 135 if ('request_headers' in config) init.headers = [...config['request_headers']]; 136 if ('name' in config) init.headers.push(['Test-Name', config.name]) 137 if ('request_body' in config) init.body = config['request_body'] 138 if ('mode' in config) init.mode = config['mode'] 139 if ('credentials' in config) init.credentials = config['credentials'] 140 if ('cache' in config) init.cache = config['cache'] 141 init.headers.push(['Test-Requests', btoa(JSON.stringify(requests))]) 142 return init 143 } 144 145 function makeCheckResponse (idx, config) { 146 return function checkResponse (response) { 147 var reqNum = idx + 1 148 var resNum = parseInt(response.headers.get('Server-Request-Count')) 149 if ('expected_type' in config) { 150 if (config.expected_type === 'error') { 151 assert_true(false, `Request ${reqNum} doesn't throw an error`) 152 return response.text() 153 } 154 if (config.expected_type === 'cached') { 155 assert_less_than(resNum, reqNum, `Response ${reqNum} does not come from cache`) 156 } 157 if (config.expected_type === 'not_cached') { 158 assert_equals(resNum, reqNum, `Response ${reqNum} comes from cache`) 159 } 160 } 161 if ('expected_status' in config) { 162 assert_equals(response.status, config.expected_status, 163 `Response ${reqNum} status is ${response.status}, not ${config.expected_status}`) 164 } else if ('response_status' in config && config.response_status[0] != 301) { 165 assert_equals(response.status, config.response_status[0], 166 `Response ${reqNum} status is ${response.status}, not ${config.response_status[0]}`) 167 } else { 168 assert_equals(response.status, 200, `Response ${reqNum} status is ${response.status}, not 200`) 169 } 170 if ('response_headers' in config) { 171 config.response_headers.forEach(function (header) { 172 if (header.len < 3 || header[2] === true) { 173 assert_equals(response.headers.get(header[0]), header[1], 174 `Response ${reqNum} header ${header[0]} is "${response.headers.get(header[0])}", not "${header[1]}"`) 175 } 176 }) 177 } 178 if ('expected_response_headers' in config) { 179 config.expected_response_headers.forEach(function (header) { 180 assert_equals(response.headers.get(header[0]), header[1], 181 `Response ${reqNum} header ${header[0]} is "${response.headers.get(header[0])}", not "${header[1]}"`) 182 }) 183 } 184 return response.text() 185 } 186 } 187 188 function makeCheckResponseBody (config, uuid) { 189 return function checkResponseBody (resBody) { 190 var statusCode = 200 191 if ('response_status' in config) { 192 statusCode = config.response_status[0] 193 } 194 if ('expected_response_text' in config) { 195 if (config.expected_response_text !== null) { 196 assert_equals(resBody, config.expected_response_text, 197 `Response body is "${resBody}", not expected "${config.expected_response_text}"`) 198 } 199 } else if ('response_body' in config && config.response_body !== null) { 200 assert_equals(resBody, config.response_body, 201 `Response body is "${resBody}", not sent "${config.response_body}"`) 202 } else if (!noBodyStatus.has(statusCode)) { 203 assert_equals(resBody, uuid, `Response body is "${resBody}", not default "${uuid}"`) 204 } 205 } 206 } 207 208 function checkRequests (test, requests, testState) { 209 var testIdx = 0 210 for (let i = 0; i < requests.length; ++i) { 211 var expectedValidatingHeaders = [] 212 var config = requests[i] 213 var serverRequest = testState[testIdx] 214 var reqNum = i + 1 215 if ('expected_type' in config) { 216 if (config.expected_type === 'cached') continue // the server will not see the request 217 if (config.expected_type === 'etag_validated') { 218 expectedValidatingHeaders.push('if-none-match') 219 } 220 if (config.expected_type === 'lm_validated') { 221 expectedValidatingHeaders.push('if-modified-since') 222 } 223 } 224 testIdx++ 225 expectedValidatingHeaders.forEach(vhdr => { 226 assert_own_property(serverRequest.request_headers, vhdr, 227 `request ${reqNum} doesn't have ${vhdr} header`) 228 }) 229 if ('expected_request_headers' in config) { 230 config.expected_request_headers.forEach(expectedHdr => { 231 assert_equals(serverRequest.request_headers[expectedHdr[0].toLowerCase()], expectedHdr[1], 232 `request ${reqNum} header ${expectedHdr[0]} value is "${serverRequest.request_headers[expectedHdr[0].toLowerCase()]}", not "${expectedHdr[1]}"`) 233 }) 234 } 235 } 236 if (test?.check_count && testState) { 237 assert_equals(requests.length, testState.length); 238 } 239 } 240 241 function pause () { 242 return new Promise(function (resolve, reject) { 243 step_timeout(function () { 244 return resolve() 245 }, 3000) 246 }) 247 } 248 249 function makeTestUrl (uuid, config) { 250 var arg = '' 251 var base_url = '' 252 if ('base_url' in config) { 253 base_url = config.base_url 254 } 255 if ('query_arg' in config) { 256 arg = `&target=${config.query_arg}` 257 } 258 if ('url_params' in config) { 259 arg = `${arg}&${config.url_params}` 260 } 261 return `${base_url}resources/http-cache.py?dispatch=test&uuid=${uuid}${arg}` 262 } 263 264 function getServerState (uuid) { 265 return fetch(`resources/http-cache.py?dispatch=state&uuid=${uuid}`) 266 .then(function (response) { 267 return response.text() 268 }).then(function (text) { 269 return JSON.parse(text) || [] 270 }) 271 } 272 273 function run_tests (tests) { 274 tests.forEach(function (test) { 275 promise_test(makeTest(test), test.name) 276 }) 277 } 278 279 var contentStore = {} 280 function http_content (csKey) { 281 if (csKey in contentStore) { 282 return contentStore[csKey] 283 } else { 284 var content = btoa(Math.random() * Date.now()) 285 contentStore[csKey] = content 286 return content 287 } 288 }