@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,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
|
}));
|
|
@@ -17,6 +17,7 @@ const chai_1 = require("chai");
|
|
|
17
17
|
const typedi_1 = require("typedi");
|
|
18
18
|
const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
|
|
19
19
|
const PluginContext_1 = require("../../src/PluginContext");
|
|
20
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
20
21
|
// Lifecycle/state only — the capture seam (openCapture) is stubbed so the test
|
|
21
22
|
// drives packet flow without a real device or screenrecord process.
|
|
22
23
|
function make() {
|
|
@@ -31,9 +32,14 @@ describe('AndroidH264StreamService', () => {
|
|
|
31
32
|
// known default before each test (streaming undefined → source 'scrcpy'); the
|
|
32
33
|
// WS-source tests override it in-test. Set (not remove) so TypeDI never hits a
|
|
33
34
|
// ServiceNotFoundError on the next Container.get.
|
|
35
|
+
// The context is put back after each test: left behind, the plain object
|
|
36
|
+
// was every later spec's PluginContext, with no setContext().
|
|
37
|
+
let restoreContainer;
|
|
34
38
|
beforeEach(() => {
|
|
39
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(PluginContext_1.PluginContext);
|
|
35
40
|
typedi_1.Container.set(PluginContext_1.PluginContext, { pluginArgs: {} });
|
|
36
41
|
});
|
|
42
|
+
afterEach(() => restoreContainer());
|
|
37
43
|
it('start() creates a multiplexer, seeds it with the config packet, and resolves', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
38
44
|
const svc = make();
|
|
39
45
|
svc.openCapture = (_udid, onPacket) => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -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* () {
|
|
@@ -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"));
|
|
@@ -29,6 +29,8 @@ const device_store_1 = require("../../src/data-service/device-store");
|
|
|
29
29
|
const UserService_1 = require("../../src/services/UserService");
|
|
30
30
|
const ApiKeyService_1 = require("../../src/services/ApiKeyService");
|
|
31
31
|
const UserSessionService_1 = require("../../src/services/UserSessionService");
|
|
32
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
33
|
+
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
32
34
|
function fakeRes() {
|
|
33
35
|
const res = { statusCode: 200 };
|
|
34
36
|
res.status = (c) => {
|
|
@@ -46,7 +48,9 @@ describe('authMiddleware — stream-ticket branch', () => {
|
|
|
46
48
|
let dir;
|
|
47
49
|
let keySvc;
|
|
48
50
|
let ticketSvc;
|
|
51
|
+
let restoreContainer;
|
|
49
52
|
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
53
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService, StreamTicketService_1.StreamTicketService, UserService_1.UserService, ApiKeyService_1.ApiKeyService, UserSessionService_1.UserSessionService);
|
|
50
54
|
dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-authmw-ticket-'));
|
|
51
55
|
keySvc = new JwtKeyService_1.JwtKeyService();
|
|
52
56
|
yield keySvc.init(dir);
|
|
@@ -62,7 +66,7 @@ describe('authMiddleware — stream-ticket branch', () => {
|
|
|
62
66
|
}));
|
|
63
67
|
afterEach(() => {
|
|
64
68
|
sinon_1.default.restore();
|
|
65
|
-
|
|
69
|
+
restoreContainer();
|
|
66
70
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
67
71
|
});
|
|
68
72
|
// req.path here mirrors what authMiddleware actually sees at runtime:
|
|
@@ -194,7 +198,12 @@ describe('authMiddleware — stream-ticket branch (real mount-order, supertest)'
|
|
|
194
198
|
let dir;
|
|
195
199
|
let app;
|
|
196
200
|
let ticketSvc;
|
|
201
|
+
let restoreContainer;
|
|
202
|
+
// Served on 127.0.0.1: request(app) listens on every address and connects
|
|
203
|
+
// to 127.0.0.1, where another process's listener on that port can answer.
|
|
204
|
+
const loopback = (0, loopbackServer_1.loopbackServers)();
|
|
197
205
|
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
206
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService, StreamTicketService_1.StreamTicketService, UserService_1.UserService, ApiKeyService_1.ApiKeyService, UserSessionService_1.UserSessionService);
|
|
198
207
|
dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-authmw-mount-'));
|
|
199
208
|
const keySvc = new JwtKeyService_1.JwtKeyService();
|
|
200
209
|
yield keySvc.init(dir);
|
|
@@ -222,21 +231,24 @@ describe('authMiddleware — stream-ticket branch (real mount-order, supertest)'
|
|
|
222
231
|
app = (0, express_1.default)();
|
|
223
232
|
app.use('/xenon/api', apiRouter);
|
|
224
233
|
}));
|
|
225
|
-
afterEach(() => {
|
|
234
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
226
235
|
sinon_1.default.restore();
|
|
227
|
-
|
|
236
|
+
restoreContainer();
|
|
237
|
+
yield loopback.closeAll();
|
|
228
238
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
229
|
-
});
|
|
239
|
+
}));
|
|
230
240
|
it('a valid ticket on GET /xenon/api/control/:udid/stream reaches the route (past 401)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
231
241
|
const ticket = yield ticketSvc.mint('UDID-1', 'actor-1');
|
|
232
|
-
const res = yield (0,
|
|
242
|
+
const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(app))
|
|
243
|
+
.get('/xenon/api/control/UDID-1/stream')
|
|
244
|
+
.query({ ticket });
|
|
233
245
|
// The ticket branch fired: request cleared auth and hit the handler's
|
|
234
246
|
// device lookup (404), rather than being rejected at the middleware (401).
|
|
235
247
|
(0, chai_1.expect)(res.status).to.not.equal(401);
|
|
236
248
|
(0, chai_1.expect)(res.status).to.equal(404); // 'Device not found' from the stubbed store
|
|
237
249
|
}));
|
|
238
250
|
it('the SAME request without a ticket is rejected 401 (proves the guard is non-vacuous)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
239
|
-
const res = yield (0,
|
|
251
|
+
const res = yield (0, loopbackRequest_1.default)(yield loopback.serve(app)).get('/xenon/api/control/UDID-1/stream');
|
|
240
252
|
(0, chai_1.expect)(res.status).to.equal(401);
|
|
241
253
|
(0, chai_1.expect)(res.body).to.deep.equal({ error: 'unauthenticated' });
|
|
242
254
|
}));
|
|
@@ -92,6 +92,9 @@ describe('authMiddleware', () => {
|
|
|
92
92
|
role: 'MEMBER',
|
|
93
93
|
status: 'ACTIVE',
|
|
94
94
|
});
|
|
95
|
+
// A member's teams are looked up; without this the lookup read the
|
|
96
|
+
// developer's own database.
|
|
97
|
+
sinon_1.default.stub(prisma_1.prisma.teamMember, 'findMany').resolves([]);
|
|
95
98
|
const req = { headers: { cookie: 'xenon_dashboard_session=legacy-key-id' } };
|
|
96
99
|
let called = false;
|
|
97
100
|
yield (0, authMiddleware_1.authMiddleware)(req, mkRes(), () => {
|
|
@@ -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 BugReportService_1 = require("../../../src/services/bug-report/BugReportService");
|
|
21
21
|
const bug_report_1 = __importDefault(require("../../../src/app/routers/bug-report"));
|
|
@@ -63,8 +63,8 @@ describe('bug-report route', () => {
|
|
|
63
63
|
throw new Error(`Session ${opts.sessionId} not found`);
|
|
64
64
|
throw new Error('built a report');
|
|
65
65
|
}));
|
|
66
|
-
const hidden = yield (0,
|
|
67
|
-
const unknown = yield (0,
|
|
66
|
+
const hidden = yield (0, loopbackRequest_1.default)(makeApp()).post('/sessions/sess-b/bug-report?mode=full');
|
|
67
|
+
const unknown = yield (0, loopbackRequest_1.default)(makeApp()).post('/sessions/sess-nope/bug-report?mode=full');
|
|
68
68
|
(0, chai_1.expect)(hidden.status).to.equal(404);
|
|
69
69
|
(0, chai_1.expect)(unknown.status).to.equal(404);
|
|
70
70
|
(0, chai_1.expect)(hidden.body).to.deep.equal({ error: 'Session sess-b not found' });
|
|
@@ -78,26 +78,26 @@ describe('bug-report route', () => {
|
|
|
78
78
|
entries: [{ name: 'a.txt', source: { kind: 'buffer', data: Buffer.from('hi') } }],
|
|
79
79
|
cleanup: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
80
80
|
});
|
|
81
|
-
const res = yield (0,
|
|
81
|
+
const res = yield (0, loopbackRequest_1.default)(makeApp({ admin: true })).post('/sessions/sess-b/bug-report?mode=full');
|
|
82
82
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
83
83
|
}));
|
|
84
84
|
it('401 when the caller is not signed in', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
85
|
-
const res = yield (0,
|
|
85
|
+
const res = yield (0, loopbackRequest_1.default)(makeApp({ signedIn: false })).post('/sessions/sess-1/bug-report?mode=full');
|
|
86
86
|
(0, chai_1.expect)(res.status).to.equal(401);
|
|
87
87
|
}));
|
|
88
88
|
it('400 on invalid mode', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
89
|
-
const res = yield (0,
|
|
89
|
+
const res = yield (0, loopbackRequest_1.default)(makeApp()).post('/sessions/sess-1/bug-report?mode=bogus');
|
|
90
90
|
(0, chai_1.expect)(res.status).to.equal(400);
|
|
91
91
|
}));
|
|
92
92
|
it('400 on out-of-range windowSec', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
-
const res = yield (0,
|
|
93
|
+
const res = yield (0, loopbackRequest_1.default)(makeApp()).post('/sessions/sess-1/bug-report?mode=slice&windowSec=9999');
|
|
94
94
|
(0, chai_1.expect)(res.status).to.equal(400);
|
|
95
95
|
}));
|
|
96
96
|
it('404 when service throws not-found', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
97
|
sinon_1.default
|
|
98
98
|
.stub(BugReportService_1.BugReportService.prototype, 'assemble')
|
|
99
99
|
.rejects(new Error('Session sess-1 not found'));
|
|
100
|
-
const res = yield (0,
|
|
100
|
+
const res = yield (0, loopbackRequest_1.default)(makeApp()).post('/sessions/sess-1/bug-report?mode=full');
|
|
101
101
|
(0, chai_1.expect)(res.status).to.equal(404);
|
|
102
102
|
}));
|
|
103
103
|
it('200 streams zip on success', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -107,7 +107,7 @@ describe('bug-report route', () => {
|
|
|
107
107
|
entries: [{ name: 'a.txt', source: { kind: 'buffer', data: Buffer.from('hi') } }],
|
|
108
108
|
cleanup: () => __awaiter(void 0, void 0, void 0, function* () { }),
|
|
109
109
|
});
|
|
110
|
-
const res = yield (0,
|
|
110
|
+
const res = yield (0, loopbackRequest_1.default)(makeApp())
|
|
111
111
|
.post('/sessions/sess-1/bug-report?mode=full')
|
|
112
112
|
.buffer(true)
|
|
113
113
|
.parse((r, cb) => {
|