@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
|
@@ -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 deviceTeamGuard_1 = require("../../src/middleware/deviceTeamGuard");
|
|
20
20
|
const controlDevice_1 = require("../../src/middleware/controlDevice");
|
|
21
21
|
const deviceVisibility_1 = require("../../src/services/device-access/deviceVisibility");
|
|
@@ -93,7 +93,7 @@ function appWith(caller, findDevice = (udid) => __awaiter(this, void 0, void 0,
|
|
|
93
93
|
return { app, seen };
|
|
94
94
|
}
|
|
95
95
|
function call(app, method, path) {
|
|
96
|
-
return (0,
|
|
96
|
+
return (0, loopbackRequest_1.default)(app)[method](path);
|
|
97
97
|
}
|
|
98
98
|
/** What a caller can observe of an answer, with the requested udid masked out. */
|
|
99
99
|
function observed(res, udid) {
|
|
@@ -153,7 +153,7 @@ describe('deviceTeamGuard', () => {
|
|
|
153
153
|
}
|
|
154
154
|
it('hands a hidden phone on as a udid no device has, keeping the original URL', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
155
155
|
const { app, seen } = appWith(ALICE);
|
|
156
|
-
yield (0,
|
|
156
|
+
yield (0, loopbackRequest_1.default)(app).get(`/control/${PHONE_B}/screenshot?x=1`);
|
|
157
157
|
(0, chai_1.expect)(seen).to.deep.equal([controlDevice_1.HIDDEN_DEVICE_UDID]);
|
|
158
158
|
(0, chai_1.expect)(DEVICES[controlDevice_1.HIDDEN_DEVICE_UDID]).to.equal(undefined);
|
|
159
159
|
}));
|
|
@@ -171,7 +171,7 @@ describe('deviceTeamGuard', () => {
|
|
|
171
171
|
res.end();
|
|
172
172
|
});
|
|
173
173
|
app.use('/control', router);
|
|
174
|
-
yield (0,
|
|
174
|
+
yield (0, loopbackRequest_1.default)(app).get(`/control/${PHONE_B}/screenshot?x=1`);
|
|
175
175
|
(0, chai_1.expect)(originalUrl).to.equal(`/control/${PHONE_B}/screenshot?x=1`);
|
|
176
176
|
}));
|
|
177
177
|
describe('restoreHiddenDeviceUrl, mounted after the router at the parent', () => {
|
|
@@ -196,7 +196,7 @@ describe('deviceTeamGuard', () => {
|
|
|
196
196
|
return { app, seenByRoute };
|
|
197
197
|
}
|
|
198
198
|
it('gives a later layer the real req.url after the router falls through', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
199
|
-
const res = yield (0,
|
|
199
|
+
const res = yield (0, loopbackRequest_1.default)(parentApp(ALICE).app).get(`/control/${PHONE_B}/bogus?x=1`);
|
|
200
200
|
(0, chai_1.expect)(res.body).to.deep.equal({
|
|
201
201
|
url: `/control/${PHONE_B}/bogus?x=1`,
|
|
202
202
|
path: `/control/${PHONE_B}/bogus`,
|
|
@@ -204,26 +204,26 @@ describe('deviceTeamGuard', () => {
|
|
|
204
204
|
}));
|
|
205
205
|
it('answers a hidden phone and an unknown udid alike, even where a layer echoes req.url', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
206
206
|
const { app } = parentApp(ALICE);
|
|
207
|
-
const hidden = yield (0,
|
|
208
|
-
const unknown = yield (0,
|
|
207
|
+
const hidden = yield (0, loopbackRequest_1.default)(app).get(`/control/${PHONE_B}/bogus`);
|
|
208
|
+
const unknown = yield (0, loopbackRequest_1.default)(app).get(`/control/${UNKNOWN}/bogus`);
|
|
209
209
|
(0, chai_1.expect)(observed(hidden, PHONE_B)).to.deep.equal(observed(unknown, UNKNOWN));
|
|
210
210
|
(0, chai_1.expect)(hidden.text).to.not.include(controlDevice_1.HIDDEN_DEVICE_UDID);
|
|
211
211
|
}));
|
|
212
212
|
it('still hands a routed request the placeholder, not the real udid', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
213
213
|
const { app, seenByRoute } = parentApp(ALICE);
|
|
214
|
-
const res = yield (0,
|
|
214
|
+
const res = yield (0, loopbackRequest_1.default)(app).get(`/control/${PHONE_B}/screenshot`);
|
|
215
215
|
(0, chai_1.expect)(res.text).to.equal('Device not found');
|
|
216
216
|
(0, chai_1.expect)(seenByRoute).to.deep.equal([controlDevice_1.HIDDEN_DEVICE_UDID]);
|
|
217
217
|
}));
|
|
218
218
|
it('leaves a request it did not rewrite alone', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
219
|
-
const res = yield (0,
|
|
219
|
+
const res = yield (0, loopbackRequest_1.default)(parentApp(ALICE).app).get(`/control/${PHONE_A}/bogus`);
|
|
220
220
|
(0, chai_1.expect)(res.body.url).to.equal(`/control/${PHONE_A}/bogus`);
|
|
221
221
|
}));
|
|
222
222
|
});
|
|
223
223
|
it('gives a member in no team the shared pool only', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
224
224
|
const { app } = appWith(NO_TEAM);
|
|
225
|
-
(0, chai_1.expect)((yield (0,
|
|
226
|
-
const a = yield (0,
|
|
225
|
+
(0, chai_1.expect)((yield (0, loopbackRequest_1.default)(app).get(`/control/${SHARED}/screenshot`)).status).to.equal(200);
|
|
226
|
+
const a = yield (0, loopbackRequest_1.default)(app).get(`/control/${PHONE_A}/screenshot`);
|
|
227
227
|
(0, chai_1.expect)(a.status).to.equal(404);
|
|
228
228
|
(0, chai_1.expect)(a.text).to.equal('Device not found');
|
|
229
229
|
}));
|
|
@@ -231,16 +231,16 @@ describe('deviceTeamGuard', () => {
|
|
|
231
231
|
// plain-text default. A hidden phone gets the same body, from the handler.
|
|
232
232
|
for (const action of ['display', 'appium-session']) {
|
|
233
233
|
it(`answers ${action} with that handler's own JSON 404 body`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
234
|
-
const res = yield (0,
|
|
234
|
+
const res = yield (0, loopbackRequest_1.default)(appWith(ALICE).app).get(`/control/${PHONE_B}/${action}`);
|
|
235
235
|
(0, chai_1.expect)(res.status).to.equal(404);
|
|
236
236
|
(0, chai_1.expect)(res.body).to.deep.equal({ status: 'error', message: 'Device not found' });
|
|
237
237
|
}));
|
|
238
238
|
}
|
|
239
239
|
it('guards an action in any letter case', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
240
240
|
const { app, seen } = appWith(ALICE);
|
|
241
|
-
const start = yield (0,
|
|
241
|
+
const start = yield (0, loopbackRequest_1.default)(app).post(`/control/${PHONE_B}/Stream/Start`);
|
|
242
242
|
(0, chai_1.expect)(start.status).to.equal(404);
|
|
243
|
-
const display = yield (0,
|
|
243
|
+
const display = yield (0, loopbackRequest_1.default)(app).get(`/control/${PHONE_B}/Display`);
|
|
244
244
|
(0, chai_1.expect)(display.status).to.equal(404);
|
|
245
245
|
(0, chai_1.expect)(display.body).to.deep.equal({ status: 'error', message: 'Device not found' });
|
|
246
246
|
(0, chai_1.expect)(seen).to.not.include(PHONE_B);
|
|
@@ -272,7 +272,7 @@ describe('deviceTeamGuard', () => {
|
|
|
272
272
|
// drive a phone the device list hides from them.
|
|
273
273
|
it('ignores an admin scope on a caller whose teams are scoped', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
274
274
|
const scoped = { role: 'MEMBER', scopes: 'admin,devices', teamIds: [TEAM_A] };
|
|
275
|
-
const res = yield (0,
|
|
275
|
+
const res = yield (0, loopbackRequest_1.default)(appWith(scoped).app).post(`/control/${PHONE_B}/tap`);
|
|
276
276
|
(0, chai_1.expect)(res.status).to.equal(404);
|
|
277
277
|
}));
|
|
278
278
|
it('does not look the device up for an admin', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -282,13 +282,13 @@ describe('deviceTeamGuard', () => {
|
|
|
282
282
|
lookups += 1;
|
|
283
283
|
return (_a = DEVICES[udid]) !== null && _a !== void 0 ? _a : null;
|
|
284
284
|
}));
|
|
285
|
-
const res = yield (0,
|
|
285
|
+
const res = yield (0, loopbackRequest_1.default)(app).get(`/control/${PHONE_B}/screenshot`);
|
|
286
286
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
287
287
|
(0, chai_1.expect)(lookups).to.equal(0);
|
|
288
288
|
}));
|
|
289
289
|
it('passes an unknown device on untouched, for the handler to answer', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
290
290
|
const { app, seen } = appWith(ALICE);
|
|
291
|
-
const res = yield (0,
|
|
291
|
+
const res = yield (0, loopbackRequest_1.default)(app).post(`/control/${UNKNOWN}/tap`);
|
|
292
292
|
(0, chai_1.expect)(res.status).to.equal(404);
|
|
293
293
|
(0, chai_1.expect)(seen).to.deep.equal([UNKNOWN]);
|
|
294
294
|
}));
|
|
@@ -296,7 +296,7 @@ describe('deviceTeamGuard', () => {
|
|
|
296
296
|
const { app, seen } = appWith(ALICE, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
297
297
|
throw new Error('store hiccup');
|
|
298
298
|
}));
|
|
299
|
-
const res = yield (0,
|
|
299
|
+
const res = yield (0, loopbackRequest_1.default)(app).get(`/control/${PHONE_A}/screenshot`);
|
|
300
300
|
(0, chai_1.expect)(res.status).to.equal(503);
|
|
301
301
|
(0, chai_1.expect)(res.body).to.deep.equal({
|
|
302
302
|
success: false,
|
|
@@ -310,13 +310,13 @@ describe('deviceTeamGuard', () => {
|
|
|
310
310
|
// An async middleware that throws never answers under Express 4, so keep
|
|
311
311
|
// a short timeout: a regression should fail fast, not after 60s.
|
|
312
312
|
this.timeout(5000);
|
|
313
|
-
const res = yield (0,
|
|
313
|
+
const res = yield (0, loopbackRequest_1.default)(appWith(ALICE).app).get('/control/100%/screenshot');
|
|
314
314
|
(0, chai_1.expect)(res.status).to.equal(400);
|
|
315
315
|
(0, chai_1.expect)(res.body).to.deep.equal({ success: false, error: 'invalid_udid' });
|
|
316
316
|
});
|
|
317
317
|
});
|
|
318
318
|
it('401s when there is no caller', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
319
|
-
const res = yield (0,
|
|
319
|
+
const res = yield (0, loopbackRequest_1.default)(appWith(null).app).get(`/control/${PHONE_A}/screenshot`);
|
|
320
320
|
(0, chai_1.expect)(res.status).to.equal(401);
|
|
321
321
|
}));
|
|
322
322
|
});
|
|
@@ -57,11 +57,18 @@ const device_service_1 = require("../../src/data-service/device-service");
|
|
|
57
57
|
const device_utils_1 = require("../../src/device-utils");
|
|
58
58
|
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
59
59
|
const test_container_1 = require("../helpers/test-container");
|
|
60
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
61
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
60
62
|
chai_1.default.should();
|
|
61
63
|
chai_1.default.use(sinon_chai_1.default);
|
|
62
64
|
const expect = chai_1.default.expect;
|
|
63
65
|
const sandbox = sinon_1.default.createSandbox();
|
|
64
66
|
describe('Device Utils', () => {
|
|
67
|
+
// The in-memory device store, also when this file runs on its own, and a
|
|
68
|
+
// database of its own for what allocation reads through Prisma. It passed
|
|
69
|
+
// only against the developer's ~/.cache/xenon/xenon.db, which has the tables.
|
|
70
|
+
(0, loki_stores_1.useLokiStores)();
|
|
71
|
+
(0, scratch_database_1.useScratchDatabase)();
|
|
65
72
|
const hub1Device = {
|
|
66
73
|
systemPort: 56205,
|
|
67
74
|
sdk: '10',
|
|
@@ -162,6 +169,7 @@ describe('Device Utils', () => {
|
|
|
162
169
|
yield (0, test_container_1.resetTestContainer)();
|
|
163
170
|
yield (0, test_container_1.setupTestContainer)();
|
|
164
171
|
}));
|
|
172
|
+
after(() => (0, test_container_1.resetTestContainer)());
|
|
165
173
|
it('Allocate devices for session with host filter', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
166
174
|
(yield db_1.XenonDatabase.DeviceModel).removeDataOnly();
|
|
167
175
|
const deviceManager = (0, test_container_1.createTestXenonManager)(Object.assign({}, pluginArgs, { maxSessions: 3, platform: 'android' }));
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const chai_1 = require("chai");
|
|
4
|
+
const XenonCapabilityManager_1 = require("../../src/XenonCapabilityManager");
|
|
5
|
+
// The team a session asks to be placed in. Documented as xe:options.team;
|
|
6
|
+
// xenon:options is read as its alias, through the same precedence helper as
|
|
7
|
+
// every other Xenon option. The flat Xenon-prefixed caps older clients send
|
|
8
|
+
// (xenon:team, xe:teamId, ...) still work, below the options. df: is never
|
|
9
|
+
// read.
|
|
10
|
+
const always = (alwaysMatch) => ({ alwaysMatch, firstMatch: [{}] });
|
|
11
|
+
describe('extractTeamCap', () => {
|
|
12
|
+
it('reads xe:options.team, the documented form', () => {
|
|
13
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({ 'xe:options': { team: 'team_a' } }))).to.equal('team_a');
|
|
14
|
+
});
|
|
15
|
+
it('reads xe:options.teamId', () => {
|
|
16
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({ 'xe:options': { teamId: 'team_a' } }))).to.equal('team_a');
|
|
17
|
+
});
|
|
18
|
+
it('still reads the xenon:options alias alone', () => {
|
|
19
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({ 'xenon:options': { team: 'team_a' } }))).to.equal('team_a');
|
|
20
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({ 'xenon:options': { teamId: 'team_a' } }))).to.equal('team_a');
|
|
21
|
+
});
|
|
22
|
+
it('lets xe:options win over xenon:options', () => {
|
|
23
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({ 'xenon:options': { team: 'team_old' }, 'xe:options': { team: 'team_new' } }))).to.equal('team_new');
|
|
24
|
+
// Across alwaysMatch and firstMatch, as allocation merges them.
|
|
25
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)({
|
|
26
|
+
alwaysMatch: { 'xenon:options': { teamId: 'team_old' } },
|
|
27
|
+
firstMatch: [{ 'xe:options': { teamId: 'team_new' } }],
|
|
28
|
+
})).to.equal('team_new');
|
|
29
|
+
});
|
|
30
|
+
it('lets an options field win over a flat cap', () => {
|
|
31
|
+
for (const flat of ['xenon:team', 'xenon:teamId', 'xe:team', 'xe:teamId', 'appium:team']) {
|
|
32
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({ [flat]: 'team_flat', 'xe:options': { team: 'team_opt' } })), flat).to.equal('team_opt');
|
|
33
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({ [flat]: 'team_flat', 'xenon:options': { teamId: 'team_opt' } })), flat).to.equal('team_opt');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
it('keeps every flat form working when no option is set', () => {
|
|
37
|
+
for (const prefix of ['xenon:', 'xe:', 'appium:', '']) {
|
|
38
|
+
for (const name of ['teamId', 'team_id', 'team']) {
|
|
39
|
+
const key = `${prefix}${name}`;
|
|
40
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({ [key]: 'team_flat' })), key).to.equal('team_flat');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
it('falls back to a flat cap when the option is empty or not a string', () => {
|
|
45
|
+
for (const team of ['', 7, null, { id: 'team_x' }]) {
|
|
46
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({ 'xe:options': { team }, 'xenon:team': 'team_flat' })), JSON.stringify(team)).to.equal('team_flat');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
it('never reads df:', () => {
|
|
50
|
+
for (const caps of [
|
|
51
|
+
{ 'df:options': { team: 'team_df' } },
|
|
52
|
+
{ 'df:options': { teamId: 'team_df' } },
|
|
53
|
+
{ 'xenon:df:options': { team: 'team_df' } },
|
|
54
|
+
{ 'appium:df:options': { team: 'team_df' } },
|
|
55
|
+
{ 'df:team': 'team_df' },
|
|
56
|
+
{ 'df:teamId': 'team_df' },
|
|
57
|
+
]) {
|
|
58
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always(caps)), JSON.stringify(caps)).to.equal(undefined);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
it('is undefined when nothing asks for a team', () => {
|
|
62
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)(always({}))).to.equal(undefined);
|
|
63
|
+
(0, chai_1.expect)((0, XenonCapabilityManager_1.extractTeamCap)({})).to.equal(undefined);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -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 grid_1 = __importDefault(require("../../src/app/routers/grid"));
|
|
20
20
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
21
21
|
/**
|
|
@@ -44,12 +44,12 @@ describe('grid operator reads are admin-only', () => {
|
|
|
44
44
|
}
|
|
45
45
|
for (const url of ['/logs/requests', '/node/status', '/node/10.0.0.2/status']) {
|
|
46
46
|
it(`a member gets 403 from GET ${url}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
47
|
-
const r = yield (0,
|
|
47
|
+
const r = yield (0, loopbackRequest_1.default)(app('MEMBER')).get(url);
|
|
48
48
|
(0, chai_1.expect)(r.status).to.equal(403);
|
|
49
49
|
}));
|
|
50
50
|
}
|
|
51
51
|
it('an admin still reads the request log', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
52
|
-
const r = yield (0,
|
|
52
|
+
const r = yield (0, loopbackRequest_1.default)(app('ADMIN')).get('/logs/requests');
|
|
53
53
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
54
54
|
}));
|
|
55
55
|
});
|
|
@@ -9,12 +9,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
require("reflect-metadata");
|
|
12
13
|
const chai_1 = require("chai");
|
|
13
14
|
const typedi_1 = require("typedi");
|
|
14
15
|
const HealingOrchestrator_1 = require("../../src/services/healing/HealingOrchestrator");
|
|
15
16
|
const HealEtalonService_1 = require("../../src/services/healing/HealEtalonService");
|
|
16
17
|
const HealedLocatorGenerator_1 = require("../../src/services/healing/HealedLocatorGenerator");
|
|
17
18
|
const types_1 = require("../../src/services/healing/types");
|
|
19
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
18
20
|
// §2.7 healing-tier capability gate: xe:options.healingTiers restricts which
|
|
19
21
|
// self-healing providers may be dispatched. Tier index mapping used by the gate
|
|
20
22
|
// (array-position based, NOT the internal HealingTier enum): 1=Resilio,
|
|
@@ -104,8 +106,9 @@ describe('HealingOrchestrator.attemptHealing allowedTiers dispatch gate', () =>
|
|
|
104
106
|
let orchestrator;
|
|
105
107
|
let mockEtalonService;
|
|
106
108
|
const dispatched = [];
|
|
109
|
+
let restoreContainer;
|
|
107
110
|
beforeEach(() => {
|
|
108
|
-
|
|
111
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(HealEtalonService_1.HealEtalonService, HealedLocatorGenerator_1.HealedLocatorGenerator);
|
|
109
112
|
dispatched.length = 0;
|
|
110
113
|
mockEtalonService = {
|
|
111
114
|
getSignature: () => __awaiter(void 0, void 0, void 0, function* () { return null; }),
|
|
@@ -123,6 +126,7 @@ describe('HealingOrchestrator.attemptHealing allowedTiers dispatch gate', () =>
|
|
|
123
126
|
});
|
|
124
127
|
});
|
|
125
128
|
});
|
|
129
|
+
afterEach(() => restoreContainer());
|
|
126
130
|
const mockDriver = {
|
|
127
131
|
getPageSource: () => __awaiter(void 0, void 0, void 0, function* () { return '<xml/>'; }),
|
|
128
132
|
getScreenshot: () => __awaiter(void 0, void 0, void 0, function* () { return 'fake-screenshot'; }),
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
require("reflect-metadata");
|
|
16
|
+
const chai_1 = require("chai");
|
|
17
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
18
|
+
const typedi_1 = require("typedi");
|
|
19
|
+
const HealthMonitorService_1 = require("../../src/device-managers/HealthMonitorService");
|
|
20
|
+
const device_managers_1 = require("../../src/device-managers");
|
|
21
|
+
const device_store_1 = require("../../src/data-service/device-store");
|
|
22
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
23
|
+
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
24
|
+
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
25
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
26
|
+
/**
|
|
27
|
+
* A hub keeps its nodes' phones in its own Device table. Its health monitor
|
|
28
|
+
* ran adb (or go-ios) health checks on every row, so it checked node phones it
|
|
29
|
+
* cannot reach from its own machine: each came back unhealthy, the hub wrote
|
|
30
|
+
* that over the node's report, started a "recovery" with its own adb, and
|
|
31
|
+
* reclaimed a busy node phone whose session it didn't hold in memory. Only
|
|
32
|
+
* this server's own phones are checked (isOwnDevice: by nodeId, else by exact
|
|
33
|
+
* host).
|
|
34
|
+
*/
|
|
35
|
+
// Named like the real manager: the monitor picks one by constructor.name.
|
|
36
|
+
class AndroidDeviceManager {
|
|
37
|
+
constructor() {
|
|
38
|
+
this.checked = [];
|
|
39
|
+
this.checkHealth = (device) => __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
this.checked.push(device.udid);
|
|
41
|
+
return { healthStatus: 'Healthy' };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
describe('HealthMonitorService checks only this server’s own phones', () => {
|
|
46
|
+
const HUB_NODE_ID = 'hub-node-id';
|
|
47
|
+
const HUB_HOST = 'http://10.0.0.1:4799';
|
|
48
|
+
let manager;
|
|
49
|
+
let updateDevice;
|
|
50
|
+
let context;
|
|
51
|
+
let saved;
|
|
52
|
+
let restore;
|
|
53
|
+
const devices = [
|
|
54
|
+
// Found by this hub's own discovery.
|
|
55
|
+
{ udid: 'hub-phone', host: HUB_HOST, nodeId: HUB_NODE_ID },
|
|
56
|
+
// A node's, reported over /register with the node's id.
|
|
57
|
+
{ udid: 'node-phone', host: 'http://10.0.0.2:4725', nodeId: 'node-peer' },
|
|
58
|
+
// A node's on the same machine, differing only by port.
|
|
59
|
+
{ udid: 'same-machine-node-phone', host: 'http://10.0.0.1:4725', nodeId: 'node-2' },
|
|
60
|
+
// A node's busy phone, its session not in this hub's memory.
|
|
61
|
+
{
|
|
62
|
+
udid: 'busy-node-phone',
|
|
63
|
+
host: 'http://10.0.0.2:4725',
|
|
64
|
+
nodeId: 'node-peer',
|
|
65
|
+
busy: true,
|
|
66
|
+
session_id: 'node-session',
|
|
67
|
+
},
|
|
68
|
+
// A row from before nodeId, known by its host alone.
|
|
69
|
+
{ udid: 'legacy-hub-phone', host: HUB_HOST, nodeId: null },
|
|
70
|
+
{ udid: 'legacy-node-phone', host: 'http://10.0.0.3:4725', nodeId: null },
|
|
71
|
+
].map((d) => (Object.assign({ platform: 'android', busy: false, cloud: null }, d)));
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
sinon_1.default.stub(process, 'kill');
|
|
74
|
+
manager = new AndroidDeviceManager();
|
|
75
|
+
restore = (0, container_registration_1.saveRegistrations)(device_managers_1.XenonManager);
|
|
76
|
+
typedi_1.Container.set(device_managers_1.XenonManager, { deviceInstances: () => __awaiter(void 0, void 0, void 0, function* () { return [manager]; }) });
|
|
77
|
+
updateDevice = sinon_1.default.spy(() => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
|
|
78
|
+
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
|
|
79
|
+
getAllDevices: () => __awaiter(void 0, void 0, void 0, function* () { return devices; }),
|
|
80
|
+
updateDevice,
|
|
81
|
+
});
|
|
82
|
+
sinon_1.default.stub(SessionManager_1.SESSION_MANAGER, 'isValidSession').returns(false);
|
|
83
|
+
context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
84
|
+
saved = Object.assign({}, context);
|
|
85
|
+
context.setContext(Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { bindHostOrIp: '10.0.0.1' }), 4799, HUB_NODE_ID, '');
|
|
86
|
+
});
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
Object.assign(context, saved);
|
|
89
|
+
sinon_1.default.restore();
|
|
90
|
+
restore();
|
|
91
|
+
});
|
|
92
|
+
const run = () => new HealthMonitorService_1.HealthMonitorService({}).checkAllDevices();
|
|
93
|
+
it('checks the hub’s own phones and none of its nodes’', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
94
|
+
yield run();
|
|
95
|
+
(0, chai_1.expect)(manager.checked.sort()).to.deep.equal(['hub-phone', 'legacy-hub-phone']);
|
|
96
|
+
}));
|
|
97
|
+
it('never writes to a node’s phone, nor reclaims a busy one', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
98
|
+
yield run();
|
|
99
|
+
const written = updateDevice.getCalls().map((c) => c.args[0]);
|
|
100
|
+
(0, chai_1.expect)(written.sort()).to.deep.equal(['hub-phone', 'legacy-hub-phone']);
|
|
101
|
+
}));
|
|
102
|
+
});
|
|
@@ -22,6 +22,7 @@ const device_store_1 = require("../../src/data-service/device-store");
|
|
|
22
22
|
const SessionManager_1 = require("../../src/sessions/SessionManager");
|
|
23
23
|
const AndroidStreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidStreamService"));
|
|
24
24
|
const IOSStreamService_1 = __importDefault(require("../../src/device-managers/ios/IOSStreamService"));
|
|
25
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
25
26
|
// Named like the real managers: the monitor picks one by constructor.name.
|
|
26
27
|
class AndroidDeviceManager {
|
|
27
28
|
constructor() {
|
|
@@ -33,12 +34,15 @@ class IOSDeviceManager {
|
|
|
33
34
|
this.checkHealth = () => __awaiter(this, void 0, void 0, function* () { return ({ healthStatus: 'Healthy' }); });
|
|
34
35
|
}
|
|
35
36
|
}
|
|
37
|
+
const THIS_SERVER = 'this-server-node-id';
|
|
36
38
|
// A device busy with an Appium session this process no longer knows about,
|
|
37
39
|
// which is the case the monitor reclaims, unless a stream is still running.
|
|
38
40
|
function lostSessionDevice(platform) {
|
|
39
41
|
return {
|
|
40
42
|
udid: `${platform}-1`,
|
|
41
43
|
host: 'http://127.0.0.1:4723',
|
|
44
|
+
// This server's own phone: the monitor checks no other server's.
|
|
45
|
+
nodeId: THIS_SERVER,
|
|
42
46
|
platform,
|
|
43
47
|
busy: true,
|
|
44
48
|
session_id: 'appium-session-not-in-memory',
|
|
@@ -51,7 +55,10 @@ describe('HealthMonitorService — stream lookups', () => {
|
|
|
51
55
|
let androidStatus;
|
|
52
56
|
let iosStatus;
|
|
53
57
|
const reclaimed = () => updateDevice.getCalls().some((c) => { var _a, _b; return ((_a = c.args[2]) === null || _a === void 0 ? void 0 : _a.busy) === false && !((_b = c.args[2]) === null || _b === void 0 ? void 0 : _b.session_id); });
|
|
58
|
+
let nodeIdBefore;
|
|
54
59
|
beforeEach(() => {
|
|
60
|
+
nodeIdBefore = typedi_1.Container.get(PluginContext_1.PluginContext).nodeId;
|
|
61
|
+
typedi_1.Container.get(PluginContext_1.PluginContext).nodeId = THIS_SERVER;
|
|
55
62
|
sinon_1.default.stub(process, 'kill');
|
|
56
63
|
updateDevice = sinon_1.default.spy(() => __awaiter(void 0, void 0, void 0, function* () { return undefined; }));
|
|
57
64
|
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
|
|
@@ -78,7 +85,10 @@ describe('HealthMonitorService — stream lookups', () => {
|
|
|
78
85
|
return real(token);
|
|
79
86
|
});
|
|
80
87
|
});
|
|
81
|
-
afterEach(() =>
|
|
88
|
+
afterEach(() => {
|
|
89
|
+
sinon_1.default.restore();
|
|
90
|
+
typedi_1.Container.get(PluginContext_1.PluginContext).nodeId = nodeIdBefore;
|
|
91
|
+
});
|
|
82
92
|
const run = () => new HealthMonitorService_1.HealthMonitorService({}).checkAllDevices();
|
|
83
93
|
it('leaves an Android device with a running stream alone', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
84
94
|
device = lostSessionDevice('android');
|
|
@@ -46,6 +46,33 @@ describe('redactSecrets', () => {
|
|
|
46
46
|
const redacted = (0, helpers_1.redactSecrets)(arr);
|
|
47
47
|
(0, chai_1.expect)(redacted[0]).to.equal('[Circular]');
|
|
48
48
|
});
|
|
49
|
+
// Half of an xe:options credential pair: without its token it proves
|
|
50
|
+
// nothing, but it names the key a leaked token would complete.
|
|
51
|
+
it('should redact access keys in every spelling', () => {
|
|
52
|
+
const KEY = 'xen_AbC123dEf456';
|
|
53
|
+
const obj = {
|
|
54
|
+
accessKey: KEY,
|
|
55
|
+
access_key: KEY,
|
|
56
|
+
ACCESS_KEY: KEY,
|
|
57
|
+
'x-xenon-access-key': KEY,
|
|
58
|
+
'xe:options': { accessKey: KEY, leaseId: 'lse_1' },
|
|
59
|
+
};
|
|
60
|
+
const redacted = (0, helpers_1.redactSecrets)(obj);
|
|
61
|
+
(0, chai_1.expect)(redacted).to.deep.equal({
|
|
62
|
+
accessKey: '***REDACTED***',
|
|
63
|
+
access_key: '***REDACTED***',
|
|
64
|
+
ACCESS_KEY: '***REDACTED***',
|
|
65
|
+
'x-xenon-access-key': '***REDACTED***',
|
|
66
|
+
'xe:options': { accessKey: '***REDACTED***', leaseId: 'lse_1' },
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
// GET /xenon/api/profile/access-key and /auth/me hand the caller their own
|
|
70
|
+
// key. Logging the same object must not take it out of the response.
|
|
71
|
+
it('should leave the object it was given untouched', () => {
|
|
72
|
+
const body = { accessKey: 'xen_AbC123dEf456', nested: { token: 't' } };
|
|
73
|
+
(0, helpers_1.redactSecrets)(body);
|
|
74
|
+
(0, chai_1.expect)(body).to.deep.equal({ accessKey: 'xen_AbC123dEf456', nested: { token: 't' } });
|
|
75
|
+
});
|
|
49
76
|
it('should redact falsy sensitive values', () => {
|
|
50
77
|
const obj = {
|
|
51
78
|
password: '',
|