@xenon-device-management/xenon 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/lib/package.json +3 -4
- package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-XpewX5nn.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-BQDv3ZCC.js} +1 -1
- package/lib/public/assets/BugReportButton-C7OmS61B.js +10 -0
- package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-wl3l2Zlt.js} +1 -1
- package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-fPxujqqE.js} +1 -1
- package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-5bCutWrs.js} +1 -1
- package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-owsBiDT1.js} +1 -1
- package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-DFhqzPSt.js} +1 -1
- package/lib/public/assets/{Modal-CceoMY7D.js → Modal-Bw-a-E-e.js} +1 -1
- package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-WtByf5bC.js} +1 -1
- package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-FpWWFNgT.js} +1 -1
- package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-Jzn9c7o8.js} +1 -1
- package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-BR7nOhpT.js} +1 -1
- package/lib/public/assets/{Table-LV7zFyZg.js → Table-B_Va3V05.js} +1 -1
- package/lib/public/assets/{activity-D17TUloi.js → activity-DxRFWvkN.js} +1 -1
- package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-CQf-9_V6.js} +1 -1
- package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-SoE1Kd7N.js} +1 -1
- package/lib/public/assets/{apps-Dbnzd0fW.js → apps-CgGXTlgn.js} +1 -1
- package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-uQ6WIwiC.js} +1 -1
- package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-C9RyfzXS.js} +1 -1
- package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-DFHi_8yG.js} +1 -1
- package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-Bii1t4qc.js} +2 -2
- package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-Bnv8Z9hi.js} +1 -1
- package/lib/public/assets/{button-xtAhYR-f.js → button-_Y78wXiP.js} +1 -1
- package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-D4MqSOc2.js} +1 -1
- package/lib/public/assets/{check-CyuEoKkd.js → check-uGHCXe-L.js} +1 -1
- package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-Dy1OqpzB.js} +1 -1
- package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-BUsldEli.js} +1 -1
- package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-D3Qq0tKp.js} +1 -1
- package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-B4IOD1DE.js} +1 -1
- package/lib/public/assets/{clock-Cd-t7xpe.js → clock-BfYfzvSg.js} +1 -1
- package/lib/public/assets/{copy-C6zerX3I.js → copy-DZwYE5Va.js} +1 -1
- package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CRG4Utt5.js} +3 -3
- package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
- package/lib/public/assets/{download-x9WP7mcL.js → download-CCDYo_TN.js} +1 -1
- package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-DuLCmVFb.js} +1 -1
- package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-CtKNPqzb.js} +1 -1
- package/lib/public/assets/{index-BR1wTIPC.js → index-3uxl3o7K.js} +43 -43
- package/lib/public/assets/{index-BvH_ekRy.js → index-Zy1Op5C1.js} +1 -1
- package/lib/public/assets/{input-DejcCiTI.js → input-CzTWvUXZ.js} +1 -1
- package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CI-nYDFM.js} +1 -1
- package/lib/public/assets/{layers-BKqYLiHE.js → layers-YZZGFyTX.js} +1 -1
- package/lib/public/assets/{lock-LFsFhc17.js → lock-L8tzWgDo.js} +1 -1
- package/lib/public/assets/{login-x2pkzt-t.js → login-DBbX-fu8.js} +1 -1
- package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-AJwkOIsE.js} +1 -1
- package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-Di_66F2m.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-Cnab2zdg.js} +1 -1
- package/lib/public/assets/{network-Z_zBhpsD.js → network-DiSumY-1.js} +1 -1
- package/lib/public/assets/{overview-BY5uv08S.js → overview-CwE4gXl5.js} +1 -1
- package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CfR4Sm9V.js} +1 -1
- package/lib/public/assets/{plus-BTD0dnQ0.js → plus-DOS8P9CQ.js} +1 -1
- package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-BYpHwQIH.js} +1 -1
- package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BRVJU_qV.js} +1 -1
- package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Bi3JvAFZ.js} +1 -1
- package/lib/public/assets/{select-Uc9E5pye.js → select-Cqx7pEou.js} +1 -1
- package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C0DsncKj.js} +1 -1
- package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CAhpdilF.js} +1 -1
- package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CrCVeW2m.js} +1 -1
- package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-F_hwYqEt.js} +1 -1
- package/lib/public/assets/{settings-CkSWiZq8.js → settings-C-s5pypL.js} +1 -1
- package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-DY5uzv6A.js} +1 -1
- package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-Dli7gwyg.js} +1 -1
- package/lib/public/assets/{teams-PpoEQz31.js → teams-ui4IhHNC.js} +1 -1
- package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV4hSmXJ.js} +1 -1
- package/lib/public/assets/{upload-C3i7sAZl.js → upload-DkGy_J-L.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-BFtMqQOh.js} +1 -1
- package/lib/public/assets/{users-Drd7ifJx.js → users-BU0nXIVe.js} +1 -1
- package/lib/public/assets/{users-CEJ3fC78.js → users-Nb_rzRmF.js} +1 -1
- package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-CUNzD2kH.js} +1 -1
- package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-Ca3UFXZe.js} +1 -1
- package/lib/public/assets/{zap-4yN8Jdfw.js → zap-B2bH2vKX.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/InternalHttpClient.js +5 -1
- package/lib/src/XenonCapabilityManager.js +15 -1
- package/lib/src/app/appiumBasePath.js +32 -0
- package/lib/src/app/index.js +12 -0
- package/lib/src/app/insertBeforeRoutes.js +24 -4
- package/lib/src/app/registerCommandAuth.js +152 -33
- package/lib/src/app/routers/control.js +11 -104
- package/lib/src/app/routers/grid.js +28 -8
- package/lib/src/app/routers/nodePhoneControl.js +301 -0
- package/lib/src/app/ws/h264StreamWs.js +43 -12
- package/lib/src/app/ws/logcatWs.js +48 -14
- package/lib/src/app/ws/nodeSocketRelay.js +167 -0
- package/lib/src/app/ws/upgradeRouter.js +215 -0
- package/lib/src/dashboard/event-manager.js +7 -6
- package/lib/src/data-service/device-service.js +152 -49
- package/lib/src/data-service/device-store.js +92 -10
- package/lib/src/data-service/deviceClaims.js +116 -0
- package/lib/src/data-service/deviceFieldOwners.js +32 -10
- package/lib/src/data-service/prisma-store.js +102 -31
- package/lib/src/device-managers/AndroidDeviceManager.js +40 -42
- package/lib/src/device-managers/HealthMonitorService.js +17 -2
- package/lib/src/device-managers/IOSDeviceManager.js +5 -1
- package/lib/src/device-managers/NodeDevices.js +7 -2
- package/lib/src/device-managers/android/AndroidH264StreamService.js +13 -5
- package/lib/src/device-managers/android/AndroidStreamService.js +9 -6
- package/lib/src/device-managers/android/previewHold.js +2 -2
- package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
- package/lib/src/device-managers/ios/IOSStreamService.js +162 -40
- package/lib/src/device-managers/localDeviceHosts.js +86 -0
- package/lib/src/device-managers/ownDeviceRow.js +38 -0
- package/lib/src/device-utils.js +78 -24
- package/lib/src/gateway/createHandoff.js +73 -0
- package/lib/src/gateway/defaultGateway.js +80 -0
- package/lib/src/gateway/forwardToNode.js +147 -0
- package/lib/src/gateway/hubSessionToken.js +356 -0
- package/lib/src/gateway/internalCall.js +90 -0
- package/lib/src/gateway/nodeSessionStatus.js +140 -0
- package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
- package/lib/src/gateway/sessionCreate.js +136 -0
- package/lib/src/gateway/sessionGateway.js +201 -0
- package/lib/src/gateway/sessionLocator.js +100 -0
- package/lib/src/generated/client/edge.js +7 -4
- package/lib/src/generated/client/index-browser.js +4 -1
- package/lib/src/generated/client/index.d.ts +112 -1
- package/lib/src/generated/client/index.js +7 -4
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +9 -0
- package/lib/src/generated/client/wasm.js +4 -1
- package/lib/src/helpers/index.js +5 -1
- package/lib/src/logger.js +5 -0
- package/lib/src/middleware/authMiddleware.js +80 -0
- package/lib/src/middleware/commandAuth.js +31 -17
- package/lib/src/middleware/csrfMiddleware.js +3 -2
- package/lib/src/middleware/deviceAccessGuard.js +4 -1
- package/lib/src/middleware/sessionListingFilter.js +153 -0
- package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
- package/lib/src/plugin.js +27 -1
- package/lib/src/prisma.js +2 -1
- package/lib/src/services/DeviceReconciler.js +5 -2
- package/lib/src/services/OrphanSweeper.js +12 -19
- package/lib/src/services/PortAllocator.js +75 -16
- package/lib/src/services/ServerManager.js +63 -30
- package/lib/src/services/SessionLifecycleService.js +335 -76
- package/lib/src/services/ShutdownCoordinator.js +7 -1
- package/lib/src/services/SocketServer.js +11 -2
- package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
- package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
- package/lib/src/services/device-access/ticketActorAccess.js +3 -1
- package/lib/src/services/lease/LeaseOrphanSweeper.js +34 -6
- package/lib/src/services/lease/LeaseService.js +19 -5
- package/lib/src/services/session/nodeCreateCaps.js +55 -0
- package/lib/src/services/session/sessionCredentials.js +7 -59
- package/lib/src/sessions/CloudSession.js +12 -0
- package/lib/src/sessions/LocalSession.js +52 -26
- package/lib/src/sessions/RemoteSession.js +116 -9
- package/lib/src/sessions/SessionManager.js +49 -6
- package/lib/src/sessions/appiumUmbrella.js +32 -0
- package/lib/test/helpers/appium-umbrella.js +84 -0
- package/lib/test/helpers/fake-device-table.js +97 -0
- package/lib/test/helpers/loki-stores.js +41 -0
- package/lib/test/helpers/loopbackRequest.js +77 -0
- package/lib/test/helpers/own-node-id.js +29 -0
- package/lib/test/helpers/scratch-database.js +100 -0
- package/lib/test/helpers/scratch-port-leases.js +119 -0
- package/lib/test/helpers/test-container.js +35 -27
- package/lib/test/integration/androidDevices.spec.js +5 -4
- package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
- package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
- package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
- package/lib/test/setup/scratch-default-database.js +32 -0
- package/lib/test/unit/AndroidDeviceManager.spec.js +56 -1
- package/lib/test/unit/DeviceModel.spec.js +9 -9
- package/lib/test/unit/HealingCascade.test.js +5 -1
- package/lib/test/unit/OrphanSweeper.test.js +17 -13
- package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
- package/lib/test/unit/StreamTicketService.spec.js +4 -1
- package/lib/test/unit/TokenEndpoint.spec.js +17 -3
- package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
- package/lib/test/unit/android-h264-service.spec.js +24 -0
- package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
- package/lib/test/unit/apps-team-visibility.spec.js +16 -16
- package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
- package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
- package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
- package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
- package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
- package/lib/test/unit/authMiddleware.test.js +3 -0
- package/lib/test/unit/bug-report/route.spec.js +9 -9
- package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
- package/lib/test/unit/clipboard-route.spec.js +22 -8
- package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
- package/lib/test/unit/command-auth.spec.js +31 -31
- package/lib/test/unit/control-device-lookup.spec.js +9 -9
- package/lib/test/unit/control-forward-decision.spec.js +115 -0
- package/lib/test/unit/control-lookup-failure.spec.js +3 -3
- package/lib/test/unit/create-error-message.spec.js +43 -0
- package/lib/test/unit/create-handoff.spec.js +86 -0
- package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
- package/lib/test/unit/device-access-guard.spec.js +66 -28
- package/lib/test/unit/device-service.spec.js +4 -0
- package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
- package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
- package/lib/test/unit/device-team-guard.spec.js +20 -20
- package/lib/test/unit/device-utils.spec.js +8 -0
- package/lib/test/unit/extract-team-cap.spec.js +65 -0
- package/lib/test/unit/grid-admin-only.spec.js +3 -3
- package/lib/test/unit/healing-tier-gate.spec.js +5 -1
- package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
- package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
- package/lib/test/unit/helpers.spec.js +27 -0
- package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
- package/lib/test/unit/hub-create-once.spec.js +190 -0
- package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
- package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
- package/lib/test/unit/hub-node-control-forward.spec.js +608 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
- package/lib/test/unit/hub-node-race.spec.js +426 -0
- package/lib/test/unit/hub-node-same-host.spec.js +356 -0
- package/lib/test/unit/hub-register-removal.spec.js +165 -0
- package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
- package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
- package/lib/test/unit/hub-session-token.spec.js +265 -0
- package/lib/test/unit/insert-before-routes.spec.js +5 -5
- package/lib/test/unit/install-repository-app-team.spec.js +6 -2
- package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
- package/lib/test/unit/internal-call.spec.js +181 -0
- package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
- package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
- package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
- package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
- package/lib/test/unit/is-xenon-running.spec.js +69 -0
- package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
- package/lib/test/unit/lease/LeaseService.spec.js +28 -1
- package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
- package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +107 -0
- package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
- package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
- package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
- package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
- package/lib/test/unit/logcat-ws.spec.js +12 -1
- package/lib/test/unit/loopback-request.spec.js +81 -0
- package/lib/test/unit/mark-node-busy-loki.spec.js +53 -0
- package/lib/test/unit/node-create-caps.spec.js +54 -0
- package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
- package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
- package/lib/test/unit/node-phone-control.spec.js +131 -0
- package/lib/test/unit/node-socket-relay.spec.js +357 -0
- package/lib/test/unit/omni-vision.spec.js +7 -3
- package/lib/test/unit/plugin.spec.js +45 -4
- package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
- package/lib/test/unit/profile-router.test.js +11 -11
- package/lib/test/unit/queue-requester.spec.js +5 -0
- package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
- package/lib/test/unit/queue-visibility.spec.js +1 -1
- package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
- package/lib/test/unit/recording-instrumentation.spec.js +5 -0
- package/lib/test/unit/recording-source-ended.spec.js +5 -0
- package/lib/test/unit/recording-store.spec.js +9 -3
- package/lib/test/unit/recordings-library-routes.spec.js +51 -51
- package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
- package/lib/test/unit/remote-session-create.spec.js +130 -0
- package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
- package/lib/test/unit/reset-link.test.js +14 -14
- package/lib/test/unit/session-app-resolution.spec.js +14 -10
- package/lib/test/unit/session-assets.spec.js +9 -9
- package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
- package/lib/test/unit/session-create-layer.spec.js +255 -0
- package/lib/test/unit/session-credential-strip.spec.js +25 -9
- package/lib/test/unit/session-credentials.spec.js +0 -47
- package/lib/test/unit/session-listing-auth.spec.js +345 -0
- package/lib/test/unit/session-locator.spec.js +250 -0
- package/lib/test/unit/session-node-calls.spec.js +174 -0
- package/lib/test/unit/session-owner-resolver.spec.js +107 -0
- package/lib/test/unit/session-recovery-routing.spec.js +118 -0
- package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
- package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
- package/lib/test/unit/socketServer.bearer.spec.js +3 -1
- package/lib/test/unit/stream-leave-route.spec.js +19 -7
- package/lib/test/unit/stream-own-device-row.spec.js +164 -0
- package/lib/test/unit/stream-stop-ownership.spec.js +17 -5
- package/lib/test/unit/stream-stop-recording.spec.js +17 -5
- package/lib/test/unit/stream-ticket-identity.spec.js +26 -6
- package/lib/test/unit/stream-watchdog-timers.spec.js +46 -0
- package/lib/test/unit/upgrade-router.spec.js +446 -0
- package/lib/test/unit/users-router.test.js +11 -8
- package/lib/test/unit/verify-credential.spec.js +4 -1
- package/lib/test/unit/webdriver-info-route.spec.js +64 -0
- package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -4
- package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
- package/prisma/schema.prisma +9 -0
- package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
- package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
- package/lib/src/proxy/wd-command-proxy.js +0 -221
|
@@ -49,7 +49,7 @@ require("reflect-metadata");
|
|
|
49
49
|
const chai_1 = require("chai");
|
|
50
50
|
const sinon_1 = __importDefault(require("sinon"));
|
|
51
51
|
const express_1 = __importDefault(require("express"));
|
|
52
|
-
const
|
|
52
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
53
53
|
const fs = __importStar(require("fs"));
|
|
54
54
|
const os = __importStar(require("os"));
|
|
55
55
|
const path = __importStar(require("path"));
|
|
@@ -198,7 +198,7 @@ describe('recordings library routes', () => {
|
|
|
198
198
|
rec({ id: 'b', group_id: 'g2', started_at: new Date(T0 + 1000) }),
|
|
199
199
|
rec({ id: 'c', group_id: 'g3', device_udid: 'HIDDEN', started_by: 'usr_carol' }),
|
|
200
200
|
];
|
|
201
|
-
const res = yield (0,
|
|
201
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
|
|
202
202
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
203
203
|
(0, chai_1.expect)(res.body.recordings.map((s) => s.groupId)).to.deep.equal(['g2', 'g1']);
|
|
204
204
|
(0, chai_1.expect)(res.body.recordings[0].phones[0].name).to.equal('Galaxy S9+');
|
|
@@ -211,15 +211,15 @@ describe('recordings library routes', () => {
|
|
|
211
211
|
rec({ id: 'a', group_id: 'g1' }),
|
|
212
212
|
rec({ id: 'b', group_id: 'g2', device_udid: 'U2', started_at: new Date(T0 + 1000) }),
|
|
213
213
|
];
|
|
214
|
-
const one = yield (0,
|
|
214
|
+
const one = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings?limit=1');
|
|
215
215
|
(0, chai_1.expect)(one.body.recordings.map((s) => s.groupId)).to.deep.equal(['g2']);
|
|
216
|
-
const two = yield (0,
|
|
216
|
+
const two = yield (0, loopbackRequest_1.default)(buildApp(alice)).get(`/xenon/api/recordings?limit=1&cursor=${encodeURIComponent(one.body.nextCursor)}`);
|
|
217
217
|
(0, chai_1.expect)(two.body.recordings.map((s) => s.groupId)).to.deep.equal(['g1']);
|
|
218
|
-
const byPhone = yield (0,
|
|
218
|
+
const byPhone = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings?udid=U2');
|
|
219
219
|
(0, chai_1.expect)(byPhone.body.recordings.map((s) => s.groupId)).to.deep.equal(['g2']);
|
|
220
220
|
}));
|
|
221
221
|
it('answers 400 for a bad query', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
222
|
-
const res = yield (0,
|
|
222
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings?since=nope');
|
|
223
223
|
(0, chai_1.expect)(res.status).to.equal(400);
|
|
224
224
|
(0, chai_1.expect)(res.body.error).to.equal('since must be an ISO time');
|
|
225
225
|
}));
|
|
@@ -238,7 +238,7 @@ describe('recordings library routes', () => {
|
|
|
238
238
|
started_at: new Date(now - 40 * 24 * 60 * 60 * 1000),
|
|
239
239
|
}),
|
|
240
240
|
];
|
|
241
|
-
const res = yield (0,
|
|
241
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
|
|
242
242
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
243
243
|
(0, chai_1.expect)(res.body.facets.phones).to.deep.equal([
|
|
244
244
|
{ udid: 'U1', name: 'Galaxy S9+', count: 1 },
|
|
@@ -250,7 +250,7 @@ describe('recordings library routes', () => {
|
|
|
250
250
|
}));
|
|
251
251
|
it('names the synthetic user of an auth-disabled server "Auth disabled"', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
252
252
|
rows = [rec({ started_by: 'auth-disabled' })];
|
|
253
|
-
const res = yield (0,
|
|
253
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(admin)).get('/xenon/api/recordings');
|
|
254
254
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
255
255
|
(0, chai_1.expect)(res.body.recordings[0].startedBy).to.deep.equal({
|
|
256
256
|
id: 'auth-disabled',
|
|
@@ -266,7 +266,7 @@ describe('recordings library routes', () => {
|
|
|
266
266
|
ctx.pluginArgs = Object.assign(Object.assign({}, original), { recordingCleanupDays: 7, recordingCleanupMaxCount: 20 });
|
|
267
267
|
try {
|
|
268
268
|
rows = [rec()];
|
|
269
|
-
const res = yield (0,
|
|
269
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
|
|
270
270
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
271
271
|
(0, chai_1.expect)(res.body.retention).to.deep.equal({ days: 7, maxCount: 20 });
|
|
272
272
|
}
|
|
@@ -296,7 +296,7 @@ describe('recordings library routes', () => {
|
|
|
296
296
|
],
|
|
297
297
|
}),
|
|
298
298
|
];
|
|
299
|
-
const res = yield (0,
|
|
299
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
|
|
300
300
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
301
301
|
(0, chai_1.expect)(res.body.summary.groupId).to.equal('g1');
|
|
302
302
|
(0, chai_1.expect)(res.body.recordings).to.have.length(1);
|
|
@@ -312,9 +312,9 @@ describe('recordings library routes', () => {
|
|
|
312
312
|
});
|
|
313
313
|
}));
|
|
314
314
|
it('answers 404 for a group that does not exist or is not visible', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
315
|
-
(0, chai_1.expect)((yield (0,
|
|
315
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(admin)).get('/xenon/api/recordings/nope')).status).to.equal(404);
|
|
316
316
|
rows = [rec({ device_udid: 'HIDDEN' })];
|
|
317
|
-
(0, chai_1.expect)((yield (0,
|
|
317
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
|
|
318
318
|
}));
|
|
319
319
|
it('sorts bookmarks and marks by timecode across every recording in the group', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
320
320
|
rows = [
|
|
@@ -357,7 +357,7 @@ describe('recordings library routes', () => {
|
|
|
357
357
|
],
|
|
358
358
|
}),
|
|
359
359
|
];
|
|
360
|
-
const res = yield (0,
|
|
360
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
|
|
361
361
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
362
362
|
(0, chai_1.expect)(res.body.bookmarks.map((b) => b.id)).to.deep.equal(['b1', 'b2']);
|
|
363
363
|
(0, chai_1.expect)(res.body.annotations.map((a) => a.id)).to.deep.equal(['a1', 'a2']);
|
|
@@ -388,8 +388,8 @@ describe('recordings library routes', () => {
|
|
|
388
388
|
});
|
|
389
389
|
/** hasComposite as the list, then the detail, reports it. */
|
|
390
390
|
const reported = (who) => __awaiter(void 0, void 0, void 0, function* () {
|
|
391
|
-
const list = yield (0,
|
|
392
|
-
const detail = yield (0,
|
|
391
|
+
const list = yield (0, loopbackRequest_1.default)(buildApp(who)).get('/xenon/api/recordings');
|
|
392
|
+
const detail = yield (0, loopbackRequest_1.default)(buildApp(who)).get('/xenon/api/recordings/g1');
|
|
393
393
|
(0, chai_1.expect)(list.status, JSON.stringify(list.body)).to.equal(200);
|
|
394
394
|
(0, chai_1.expect)(detail.status, JSON.stringify(detail.body)).to.equal(200);
|
|
395
395
|
return [list.body.recordings[0].hasComposite, detail.body.summary.hasComposite];
|
|
@@ -419,7 +419,7 @@ describe('recordings library routes', () => {
|
|
|
419
419
|
});
|
|
420
420
|
});
|
|
421
421
|
const enabled = (who) => __awaiter(void 0, void 0, void 0, function* () {
|
|
422
|
-
const res = yield (0,
|
|
422
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(who)).get('/xenon/api/recordings/active');
|
|
423
423
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
424
424
|
return res.body.groups.map((g) => [g.groupId, g.compositeEnabled]);
|
|
425
425
|
});
|
|
@@ -435,7 +435,7 @@ describe('recordings library routes', () => {
|
|
|
435
435
|
devices.set('U2', { name: 'Team B phone', platform: 'android' });
|
|
436
436
|
visible = new Set(['U1']);
|
|
437
437
|
rows = rows.map((r) => (Object.assign(Object.assign({}, r), { annotations: [{ id: `a-${r.id}`, t_ms: 1, kind: 'rect', color: '#f00', payload: '{}' }] })));
|
|
438
|
-
const res = yield (0,
|
|
438
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/active');
|
|
439
439
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
440
440
|
const [group] = res.body.groups;
|
|
441
441
|
(0, chai_1.expect)(group.recordings.map((r) => r.udid)).to.deep.equal(['U1']);
|
|
@@ -452,14 +452,14 @@ describe('recordings library routes', () => {
|
|
|
452
452
|
});
|
|
453
453
|
}),
|
|
454
454
|
});
|
|
455
|
-
const res = yield (0,
|
|
455
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(admin)).get('/xenon/api/recordings/active');
|
|
456
456
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
457
457
|
(0, chai_1.expect)(res.body.groups[0].recordings.map((r) => r.udid)).to.deep.equal(['U1', 'U2']);
|
|
458
458
|
}));
|
|
459
459
|
});
|
|
460
460
|
});
|
|
461
461
|
describe('DELETE /recordings/:groupId', () => {
|
|
462
|
-
const del = (who) => (0,
|
|
462
|
+
const del = (who) => (0, loopbackRequest_1.default)(buildApp(who)).delete('/xenon/api/recordings/g1');
|
|
463
463
|
it('lets the person who recorded it delete it: rows, then files', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
464
464
|
rows = [
|
|
465
465
|
rec(),
|
|
@@ -596,20 +596,20 @@ describe('recordings library routes', () => {
|
|
|
596
596
|
const unplugged = (over = {}) => rec(Object.assign({ id: 'r2', device_udid: 'GONE', started_at: new Date(T0 + 500) }, over));
|
|
597
597
|
it('still lists it, and opens it with every phone', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
598
598
|
rows = [rec({ group_id: 'gone', id: 'solo', device_udid: 'GONE' }), rec(), unplugged()];
|
|
599
|
-
const list = yield (0,
|
|
599
|
+
const list = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
|
|
600
600
|
(0, chai_1.expect)(list.status, JSON.stringify(list.body)).to.equal(200);
|
|
601
601
|
(0, chai_1.expect)(list.body.recordings.map((s) => s.groupId).sort()).to.deep.equal(['g1', 'gone']);
|
|
602
602
|
const g1 = list.body.recordings.find((s) => s.groupId === 'g1');
|
|
603
603
|
(0, chai_1.expect)(g1.phones.map((p) => p.udid)).to.deep.equal(['U1', 'GONE']);
|
|
604
|
-
const detail = yield (0,
|
|
604
|
+
const detail = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/gone');
|
|
605
605
|
(0, chai_1.expect)(detail.status, JSON.stringify(detail.body)).to.equal(200);
|
|
606
606
|
(0, chai_1.expect)(detail.body.summary.phones.map((p) => p.udid)).to.deep.equal(['GONE']);
|
|
607
|
-
const both = yield (0,
|
|
607
|
+
const both = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
|
|
608
608
|
(0, chai_1.expect)(both.body.summary.phones.map((p) => p.udid)).to.deep.equal(['U1', 'GONE']);
|
|
609
609
|
}));
|
|
610
610
|
it('can still delete it', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
611
611
|
rows = [rec({ device_udid: 'GONE' })];
|
|
612
|
-
const res = yield (0,
|
|
612
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).delete('/xenon/api/recordings/g1');
|
|
613
613
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(204);
|
|
614
614
|
(0, chai_1.expect)(store.deleteGroupRows.calledOnceWith('g1')).to.equal(true);
|
|
615
615
|
}));
|
|
@@ -617,7 +617,7 @@ describe('recordings library routes', () => {
|
|
|
617
617
|
const file = path.join(dir, 'r2.mp4');
|
|
618
618
|
fs.writeFileSync(file, 'x');
|
|
619
619
|
rows = [rec(), unplugged({ file_path: file })];
|
|
620
|
-
const res = yield (0,
|
|
620
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
|
|
621
621
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
622
622
|
}));
|
|
623
623
|
it('is the person who started it, not someone who added a phone later', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -630,14 +630,14 @@ describe('recordings library routes', () => {
|
|
|
630
630
|
unplugged(),
|
|
631
631
|
rec({ id: 'r3', started_by: 'usr_bob', started_at: new Date(T0 + 900) }),
|
|
632
632
|
];
|
|
633
|
-
const asAlice = yield (0,
|
|
633
|
+
const asAlice = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
|
|
634
634
|
(0, chai_1.expect)(asAlice.body.summary.phones.map((p) => p.recordingId).sort()).to.deep.equal([
|
|
635
635
|
'r0',
|
|
636
636
|
'r2',
|
|
637
637
|
'r3',
|
|
638
638
|
'rn',
|
|
639
639
|
]);
|
|
640
|
-
const asBob = yield (0,
|
|
640
|
+
const asBob = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
|
|
641
641
|
(0, chai_1.expect)(asBob.body.summary.phones.map((p) => p.recordingId).sort()).to.deep.equal([
|
|
642
642
|
'r0',
|
|
643
643
|
'r3',
|
|
@@ -650,9 +650,9 @@ describe('recordings library routes', () => {
|
|
|
650
650
|
const file = path.join(dir, 'r2.mp4');
|
|
651
651
|
fs.writeFileSync(file, 'x');
|
|
652
652
|
rows = [rec(), unplugged({ started_by: 'usr_bob', file_path: file })];
|
|
653
|
-
const detail = yield (0,
|
|
653
|
+
const detail = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
|
|
654
654
|
(0, chai_1.expect)(detail.body.summary.phones.map((p) => p.recordingId)).to.deep.equal(['r1']);
|
|
655
|
-
const src = yield (0,
|
|
655
|
+
const src = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
|
|
656
656
|
(0, chai_1.expect)(src.status).to.equal(404);
|
|
657
657
|
}));
|
|
658
658
|
// The exemption is for a phone that is gone, not for one that moved to
|
|
@@ -662,13 +662,13 @@ describe('recordings library routes', () => {
|
|
|
662
662
|
fs.writeFileSync(file, 'x');
|
|
663
663
|
devices.set('OTHER', { name: 'Team B phone', platform: 'android' });
|
|
664
664
|
rows = [rec(), rec({ id: 'r2', device_udid: 'OTHER', file_path: file })];
|
|
665
|
-
const list = yield (0,
|
|
665
|
+
const list = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
|
|
666
666
|
(0, chai_1.expect)(list.status, JSON.stringify(list.body)).to.equal(200);
|
|
667
667
|
(0, chai_1.expect)(list.body.recordings[0].phones.map((p) => p.udid)).to.deep.equal(['U1']);
|
|
668
|
-
const detail = yield (0,
|
|
668
|
+
const detail = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
|
|
669
669
|
(0, chai_1.expect)(detail.body.summary.phones.map((p) => p.udid)).to.deep.equal(['U1']);
|
|
670
670
|
(0, chai_1.expect)(detail.body.recordings.map((r) => r.id)).to.deep.equal(['r1']);
|
|
671
|
-
const src = yield (0,
|
|
671
|
+
const src = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
|
|
672
672
|
(0, chai_1.expect)(src.status).to.equal(404);
|
|
673
673
|
}));
|
|
674
674
|
// The Delete button shows for the group's owner. A member who only added
|
|
@@ -679,23 +679,23 @@ describe('recordings library routes', () => {
|
|
|
679
679
|
rec({ device_udid: 'HIDDEN' }),
|
|
680
680
|
rec({ id: 'r2', started_by: 'usr_bob', started_at: new Date(T0 + 500) }),
|
|
681
681
|
];
|
|
682
|
-
const list = yield (0,
|
|
682
|
+
const list = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
|
|
683
683
|
(0, chai_1.expect)(list.body.recordings[0].phones.map((p) => p.recordingId)).to.deep.equal(['r2']);
|
|
684
684
|
(0, chai_1.expect)(list.body.recordings[0].startedBy).to.deep.equal({ id: 'usr_alice', name: 'Alice' });
|
|
685
|
-
const detail = yield (0,
|
|
685
|
+
const detail = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
|
|
686
686
|
(0, chai_1.expect)(detail.body.summary.startedBy).to.deep.equal({ id: 'usr_alice', name: 'Alice' });
|
|
687
|
-
(0, chai_1.expect)((yield (0,
|
|
687
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(403);
|
|
688
688
|
}));
|
|
689
689
|
it('does not open it to another member: 404 everywhere', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
690
690
|
const file = path.join(dir, 'r1.mp4');
|
|
691
691
|
fs.writeFileSync(file, 'x');
|
|
692
692
|
rows = [rec({ device_udid: 'GONE', file_path: file })];
|
|
693
|
-
const list = yield (0,
|
|
693
|
+
const list = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
|
|
694
694
|
(0, chai_1.expect)(list.body.recordings).to.deep.equal([]);
|
|
695
|
-
(0, chai_1.expect)((yield (0,
|
|
696
|
-
(0, chai_1.expect)((yield (0,
|
|
695
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
|
|
696
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1'))
|
|
697
697
|
.status).to.equal(404);
|
|
698
|
-
(0, chai_1.expect)((yield (0,
|
|
698
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(404);
|
|
699
699
|
(0, chai_1.expect)(store.deleteGroupRows.called).to.equal(false);
|
|
700
700
|
}));
|
|
701
701
|
});
|
|
@@ -738,7 +738,7 @@ describe('recordings library routes', () => {
|
|
|
738
738
|
{ id: 'r-a', udid: 'U1', status: 'STOPPED', durationMs: 1000, sizeBytes: 10 },
|
|
739
739
|
{ id: 'r-b', udid: 'U2', status: 'STOPPED', durationMs: 2000, sizeBytes: 20 },
|
|
740
740
|
]));
|
|
741
|
-
const res = yield (0,
|
|
741
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
|
|
742
742
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
743
743
|
(0, chai_1.expect)(res.body.groupId).to.equal('g1');
|
|
744
744
|
(0, chai_1.expect)(res.body.recordings.map((r) => r.id)).to.deep.equal(['r-a']);
|
|
@@ -752,7 +752,7 @@ describe('recordings library routes', () => {
|
|
|
752
752
|
{ id: 'r-a', udid: 'U1', status: 'STOPPED' },
|
|
753
753
|
{ id: 'r-b', udid: 'U2', status: 'STOPPED' },
|
|
754
754
|
]));
|
|
755
|
-
const res = yield (0,
|
|
755
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(admin)).post('/xenon/api/recordings/g1/stop');
|
|
756
756
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
757
757
|
(0, chai_1.expect)(res.body.recordings.map((r) => r.id).sort()).to.deep.equal(['r-a', 'r-b']);
|
|
758
758
|
}));
|
|
@@ -773,7 +773,7 @@ describe('recordings library routes', () => {
|
|
|
773
773
|
{ id: 'r-a', udid: 'U1', status: 'STOPPED' },
|
|
774
774
|
{ id: 'r-b', udid: 'U2', status: 'STOPPED' },
|
|
775
775
|
]));
|
|
776
|
-
const res = yield (0,
|
|
776
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
|
|
777
777
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
778
778
|
(0, chai_1.expect)(res.body.recordings.map((r) => r.id).sort()).to.deep.equal(['r-a', 'r-b']);
|
|
779
779
|
}));
|
|
@@ -786,21 +786,21 @@ describe('recordings library routes', () => {
|
|
|
786
786
|
{ id: 'r-a', udid: 'U1', status: 'STOPPED' },
|
|
787
787
|
{ id: 'r-b', udid: 'U2', status: 'STOPPED' },
|
|
788
788
|
]));
|
|
789
|
-
const res = yield (0,
|
|
789
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
|
|
790
790
|
(0, chai_1.expect)(res.body.recordings.map((r) => r.id)).to.deep.equal(['r-a']);
|
|
791
791
|
}));
|
|
792
792
|
it('still 500s when the orchestrator throws, without ever filtering', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
793
793
|
twoTeamRows();
|
|
794
794
|
visible = new Set(['U1']);
|
|
795
795
|
stop.rejects(new Error('ffmpeg exploded'));
|
|
796
|
-
const res = yield (0,
|
|
796
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
|
|
797
797
|
(0, chai_1.expect)(res.status).to.equal(500);
|
|
798
798
|
(0, chai_1.expect)(res.body).to.deep.equal({ error: 'internal', message: 'ffmpeg exploded' });
|
|
799
799
|
}));
|
|
800
800
|
it('a group with no visible phones is not found, and is not stopped', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
801
801
|
twoTeamRows();
|
|
802
802
|
visible = new Set(); // neither U1 nor U2 is on alice's team
|
|
803
|
-
const res = yield (0,
|
|
803
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
|
|
804
804
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(404);
|
|
805
805
|
(0, chai_1.expect)(res.body).to.deep.equal({ error: 'not_found' });
|
|
806
806
|
(0, chai_1.expect)(stop.called).to.equal(false);
|
|
@@ -820,7 +820,7 @@ describe('recordings library routes', () => {
|
|
|
820
820
|
typedi_1.Container.set(annotation_render_1.AnnotationRenderService, { renderForRecording: render });
|
|
821
821
|
});
|
|
822
822
|
afterEach(() => restoreRender());
|
|
823
|
-
const get = (who, id) => (0,
|
|
823
|
+
const get = (who, id) => (0, loopbackRequest_1.default)(buildApp(who))
|
|
824
824
|
.get(`/xenon/api/recordings/g1/exports/annotated.mp4?recordingId=${id}`)
|
|
825
825
|
.buffer(true)
|
|
826
826
|
.parse((r, cb) => {
|
|
@@ -865,7 +865,7 @@ describe('recordings library routes', () => {
|
|
|
865
865
|
const file = path.join(dir, 'r1.mp4');
|
|
866
866
|
fs.writeFileSync(file, Buffer.from('0123456789'));
|
|
867
867
|
rows = [rec({ file_path: file })];
|
|
868
|
-
const res = yield (0,
|
|
868
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice))
|
|
869
869
|
.get('/xenon/api/recordings/g1/source.mp4?recordingId=r1')
|
|
870
870
|
.set('Range', 'bytes=2-5')
|
|
871
871
|
.buffer(true)
|
|
@@ -891,7 +891,7 @@ describe('recordings library routes', () => {
|
|
|
891
891
|
rec({ file_path: visibleFile }),
|
|
892
892
|
rec({ id: 'r2', device_udid: 'GONE', file_path: file }),
|
|
893
893
|
];
|
|
894
|
-
const get = (who, id) => (0,
|
|
894
|
+
const get = (who, id) => (0, loopbackRequest_1.default)(buildApp(who)).get(`/xenon/api/recordings/g1/source.mp4?recordingId=${id}`);
|
|
895
895
|
(0, chai_1.expect)((yield get(bob, 'r1')).status).to.equal(200);
|
|
896
896
|
(0, chai_1.expect)(store.findVideo.calledWith('r1')).to.equal(true);
|
|
897
897
|
(0, chai_1.expect)(store.listGroupStarts.called, 'a visible phone needs no owner').to.equal(false);
|
|
@@ -908,9 +908,9 @@ describe('recordings library routes', () => {
|
|
|
908
908
|
fs.writeFileSync(file, Buffer.from('0123456789'));
|
|
909
909
|
rows = [rec({ file_path: file })];
|
|
910
910
|
const url = '/xenon/api/recordings/g1/source.mp4?recordingId=r1&download=1';
|
|
911
|
-
const fileEtag = (yield (0,
|
|
911
|
+
const fileEtag = (yield (0, loopbackRequest_1.default)(buildApp(alice)).get(url)).headers.etag;
|
|
912
912
|
(0, chai_1.expect)(fileEtag, "the file's own ETag").to.be.a('string');
|
|
913
|
-
const res = yield (0,
|
|
913
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get(url).set('Range', 'bytes=9999-10000');
|
|
914
914
|
(0, chai_1.expect)(res.status).to.equal(416);
|
|
915
915
|
(0, chai_1.expect)(res.headers['content-type']).to.match(/^application\/json/);
|
|
916
916
|
(0, chai_1.expect)(res.headers['content-range']).to.equal('bytes */10');
|
|
@@ -922,13 +922,13 @@ describe('recordings library routes', () => {
|
|
|
922
922
|
const file = path.join(dir, 'r1.mp4');
|
|
923
923
|
fs.writeFileSync(file, 'x');
|
|
924
924
|
rows = [rec({ file_path: file })];
|
|
925
|
-
const res = yield (0,
|
|
925
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1&download=1');
|
|
926
926
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
927
927
|
(0, chai_1.expect)(res.headers['content-disposition']).to.match(/attachment; filename="U1\.mp4"/);
|
|
928
928
|
}));
|
|
929
929
|
it('answers 400 without a recording, and 404 for another group, an invisible phone or a missing file', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
930
930
|
rows = [rec(), rec({ id: 'rx', group_id: 'g2' }), rec({ id: 'rh', device_udid: 'HIDDEN' })];
|
|
931
|
-
const get = (q) => (0,
|
|
931
|
+
const get = (q) => (0, loopbackRequest_1.default)(buildApp(bob)).get(`/xenon/api/recordings/g1/source.mp4${q}`);
|
|
932
932
|
(0, chai_1.expect)((yield get('')).status).to.equal(400);
|
|
933
933
|
(0, chai_1.expect)((yield get('?recordingId=rx')).status).to.equal(404);
|
|
934
934
|
(0, chai_1.expect)((yield get('?recordingId=rh')).status).to.equal(404);
|
|
@@ -49,7 +49,7 @@ require("reflect-metadata");
|
|
|
49
49
|
const chai_1 = require("chai");
|
|
50
50
|
const sinon_1 = __importDefault(require("sinon"));
|
|
51
51
|
const express_1 = __importDefault(require("express"));
|
|
52
|
-
const
|
|
52
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
53
53
|
const typedi_1 = require("typedi");
|
|
54
54
|
const recordings_1 = __importDefault(require("../../src/app/routers/recordings"));
|
|
55
55
|
const RecordingOrchestrator_1 = require("../../src/services/recording/RecordingOrchestrator");
|
|
@@ -164,7 +164,7 @@ describe('recording write routes: team visibility', () => {
|
|
|
164
164
|
const alice = { userId: 'usr_alice', teamIds: ['t1'] };
|
|
165
165
|
const bob = { userId: 'usr_bob', teamIds: ['t1'] };
|
|
166
166
|
const admin = { userId: 'usr_root', role: 'ADMIN' };
|
|
167
|
-
const post = (who, url, body = {}) => (0,
|
|
167
|
+
const post = (who, url, body = {}) => (0, loopbackRequest_1.default)(buildApp(who)).post(`/xenon/api/recordings${url}`).send(body);
|
|
168
168
|
const notFound = (res) => {
|
|
169
169
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(404);
|
|
170
170
|
(0, chai_1.expect)(res.body).to.deep.equal({ error: 'not_found' });
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
require("reflect-metadata");
|
|
49
|
+
const chai_1 = require("chai");
|
|
50
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
51
|
+
const express_1 = __importDefault(require("express"));
|
|
52
|
+
const typedi_1 = require("typedi");
|
|
53
|
+
const SessionLifecycleService_1 = require("../../src/services/SessionLifecycleService");
|
|
54
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
55
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
56
|
+
const deviceService = __importStar(require("../../src/data-service/device-service"));
|
|
57
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
58
|
+
const SessionType_1 = __importDefault(require("../../src/enums/SessionType"));
|
|
59
|
+
const nodeWebDriverUrl_1 = require("../../src/gateway/nodeWebDriverUrl");
|
|
60
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
61
|
+
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
62
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
63
|
+
/**
|
|
64
|
+
* A session the hub creates on a node's phone (today's createSession path,
|
|
65
|
+
* through the plugin):
|
|
66
|
+
* - it is created under the node's own base path, which need not be the hub's;
|
|
67
|
+
* - it is registered in SESSION_MANAGER whatever the dashboard and video
|
|
68
|
+
* settings, because that is where the session gateway finds it.
|
|
69
|
+
*/
|
|
70
|
+
describe('a session the hub creates on a node', () => {
|
|
71
|
+
// The in-memory stores, also when this file runs on its own: without
|
|
72
|
+
// NODE_ENV=test the factory handed out the Prisma stores, which are the
|
|
73
|
+
// developer's ~/.cache/xenon/xenon.db.
|
|
74
|
+
(0, loki_stores_1.useLokiStores)();
|
|
75
|
+
const loopback = (0, loopbackServer_1.loopbackServers)();
|
|
76
|
+
let nodeOrigin;
|
|
77
|
+
let created;
|
|
78
|
+
let context;
|
|
79
|
+
let saved;
|
|
80
|
+
let restore;
|
|
81
|
+
const SESSION = 'node-session-1';
|
|
82
|
+
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
+
created = [];
|
|
84
|
+
const node = (0, express_1.default)();
|
|
85
|
+
node.use(express_1.default.json());
|
|
86
|
+
// The node's Appium runs at the root; the hub's at /wd/hub.
|
|
87
|
+
node.get('/xenon/api/webdriver', (0, nodeWebDriverUrl_1.webdriverInfoHandler)(() => ''));
|
|
88
|
+
node.post('/session', (req, res) => {
|
|
89
|
+
created.push({ url: req.originalUrl, body: req.body });
|
|
90
|
+
res.json({ value: { sessionId: SESSION, capabilities: { platformName: 'Android' } } });
|
|
91
|
+
});
|
|
92
|
+
const server = yield loopback.serve(node);
|
|
93
|
+
nodeOrigin = `http://127.0.0.1:${server.address().port}`;
|
|
94
|
+
restore = (0, container_registration_1.saveRegistrations)(nodeWebDriverUrl_1.NodeBasePathResolver);
|
|
95
|
+
typedi_1.Container.set(nodeWebDriverUrl_1.NodeBasePathResolver, new nodeWebDriverUrl_1.NodeBasePathResolver());
|
|
96
|
+
context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
97
|
+
saved = Object.assign({}, context);
|
|
98
|
+
context.pluginArgs = Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { enableDashboard: false });
|
|
99
|
+
context.nodeId = 'hub-node-id';
|
|
100
|
+
context.nodeBasePath = '/wd/hub';
|
|
101
|
+
sinon_1.default.stub(deviceService, 'updatedAllocatedDevice').resolves();
|
|
102
|
+
}));
|
|
103
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
104
|
+
SessionManager_1.SESSION_MANAGER.removeSession(SESSION);
|
|
105
|
+
Object.assign(context, saved);
|
|
106
|
+
sinon_1.default.restore();
|
|
107
|
+
restore();
|
|
108
|
+
yield loopback.closeAll();
|
|
109
|
+
}));
|
|
110
|
+
const device = () => ({ udid: 'u1', host: nodeOrigin, nodeId: 'node-peer', platform: 'android' });
|
|
111
|
+
const caps = {
|
|
112
|
+
alwaysMatch: { platformName: 'Android', 'xe:options': { recordVideo: false } },
|
|
113
|
+
firstMatch: [{}],
|
|
114
|
+
};
|
|
115
|
+
it('is created under the node’s own base path', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
116
|
+
const svc = new SessionLifecycleService_1.SessionLifecycleService();
|
|
117
|
+
const response = yield svc.forwardSessionRequest(device(), caps);
|
|
118
|
+
(0, chai_1.expect)(created.map((c) => c.url)).to.deep.equal(['/session']);
|
|
119
|
+
(0, chai_1.expect)(response.value[0]).to.equal(SESSION);
|
|
120
|
+
}));
|
|
121
|
+
it('is registered in SESSION_MANAGER with the node’s URL, with the dashboard and video off', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
122
|
+
const svc = new SessionLifecycleService_1.SessionLifecycleService();
|
|
123
|
+
sinon_1.default.stub(svc, 'getFreshDevice').callsFake((d) => __awaiter(void 0, void 0, void 0, function* () { return d; }));
|
|
124
|
+
yield svc.finalizeSession({ protocol: 'W3C', value: [SESSION, { platformName: 'Android' }, 'W3C'] }, device(), caps, {}, true);
|
|
125
|
+
const session = SessionManager_1.SESSION_MANAGER.getSession(SESSION);
|
|
126
|
+
(0, chai_1.expect)(session, 'registered').to.not.equal(undefined);
|
|
127
|
+
(0, chai_1.expect)(session.getType()).to.equal(SessionType_1.default.REMOTE);
|
|
128
|
+
(0, chai_1.expect)(session.getWebDriverUrl()).to.equal(nodeOrigin);
|
|
129
|
+
}));
|
|
130
|
+
});
|
|
@@ -49,7 +49,7 @@ require("reflect-metadata");
|
|
|
49
49
|
const chai_1 = require("chai");
|
|
50
50
|
const sinon_1 = __importDefault(require("sinon"));
|
|
51
51
|
const express_1 = __importDefault(require("express"));
|
|
52
|
-
const
|
|
52
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
53
53
|
const reservation_1 = __importDefault(require("../../src/app/routers/reservation"));
|
|
54
54
|
const deviceService = __importStar(require("../../src/data-service/device-service"));
|
|
55
55
|
const prisma_1 = require("../../src/prisma");
|
|
@@ -105,13 +105,13 @@ function buildApp(caller) {
|
|
|
105
105
|
app.use('/reservation', reservation_1.default);
|
|
106
106
|
return app;
|
|
107
107
|
}
|
|
108
|
-
const reserve = (caller, udid) => (0,
|
|
108
|
+
const reserve = (caller, udid) => (0, loopbackRequest_1.default)(buildApp(caller))
|
|
109
109
|
.post('/reservation')
|
|
110
110
|
.send({ udid, host: HOST, reservedBy: 'alice', duration: '1h' });
|
|
111
|
-
const extend = (caller, udid) => (0,
|
|
111
|
+
const extend = (caller, udid) => (0, loopbackRequest_1.default)(buildApp(caller))
|
|
112
112
|
.post(`/reservation/${udid}/${encodeURIComponent(HOST)}/extend`)
|
|
113
113
|
.send({ duration: '1h' });
|
|
114
|
-
const release = (caller, udid) => (0,
|
|
114
|
+
const release = (caller, udid) => (0, loopbackRequest_1.default)(buildApp(caller)).delete(`/reservation/${udid}/${encodeURIComponent(HOST)}`);
|
|
115
115
|
describe('reservations respect the team boundary', () => {
|
|
116
116
|
let reserveDevice;
|
|
117
117
|
let releaseReservation;
|
|
@@ -176,7 +176,7 @@ describe('reservations respect the team boundary', () => {
|
|
|
176
176
|
}));
|
|
177
177
|
describe('the list', () => {
|
|
178
178
|
const listed = (caller) => __awaiter(void 0, void 0, void 0, function* () {
|
|
179
|
-
const res = yield (0,
|
|
179
|
+
const res = yield (0, loopbackRequest_1.default)(buildApp(caller)).get('/reservation');
|
|
180
180
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
181
181
|
return res.body.reservations.map((r) => r.udid).sort();
|
|
182
182
|
});
|