@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
|
@@ -48,22 +48,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
48
48
|
require("reflect-metadata");
|
|
49
49
|
const chai_1 = require("chai");
|
|
50
50
|
const sinon_1 = __importDefault(require("sinon"));
|
|
51
|
-
const prisma_1 = require("../../src/prisma");
|
|
52
51
|
const test_container_1 = require("../helpers/test-container");
|
|
52
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
53
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
54
|
+
const prisma_store_1 = require("../../src/data-service/prisma-store");
|
|
53
55
|
const event_manager_1 = require("../../src/dashboard/event-manager");
|
|
54
56
|
const SessionStatus_1 = require("../../src/types/SessionStatus");
|
|
55
57
|
describe('OrphanSweeper', () => {
|
|
56
|
-
|
|
58
|
+
// Its own migrated database, never the developer's ~/.cache/xenon/xenon.db:
|
|
59
|
+
// that file's schema is whatever the last local migrate left, so a new
|
|
60
|
+
// column failed this spec there and nowhere else.
|
|
61
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
62
|
+
let savedStore;
|
|
57
63
|
const STALE_SESSION_ID = 'orphan-stale-sess-001';
|
|
58
64
|
const STALE_UDID = 'udid-orphan-1';
|
|
59
65
|
const NODE_ID = 'node-a';
|
|
60
66
|
const FRESH_SESSION_ID = 'orphan-fresh-sess-002';
|
|
61
67
|
before(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
62
68
|
(0, test_container_1.setupTestContainer)();
|
|
63
|
-
|
|
69
|
+
savedStore = device_store_1.DeviceStoreFactory._deviceStore;
|
|
70
|
+
device_store_1.DeviceStoreFactory._deviceStore = new prisma_store_1.PrismaDeviceStore();
|
|
64
71
|
// Create a stale session (last_heartbeat_at is old / null with old updatedAt)
|
|
65
72
|
const oldDate = new Date(Date.now() - 5 * 60 * 1000); // 5 minutes ago
|
|
66
|
-
yield
|
|
73
|
+
yield scratch.db.session.upsert({
|
|
67
74
|
where: { id: STALE_SESSION_ID },
|
|
68
75
|
create: {
|
|
69
76
|
id: STALE_SESSION_ID,
|
|
@@ -87,7 +94,7 @@ describe('OrphanSweeper', () => {
|
|
|
87
94
|
},
|
|
88
95
|
});
|
|
89
96
|
// Create a device for the stale session
|
|
90
|
-
yield
|
|
97
|
+
yield scratch.db.device.upsert({
|
|
91
98
|
where: { udid_host: { udid: STALE_UDID, host: 'http://localhost:4723' } },
|
|
92
99
|
create: {
|
|
93
100
|
udid: STALE_UDID,
|
|
@@ -105,10 +112,7 @@ describe('OrphanSweeper', () => {
|
|
|
105
112
|
});
|
|
106
113
|
}));
|
|
107
114
|
after(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
108
|
-
|
|
109
|
-
where: { id: { in: [STALE_SESSION_ID, FRESH_SESSION_ID] } },
|
|
110
|
-
});
|
|
111
|
-
yield prismaClient.device.deleteMany({ where: { udid: STALE_UDID } });
|
|
115
|
+
device_store_1.DeviceStoreFactory._deviceStore = savedStore;
|
|
112
116
|
yield (0, test_container_1.resetTestContainer)();
|
|
113
117
|
}));
|
|
114
118
|
afterEach(() => {
|
|
@@ -122,13 +126,13 @@ describe('OrphanSweeper', () => {
|
|
|
122
126
|
const { OrphanSweeper } = yield Promise.resolve().then(() => __importStar(require('../../src/services/OrphanSweeper')));
|
|
123
127
|
const sweeper = new OrphanSweeper();
|
|
124
128
|
yield sweeper.sweep({ heartbeatIntervalMs: 30000 });
|
|
125
|
-
const session = yield
|
|
129
|
+
const session = yield scratch.db.session.findUnique({
|
|
126
130
|
where: { id: STALE_SESSION_ID },
|
|
127
131
|
select: { status: true, failure_reason: true },
|
|
128
132
|
});
|
|
129
133
|
(0, chai_1.expect)(session.status).to.equal('failed');
|
|
130
134
|
(0, chai_1.expect)(session.failure_reason).to.match(/heartbeat timeout/i);
|
|
131
|
-
const device = yield
|
|
135
|
+
const device = yield scratch.db.device.findFirst({
|
|
132
136
|
where: { udid: STALE_UDID },
|
|
133
137
|
select: { busy: true, owningSessionId: true },
|
|
134
138
|
});
|
|
@@ -138,7 +142,7 @@ describe('OrphanSweeper', () => {
|
|
|
138
142
|
}));
|
|
139
143
|
it('leaves fresh sessions alone', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
140
144
|
// Create a fresh session (heartbeat just now)
|
|
141
|
-
yield
|
|
145
|
+
yield scratch.db.session.upsert({
|
|
142
146
|
where: { id: FRESH_SESSION_ID },
|
|
143
147
|
create: {
|
|
144
148
|
id: FRESH_SESSION_ID,
|
|
@@ -164,7 +168,7 @@ describe('OrphanSweeper', () => {
|
|
|
164
168
|
const { OrphanSweeper } = yield Promise.resolve().then(() => __importStar(require('../../src/services/OrphanSweeper')));
|
|
165
169
|
const sweeper = new OrphanSweeper();
|
|
166
170
|
yield sweeper.sweep({ heartbeatIntervalMs: 30000 });
|
|
167
|
-
const session = yield
|
|
171
|
+
const session = yield scratch.db.session.findUnique({
|
|
168
172
|
where: { id: FRESH_SESSION_ID },
|
|
169
173
|
select: { status: true },
|
|
170
174
|
});
|
|
@@ -15,18 +15,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
require("reflect-metadata");
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const sinon_1 = __importDefault(require("sinon"));
|
|
18
|
-
const prisma_1 = require("../../src/prisma");
|
|
19
18
|
const test_container_1 = require("../helpers/test-container");
|
|
19
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
20
20
|
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
21
21
|
const SessionHeartbeatService_1 = require("../../src/services/SessionHeartbeatService");
|
|
22
22
|
describe('SessionHeartbeatService write path', () => {
|
|
23
|
+
// Its own migrated database. It used the server's client directly, so it
|
|
24
|
+
// wrote this row to the developer's ~/.cache/xenon/xenon.db.
|
|
25
|
+
const scratch = (0, scratch_database_1.useScratchDatabase)();
|
|
23
26
|
const TEST_SESSION_ID = 'test-heartbeat-sess-001';
|
|
24
|
-
let prismaClient;
|
|
25
27
|
before(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
26
28
|
(0, test_container_1.setupTestContainer)();
|
|
27
|
-
prismaClient = (0, prisma_1.getPrismaClient)();
|
|
28
29
|
// Create a minimal session row for the test
|
|
29
|
-
yield
|
|
30
|
+
yield scratch.db.session.upsert({
|
|
30
31
|
where: { id: TEST_SESSION_ID },
|
|
31
32
|
create: {
|
|
32
33
|
id: TEST_SESSION_ID,
|
|
@@ -50,8 +51,6 @@ describe('SessionHeartbeatService write path', () => {
|
|
|
50
51
|
});
|
|
51
52
|
}));
|
|
52
53
|
after(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
-
// Cleanup test session
|
|
54
|
-
yield prismaClient.session.deleteMany({ where: { id: TEST_SESSION_ID } });
|
|
55
54
|
yield (0, test_container_1.resetTestContainer)();
|
|
56
55
|
}));
|
|
57
56
|
afterEach(() => {
|
|
@@ -72,7 +71,7 @@ describe('SessionHeartbeatService write path', () => {
|
|
|
72
71
|
const svc = new SessionHeartbeatService_1.SessionHeartbeatService();
|
|
73
72
|
yield svc.checkAllSessions();
|
|
74
73
|
// Verify the session was updated with heartbeat fields in the DB
|
|
75
|
-
const updated = yield
|
|
74
|
+
const updated = yield scratch.db.session.findUnique({
|
|
76
75
|
where: { id: TEST_SESSION_ID },
|
|
77
76
|
select: { last_heartbeat_at: true, heartbeat_pid: true, heartbeat_host: true },
|
|
78
77
|
});
|
|
@@ -22,10 +22,13 @@ const crypto_1 = require("crypto");
|
|
|
22
22
|
const typedi_1 = require("typedi");
|
|
23
23
|
const JwtKeyService_1 = require("../../src/services/token/JwtKeyService");
|
|
24
24
|
const StreamTicketService_1 = require("../../src/services/token/StreamTicketService");
|
|
25
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
25
26
|
describe('StreamTicketService', () => {
|
|
26
27
|
let dir;
|
|
27
28
|
let svc;
|
|
29
|
+
let restoreContainer;
|
|
28
30
|
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
31
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService);
|
|
29
32
|
dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-ticket-'));
|
|
30
33
|
const keys = new JwtKeyService_1.JwtKeyService();
|
|
31
34
|
yield keys.init(dir);
|
|
@@ -33,7 +36,7 @@ describe('StreamTicketService', () => {
|
|
|
33
36
|
svc = new StreamTicketService_1.StreamTicketService();
|
|
34
37
|
}));
|
|
35
38
|
afterEach(() => {
|
|
36
|
-
|
|
39
|
+
restoreContainer();
|
|
37
40
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
38
41
|
});
|
|
39
42
|
it('mints and redeems a ticket once for the bound udid', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -55,9 +55,12 @@ const path_1 = __importDefault(require("path"));
|
|
|
55
55
|
const JwtKeyService_1 = require("../../src/services/token/JwtKeyService");
|
|
56
56
|
const auth_1 = require("../../src/app/routers/auth");
|
|
57
57
|
const mcpScopes_1 = require("../../src/services/token/mcpScopes");
|
|
58
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
58
59
|
describe('POST /auth/token handler (issueToken)', () => {
|
|
59
60
|
let dir;
|
|
61
|
+
let restoreContainer;
|
|
60
62
|
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
63
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService);
|
|
61
64
|
dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-tok-'));
|
|
62
65
|
const svc = new JwtKeyService_1.JwtKeyService();
|
|
63
66
|
yield svc.init(dir);
|
|
@@ -65,7 +68,7 @@ describe('POST /auth/token handler (issueToken)', () => {
|
|
|
65
68
|
}));
|
|
66
69
|
afterEach(() => {
|
|
67
70
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
68
|
-
|
|
71
|
+
restoreContainer();
|
|
69
72
|
});
|
|
70
73
|
const auth = {
|
|
71
74
|
kind: 'api-key',
|
|
@@ -103,10 +106,21 @@ describe('POST /auth/token handler (issueToken)', () => {
|
|
|
103
106
|
});
|
|
104
107
|
describe('issueToken (xenon-mcp granular claims)', () => {
|
|
105
108
|
const auth = { userId: 'u1', role: 'MEMBER', scopes: 'devices,sessions,read', teamId: 't1' };
|
|
109
|
+
let dir;
|
|
110
|
+
let restoreContainer;
|
|
111
|
+
// Its own key service, not the process-wide one: init() would give the
|
|
112
|
+
// shared singleton this suite's temporary keys for every later spec.
|
|
106
113
|
before(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService);
|
|
115
|
+
dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'jwtkeys-'));
|
|
116
|
+
const svc = new JwtKeyService_1.JwtKeyService();
|
|
117
|
+
yield svc.init(dir);
|
|
118
|
+
typedi_1.Container.set(JwtKeyService_1.JwtKeyService, svc);
|
|
109
119
|
}));
|
|
120
|
+
after(() => {
|
|
121
|
+
restoreContainer();
|
|
122
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
123
|
+
});
|
|
110
124
|
function decode(token) {
|
|
111
125
|
return __awaiter(this, void 0, void 0, function* () {
|
|
112
126
|
// decodeJwt skips signature verification — fine for claim-shape assertions
|
|
@@ -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 prisma_1 = require("../../src/prisma");
|
|
54
54
|
const dashboard_1 = __importStar(require("../../src/app/routers/dashboard"));
|
|
55
55
|
/**
|
|
@@ -144,7 +144,7 @@ describe('Selector Health reads — scoped to the caller’s sessions', () => {
|
|
|
144
144
|
];
|
|
145
145
|
for (const url of ROUTES) {
|
|
146
146
|
it(`${url}: a member counts only the sessions they may see`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
147
|
-
const r = yield (0,
|
|
147
|
+
const r = yield (0, loopbackRequest_1.default)(app(member)).get(url);
|
|
148
148
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
149
149
|
const session = findMany.firstCall.args[0].where.session;
|
|
150
150
|
if (url.includes('build=')) {
|
|
@@ -155,7 +155,7 @@ describe('Selector Health reads — scoped to the caller’s sessions', () => {
|
|
|
155
155
|
}
|
|
156
156
|
}));
|
|
157
157
|
it(`${url}: an admin counts every session`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
158
|
-
const r = yield (0,
|
|
158
|
+
const r = yield (0, loopbackRequest_1.default)(app(admin)).get(url);
|
|
159
159
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
160
160
|
const session = findMany.firstCall.args[0].where.session;
|
|
161
161
|
if (url.includes('build=')) {
|
|
@@ -183,7 +183,7 @@ describe('Selector Health reads — scoped to the caller’s sessions', () => {
|
|
|
183
183
|
findFirst = sinon_1.default.stub(prisma_1.prisma.sessionLog, 'findFirst').resolves(null);
|
|
184
184
|
});
|
|
185
185
|
it('a member: the last heal among the sessions they may see', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
186
|
-
const r = yield (0,
|
|
186
|
+
const r = yield (0, loopbackRequest_1.default)(app(member)).get('/healing/state/muted');
|
|
187
187
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
188
188
|
(0, chai_1.expect)(r.body.muted).to.have.length(1);
|
|
189
189
|
(0, chai_1.expect)(findFirst.firstCall.args[0].where).to.deep.equal({
|
|
@@ -194,7 +194,7 @@ describe('Selector Health reads — scoped to the caller’s sessions', () => {
|
|
|
194
194
|
});
|
|
195
195
|
}));
|
|
196
196
|
it('an admin: the last heal on any session', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
197
|
-
const r = yield (0,
|
|
197
|
+
const r = yield (0, loopbackRequest_1.default)(app(admin)).get('/healing/state/muted');
|
|
198
198
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
199
199
|
(0, chai_1.expect)(findFirst.firstCall.args[0].where).to.not.have.property('session');
|
|
200
200
|
}));
|
|
@@ -14,9 +14,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
require("reflect-metadata");
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
17
18
|
const typedi_1 = require("typedi");
|
|
18
19
|
const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
|
|
19
20
|
const PluginContext_1 = require("../../src/PluginContext");
|
|
21
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
20
22
|
// Lifecycle/state only — the capture seam (openCapture) is stubbed so the test
|
|
21
23
|
// drives packet flow without a real device or screenrecord process.
|
|
22
24
|
function make() {
|
|
@@ -31,9 +33,14 @@ describe('AndroidH264StreamService', () => {
|
|
|
31
33
|
// known default before each test (streaming undefined → source 'scrcpy'); the
|
|
32
34
|
// WS-source tests override it in-test. Set (not remove) so TypeDI never hits a
|
|
33
35
|
// ServiceNotFoundError on the next Container.get.
|
|
36
|
+
// The context is put back after each test: left behind, the plain object
|
|
37
|
+
// was every later spec's PluginContext, with no setContext().
|
|
38
|
+
let restoreContainer;
|
|
34
39
|
beforeEach(() => {
|
|
40
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(PluginContext_1.PluginContext);
|
|
35
41
|
typedi_1.Container.set(PluginContext_1.PluginContext, { pluginArgs: {} });
|
|
36
42
|
});
|
|
43
|
+
afterEach(() => restoreContainer());
|
|
37
44
|
it('start() creates a multiplexer, seeds it with the config packet, and resolves', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
45
|
const svc = make();
|
|
39
46
|
svc.openCapture = (_udid, onPacket) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -44,6 +51,23 @@ describe('AndroidH264StreamService', () => {
|
|
|
44
51
|
(0, chai_1.expect)(mux).to.equal(svc.getMultiplexer('udid-x'));
|
|
45
52
|
(0, chai_1.expect)(mux.clientCount).to.equal(0);
|
|
46
53
|
}));
|
|
54
|
+
it('start() leaves no timer behind once the config packet arrives', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
55
|
+
const clock = sinon_1.default.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] });
|
|
56
|
+
try {
|
|
57
|
+
const svc = make();
|
|
58
|
+
svc.openCapture = (_udid, onPacket) => __awaiter(void 0, void 0, void 0, function* () {
|
|
59
|
+
onPacket({ type: 'config', data: Buffer.from([0]), ptsMs: 0 });
|
|
60
|
+
return { kill: () => undefined };
|
|
61
|
+
});
|
|
62
|
+
yield svc.start('udid-t');
|
|
63
|
+
// The 3 s wait is only a cap: a start that got its config in time must
|
|
64
|
+
// not leave it pending for the process to wait out.
|
|
65
|
+
(0, chai_1.expect)(clock.countTimers()).to.equal(0);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
clock.restore();
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
47
71
|
it('start() is idempotent per udid (concurrent starts share one capture)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
48
72
|
const svc = make();
|
|
49
73
|
let opens = 0;
|
|
@@ -31,8 +31,15 @@ function makeService() {
|
|
|
31
31
|
}
|
|
32
32
|
describe('Android MJPEG server: a stalled capture ends client responses', () => {
|
|
33
33
|
let server;
|
|
34
|
+
// Stopped after each test. close() leaves an open response streaming, and a
|
|
35
|
+
// test whose client never ends left its writeFrame loop writing a frame
|
|
36
|
+
// every 60ms for the rest of the run.
|
|
37
|
+
const sessions = [];
|
|
34
38
|
afterEach(() => {
|
|
39
|
+
for (const session of sessions.splice(0))
|
|
40
|
+
session.status = 'stopped';
|
|
35
41
|
try {
|
|
42
|
+
server === null || server === void 0 ? void 0 : server.closeAllConnections();
|
|
36
43
|
server === null || server === void 0 ? void 0 : server.close();
|
|
37
44
|
}
|
|
38
45
|
catch (_a) {
|
|
@@ -64,6 +71,7 @@ describe('Android MJPEG server: a stalled capture ends client responses', () =>
|
|
|
64
71
|
viewerCount: 0,
|
|
65
72
|
captureHealth: new androidStreamReuse_1.CaptureHealth(),
|
|
66
73
|
};
|
|
74
|
+
sessions.push(session);
|
|
67
75
|
const now = Date.now();
|
|
68
76
|
session.captureHealth.recordFailure(now - androidStreamReuse_1.CAPTURE_STALL_MS * 2, now);
|
|
69
77
|
server = yield svc.createAndListenMjpegServer(session, 0);
|
|
@@ -93,6 +101,7 @@ describe('Android MJPEG server: a stalled capture ends client responses', () =>
|
|
|
93
101
|
viewerCount: 0,
|
|
94
102
|
captureHealth: new androidStreamReuse_1.CaptureHealth(),
|
|
95
103
|
};
|
|
104
|
+
sessions.push(session);
|
|
96
105
|
// Port 0 → the OS picks a free one, so this never collides with a real lease.
|
|
97
106
|
server = yield svc.createAndListenMjpegServer(session, 0);
|
|
98
107
|
const port = server.address().port;
|
|
@@ -133,6 +142,7 @@ describe('Android MJPEG server: a stalled capture ends client responses', () =>
|
|
|
133
142
|
viewerCount: 0,
|
|
134
143
|
captureHealth: new androidStreamReuse_1.CaptureHealth(),
|
|
135
144
|
};
|
|
145
|
+
sessions.push(session);
|
|
136
146
|
server = yield svc.createAndListenMjpegServer(session, 0);
|
|
137
147
|
const port = server.address().port;
|
|
138
148
|
let ended = false;
|
|
@@ -16,7 +16,7 @@ require("reflect-metadata");
|
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const sinon_1 = __importDefault(require("sinon"));
|
|
18
18
|
const express_1 = __importDefault(require("express"));
|
|
19
|
-
const
|
|
19
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
20
20
|
const fs_1 = __importDefault(require("fs"));
|
|
21
21
|
const os_1 = __importDefault(require("os"));
|
|
22
22
|
const path_1 = __importDefault(require("path"));
|
|
@@ -101,20 +101,20 @@ describe('apps follow the device team rule', () => {
|
|
|
101
101
|
findMany = sinon_1.default.stub(prisma_1.prisma.app, 'findMany').resolves([rows['app-shared']]);
|
|
102
102
|
});
|
|
103
103
|
it("asks the database for a member's shared and own-team apps only", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
104
|
-
const r = yield (0,
|
|
104
|
+
const r = yield (0, loopbackRequest_1.default)(server('member-a')).get('/apps');
|
|
105
105
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
106
106
|
const args = findMany.firstCall.args[0];
|
|
107
107
|
(0, chai_1.expect)(args.where).to.deep.equal({ OR: [{ teamId: null }, { teamId: { in: ['team-a'] } }] });
|
|
108
108
|
(0, chai_1.expect)(args.include).to.deep.equal({ team: { select: { id: true, name: true } } });
|
|
109
109
|
}));
|
|
110
110
|
it('a member in no team gets the shared apps only', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
111
|
-
yield (0,
|
|
111
|
+
yield (0, loopbackRequest_1.default)(server('member-none')).get('/apps');
|
|
112
112
|
(0, chai_1.expect)(findMany.firstCall.args[0].where).to.deep.equal({
|
|
113
113
|
OR: [{ teamId: null }, { teamId: { in: [] } }],
|
|
114
114
|
});
|
|
115
115
|
}));
|
|
116
116
|
it('an admin is not filtered, and still gets team names', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
|
-
yield (0,
|
|
117
|
+
yield (0, loopbackRequest_1.default)(server('admin')).get('/apps');
|
|
118
118
|
const args = findMany.firstCall.args[0];
|
|
119
119
|
(0, chai_1.expect)(args.where).to.equal(undefined);
|
|
120
120
|
(0, chai_1.expect)(args.include).to.deep.equal({ team: { select: { id: true, name: true } } });
|
|
@@ -123,26 +123,26 @@ describe('apps follow the device team rule', () => {
|
|
|
123
123
|
describe('GET /apps/:id/download', () => {
|
|
124
124
|
it("serves a member their team's app and a shared one", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
125
125
|
for (const id of ['app-a', 'app-shared']) {
|
|
126
|
-
const r = yield (0,
|
|
126
|
+
const r = yield (0, loopbackRequest_1.default)(server('member-a')).get(`/apps/${id}/download`);
|
|
127
127
|
(0, chai_1.expect)(r.status, id).to.equal(200);
|
|
128
128
|
}
|
|
129
129
|
}));
|
|
130
130
|
it("answers another team's app exactly as an unknown id", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
131
|
-
const hidden = yield (0,
|
|
132
|
-
const unknown = yield (0,
|
|
131
|
+
const hidden = yield (0, loopbackRequest_1.default)(server('member-a')).get('/apps/app-b/download');
|
|
132
|
+
const unknown = yield (0, loopbackRequest_1.default)(server('member-a')).get('/apps/no-such-app/download');
|
|
133
133
|
(0, chai_1.expect)(hidden.status).to.equal(404);
|
|
134
134
|
(0, chai_1.expect)(hidden.body).to.deep.equal(UNKNOWN);
|
|
135
135
|
(0, chai_1.expect)(unknown.status).to.equal(hidden.status);
|
|
136
136
|
(0, chai_1.expect)(unknown.body).to.deep.equal(hidden.body);
|
|
137
137
|
}));
|
|
138
138
|
it('serves an admin any team’s app', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
139
|
-
const r = yield (0,
|
|
139
|
+
const r = yield (0, loopbackRequest_1.default)(server('admin')).get('/apps/app-b/download');
|
|
140
140
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
141
141
|
}));
|
|
142
142
|
it("a download ticket's request sees its own app only, though it carries no teams", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
143
|
-
(0, chai_1.expect)((yield (0,
|
|
143
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(server('ticket-for-app-a')).get('/apps/app-a/download')).status).to.equal(200);
|
|
144
144
|
for (const id of ['app-b', 'app-shared']) {
|
|
145
|
-
const r = yield (0,
|
|
145
|
+
const r = yield (0, loopbackRequest_1.default)(server('ticket-for-app-a')).get(`/apps/${id}/download`);
|
|
146
146
|
(0, chai_1.expect)(r.status, id).to.equal(404);
|
|
147
147
|
(0, chai_1.expect)(r.body, id).to.deep.equal(UNKNOWN);
|
|
148
148
|
}
|
|
@@ -154,20 +154,20 @@ describe('apps follow the device team rule', () => {
|
|
|
154
154
|
deleteApp = sinon_1.default.stub(app_service_1.APP_SERVICE, 'deleteApp').resolves(undefined);
|
|
155
155
|
});
|
|
156
156
|
it('is refused to a member', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
157
|
-
const r = yield (0,
|
|
157
|
+
const r = yield (0, loopbackRequest_1.default)(server('member-a'))
|
|
158
158
|
.delete('/apps/app-a')
|
|
159
159
|
.set('Origin', 'http://127.0.0.1');
|
|
160
160
|
(0, chai_1.expect)(r.status).to.equal(403);
|
|
161
161
|
(0, chai_1.expect)(deleteApp.called).to.equal(false);
|
|
162
162
|
}));
|
|
163
163
|
it('lets an admin delete any team’s app', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
164
|
-
const r = yield (0,
|
|
164
|
+
const r = yield (0, loopbackRequest_1.default)(server('admin')).delete('/apps/app-b');
|
|
165
165
|
(0, chai_1.expect)(r.status).to.equal(204);
|
|
166
166
|
(0, chai_1.expect)(deleteApp.calledOnceWith('app-b')).to.equal(true);
|
|
167
167
|
}));
|
|
168
168
|
it('answers an unknown id 404, and a hidden app the same way, deleting nothing', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
|
-
const unknown = yield (0,
|
|
170
|
-
const hidden = yield (0,
|
|
169
|
+
const unknown = yield (0, loopbackRequest_1.default)(server('admin')).delete('/apps/no-such-app');
|
|
170
|
+
const hidden = yield (0, loopbackRequest_1.default)(server('team-scoped-admin')).delete('/apps/app-b');
|
|
171
171
|
(0, chai_1.expect)(unknown.status).to.equal(404);
|
|
172
172
|
(0, chai_1.expect)(unknown.body).to.deep.equal(UNKNOWN);
|
|
173
173
|
(0, chai_1.expect)(hidden.status).to.equal(unknown.status);
|
|
@@ -187,7 +187,7 @@ describe('apps follow the device team rule', () => {
|
|
|
187
187
|
.callsFake(((args) => __awaiter(void 0, void 0, void 0, function* () { return args.where.id === 'team-a' ? { id: 'team-a' } : null; })));
|
|
188
188
|
});
|
|
189
189
|
const post = (caller, teamId) => {
|
|
190
|
-
const r = (0,
|
|
190
|
+
const r = (0, loopbackRequest_1.default)(server(caller))
|
|
191
191
|
.post('/apps/upload')
|
|
192
192
|
.attach('app', Buffer.from('ipa bytes'), 'app.ipa');
|
|
193
193
|
return teamId === undefined ? r : r.field('teamId', teamId);
|
|
@@ -229,7 +229,7 @@ describe('apps follow the device team rule', () => {
|
|
|
229
229
|
.stub(prisma_1.prisma.team, 'findUnique')
|
|
230
230
|
.callsFake(((args) => __awaiter(void 0, void 0, void 0, function* () { return ['team-a', 'team-b'].includes(args.where.id) ? { id: args.where.id } : null; })));
|
|
231
231
|
});
|
|
232
|
-
const put = (caller, id, body) => (0,
|
|
232
|
+
const put = (caller, id, body) => (0, loopbackRequest_1.default)(server(caller))
|
|
233
233
|
.put(`/apps/${id}/team`)
|
|
234
234
|
.send(body);
|
|
235
235
|
it('moves an app to a team, as PUT /device/:udid/team does a phone', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -16,7 +16,7 @@ require("reflect-metadata");
|
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const sinon_1 = __importDefault(require("sinon"));
|
|
18
18
|
const express_1 = __importDefault(require("express"));
|
|
19
|
-
const
|
|
19
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
20
20
|
const typedi_1 = require("typedi");
|
|
21
21
|
const auth_1 = require("../../src/app/routers/auth");
|
|
22
22
|
const UserService_1 = require("../../src/services/UserService");
|
|
@@ -58,7 +58,7 @@ describe('GET /auth/me with authDisabled', () => {
|
|
|
58
58
|
config_1.config.authDisabled = true;
|
|
59
59
|
// No User row exists for the synthetic id — this is the real-world state.
|
|
60
60
|
const findById = sinon_1.default.stub(typedi_1.Container.get(UserService_1.UserService), 'findById').resolves(null);
|
|
61
|
-
const res = yield (0,
|
|
61
|
+
const res = yield (0, loopbackRequest_1.default)(appWithSyntheticAuth()).get('/auth/me');
|
|
62
62
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
63
63
|
(0, chai_1.expect)(res.body.userId).to.equal('auth-disabled');
|
|
64
64
|
(0, chai_1.expect)(res.body.role).to.equal('SUPER_ADMIN');
|
|
@@ -74,14 +74,17 @@ describe('GET /auth/me with authDisabled', () => {
|
|
|
74
74
|
sinon_1.default.stub(typedi_1.Container.get(UserService_1.UserService), 'findById').resolves({
|
|
75
75
|
id: 'auth-disabled', email: 'u@x.local', name: 'U', role: 'SUPER_ADMIN', accessKey: 'ak',
|
|
76
76
|
});
|
|
77
|
-
const res = yield (0,
|
|
77
|
+
const res = yield (0, loopbackRequest_1.default)(appWithSyntheticAuth()).get('/auth/me');
|
|
78
78
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
79
79
|
(0, chai_1.expect)(res.body.authDisabled).to.equal(false);
|
|
80
|
+
// The caller's own key, unredacted: the logger's accessKey redaction
|
|
81
|
+
// never touches a response.
|
|
82
|
+
(0, chai_1.expect)(res.body.accessKey).to.equal('ak');
|
|
80
83
|
}));
|
|
81
84
|
it('still 401s when auth is enabled and the user does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
85
|
config_1.config.authDisabled = false;
|
|
83
86
|
sinon_1.default.stub(typedi_1.Container.get(UserService_1.UserService), 'findById').resolves(null);
|
|
84
|
-
const res = yield (0,
|
|
87
|
+
const res = yield (0, loopbackRequest_1.default)(appWithSyntheticAuth()).get('/auth/me');
|
|
85
88
|
(0, chai_1.expect)(res.status).to.equal(401);
|
|
86
89
|
(0, chai_1.expect)(res.body.error).to.equal('unauthenticated');
|
|
87
90
|
}));
|
|
@@ -16,7 +16,7 @@ require("reflect-metadata");
|
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const sinon_1 = __importDefault(require("sinon"));
|
|
18
18
|
const express_1 = __importDefault(require("express"));
|
|
19
|
-
const
|
|
19
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
20
20
|
const typedi_1 = require("typedi");
|
|
21
21
|
const fs_1 = __importDefault(require("fs"));
|
|
22
22
|
const os_1 = __importDefault(require("os"));
|
|
@@ -110,7 +110,7 @@ describe('authMiddleware — app download ticket', () => {
|
|
|
110
110
|
res.on('end', () => done(null, Buffer.concat(chunks)));
|
|
111
111
|
};
|
|
112
112
|
const download = (id, ticket) => {
|
|
113
|
-
const r = (0,
|
|
113
|
+
const r = (0, loopbackRequest_1.default)(app).get(`/xenon/api/apps/${id}/download`);
|
|
114
114
|
return ticket === undefined ? r : r.query({ ticket });
|
|
115
115
|
};
|
|
116
116
|
const INVALID = { error: 'invalid ticket' };
|
|
@@ -158,16 +158,16 @@ describe('authMiddleware — app download ticket', () => {
|
|
|
158
158
|
}));
|
|
159
159
|
it('an app ticket is refused on the stream route', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
160
160
|
const t = yield tickets.mint('UDID-1');
|
|
161
|
-
const r = yield (0,
|
|
161
|
+
const r = yield (0, loopbackRequest_1.default)(app).get('/xenon/api/control/UDID-1/stream').query({ ticket: t });
|
|
162
162
|
(0, chai_1.expect)(r.status).to.equal(401);
|
|
163
163
|
(0, chai_1.expect)(r.body).to.deep.equal(INVALID);
|
|
164
164
|
}));
|
|
165
165
|
it('opens nothing but the download: not the list, nor another method', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
166
166
|
const t = yield tickets.mint('app-A');
|
|
167
|
-
const list = yield (0,
|
|
167
|
+
const list = yield (0, loopbackRequest_1.default)(app).get('/xenon/api/apps').query({ ticket: t });
|
|
168
168
|
(0, chai_1.expect)(list.status).to.equal(401);
|
|
169
169
|
(0, chai_1.expect)(list.body).to.deep.equal({ error: 'unauthenticated' });
|
|
170
|
-
const del = yield (0,
|
|
170
|
+
const del = yield (0, loopbackRequest_1.default)(app).delete('/xenon/api/apps/app-A/download').query({ ticket: t });
|
|
171
171
|
(0, chai_1.expect)(del.status).to.equal(401);
|
|
172
172
|
(0, chai_1.expect)(del.body).to.deep.equal({ error: 'unauthenticated' });
|
|
173
173
|
// Neither attempt spent it.
|
|
@@ -25,6 +25,7 @@ const UserService_1 = require("../../src/services/UserService");
|
|
|
25
25
|
const ApiKeyService_1 = require("../../src/services/ApiKeyService");
|
|
26
26
|
const UserSessionService_1 = require("../../src/services/UserSessionService");
|
|
27
27
|
const prisma_1 = require("../../src/prisma");
|
|
28
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
28
29
|
function fakeRes() {
|
|
29
30
|
const res = { statusCode: 200 };
|
|
30
31
|
res.status = (c) => { res.statusCode = c; return res; };
|
|
@@ -36,7 +37,9 @@ describe('authMiddleware — Bearer branch', () => {
|
|
|
36
37
|
let dir;
|
|
37
38
|
let keySvc;
|
|
38
39
|
const user = { id: 'u1', role: 'MEMBER', status: 'ACTIVE' };
|
|
40
|
+
let restoreContainer;
|
|
39
41
|
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService, UserService_1.UserService, ApiKeyService_1.ApiKeyService, UserSessionService_1.UserSessionService);
|
|
40
43
|
dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-bearer-'));
|
|
41
44
|
keySvc = new JwtKeyService_1.JwtKeyService();
|
|
42
45
|
yield keySvc.init(dir);
|
|
@@ -48,7 +51,7 @@ describe('authMiddleware — Bearer branch', () => {
|
|
|
48
51
|
}));
|
|
49
52
|
afterEach(() => {
|
|
50
53
|
sinon_1.default.restore();
|
|
51
|
-
|
|
54
|
+
restoreContainer();
|
|
52
55
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
53
56
|
});
|
|
54
57
|
it('accepts a valid xenon-rest Bearer token and sets req.auth', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -25,6 +25,7 @@ const UserService_1 = require("../../src/services/UserService");
|
|
|
25
25
|
const ApiKeyService_1 = require("../../src/services/ApiKeyService");
|
|
26
26
|
const UserSessionService_1 = require("../../src/services/UserSessionService");
|
|
27
27
|
const prisma_1 = require("../../src/prisma");
|
|
28
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
28
29
|
function fakeRes() {
|
|
29
30
|
const res = { statusCode: 200 };
|
|
30
31
|
res.status = (c) => { res.statusCode = c; return res; };
|
|
@@ -39,7 +40,9 @@ describe('authMiddleware — Bearer branch accepts xenon-mcp audience', () => {
|
|
|
39
40
|
let dir;
|
|
40
41
|
let keySvc;
|
|
41
42
|
const user = { id: 'u1', role: 'MEMBER', status: 'ACTIVE' };
|
|
43
|
+
let restoreContainer;
|
|
42
44
|
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
45
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService, UserService_1.UserService, ApiKeyService_1.ApiKeyService, UserSessionService_1.UserSessionService);
|
|
43
46
|
dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-mcp-aud-'));
|
|
44
47
|
keySvc = new JwtKeyService_1.JwtKeyService();
|
|
45
48
|
yield keySvc.init(dir);
|
|
@@ -51,7 +54,7 @@ describe('authMiddleware — Bearer branch accepts xenon-mcp audience', () => {
|
|
|
51
54
|
}));
|
|
52
55
|
afterEach(() => {
|
|
53
56
|
sinon_1.default.restore();
|
|
54
|
-
|
|
57
|
+
restoreContainer();
|
|
55
58
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
56
59
|
});
|
|
57
60
|
it('accepts a valid xenon-mcp Bearer token (ACTIVE user) and sets req.auth.kind=bearer', () => __awaiter(void 0, void 0, void 0, function* () {
|