1/* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17import { 18 HttpRequest, 19 HttpRequestHeaderType, 20 HttpRequestStatus, 21 HttpResponse, 22} from 'common/http_request'; 23import {UserNotification} from 'messaging/user_notification'; 24import { 25 ProxyTracingErrors, 26 ProxyTracingWarnings, 27} from 'messaging/user_warnings'; 28import {UserNotifierChecker} from 'test/unit/user_notifier_checker'; 29import {waitToBeCalled} from 'test/utils'; 30import {AdbDevice} from 'trace_collection/adb_device'; 31import {ConnectionState} from 'trace_collection/connection_state'; 32import {ProxyConnection} from 'trace_collection/proxy_connection'; 33import {ProxyEndpoint} from './proxy_endpoint'; 34import {TraceRequest} from './trace_request'; 35 36type HttpRequestGetType = ( 37 path: string, 38 headers: HttpRequestHeaderType, 39 type?: XMLHttpRequest['responseType'], 40) => Promise<HttpResponse>; 41 42type HttpRequestPostType = ( 43 path: string, 44 headers: HttpRequestHeaderType, 45 jsonRequest?: object, 46) => Promise<HttpResponse>; 47 48describe('ProxyConnection', () => { 49 const detectStateChangesInUi = jasmine.createSpy(); 50 const configOptionsChangeCallback = jasmine.createSpy(); 51 const availableTracesChangeCallback = jasmine.createSpy(); 52 const mockDevice: AdbDevice = { 53 id: '35562', 54 model: 'Pixel 6', 55 authorized: true, 56 displays: ['"Test Display" 12345 Extra Info'], 57 multiDisplayScreenRecordingAvailable: false, 58 }; 59 const mockTraceRequest: TraceRequest = { 60 name: 'layers_trace', 61 config: [], 62 }; 63 const getVersionHeader = () => ProxyConnection.VERSION; 64 const successfulEndTraceResponse: HttpResponse = { 65 status: HttpRequestStatus.SUCCESS, 66 type: '', 67 text: '[]', 68 body: '[]', 69 getHeader: getVersionHeader, 70 }; 71 72 let connection: ProxyConnection; 73 let getSpy: jasmine.Spy<HttpRequestGetType>; 74 let postSpy: jasmine.Spy<HttpRequestPostType>; 75 76 describe('server not found', () => { 77 const unsentResponse: HttpResponse = { 78 status: HttpRequestStatus.UNSENT, 79 type: '', 80 text: '', 81 body: undefined, 82 getHeader: getVersionHeader, 83 }; 84 85 beforeEach(async () => { 86 await setUpTestEnvironment(unsentResponse); 87 }); 88 89 afterEach(() => { 90 localStorage.clear(); 91 }); 92 93 it('requests devices on initialization', () => { 94 checkGetDevicesRequest(); 95 expect(connection.getState()).toEqual(ConnectionState.NOT_FOUND); 96 }); 97 98 it('requests devices on restarting connection', async () => { 99 resetSpies(); 100 await connection.restartConnection(); 101 checkGetDevicesRequest(); 102 expect(connection.getState()).toEqual(ConnectionState.NOT_FOUND); 103 }); 104 105 it('posts start trace request to proxy and updates state if proxy not found', async () => { 106 const requestObj = [mockTraceRequest]; 107 await connection.startTrace(mockDevice, requestObj); 108 expect(postSpy).toHaveBeenCalledOnceWith( 109 ProxyConnection.WINSCOPE_PROXY_URL + 110 ProxyEndpoint.START_TRACE + 111 `${mockDevice.id}/`, 112 [['Winscope-Token', '']], 113 requestObj, 114 ); 115 expect(connection.getState()).toEqual(ConnectionState.NOT_FOUND); 116 }); 117 }); 118 119 describe('unsuccessful request', () => { 120 afterEach(() => { 121 localStorage.clear(); 122 }); 123 124 it('unauthorized server', async () => { 125 const unauthResponse: HttpResponse = { 126 status: HttpRequestStatus.UNAUTH, 127 type: '', 128 text: '', 129 body: undefined, 130 getHeader: getVersionHeader, 131 }; 132 await setUpTestEnvironment(unauthResponse); 133 expect(connection.getState()).toEqual(ConnectionState.UNAUTH); 134 }); 135 136 it('invalid version - header undefined', async () => { 137 await checkInvalidVersion(() => undefined); 138 }); 139 140 it('invalid version - old major', async () => { 141 await checkInvalidVersion(() => '0.0.0'); 142 }); 143 144 it('invalid version - old minor', async () => { 145 const [major, minor, patch] = ProxyConnection.VERSION.split('.'); 146 await checkInvalidVersion(() => 147 [major, Number(minor) - 1, patch].join('.'), 148 ); 149 }); 150 151 it('invalid version - old patch', async () => { 152 const [major, minor, patch] = ProxyConnection.VERSION.split('.'); 153 await checkInvalidVersion(() => 154 [major, minor, Number(patch) - 1].join('.'), 155 ); 156 }); 157 158 it('error state with response type text', async () => { 159 const errorResponse: HttpResponse = { 160 status: HttpRequestStatus.ERROR, 161 type: 'text', 162 text: 'test error message', 163 body: undefined, 164 getHeader: getVersionHeader, 165 }; 166 await setUpTestEnvironment(errorResponse); 167 expect(connection.getState()).toEqual(ConnectionState.ERROR); 168 expect(connection.getErrorText()).toEqual(errorResponse.text); 169 }); 170 171 it('error state with response type empty', async () => { 172 const errorResponse: HttpResponse = { 173 status: HttpRequestStatus.ERROR, 174 type: '', 175 text: 'test error message', 176 body: undefined, 177 getHeader: getVersionHeader, 178 }; 179 await setUpTestEnvironment(errorResponse); 180 expect(connection.getState()).toEqual(ConnectionState.ERROR); 181 expect(connection.getErrorText()).toEqual(errorResponse.text); 182 }); 183 184 it('error state with response type array buffer', async () => { 185 const errorResponse: HttpResponse = { 186 status: HttpRequestStatus.ERROR, 187 type: 'arraybuffer', 188 text: '', 189 body: [], 190 getHeader: getVersionHeader, 191 }; 192 await setUpTestEnvironment(errorResponse); 193 expect(connection.getState()).toEqual(ConnectionState.ERROR); 194 }); 195 196 async function checkInvalidVersion(getHeader: () => string | undefined) { 197 const invalidResponse: HttpResponse = { 198 status: HttpRequestStatus.SUCCESS, 199 type: '', 200 text: '', 201 body: undefined, 202 getHeader, 203 }; 204 await setUpTestEnvironment(invalidResponse); 205 expect(connection.getState()).toEqual(ConnectionState.INVALID_VERSION); 206 } 207 }); 208 209 describe('successful responses to tracing process', () => { 210 let userNotifierChecker: UserNotifierChecker; 211 212 beforeAll(() => { 213 userNotifierChecker = new UserNotifierChecker(); 214 }); 215 216 beforeEach(async () => { 217 userNotifierChecker.reset(); 218 const successfulResponse: HttpResponse = { 219 status: HttpRequestStatus.SUCCESS, 220 type: '', 221 text: 'True', 222 body: undefined, 223 getHeader: getVersionHeader, 224 }; 225 await setUpTestEnvironment(successfulResponse); 226 }); 227 228 afterEach(() => { 229 localStorage.clear(); 230 }); 231 232 it('uses stored token on initialization', async () => { 233 resetSpies(); 234 connection.setSecurityToken('test_initial_token'); 235 connection = await createProxyConnection(); 236 checkGetDevicesRequest('test_initial_token'); 237 }); 238 239 it('sets security token and sends as header', async () => { 240 resetSpies(); 241 connection.setSecurityToken('test_token'); 242 await connection.restartConnection(); 243 checkGetDevicesRequest('test_token'); 244 }); 245 246 it('does not set empty token', async () => { 247 connection.setSecurityToken('test_initial_token'); 248 connection = await createProxyConnection(); 249 resetSpies(); 250 connection.setSecurityToken(''); 251 await connection.restartConnection(); 252 checkGetDevicesRequest('test_initial_token'); 253 }); 254 255 it('throws error on startTrace if no traces requested', async () => { 256 await expectAsync( 257 connection.startTrace(mockDevice, []), 258 ).toBeRejectedWithError('No traces requested'); 259 }); 260 261 it('throws error on endTrace if no traces requested', async () => { 262 await expectAsync(connection.endTrace()).toBeRejectedWithError( 263 'Trace not started before stopping', 264 ); 265 }); 266 267 it('throws error on dumpState if no dumps requested', async () => { 268 await expectAsync( 269 connection.dumpState(mockDevice, []), 270 ).toBeRejectedWithError('No dumps requested'); 271 }); 272 273 it('posts start trace request to proxy and updates state to tracing', async () => { 274 await checkSuccessfulTraceRequest([mockTraceRequest]); 275 }); 276 277 it('posts start trace requests without updating screen recording config', async () => { 278 const requestEmptyStrDisplays: TraceRequest[] = [ 279 { 280 name: 'screen_recording', 281 config: [{key: 'displays', value: ''}], 282 }, 283 ]; 284 await checkSuccessfulTraceRequest(requestEmptyStrDisplays); 285 286 const requestEmptyArrDisplays: TraceRequest[] = [ 287 { 288 name: 'screen_recording', 289 config: [{key: 'displays', value: []}], 290 }, 291 ]; 292 await checkSuccessfulTraceRequest(requestEmptyArrDisplays); 293 294 const requestDisplayStrWithoutName: TraceRequest[] = [ 295 { 296 name: 'screen_recording', 297 config: [{key: 'displays', value: '12345 Other Info'}], 298 }, 299 ]; 300 await checkSuccessfulTraceRequest(requestDisplayStrWithoutName); 301 302 const requestDisplayArrWithoutName: TraceRequest[] = [ 303 { 304 name: 'screen_recording', 305 config: [{key: 'displays', value: ['12345 Other Info']}], 306 }, 307 ]; 308 await checkSuccessfulTraceRequest(requestDisplayArrWithoutName); 309 }); 310 311 it('posts start trace requests with updated screen recording config', async () => { 312 const requestDisplayStrWithName: TraceRequest[] = [ 313 { 314 name: 'screen_recording', 315 config: [{key: 'displays', value: '"Test Display" 12345 Other Info'}], 316 }, 317 ]; 318 await checkSuccessfulTraceRequest(requestDisplayStrWithName, [ 319 { 320 name: 'screen_recording', 321 config: [{key: 'displays', value: '12345 Other Info'}], 322 }, 323 ]); 324 325 const requestDisplayArrWithName: TraceRequest[] = [ 326 { 327 name: 'screen_recording', 328 config: [ 329 {key: 'displays', value: ['"Test Display" 12345 Other Info']}, 330 ], 331 }, 332 ]; 333 await checkSuccessfulTraceRequest(requestDisplayArrWithName, [ 334 { 335 name: 'screen_recording', 336 config: [{key: 'displays', value: ['12345 Other Info']}], 337 }, 338 ]); 339 }); 340 341 it('posts start trace request to proxy and handles response with warnings', async () => { 342 postSpy.and.returnValue( 343 Promise.resolve({ 344 status: HttpRequestStatus.SUCCESS, 345 type: '', 346 text: '', 347 body: '["test warning"]', 348 getHeader: getVersionHeader, 349 }), 350 ); 351 await checkSuccessfulTraceRequest( 352 [mockTraceRequest], 353 [mockTraceRequest], 354 [new ProxyTracingWarnings(['test warning'])], 355 ); 356 }); 357 358 it('handles trace timeout', async () => { 359 const requestObj = [mockTraceRequest]; 360 getSpy.and.returnValue( 361 Promise.resolve({ 362 status: HttpRequestStatus.SUCCESS, 363 type: '', 364 text: 'False', 365 body: undefined, 366 getHeader: getVersionHeader, 367 }), 368 ); 369 postSpy.and.returnValue( 370 Promise.resolve({ 371 status: HttpRequestStatus.SUCCESS, 372 type: '', 373 text: 'True', 374 body: '[]', 375 getHeader: getVersionHeader, 376 }), 377 ); 378 await connection.startTrace(mockDevice, requestObj); 379 380 expect(postSpy).toHaveBeenCalledWith( 381 ProxyConnection.WINSCOPE_PROXY_URL + 382 ProxyEndpoint.START_TRACE + 383 `${mockDevice.id}/`, 384 [['Winscope-Token', '']], 385 requestObj, 386 ); 387 expect(postSpy).toHaveBeenCalledWith( 388 ProxyConnection.WINSCOPE_PROXY_URL + 389 ProxyEndpoint.END_TRACE + 390 `${mockDevice.id}/`, 391 [['Winscope-Token', '']], 392 undefined, 393 ); 394 expect(connection.getState()).toEqual(ConnectionState.TRACE_TIMEOUT); 395 }); 396 397 it('posts end trace request to proxy and handles response without errors', async () => { 398 await startAndEndTrace(successfulEndTraceResponse); 399 checkTraceEndedSuccessfully(); 400 userNotifierChecker.expectNone(); 401 }); 402 403 it('posts end trace request to proxy and handles response with errors', async () => { 404 await startAndEndTrace({ 405 status: HttpRequestStatus.SUCCESS, 406 type: '', 407 text: '["please check your display state", "b\'unknown error\'"]', 408 body: '["please check your display state", "b\'unknown error\'"]', 409 getHeader: getVersionHeader, 410 }); 411 checkTraceEndedSuccessfully(); 412 userNotifierChecker.expectAdded([ 413 new ProxyTracingErrors([ 414 'please check your display state (must be on at start of trace)', 415 "'unknown error'", 416 ]), 417 ]); 418 }); 419 420 it('posts end trace request to proxy and handles non-serializable errors', async () => { 421 await startAndEndTrace({ 422 status: HttpRequestStatus.SUCCESS, 423 type: '', 424 text: '["please check your display state", "b\'unknown error\'"]', 425 body: undefined, 426 getHeader: getVersionHeader, 427 }); 428 expect(postSpy).toHaveBeenCalledOnceWith( 429 ProxyConnection.WINSCOPE_PROXY_URL + 430 ProxyEndpoint.END_TRACE + 431 `${mockDevice.id}/`, 432 [['Winscope-Token', '']], 433 undefined, 434 ); 435 expect(connection.getState()).toEqual(ConnectionState.ERROR); 436 expect(connection.getErrorText()).toContain( 437 'Error handling request response', 438 ); 439 }); 440 441 it('posts dump state request to proxy', async () => { 442 await checkSuccessfulDumpRequest([mockTraceRequest]); 443 }); 444 445 it('posts dump requests without updating screenshot config', async () => { 446 const requestEmptyArrDisplays: TraceRequest[] = [ 447 { 448 name: 'screenshot', 449 config: [{key: 'displays', value: []}], 450 }, 451 ]; 452 await checkSuccessfulDumpRequest(requestEmptyArrDisplays); 453 454 const requestDisplayArrWithoutName: TraceRequest[] = [ 455 { 456 name: 'screenshot', 457 config: [{key: 'displays', value: ['12345 Other Info']}], 458 }, 459 ]; 460 await checkSuccessfulDumpRequest(requestDisplayArrWithoutName); 461 }); 462 463 it('posts dump requests with updated screenshot config', async () => { 464 const requestDisplayArrWithName: TraceRequest[] = [ 465 { 466 name: 'screenshot', 467 config: [ 468 {key: 'displays', value: ['"Test Display" 12345 Other Info']}, 469 ], 470 }, 471 ]; 472 await checkSuccessfulDumpRequest(requestDisplayArrWithName, [ 473 { 474 name: 'screenshot', 475 config: [{key: 'displays', value: ['12345 Other Info']}], 476 }, 477 ]); 478 }); 479 480 it('posts dump request and handles response with warnings', async () => { 481 postSpy.and.returnValue( 482 Promise.resolve({ 483 status: HttpRequestStatus.SUCCESS, 484 type: '', 485 text: '', 486 body: '["test warning"]', 487 getHeader: getVersionHeader, 488 }), 489 ); 490 await checkSuccessfulDumpRequest( 491 [mockTraceRequest], 492 [mockTraceRequest], 493 [new ProxyTracingWarnings(['test warning'])], 494 ); 495 }); 496 497 function checkTraceEndedSuccessfully() { 498 expect(postSpy).toHaveBeenCalledOnceWith( 499 ProxyConnection.WINSCOPE_PROXY_URL + 500 ProxyEndpoint.END_TRACE + 501 `${mockDevice.id}/`, 502 [['Winscope-Token', '']], 503 undefined, 504 ); 505 expect(connection.getState()).toEqual(ConnectionState.ENDING_TRACE); 506 } 507 508 async function checkSuccessfulTraceRequest( 509 requests: TraceRequest[], 510 updatedRequests = requests, 511 warnings?: UserNotification[], 512 ) { 513 postSpy.calls.reset(); 514 await connection.startTrace(mockDevice, requests); 515 516 expect(postSpy).toHaveBeenCalledOnceWith( 517 ProxyConnection.WINSCOPE_PROXY_URL + 518 ProxyEndpoint.START_TRACE + 519 `${mockDevice.id}/`, 520 [['Winscope-Token', '']], 521 updatedRequests, 522 ); 523 expect(connection.getState()).toEqual(ConnectionState.TRACING); 524 if (warnings !== undefined) { 525 userNotifierChecker.expectNotified(warnings); 526 } else { 527 userNotifierChecker.expectNone(); 528 } 529 } 530 531 async function checkSuccessfulDumpRequest( 532 requests: TraceRequest[], 533 updatedRequests = requests, 534 warnings?: UserNotification[], 535 ) { 536 postSpy.calls.reset(); 537 await connection.dumpState(mockDevice, requests); 538 539 expect(postSpy).toHaveBeenCalledOnceWith( 540 ProxyConnection.WINSCOPE_PROXY_URL + 541 ProxyEndpoint.DUMP + 542 `${mockDevice.id}/`, 543 [['Winscope-Token', '']], 544 updatedRequests, 545 ); 546 expect(connection.getState()).toEqual(ConnectionState.DUMPING_STATE); 547 if (warnings !== undefined) { 548 userNotifierChecker.expectNotified(warnings); 549 } else { 550 userNotifierChecker.expectNone(); 551 } 552 } 553 }); 554 555 describe('wayland trace availability', () => { 556 beforeEach(() => { 557 availableTracesChangeCallback.calls.reset(); 558 }); 559 560 afterEach(() => { 561 localStorage.clear(); 562 }); 563 564 it('updates availability of wayland trace if available', async () => { 565 const successfulResponse: HttpResponse = { 566 status: HttpRequestStatus.SUCCESS, 567 type: '', 568 text: 'true', 569 body: undefined, 570 getHeader: getVersionHeader, 571 }; 572 await setUpTestEnvironment(successfulResponse); 573 expect(availableTracesChangeCallback).toHaveBeenCalledOnceWith([ 574 'wayland_trace', 575 ]); 576 }); 577 578 it('does not update availability of traces if call fails', async () => { 579 const unsuccessfulResponse: HttpResponse = { 580 status: HttpRequestStatus.SUCCESS, 581 type: '', 582 text: 'false', 583 body: undefined, 584 getHeader: getVersionHeader, 585 }; 586 await setUpTestEnvironment(unsuccessfulResponse); 587 expect(availableTracesChangeCallback).not.toHaveBeenCalled(); 588 }); 589 }); 590 591 describe('finding devices', () => { 592 const successfulResponse: HttpResponse = { 593 status: HttpRequestStatus.SUCCESS, 594 type: 'text', 595 text: JSON.stringify({ 596 '35562': { 597 authorized: mockDevice.authorized, 598 model: mockDevice.model, 599 displays: ['Display 12345 Extra Info displayName="Test Display"'], 600 }, 601 }), 602 body: undefined, 603 getHeader: getVersionHeader, 604 }; 605 606 afterEach(() => { 607 localStorage.clear(); 608 }); 609 610 it('sets error state if onSuccess callback fails', async () => { 611 const noDevicesResponse: HttpResponse = { 612 status: HttpRequestStatus.SUCCESS, 613 type: 'arraybuffer', 614 text: '[0,]', 615 body: undefined, 616 getHeader: getVersionHeader, 617 }; 618 await setUpTestEnvironment(noDevicesResponse); 619 checkGetDevicesRequest(); 620 expect(connection.getState()).toEqual(ConnectionState.ERROR); 621 expect(connection.getErrorText()).toEqual( 622 'Could not find devices. Received:\n[0,]', 623 ); 624 }); 625 626 it('fetches devices', async () => { 627 await setUpTestEnvironment(successfulResponse); 628 checkGetDevicesRequest(); 629 expect(connection.getState()).toEqual(ConnectionState.IDLE); 630 expect(connection.getDevices()).toEqual([mockDevice]); 631 }); 632 633 it('sets up worker to fetch devices', async () => { 634 await setUpTestEnvironment(successfulResponse); 635 checkGetDevicesRequest(); 636 expect(connection.getState()).toEqual(ConnectionState.IDLE); 637 expect(connection.getDevices()).toEqual([mockDevice]); 638 expect(getSpy).toHaveBeenCalledWith( 639 ProxyConnection.WINSCOPE_PROXY_URL + ProxyEndpoint.CHECK_WAYLAND, 640 [['Winscope-Token', '']], 641 undefined, 642 ); 643 644 getSpy.calls.reset(); 645 detectStateChangesInUi.calls.reset(); 646 647 await waitToBeCalled(detectStateChangesInUi, 1); 648 await waitToBeCalled(getSpy, 1); 649 checkGetDevicesRequest(); 650 expect(connection.getState()).toEqual(ConnectionState.IDLE); 651 expect(connection.getDevices()).toEqual([mockDevice]); 652 expect(getSpy).not.toHaveBeenCalledWith( 653 ProxyConnection.WINSCOPE_PROXY_URL + ProxyEndpoint.CHECK_WAYLAND, 654 [['Winscope-Token', '']], 655 undefined, 656 ); 657 }); 658 659 it('handles missing displayName', async () => { 660 const response: HttpResponse = { 661 status: HttpRequestStatus.SUCCESS, 662 type: 'text', 663 text: JSON.stringify({ 664 '35562': { 665 authorized: mockDevice.authorized, 666 model: mockDevice.model, 667 displays: ['Display 12345 Extra Info'], 668 }, 669 }), 670 body: undefined, 671 getHeader: getVersionHeader, 672 }; 673 674 await setUpTestEnvironment(response); 675 checkGetDevicesRequest(); 676 expect(connection.getState()).toEqual(ConnectionState.IDLE); 677 expect(connection.getDevices()).toEqual([ 678 { 679 id: '35562', 680 model: 'Pixel 6', 681 authorized: true, 682 displays: ['12345 Extra Info'], 683 multiDisplayScreenRecordingAvailable: false, 684 }, 685 ]); 686 }); 687 688 it('updates multi display screen recording availability for incompatible version', async () => { 689 const oldVersionResponse: HttpResponse = { 690 status: HttpRequestStatus.SUCCESS, 691 type: 'text', 692 text: JSON.stringify({ 693 '35562': { 694 authorized: mockDevice.authorized, 695 model: mockDevice.model, 696 displays: ['Display 12345 Extra Info displayName="Test Display"'], 697 screenrecord_version: '1.3', 698 }, 699 }), 700 body: undefined, 701 getHeader: getVersionHeader, 702 }; 703 await setUpTestEnvironment(oldVersionResponse); 704 checkGetDevicesRequest(); 705 expect(connection.getState()).toEqual(ConnectionState.IDLE); 706 expect(connection.getDevices()).toEqual([mockDevice]); 707 }); 708 709 it('updates multi display screen recording availability for compatible version', async () => { 710 const compatibleVersionResponse: HttpResponse = { 711 status: HttpRequestStatus.SUCCESS, 712 type: 'text', 713 text: JSON.stringify({ 714 '35562': { 715 authorized: mockDevice.authorized, 716 model: mockDevice.model, 717 displays: ['Display 12345 Extra Info displayName="Test Display"'], 718 screenrecord_version: '1.4', 719 }, 720 }), 721 body: undefined, 722 getHeader: getVersionHeader, 723 }; 724 const mockDeviceWithMultiDisplayScreenRecording: AdbDevice = { 725 id: '35562', 726 model: 'Pixel 6', 727 authorized: true, 728 displays: ['"Test Display" 12345 Extra Info'], 729 multiDisplayScreenRecordingAvailable: true, 730 }; 731 732 await setUpTestEnvironment(compatibleVersionResponse); 733 checkGetDevicesRequest(); 734 expect(connection.getState()).toEqual(ConnectionState.IDLE); 735 expect(connection.getDevices()).toEqual([ 736 mockDeviceWithMultiDisplayScreenRecording, 737 ]); 738 }); 739 }); 740 741 describe('files', () => { 742 const testFileArray = [window.btoa('[20]')]; 743 const testFile = new File(testFileArray, 'test_file'); 744 745 const successfulResponse: HttpResponse = { 746 status: HttpRequestStatus.SUCCESS, 747 type: 'arraybuffer', 748 text: 'True', 749 body: new TextEncoder().encode( 750 JSON.stringify({'test_file': testFileArray}), 751 ), 752 getHeader: getVersionHeader, 753 }; 754 afterEach(() => { 755 localStorage.clear(); 756 }); 757 758 it('sets error state if fetching files fails', async () => { 759 const successfulResponse: HttpResponse = { 760 status: HttpRequestStatus.SUCCESS, 761 type: 'arraybuffer', 762 text: 'False', 763 body: new TextEncoder().encode('[0,]'), 764 getHeader: getVersionHeader, 765 }; 766 await setUpTestEnvironment(successfulResponse); 767 resetSpies(); 768 await connection.fetchLastTracingSessionData(mockDevice); 769 770 expect(getSpy).toHaveBeenCalledOnceWith( 771 ProxyConnection.WINSCOPE_PROXY_URL + 772 ProxyEndpoint.FETCH + 773 `${mockDevice.id}/`, 774 [['Winscope-Token', '']], 775 'arraybuffer', 776 ); 777 expect(connection.getState()).toEqual(ConnectionState.ERROR); 778 expect(connection.getErrorText()).toEqual( 779 'Could not fetch files. Received:\nFalse', 780 ); 781 }); 782 783 it('fetches last tracing session data without ongoing tracing', async () => { 784 await setUpTestEnvironment(successfulResponse); 785 resetSpies(); 786 const files = await connection.fetchLastTracingSessionData(mockDevice); 787 788 expect(getSpy).toHaveBeenCalledOnceWith( 789 ProxyConnection.WINSCOPE_PROXY_URL + 790 ProxyEndpoint.FETCH + 791 `${mockDevice.id}/`, 792 [['Winscope-Token', '']], 793 'arraybuffer', 794 ); 795 expect(connection.getState()).toEqual(ConnectionState.LOADING_DATA); 796 expect(files).toEqual([testFile]); 797 }); 798 799 it('fetches last tracing session data from ongoing tracing', async () => { 800 await setUpTestEnvironment(successfulResponse); 801 await startAndEndTrace(successfulEndTraceResponse); 802 resetSpies(); 803 const files = await connection.fetchLastTracingSessionData(mockDevice); 804 805 expect(getSpy).toHaveBeenCalledOnceWith( 806 ProxyConnection.WINSCOPE_PROXY_URL + 807 ProxyEndpoint.FETCH + 808 `${mockDevice.id}/`, 809 [['Winscope-Token', '']], 810 'arraybuffer', 811 ); 812 expect(connection.getState()).toEqual(ConnectionState.LOADING_DATA); 813 expect(files).toEqual([testFile]); 814 }); 815 }); 816 817 async function setUpTestEnvironment(response: HttpResponse) { 818 getSpy = spyOn(HttpRequest, 'get').and.returnValue( 819 Promise.resolve(response), 820 ); 821 postSpy = spyOn(HttpRequest, 'post').and.returnValue( 822 Promise.resolve(response), 823 ); 824 connection = await createProxyConnection(); 825 } 826 827 function checkGetDevicesRequest(header = '') { 828 expect(getSpy).toHaveBeenCalledWith( 829 ProxyConnection.WINSCOPE_PROXY_URL + ProxyEndpoint.DEVICES, 830 [['Winscope-Token', header]], 831 undefined, 832 ); 833 } 834 835 async function startAndEndTrace(endingTraceResponse: HttpResponse) { 836 await connection.startTrace(mockDevice, [mockTraceRequest]); 837 resetSpies(); 838 postSpy.and.returnValue(Promise.resolve(endingTraceResponse)); 839 await connection.endTrace(); 840 } 841 842 function resetSpies() { 843 getSpy.calls.reset(); 844 postSpy.calls.reset(); 845 } 846 847 async function createProxyConnection() { 848 const connection = new ProxyConnection(); 849 await connection.initialize( 850 detectStateChangesInUi, 851 availableTracesChangeCallback, 852 configOptionsChangeCallback, 853 ); 854 return connection; 855 } 856}); 857