DeviceRequestPrompt.test.ts (16230B)
1 /** 2 * @license 3 * Copyright 2022 Google Inc. 4 * SPDX-License-Identifier: Apache-2.0 5 */ 6 7 import {describe, it} from 'node:test'; 8 9 import expect from 'expect'; 10 11 import type {CDPSessionEvents} from '../api/CDPSession.js'; 12 import {TimeoutError} from '../common/Errors.js'; 13 import {EventEmitter} from '../common/EventEmitter.js'; 14 import {TimeoutSettings} from '../common/TimeoutSettings.js'; 15 16 import { 17 DeviceRequestPrompt, 18 DeviceRequestPromptDevice, 19 DeviceRequestPromptManager, 20 } from './DeviceRequestPrompt.js'; 21 22 class MockCDPSession extends EventEmitter<CDPSessionEvents> { 23 async send(): Promise<any> {} 24 connection() { 25 return undefined; 26 } 27 readonly detached = false; 28 async detach() {} 29 id() { 30 return '1'; 31 } 32 parentSession() { 33 return undefined; 34 } 35 } 36 37 describe('DeviceRequestPrompt', function () { 38 describe('waitForDevicePrompt', function () { 39 it('should return prompt', async () => { 40 const client = new MockCDPSession(); 41 const timeoutSettings = new TimeoutSettings(); 42 const manager = new DeviceRequestPromptManager(client, timeoutSettings); 43 44 const [prompt] = await Promise.all([ 45 manager.waitForDevicePrompt(), 46 (() => { 47 client.emit('DeviceAccess.deviceRequestPrompted', { 48 id: '00000000000000000000000000000000', 49 devices: [], 50 }); 51 })(), 52 ]); 53 expect(prompt).toBeTruthy(); 54 }); 55 56 it('should respect timeout', async () => { 57 const client = new MockCDPSession(); 58 const timeoutSettings = new TimeoutSettings(); 59 const manager = new DeviceRequestPromptManager(client, timeoutSettings); 60 61 await expect( 62 manager.waitForDevicePrompt({timeout: 1}), 63 ).rejects.toBeInstanceOf(TimeoutError); 64 }); 65 66 it('should respect default timeout when there is no custom timeout', async () => { 67 const client = new MockCDPSession(); 68 const timeoutSettings = new TimeoutSettings(); 69 const manager = new DeviceRequestPromptManager(client, timeoutSettings); 70 71 timeoutSettings.setDefaultTimeout(1); 72 await expect(manager.waitForDevicePrompt()).rejects.toBeInstanceOf( 73 TimeoutError, 74 ); 75 }); 76 77 it('should prioritize exact timeout over default timeout', async () => { 78 const client = new MockCDPSession(); 79 const timeoutSettings = new TimeoutSettings(); 80 const manager = new DeviceRequestPromptManager(client, timeoutSettings); 81 82 timeoutSettings.setDefaultTimeout(0); 83 await expect( 84 manager.waitForDevicePrompt({timeout: 1}), 85 ).rejects.toBeInstanceOf(TimeoutError); 86 }); 87 88 it('should work with no timeout', async () => { 89 const client = new MockCDPSession(); 90 const timeoutSettings = new TimeoutSettings(); 91 const manager = new DeviceRequestPromptManager(client, timeoutSettings); 92 93 const [prompt] = await Promise.all([ 94 manager.waitForDevicePrompt({timeout: 0}), 95 (async () => { 96 await new Promise(resolve => { 97 setTimeout(resolve, 50); 98 }); 99 client.emit('DeviceAccess.deviceRequestPrompted', { 100 id: '00000000000000000000000000000000', 101 devices: [], 102 }); 103 })(), 104 ]); 105 expect(prompt).toBeTruthy(); 106 }); 107 108 it('should return the same prompt when there are many watchdogs simultaneously', async () => { 109 const client = new MockCDPSession(); 110 const timeoutSettings = new TimeoutSettings(); 111 const manager = new DeviceRequestPromptManager(client, timeoutSettings); 112 113 const [prompt1, prompt2] = await Promise.all([ 114 manager.waitForDevicePrompt(), 115 manager.waitForDevicePrompt(), 116 (() => { 117 client.emit('DeviceAccess.deviceRequestPrompted', { 118 id: '00000000000000000000000000000000', 119 devices: [], 120 }); 121 })(), 122 ]); 123 expect(prompt1 === prompt2).toBeTruthy(); 124 }); 125 126 it('should listen and shortcut when there are no watchdogs', async () => { 127 const client = new MockCDPSession(); 128 const timeoutSettings = new TimeoutSettings(); 129 const manager = new DeviceRequestPromptManager(client, timeoutSettings); 130 131 client.emit('DeviceAccess.deviceRequestPrompted', { 132 id: '00000000000000000000000000000000', 133 devices: [], 134 }); 135 136 expect(manager).toBeTruthy(); 137 }); 138 }); 139 140 describe('DeviceRequestPrompt.devices', function () { 141 it('lists devices as they arrive', function () { 142 const client = new MockCDPSession(); 143 const timeoutSettings = new TimeoutSettings(); 144 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 145 id: '00000000000000000000000000000000', 146 devices: [], 147 }); 148 149 expect(prompt.devices).toHaveLength(0); 150 client.emit('DeviceAccess.deviceRequestPrompted', { 151 id: '00000000000000000000000000000000', 152 devices: [{id: '00000000', name: 'Device 0'}], 153 }); 154 expect(prompt.devices).toHaveLength(1); 155 client.emit('DeviceAccess.deviceRequestPrompted', { 156 id: '00000000000000000000000000000000', 157 devices: [ 158 {id: '00000000', name: 'Device 0'}, 159 {id: '11111111', name: 'Device 1'}, 160 ], 161 }); 162 expect(prompt.devices).toHaveLength(2); 163 expect(prompt.devices[0]).toBeInstanceOf(DeviceRequestPromptDevice); 164 expect(prompt.devices[1]).toBeInstanceOf(DeviceRequestPromptDevice); 165 }); 166 167 it('does not list devices from events of another prompt', function () { 168 const client = new MockCDPSession(); 169 const timeoutSettings = new TimeoutSettings(); 170 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 171 id: '00000000000000000000000000000000', 172 devices: [], 173 }); 174 175 expect(prompt.devices).toHaveLength(0); 176 client.emit('DeviceAccess.deviceRequestPrompted', { 177 id: '88888888888888888888888888888888', 178 devices: [ 179 {id: '00000000', name: 'Device 0'}, 180 {id: '11111111', name: 'Device 1'}, 181 ], 182 }); 183 expect(prompt.devices).toHaveLength(0); 184 }); 185 }); 186 187 describe('DeviceRequestPrompt.waitForDevice', function () { 188 it('should return first matching device', async () => { 189 const client = new MockCDPSession(); 190 const timeoutSettings = new TimeoutSettings(); 191 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 192 id: '00000000000000000000000000000000', 193 devices: [], 194 }); 195 196 const [device] = await Promise.all([ 197 prompt.waitForDevice(({name}) => { 198 return name.includes('1'); 199 }), 200 (() => { 201 client.emit('DeviceAccess.deviceRequestPrompted', { 202 id: '00000000000000000000000000000000', 203 devices: [{id: '00000000', name: 'Device 0'}], 204 }); 205 client.emit('DeviceAccess.deviceRequestPrompted', { 206 id: '00000000000000000000000000000000', 207 devices: [ 208 {id: '00000000', name: 'Device 0'}, 209 {id: '11111111', name: 'Device 1'}, 210 ], 211 }); 212 })(), 213 ]); 214 expect(device).toBeInstanceOf(DeviceRequestPromptDevice); 215 }); 216 217 it('should return first matching device from already known devices', async () => { 218 const client = new MockCDPSession(); 219 const timeoutSettings = new TimeoutSettings(); 220 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 221 id: '00000000000000000000000000000000', 222 devices: [ 223 {id: '00000000', name: 'Device 0'}, 224 {id: '11111111', name: 'Device 1'}, 225 ], 226 }); 227 228 const device = await prompt.waitForDevice(({name}) => { 229 return name.includes('1'); 230 }); 231 expect(device).toBeInstanceOf(DeviceRequestPromptDevice); 232 }); 233 234 it('should return device in the devices list', async () => { 235 const client = new MockCDPSession(); 236 const timeoutSettings = new TimeoutSettings(); 237 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 238 id: '00000000000000000000000000000000', 239 devices: [], 240 }); 241 242 const [device] = await Promise.all([ 243 prompt.waitForDevice(({name}) => { 244 return name.includes('1'); 245 }), 246 (() => { 247 client.emit('DeviceAccess.deviceRequestPrompted', { 248 id: '00000000000000000000000000000000', 249 devices: [ 250 {id: '00000000', name: 'Device 0'}, 251 {id: '11111111', name: 'Device 1'}, 252 ], 253 }); 254 })(), 255 ]); 256 expect(prompt.devices).toContain(device); 257 }); 258 259 it('should respect timeout', async () => { 260 const client = new MockCDPSession(); 261 const timeoutSettings = new TimeoutSettings(); 262 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 263 id: '00000000000000000000000000000000', 264 devices: [], 265 }); 266 267 await expect( 268 prompt.waitForDevice( 269 ({name}) => { 270 return name.includes('Device'); 271 }, 272 {timeout: 1}, 273 ), 274 ).rejects.toBeInstanceOf(TimeoutError); 275 }); 276 277 it('should respect default timeout when there is no custom timeout', async () => { 278 const client = new MockCDPSession(); 279 const timeoutSettings = new TimeoutSettings(); 280 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 281 id: '00000000000000000000000000000000', 282 devices: [], 283 }); 284 285 timeoutSettings.setDefaultTimeout(1); 286 await expect( 287 prompt.waitForDevice( 288 ({name}) => { 289 return name.includes('Device'); 290 }, 291 {timeout: 1}, 292 ), 293 ).rejects.toBeInstanceOf(TimeoutError); 294 }); 295 296 it('should prioritize exact timeout over default timeout', async () => { 297 const client = new MockCDPSession(); 298 const timeoutSettings = new TimeoutSettings(); 299 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 300 id: '00000000000000000000000000000000', 301 devices: [], 302 }); 303 304 timeoutSettings.setDefaultTimeout(0); 305 await expect( 306 prompt.waitForDevice( 307 ({name}) => { 308 return name.includes('Device'); 309 }, 310 {timeout: 1}, 311 ), 312 ).rejects.toBeInstanceOf(TimeoutError); 313 }); 314 315 it('should work with no timeout', async () => { 316 const client = new MockCDPSession(); 317 const timeoutSettings = new TimeoutSettings(); 318 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 319 id: '00000000000000000000000000000000', 320 devices: [], 321 }); 322 323 const [device] = await Promise.all([ 324 prompt.waitForDevice( 325 ({name}) => { 326 return name.includes('1'); 327 }, 328 {timeout: 0}, 329 ), 330 (() => { 331 client.emit('DeviceAccess.deviceRequestPrompted', { 332 id: '00000000000000000000000000000000', 333 devices: [{id: '00000000', name: 'Device 0'}], 334 }); 335 client.emit('DeviceAccess.deviceRequestPrompted', { 336 id: '00000000000000000000000000000000', 337 devices: [ 338 {id: '00000000', name: 'Device 0'}, 339 {id: '11111111', name: 'Device 1'}, 340 ], 341 }); 342 })(), 343 ]); 344 expect(device).toBeInstanceOf(DeviceRequestPromptDevice); 345 }); 346 347 it('should be able to abort', async () => { 348 const client = new MockCDPSession(); 349 const timeoutSettings = new TimeoutSettings(); 350 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 351 id: '00000000000000000000000000000000', 352 devices: [], 353 }); 354 const abortController = new AbortController(); 355 356 const task = prompt.waitForDevice( 357 () => { 358 return false; 359 }, 360 {signal: abortController.signal}, 361 ); 362 abortController.abort(); 363 await expect(task).rejects.toThrow(/aborted/); 364 }); 365 366 it('should return same device from multiple watchdogs', async () => { 367 const client = new MockCDPSession(); 368 const timeoutSettings = new TimeoutSettings(); 369 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 370 id: '00000000000000000000000000000000', 371 devices: [], 372 }); 373 374 const [device1, device2] = await Promise.all([ 375 prompt.waitForDevice(({name}) => { 376 return name.includes('1'); 377 }), 378 prompt.waitForDevice(({name}) => { 379 return name.includes('1'); 380 }), 381 (() => { 382 client.emit('DeviceAccess.deviceRequestPrompted', { 383 id: '00000000000000000000000000000000', 384 devices: [{id: '00000000', name: 'Device 0'}], 385 }); 386 client.emit('DeviceAccess.deviceRequestPrompted', { 387 id: '00000000000000000000000000000000', 388 devices: [ 389 {id: '00000000', name: 'Device 0'}, 390 {id: '11111111', name: 'Device 1'}, 391 ], 392 }); 393 })(), 394 ]); 395 expect(device1 === device2).toBeTruthy(); 396 }); 397 }); 398 399 describe('DeviceRequestPrompt.select', function () { 400 it('should succeed with listed device', async () => { 401 const client = new MockCDPSession(); 402 const timeoutSettings = new TimeoutSettings(); 403 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 404 id: '00000000000000000000000000000000', 405 devices: [], 406 }); 407 408 const [device] = await Promise.all([ 409 prompt.waitForDevice(({name}) => { 410 return name.includes('1'); 411 }), 412 (() => { 413 client.emit('DeviceAccess.deviceRequestPrompted', { 414 id: '00000000000000000000000000000000', 415 devices: [ 416 {id: '00000000', name: 'Device 0'}, 417 {id: '11111111', name: 'Device 1'}, 418 ], 419 }); 420 })(), 421 ]); 422 await prompt.select(device); 423 }); 424 425 it('should error for device not listed in devices', async () => { 426 const client = new MockCDPSession(); 427 const timeoutSettings = new TimeoutSettings(); 428 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 429 id: '00000000000000000000000000000000', 430 devices: [], 431 }); 432 433 await expect( 434 prompt.select(new DeviceRequestPromptDevice('11111111', 'Device 1')), 435 ).rejects.toThrowError('Cannot select unknown device!'); 436 }); 437 438 it('should fail when selecting prompt twice', async () => { 439 const client = new MockCDPSession(); 440 const timeoutSettings = new TimeoutSettings(); 441 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 442 id: '00000000000000000000000000000000', 443 devices: [], 444 }); 445 446 const [device] = await Promise.all([ 447 prompt.waitForDevice(({name}) => { 448 return name.includes('1'); 449 }), 450 (() => { 451 client.emit('DeviceAccess.deviceRequestPrompted', { 452 id: '00000000000000000000000000000000', 453 devices: [ 454 {id: '00000000', name: 'Device 0'}, 455 {id: '11111111', name: 'Device 1'}, 456 ], 457 }); 458 })(), 459 ]); 460 await prompt.select(device); 461 await expect(prompt.select(device)).rejects.toThrowError( 462 'Cannot select DeviceRequestPrompt which is already handled!', 463 ); 464 }); 465 }); 466 467 describe('DeviceRequestPrompt.cancel', function () { 468 it('should succeed on first call', async () => { 469 const client = new MockCDPSession(); 470 const timeoutSettings = new TimeoutSettings(); 471 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 472 id: '00000000000000000000000000000000', 473 devices: [], 474 }); 475 await prompt.cancel(); 476 }); 477 478 it('should fail when canceling prompt twice', async () => { 479 const client = new MockCDPSession(); 480 const timeoutSettings = new TimeoutSettings(); 481 const prompt = new DeviceRequestPrompt(client, timeoutSettings, { 482 id: '00000000000000000000000000000000', 483 devices: [], 484 }); 485 await prompt.cancel(); 486 await expect(prompt.cancel()).rejects.toThrowError( 487 'Cannot cancel DeviceRequestPrompt which is already handled!', 488 ); 489 }); 490 }); 491 });