test_merinoClient_sessions.js (11291B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 // Test for MerinoClient sessions. 6 7 "use strict"; 8 9 const { MerinoClient } = ChromeUtils.importESModule( 10 "moz-src:///browser/components/urlbar/MerinoClient.sys.mjs" 11 ); 12 13 const { SEARCH_PARAMS } = MerinoClient; 14 15 let gClient; 16 17 add_setup(async () => { 18 gClient = new MerinoClient(); 19 await MerinoTestUtils.server.start(); 20 }); 21 22 // In a single session, all requests should use the same session ID and the 23 // sequence number should be incremented. 24 add_task(async function singleSession() { 25 for (let i = 0; i < 3; i++) { 26 let query = "search" + i; 27 await gClient.fetch({ query }); 28 29 MerinoTestUtils.server.checkAndClearRequests([ 30 { 31 params: { 32 [SEARCH_PARAMS.QUERY]: query, 33 [SEARCH_PARAMS.SEQUENCE_NUMBER]: i, 34 }, 35 }, 36 ]); 37 } 38 39 gClient.resetSession(); 40 }); 41 42 // Different sessions should use different session IDs and the sequence number 43 // should be reset. 44 add_task(async function manySessions() { 45 for (let i = 0; i < 3; i++) { 46 let query = "search" + i; 47 await gClient.fetch({ query }); 48 49 MerinoTestUtils.server.checkAndClearRequests([ 50 { 51 params: { 52 [SEARCH_PARAMS.QUERY]: query, 53 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 0, 54 }, 55 }, 56 ]); 57 58 gClient.resetSession(); 59 } 60 }); 61 62 // Tests two consecutive fetches: 63 // 64 // 1. Start a fetch 65 // 2. Wait for the mock Merino server to receive the request 66 // 3. Start a second fetch before the client receives the response 67 // 68 // The first fetch will be canceled by the second but the sequence number in the 69 // second fetch should still be incremented. 70 add_task(async function twoFetches_wait() { 71 for (let i = 0; i < 3; i++) { 72 // Send the first response after a delay to make sure the client will not 73 // receive it before we start the second fetch. 74 MerinoTestUtils.server.response.delay = UrlbarPrefs.get("merino.timeoutMs"); 75 76 // Start the first fetch but don't wait for it to finish. 77 let requestPromise = MerinoTestUtils.server.waitForNextRequest(); 78 let query1 = "search" + i; 79 gClient.fetch({ query: query1 }); 80 81 // Wait until the first request is received before starting the second 82 // fetch, which will cancel the first. The response doesn't need to be 83 // delayed, so remove it to make the test run faster. 84 await requestPromise; 85 delete MerinoTestUtils.server.response.delay; 86 let query2 = query1 + "again"; 87 await gClient.fetch({ query: query2 }); 88 89 // The sequence number should have been incremented for each fetch. 90 MerinoTestUtils.server.checkAndClearRequests([ 91 { 92 params: { 93 [SEARCH_PARAMS.QUERY]: query1, 94 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i, 95 }, 96 }, 97 { 98 params: { 99 [SEARCH_PARAMS.QUERY]: query2, 100 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i + 1, 101 }, 102 }, 103 ]); 104 } 105 106 gClient.resetSession(); 107 }); 108 109 // Tests two consecutive fetches: 110 // 111 // 1. Start a fetch 112 // 2. Immediately start a second fetch 113 // 114 // The first fetch will be canceled by the second but the sequence number in the 115 // second fetch should still be incremented. 116 add_task(async function twoFetches_immediate() { 117 for (let i = 0; i < 3; i++) { 118 // Send the first response after a delay to make sure the client will not 119 // receive it before we start the second fetch. 120 MerinoTestUtils.server.response.delay = 121 100 * UrlbarPrefs.get("merino.timeoutMs"); 122 123 // Start the first fetch but don't wait for it to finish. 124 let query1 = "search" + i; 125 gClient.fetch({ query: query1 }); 126 127 // Immediately do a second fetch that cancels the first. The response 128 // doesn't need to be delayed, so remove it to make the test run faster. 129 delete MerinoTestUtils.server.response.delay; 130 let query2 = query1 + "again"; 131 await gClient.fetch({ query: query2 }); 132 133 // The sequence number should have been incremented for each fetch, but the 134 // first won't have reached the server since it was immediately canceled. 135 MerinoTestUtils.server.checkAndClearRequests([ 136 { 137 params: { 138 [SEARCH_PARAMS.QUERY]: query2, 139 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i + 1, 140 }, 141 }, 142 ]); 143 } 144 145 gClient.resetSession(); 146 }); 147 148 // When a network error occurs, the sequence number should still be incremented. 149 add_task(async function networkError() { 150 for (let i = 0; i < 3; i++) { 151 // Do a fetch that fails with a network error. 152 let query1 = "search" + i; 153 await MerinoTestUtils.server.withNetworkError(async () => { 154 await gClient.fetch({ query: query1 }); 155 }); 156 157 Assert.equal( 158 gClient.lastFetchStatus, 159 "network_error", 160 "The request failed with a network error" 161 ); 162 163 // Do another fetch that successfully finishes. 164 let query2 = query1 + "again"; 165 await gClient.fetch({ query: query2 }); 166 167 Assert.equal( 168 gClient.lastFetchStatus, 169 "success", 170 "The request completed successfully" 171 ); 172 173 // Only the second request should have been received but the sequence number 174 // should have been incremented for each. 175 MerinoTestUtils.server.checkAndClearRequests([ 176 { 177 params: { 178 [SEARCH_PARAMS.QUERY]: query2, 179 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i + 1, 180 }, 181 }, 182 ]); 183 } 184 185 gClient.resetSession(); 186 }); 187 188 // When the server returns a response with an HTTP error, the sequence number 189 // should be incremented. 190 add_task(async function httpError() { 191 for (let i = 0; i < 3; i++) { 192 // Do a fetch that fails with an HTTP error. 193 MerinoTestUtils.server.response.status = 500; 194 let query1 = "search" + i; 195 await gClient.fetch({ query: query1 }); 196 197 Assert.equal( 198 gClient.lastFetchStatus, 199 "http_error", 200 "The last request failed with a network error" 201 ); 202 203 // Do another fetch that successfully finishes. 204 MerinoTestUtils.server.response.status = 200; 205 let query2 = query1 + "again"; 206 await gClient.fetch({ query: query2 }); 207 208 Assert.equal( 209 gClient.lastFetchStatus, 210 "success", 211 "The last request completed successfully" 212 ); 213 214 // Both requests should have been received and the sequence number should 215 // have been incremented for each. 216 MerinoTestUtils.server.checkAndClearRequests([ 217 { 218 params: { 219 [SEARCH_PARAMS.QUERY]: query1, 220 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i, 221 }, 222 }, 223 { 224 params: { 225 [SEARCH_PARAMS.QUERY]: query2, 226 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i + 1, 227 }, 228 }, 229 ]); 230 231 MerinoTestUtils.server.reset(); 232 } 233 234 gClient.resetSession(); 235 }); 236 237 // When the client times out waiting for a response but later receives it and no 238 // other fetch happens in the meantime, the sequence number should be 239 // incremented. 240 add_task(async function clientTimeout_wait() { 241 for (let i = 0; i < 3; i++) { 242 // Do a fetch that causes the client to time out. 243 MerinoTestUtils.server.response.delay = 244 2 * UrlbarPrefs.get("merino.timeoutMs"); 245 let responsePromise = gClient.waitForNextResponse(); 246 let query1 = "search" + i; 247 await gClient.fetch({ query: query1 }); 248 249 Assert.equal( 250 gClient.lastFetchStatus, 251 "timeout", 252 "The last request failed with a client timeout" 253 ); 254 255 // Wait for the client to receive the response. 256 await responsePromise; 257 258 // Do another fetch that successfully finishes. 259 delete MerinoTestUtils.server.response.delay; 260 let query2 = query1 + "again"; 261 await gClient.fetch({ query: query2 }); 262 263 Assert.equal( 264 gClient.lastFetchStatus, 265 "success", 266 "The last request completed successfully" 267 ); 268 269 MerinoTestUtils.server.checkAndClearRequests([ 270 { 271 params: { 272 [SEARCH_PARAMS.QUERY]: query1, 273 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i, 274 }, 275 }, 276 { 277 params: { 278 [SEARCH_PARAMS.QUERY]: query2, 279 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i + 1, 280 }, 281 }, 282 ]); 283 } 284 285 gClient.resetSession(); 286 }); 287 288 // When the client times out waiting for a response and a second fetch starts 289 // before the response is received, the first fetch should be canceled but the 290 // sequence number should still be incremented. 291 add_task(async function clientTimeout_canceled() { 292 for (let i = 0; i < 3; i++) { 293 // Do a fetch that causes the client to time out. 294 MerinoTestUtils.server.response.delay = 295 2 * UrlbarPrefs.get("merino.timeoutMs"); 296 let query1 = "search" + i; 297 await gClient.fetch({ query: query1 }); 298 299 Assert.equal( 300 gClient.lastFetchStatus, 301 "timeout", 302 "The last request failed with a client timeout" 303 ); 304 305 // Do another fetch that successfully finishes. 306 delete MerinoTestUtils.server.response.delay; 307 let query2 = query1 + "again"; 308 await gClient.fetch({ query: query2 }); 309 310 Assert.equal( 311 gClient.lastFetchStatus, 312 "success", 313 "The last request completed successfully" 314 ); 315 316 MerinoTestUtils.server.checkAndClearRequests([ 317 { 318 params: { 319 [SEARCH_PARAMS.QUERY]: query1, 320 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i, 321 }, 322 }, 323 { 324 params: { 325 [SEARCH_PARAMS.QUERY]: query2, 326 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 2 * i + 1, 327 }, 328 }, 329 ]); 330 } 331 332 gClient.resetSession(); 333 }); 334 335 // When the session times out, the next fetch should use a new session ID and 336 // the sequence number should be reset. 337 add_task(async function sessionTimeout() { 338 // Set the session timeout to something reasonable to test. 339 let originalTimeoutMs = gClient.sessionTimeoutMs; 340 gClient.sessionTimeoutMs = 500; 341 342 // Do a fetch. 343 let query1 = "search"; 344 await gClient.fetch({ query: query1 }); 345 346 // Wait for the session to time out. 347 await gClient.waitForNextSessionReset(); 348 349 Assert.strictEqual( 350 gClient.sessionID, 351 null, 352 "sessionID is null after session timeout" 353 ); 354 Assert.strictEqual( 355 gClient.sequenceNumber, 356 0, 357 "sequenceNumber is zero after session timeout" 358 ); 359 Assert.strictEqual( 360 gClient._test_sessionTimer, 361 null, 362 "sessionTimer is null after session timeout" 363 ); 364 365 // Do another fetch. 366 let query2 = query1 + "again"; 367 await gClient.fetch({ query: query2 }); 368 369 // The second request's sequence number should be zero due to the session 370 // timeout. 371 MerinoTestUtils.server.checkAndClearRequests([ 372 { 373 params: { 374 [SEARCH_PARAMS.QUERY]: query1, 375 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 0, 376 }, 377 }, 378 { 379 params: { 380 [SEARCH_PARAMS.QUERY]: query2, 381 [SEARCH_PARAMS.SEQUENCE_NUMBER]: 0, 382 }, 383 }, 384 ]); 385 386 Assert.ok( 387 gClient.sessionID, 388 "sessionID is non-null after first request in a new session" 389 ); 390 Assert.equal( 391 gClient.sequenceNumber, 392 1, 393 "sequenceNumber is one after first request in a new session" 394 ); 395 Assert.ok( 396 gClient._test_sessionTimer, 397 "sessionTimer is non-null after first request in a new session" 398 ); 399 400 gClient.sessionTimeoutMs = originalTimeoutMs; 401 gClient.resetSession(); 402 });