@xenon-device-management/xenon 2.0.0 → 2.1.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 +1 -2
- package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-C-Pw_hvw.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-DmYQ0oSk.js} +1 -1
- package/lib/public/assets/BugReportButton-n8T1dFUp.js +10 -0
- package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-D-rGOBbJ.js} +1 -1
- package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-DExETevp.js} +1 -1
- package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-CCdFlZm6.js} +1 -1
- package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-DiUSOSL-.js} +1 -1
- package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-ZOfpH6gh.js} +1 -1
- package/lib/public/assets/{Modal-CceoMY7D.js → Modal-CZNtoB-1.js} +1 -1
- package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-BKp6h8Jl.js} +1 -1
- package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-Bf881FX4.js} +1 -1
- package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-EUoyizvW.js} +1 -1
- package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-ClT4hf4_.js} +1 -1
- package/lib/public/assets/{Table-LV7zFyZg.js → Table-ucR4bnmc.js} +1 -1
- package/lib/public/assets/{activity-D17TUloi.js → activity-K-hdylwq.js} +1 -1
- package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-BLhdwI3c.js} +1 -1
- package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-BbiN7rBI.js} +1 -1
- package/lib/public/assets/{apps-Dbnzd0fW.js → apps-Dez1petz.js} +1 -1
- package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-UjmFHLAJ.js} +1 -1
- package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-BVyEigbu.js} +1 -1
- package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-B-ZlBof3.js} +1 -1
- package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-DAQjAgUv.js} +2 -2
- package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-CmIPa5JK.js} +1 -1
- package/lib/public/assets/{button-xtAhYR-f.js → button-Dd-IhMC1.js} +1 -1
- package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-CAIeJwa5.js} +1 -1
- package/lib/public/assets/{check-CyuEoKkd.js → check-C4Jrjg8f.js} +1 -1
- package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-BGzlf-4i.js} +1 -1
- package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-_wYqDjDC.js} +1 -1
- package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-Ckn00yMB.js} +1 -1
- package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-pdVkomLa.js} +1 -1
- package/lib/public/assets/{clock-Cd-t7xpe.js → clock-B8vmwkHj.js} +1 -1
- package/lib/public/assets/{copy-C6zerX3I.js → copy-BAqnFI-F.js} +1 -1
- package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CYq_4F_N.js} +3 -3
- package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
- package/lib/public/assets/{download-x9WP7mcL.js → download-B6UEAp_1.js} +1 -1
- package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-CLqNvmrN.js} +1 -1
- package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-Da90BBbT.js} +1 -1
- package/lib/public/assets/{index-BR1wTIPC.js → index-BuIIcmGp.js} +2 -2
- package/lib/public/assets/{index-BvH_ekRy.js → index-MX7IzqyW.js} +1 -1
- package/lib/public/assets/{input-DejcCiTI.js → input-Bq9aY-We.js} +1 -1
- package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CIhXIJyP.js} +1 -1
- package/lib/public/assets/{layers-BKqYLiHE.js → layers-bXjpswQI.js} +1 -1
- package/lib/public/assets/{lock-LFsFhc17.js → lock-pJ72y5Fy.js} +1 -1
- package/lib/public/assets/{login-x2pkzt-t.js → login-UOcHALV2.js} +1 -1
- package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-XnJgp5yb.js} +1 -1
- package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-QkAhI1Jx.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-B42Da0m4.js} +1 -1
- package/lib/public/assets/{network-Z_zBhpsD.js → network-CKVqcyp6.js} +1 -1
- package/lib/public/assets/{overview-BY5uv08S.js → overview-BORGkN3N.js} +1 -1
- package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CKWFOjOI.js} +1 -1
- package/lib/public/assets/{plus-BTD0dnQ0.js → plus-BpFKVdC8.js} +1 -1
- package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-CcbKBicY.js} +1 -1
- package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BA0W9bxl.js} +1 -1
- package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Cp7ZBq3s.js} +1 -1
- package/lib/public/assets/{select-Uc9E5pye.js → select-DIZWIoVR.js} +1 -1
- package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C_4YuvAp.js} +1 -1
- package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CXjD_Xth.js} +1 -1
- package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CTqIHeoG.js} +1 -1
- package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-B0KYkk8z.js} +1 -1
- package/lib/public/assets/{settings-CkSWiZq8.js → settings-BcRBKUXd.js} +1 -1
- package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-Dh2PXSf_.js} +1 -1
- package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-_sXqZztt.js} +1 -1
- package/lib/public/assets/{teams-PpoEQz31.js → teams-DGHT8MKm.js} +1 -1
- package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV88cV3Z.js} +1 -1
- package/lib/public/assets/{upload-C3i7sAZl.js → upload-CIDH9wkj.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-DYGuSNwl.js} +1 -1
- package/lib/public/assets/{users-CEJ3fC78.js → users-7yviqflc.js} +1 -1
- package/lib/public/assets/{users-Drd7ifJx.js → users-BUE7MoTq.js} +1 -1
- package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-BTRxtlGo.js} +1 -1
- package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-DMKD9Pgm.js} +1 -1
- package/lib/public/assets/{zap-4yN8Jdfw.js → zap-CWE94qcK.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 +57 -13
- package/lib/src/app/routers/grid.js +28 -8
- package/lib/src/app/ws/h264StreamWs.js +13 -8
- package/lib/src/app/ws/logcatWs.js +12 -7
- 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 +81 -10
- package/lib/src/data-service/deviceClaims.js +98 -0
- package/lib/src/data-service/deviceFieldOwners.js +32 -10
- package/lib/src/data-service/prisma-store.js +94 -31
- package/lib/src/device-managers/AndroidDeviceManager.js +13 -15
- 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 +3 -3
- package/lib/src/device-managers/android/AndroidStreamService.js +6 -5
- 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 +158 -38
- 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/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 +50 -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/lease/LeaseOrphanSweeper.js +32 -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/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/unit/AndroidDeviceManager.spec.js +8 -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 +6 -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 +18 -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 +28 -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 +291 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
- package/lib/test/unit/hub-node-race.spec.js +401 -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 +2 -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 +92 -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 +4 -1
- package/lib/test/unit/loopback-request.spec.js +81 -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/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 +15 -7
- package/lib/test/unit/stream-own-device-row.spec.js +164 -0
- package/lib/test/unit/stream-stop-ownership.spec.js +13 -5
- package/lib/test/unit/stream-stop-recording.spec.js +13 -5
- package/lib/test/unit/stream-ticket-identity.spec.js +15 -5
- 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 +1 -2
- 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
|
@@ -15,7 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
require("reflect-metadata");
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const express_1 = __importDefault(require("express"));
|
|
18
|
-
const
|
|
18
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
19
19
|
const sinon_1 = __importDefault(require("sinon"));
|
|
20
20
|
const grid_1 = __importDefault(require("../../src/app/routers/grid"));
|
|
21
21
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
@@ -120,24 +120,24 @@ describe('the session queue is team-scoped: own teams + a count', () => {
|
|
|
120
120
|
afterEach(() => sinon_1.default.restore());
|
|
121
121
|
describe('a member in team A', () => {
|
|
122
122
|
it("GET /queue shows the request for her team's phone and her teammate's, nothing else", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
|
-
const r = yield (0,
|
|
123
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue');
|
|
124
124
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
125
125
|
(0, chai_1.expect)(ids(r.body)).to.deep.equal(['targets-a', 'untargeted-a']);
|
|
126
126
|
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('bob-secret-release');
|
|
127
127
|
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('bob-targeted');
|
|
128
128
|
}));
|
|
129
129
|
it('GET /queue never returns who asked', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
130
|
-
const r = yield (0,
|
|
130
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue');
|
|
131
131
|
for (const shown of r.body)
|
|
132
132
|
(0, chai_1.expect)(shown).to.not.have.property(REQUESTER);
|
|
133
133
|
}));
|
|
134
134
|
it('GET /queue/length is the whole queue, the number every caller gets', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
-
const r = yield (0,
|
|
135
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue/length');
|
|
136
136
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
137
137
|
(0, chai_1.expect)(r.body).to.equal(4);
|
|
138
138
|
}));
|
|
139
139
|
it('GET /queue/summary agrees with /queue/length, and counts what she cannot see', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
140
|
-
const r = yield (0,
|
|
140
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue/summary');
|
|
141
141
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
142
142
|
(0, chai_1.expect)(r.body.total).to.equal(4);
|
|
143
143
|
(0, chai_1.expect)(r.body.otherCount).to.equal(2);
|
|
@@ -145,17 +145,17 @@ describe('the session queue is team-scoped: own teams + a count', () => {
|
|
|
145
145
|
(0, chai_1.expect)(byPlatform.reduce((n, p) => n + p.count, 0)).to.equal(4);
|
|
146
146
|
}));
|
|
147
147
|
it("GET /queue/status answers for another team's request exactly as for an unknown id", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
148
|
-
const unknown = yield (0,
|
|
148
|
+
const unknown = yield (0, loopbackRequest_1.default)(alice()).get('/queue/status/no-such-request');
|
|
149
149
|
(0, chai_1.expect)(unknown.status).to.equal(404);
|
|
150
150
|
for (const hidden of ['targets-b', 'untargeted-b']) {
|
|
151
|
-
const r = yield (0,
|
|
151
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get(`/queue/status/${hidden}`);
|
|
152
152
|
(0, chai_1.expect)(r.status, hidden).to.equal(404);
|
|
153
153
|
(0, chai_1.expect)(r.body, hidden).to.deep.equal(unknown.body);
|
|
154
154
|
}
|
|
155
155
|
}));
|
|
156
156
|
it('GET /queue/status still answers for the requests she can see', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
157
|
for (const shown of ['targets-a', 'untargeted-a']) {
|
|
158
|
-
const r = yield (0,
|
|
158
|
+
const r = yield (0, loopbackRequest_1.default)(alice()).get(`/queue/status/${shown}`);
|
|
159
159
|
(0, chai_1.expect)(r.status, shown).to.equal(200);
|
|
160
160
|
(0, chai_1.expect)(r.body.platform, shown).to.equal('android');
|
|
161
161
|
}
|
|
@@ -163,25 +163,25 @@ describe('the session queue is team-scoped: own teams + a count', () => {
|
|
|
163
163
|
});
|
|
164
164
|
describe('an admin', () => {
|
|
165
165
|
it('GET /queue shows all four', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
166
|
-
const r = yield (0,
|
|
166
|
+
const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue');
|
|
167
167
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
168
168
|
(0, chai_1.expect)(ids(r.body)).to.deep.equal(['targets-a', 'targets-b', 'untargeted-a', 'untargeted-b']);
|
|
169
169
|
for (const shown of r.body)
|
|
170
170
|
(0, chai_1.expect)(shown).to.not.have.property(REQUESTER);
|
|
171
171
|
}));
|
|
172
172
|
it('with no lookup of phones or teams', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
173
|
-
yield (0,
|
|
173
|
+
yield (0, loopbackRequest_1.default)(admin()).get('/queue');
|
|
174
174
|
(0, chai_1.expect)(prisma_1.prisma.device.findMany.called).to.equal(false);
|
|
175
175
|
(0, chai_1.expect)(prisma_1.prisma.teamMember.findMany.called).to.equal(false);
|
|
176
176
|
}));
|
|
177
177
|
it('GET /queue/summary counts nothing as hidden', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
178
|
-
const r = yield (0,
|
|
178
|
+
const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue/summary');
|
|
179
179
|
(0, chai_1.expect)(r.body.total).to.equal(4);
|
|
180
180
|
(0, chai_1.expect)(r.body.otherCount).to.equal(0);
|
|
181
181
|
}));
|
|
182
182
|
it('GET /queue/length and /queue/status answer as before', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
183
|
-
(0, chai_1.expect)((yield (0,
|
|
184
|
-
const r = yield (0,
|
|
183
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(admin()).get('/queue/length')).body).to.equal(4);
|
|
184
|
+
const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue/status/untargeted-b');
|
|
185
185
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
186
186
|
}));
|
|
187
187
|
});
|
|
@@ -195,13 +195,14 @@ describe('the session queue is team-scoped: own teams + a count', () => {
|
|
|
195
195
|
['an admin', admin],
|
|
196
196
|
]) {
|
|
197
197
|
it(`are never returned to ${who}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
198
|
-
const r = yield (0,
|
|
198
|
+
const r = yield (0, loopbackRequest_1.default)(caller()).get('/queue');
|
|
199
199
|
const shown = r.body.find((x) => x.capability_id === 'untargeted-a');
|
|
200
200
|
(0, chai_1.expect)(shown, 'the request itself is shown').to.not.equal(undefined);
|
|
201
201
|
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('amy-api-token');
|
|
202
202
|
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('amy.session.jwt');
|
|
203
|
-
//
|
|
204
|
-
(0, chai_1.expect)(
|
|
203
|
+
// Nor the access key: half of the pair, and it names Amy's key.
|
|
204
|
+
(0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('ak_amy');
|
|
205
|
+
// What isn't a credential still reads as before.
|
|
205
206
|
(0, chai_1.expect)(shown['xenon:options'].sessionName).to.equal('amy-login');
|
|
206
207
|
}));
|
|
207
208
|
}
|
|
@@ -102,7 +102,7 @@ describe('queue visibility', () => {
|
|
|
102
102
|
(0, chai_1.expect)(yield seen([untargeted, targeted], alice)).to.deep.equal([false, false]);
|
|
103
103
|
}));
|
|
104
104
|
it('a credential narrowed to her team, whatever teams its user is in', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
105
|
-
// A team-bound key, a session token's teamId claim, or
|
|
105
|
+
// A team-bound key, a session token's teamId claim, or xe:options.team.
|
|
106
106
|
(0, chai_1.expect)(yield seen([req({ userId: 'bob', teamId: 'team-a' })], alice)).to.deep.equal([true]);
|
|
107
107
|
}));
|
|
108
108
|
it("not a teammate's request narrowed to another team", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -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"));
|
|
@@ -104,7 +104,7 @@ function binary(res, cb) {
|
|
|
104
104
|
}
|
|
105
105
|
function get(caller_1, url_1) {
|
|
106
106
|
return __awaiter(this, arguments, void 0, function* (caller, url, headers = {}) {
|
|
107
|
-
let req = (0,
|
|
107
|
+
let req = (0, loopbackRequest_1.default)(buildApp(caller)).get(`/xenon/api/recordings/${GROUP}/${url}`);
|
|
108
108
|
for (const [k, v] of Object.entries(headers))
|
|
109
109
|
req = req.set(k, v);
|
|
110
110
|
const res = yield req.buffer(true).parse(binary);
|
|
@@ -21,6 +21,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
21
21
|
const api_1 = require("@opentelemetry/api");
|
|
22
22
|
const attributes_1 = require("../../src/services/telemetry/attributes");
|
|
23
23
|
const artifact_store_1 = require("../helpers/artifact-store");
|
|
24
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
24
25
|
// Stub metrics + trace BEFORE the module loads so the module-level
|
|
25
26
|
// ensureOtelInstruments() picks up our mocks. Inline import after stubbing.
|
|
26
27
|
function makeMockMeter() {
|
|
@@ -56,6 +57,10 @@ function makeMockSpan() {
|
|
|
56
57
|
};
|
|
57
58
|
}
|
|
58
59
|
describe('RecordingOrchestrator instrumentation', () => {
|
|
60
|
+
// The in-memory stores, also when this file runs on its own: without
|
|
61
|
+
// NODE_ENV=test the factory handed out the Prisma stores, which are the
|
|
62
|
+
// developer's ~/.cache/xenon/xenon.db.
|
|
63
|
+
(0, loki_stores_1.useLokiStores)();
|
|
59
64
|
// RecordingOrchestrator resolves ARTIFACT_STORE from the container at runtime.
|
|
60
65
|
(0, artifact_store_1.useArtifactStore)();
|
|
61
66
|
let getMeterStub;
|
|
@@ -21,6 +21,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
21
21
|
const RecordingOrchestrator_1 = require("../../src/services/recording/RecordingOrchestrator");
|
|
22
22
|
const concurrency_gate_1 = require("../../src/services/recording/concurrency-gate");
|
|
23
23
|
const artifact_store_1 = require("../helpers/artifact-store");
|
|
24
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
24
25
|
// Issue #203. When the ffmpeg process for a recording exits on its own nothing
|
|
25
26
|
// told the orchestrator, so the row sat at RECORDING — no ended_at, no duration,
|
|
26
27
|
// no size — until a human clicked Stop. Observed live during the #200 cable-pull
|
|
@@ -69,6 +70,10 @@ function makeOrch(row, overrides = {}) {
|
|
|
69
70
|
return { orch, store, gate, videoPipeline, eventMgr, unblockDeviceFn, probeDurationMsFn };
|
|
70
71
|
}
|
|
71
72
|
describe('RecordingOrchestrator: ffmpeg exiting on its own', () => {
|
|
73
|
+
// The in-memory stores, also when this file runs on its own: without
|
|
74
|
+
// NODE_ENV=test the factory handed out the Prisma stores, which are the
|
|
75
|
+
// developer's ~/.cache/xenon/xenon.db.
|
|
76
|
+
(0, loki_stores_1.useLokiStores)();
|
|
72
77
|
// compositeOutputPath and per-device paths resolve through ArtifactStore.
|
|
73
78
|
(0, artifact_store_1.useArtifactStore)();
|
|
74
79
|
afterEach(() => sinon_1.default.restore());
|
|
@@ -13,12 +13,18 @@ require("reflect-metadata");
|
|
|
13
13
|
const chai_1 = require("chai");
|
|
14
14
|
const recording_store_1 = require("../../src/services/recording/recording-store");
|
|
15
15
|
const prisma_1 = require("../../src/prisma");
|
|
16
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
16
17
|
describe('RecordingStore (Prisma round-trip)', () => {
|
|
18
|
+
// Its own migrated database: the round trip wrote these rows to the
|
|
19
|
+
// developer's ~/.cache/xenon/xenon.db, and failed where it lacked the tables.
|
|
20
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
17
21
|
const store = new recording_store_1.RecordingStore();
|
|
22
|
+
// Through the scratch client itself: the helper's own afterEach, which
|
|
23
|
+
// runs first, has already put `prisma` back on the server's database.
|
|
18
24
|
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
19
|
-
yield
|
|
20
|
-
yield
|
|
21
|
-
yield
|
|
25
|
+
yield scratch.db.annotation.deleteMany({ where: { recording_id: { contains: 'test-' } } });
|
|
26
|
+
yield scratch.db.bookmark.deleteMany({ where: { recording_id: { contains: 'test-' } } });
|
|
27
|
+
yield scratch.db.recording.deleteMany({ where: { group_id: { startsWith: 'test-' } } });
|
|
22
28
|
}));
|
|
23
29
|
it('creates a recording row with status=RECORDING', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
30
|
const rec = yield store.create({
|
|
@@ -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' });
|