@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
|
@@ -49,7 +49,7 @@ require("reflect-metadata");
|
|
|
49
49
|
const chai_1 = require("chai");
|
|
50
50
|
const sinon_1 = __importDefault(require("sinon"));
|
|
51
51
|
const express_1 = __importDefault(require("express"));
|
|
52
|
-
const
|
|
52
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
53
53
|
const typedi_1 = require("typedi");
|
|
54
54
|
const prisma_1 = require("../../src/prisma");
|
|
55
55
|
const config_1 = require("../../src/config");
|
|
@@ -125,7 +125,7 @@ describe('GET /xenon/api/cliArgs', () => {
|
|
|
125
125
|
sinon_1.default.restore();
|
|
126
126
|
config_1.config.authDisabled = savedAuthDisabled;
|
|
127
127
|
});
|
|
128
|
-
const asCookie = (sessionId) => (0,
|
|
128
|
+
const asCookie = (sessionId) => (0, loopbackRequest_1.default)(app).get('/xenon/api/cliArgs').set('Cookie', `xenon_dashboard_session=${sessionId}`);
|
|
129
129
|
function asApiKey(scopes, ownerId) {
|
|
130
130
|
sinon_1.default.stub(typedi_1.Container.get(ApiKeyService_1.ApiKeyService), 'verifyPair').resolves({
|
|
131
131
|
id: 'key-1',
|
|
@@ -138,7 +138,7 @@ describe('GET /xenon/api/cliArgs', () => {
|
|
|
138
138
|
userId: ownerId,
|
|
139
139
|
teamId: null,
|
|
140
140
|
});
|
|
141
|
-
return (0,
|
|
141
|
+
return (0, loopbackRequest_1.default)(app)
|
|
142
142
|
.get('/xenon/api/cliArgs')
|
|
143
143
|
.set('x-xenon-access-key', 'ak')
|
|
144
144
|
.set('x-xenon-token', 'tok');
|
|
@@ -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 control_1 = __importDefault(require("../../src/app/routers/control"));
|
|
22
22
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
@@ -24,6 +24,8 @@ const ApiKeyService_1 = require("../../src/services/ApiKeyService");
|
|
|
24
24
|
const device_managers_1 = require("../../src/device-managers");
|
|
25
25
|
const clipboardErrors_1 = require("../../src/device-managers/clipboardErrors");
|
|
26
26
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
27
|
+
const container_registration_1 = require("../helpers/container-registration");
|
|
28
|
+
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
27
29
|
/**
|
|
28
30
|
* GET/POST /control/:udid/clipboard answered 200 whatever happened on the
|
|
29
31
|
* device: the Android manager swallowed its errors, and its "set" had no
|
|
@@ -57,7 +59,14 @@ function buildApp() {
|
|
|
57
59
|
}
|
|
58
60
|
describe('GET/POST /control/:udid/clipboard', () => {
|
|
59
61
|
let manager;
|
|
62
|
+
let restoreContainer;
|
|
63
|
+
// Served on 127.0.0.1, not by request(app): that listens on every address
|
|
64
|
+
// and connects to 127.0.0.1, where another process's listener on the same
|
|
65
|
+
// port answers instead (a stray 501 or a socket hang-up in a full run).
|
|
66
|
+
const loopback = (0, loopbackServer_1.loopbackServers)();
|
|
67
|
+
const app = () => __awaiter(void 0, void 0, void 0, function* () { return loopback.serve(buildApp()); });
|
|
60
68
|
beforeEach(() => {
|
|
69
|
+
restoreContainer = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, device_managers_1.XenonManager);
|
|
61
70
|
manager = new AndroidDeviceManager();
|
|
62
71
|
sinon_1.default.stub(device_store_1.DeviceStoreFactory, 'getStore').returns({
|
|
63
72
|
findDevice: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -73,19 +82,20 @@ describe('GET/POST /control/:udid/clipboard', () => {
|
|
|
73
82
|
typedi_1.Container.set(ApiKeyService_1.ApiKeyService, new ApiKeyService_1.ApiKeyService());
|
|
74
83
|
typedi_1.Container.set(device_managers_1.XenonManager, { deviceInstances: () => __awaiter(void 0, void 0, void 0, function* () { return [manager]; }) });
|
|
75
84
|
});
|
|
76
|
-
afterEach(() => {
|
|
85
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
77
86
|
sinon_1.default.restore();
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
restoreContainer();
|
|
88
|
+
yield loopback.closeAll();
|
|
89
|
+
}));
|
|
80
90
|
it('returns the clipboard text', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
81
91
|
manager.getClipboard.resolves('xenonread');
|
|
82
|
-
const res = yield (0,
|
|
92
|
+
const res = yield (0, loopbackRequest_1.default)(yield app()).get(`/xenon/api/control/${UDID}/clipboard`);
|
|
83
93
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
84
94
|
(0, chai_1.expect)(res.body).to.deep.equal({ content: 'xenonread' });
|
|
85
95
|
}));
|
|
86
96
|
it('says why a read failed, with a 500', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
87
97
|
manager.getClipboard.rejects(new Error('Appium Settings didn’t return the clipboard.'));
|
|
88
|
-
const res = yield (0,
|
|
98
|
+
const res = yield (0, loopbackRequest_1.default)(yield app()).get(`/xenon/api/control/${UDID}/clipboard`);
|
|
89
99
|
(0, chai_1.expect)(res.status).to.equal(500);
|
|
90
100
|
(0, chai_1.expect)(res.body.error).to.match(/Appium Settings/);
|
|
91
101
|
}));
|
|
@@ -93,7 +103,7 @@ describe('GET/POST /control/:udid/clipboard', () => {
|
|
|
93
103
|
// won't help. 501 says exactly that.
|
|
94
104
|
it('answers 501 when the device cannot set its clipboard', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
105
|
manager.setClipboard.rejects(new clipboardErrors_1.ClipboardUnsupportedError('Appium Settings can only read it.'));
|
|
96
|
-
const res = yield (0,
|
|
106
|
+
const res = yield (0, loopbackRequest_1.default)(yield app())
|
|
97
107
|
.post(`/xenon/api/control/${UDID}/clipboard`)
|
|
98
108
|
.send({ content: 'x' });
|
|
99
109
|
(0, chai_1.expect)(res.status).to.equal(501);
|
|
@@ -101,7 +111,7 @@ describe('GET/POST /control/:udid/clipboard', () => {
|
|
|
101
111
|
}));
|
|
102
112
|
it('still answers 500 for any other write failure', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
113
|
manager.setClipboard.rejects(new Error('WDA is not running'));
|
|
104
|
-
const res = yield (0,
|
|
114
|
+
const res = yield (0, loopbackRequest_1.default)(yield app())
|
|
105
115
|
.post(`/xenon/api/control/${UDID}/clipboard`)
|
|
106
116
|
.send({ content: 'x' });
|
|
107
117
|
(0, chai_1.expect)(res.status).to.equal(500);
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
36
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
37
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -16,7 +49,8 @@ const chai_1 = require("chai");
|
|
|
16
49
|
const http_1 = __importDefault(require("http"));
|
|
17
50
|
const path_1 = __importDefault(require("path"));
|
|
18
51
|
const sinon_1 = __importDefault(require("sinon"));
|
|
19
|
-
const
|
|
52
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
53
|
+
const ws_1 = __importStar(require("ws"));
|
|
20
54
|
const commandCaller_1 = require("../../src/middleware/commandCaller");
|
|
21
55
|
const commandAuth_1 = require("../../src/middleware/commandAuth");
|
|
22
56
|
const registerCommandAuth_1 = require("../../src/app/registerCommandAuth");
|
|
@@ -29,9 +63,22 @@ const registerCommandAuth_1 = require("../../src/app/registerCommandAuth");
|
|
|
29
63
|
* Express 5 as Appium builds it: its middleware, then every WebDriver route,
|
|
30
64
|
* then the plugin updaters, then its catch-all 404. The fake driver records
|
|
31
65
|
* each command that reaches it, which shows whether the check ran first.
|
|
66
|
+
*
|
|
67
|
+
* The session listing and the BiDi WebSocket go through the same server:
|
|
68
|
+
* registerSessionUpgradeGuard is a server updater too, and the BiDi handler is
|
|
69
|
+
* added after boot with addWebSocketHandler, as Appium's main adds it.
|
|
32
70
|
*/
|
|
33
71
|
const EXACT_UNKNOWN_SESSION = JSON.stringify(commandAuth_1.UNKNOWN_SESSION_BODY);
|
|
34
72
|
const LIVE_SESSION = 'live-session';
|
|
73
|
+
const BOBS_SESSION = 'bobs-session';
|
|
74
|
+
const LISTING = [
|
|
75
|
+
{ id: LIVE_SESSION, created: 1, capabilities: { platformName: 'Fake' } },
|
|
76
|
+
{ id: BOBS_SESSION, created: 2, capabilities: { platformName: 'Fake' } },
|
|
77
|
+
];
|
|
78
|
+
const OWNERS = { [LIVE_SESSION]: 'alice', [BOBS_SESSION]: 'bob' };
|
|
79
|
+
function nativelyUsesUpgradeListener() {
|
|
80
|
+
return typeof http_1.default.createServer().shouldUpgradeCallback !== 'undefined';
|
|
81
|
+
}
|
|
35
82
|
function appiumBaseDriver() {
|
|
36
83
|
const appiumDir = path_1.default.dirname(require.resolve('appium/package.json'));
|
|
37
84
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
@@ -47,11 +94,14 @@ describe("per-command auth in Appium 3's own server()", () => {
|
|
|
47
94
|
let server;
|
|
48
95
|
let commands;
|
|
49
96
|
let enabled;
|
|
97
|
+
let infos;
|
|
50
98
|
let warnings;
|
|
99
|
+
let bidiConnections;
|
|
51
100
|
function boot() {
|
|
52
|
-
return __awaiter(this,
|
|
101
|
+
return __awaiter(this, arguments, void 0, function* (opts = {}) {
|
|
53
102
|
const baseDriver = appiumBaseDriver();
|
|
54
103
|
commands = [];
|
|
104
|
+
bidiConnections = [];
|
|
55
105
|
const driver = {
|
|
56
106
|
log: quiet,
|
|
57
107
|
protocol: 'W3C',
|
|
@@ -63,44 +113,88 @@ describe("per-command auth in Appium 3's own server()", () => {
|
|
|
63
113
|
commands.push(command);
|
|
64
114
|
if (command === 'createSession')
|
|
65
115
|
return [LIVE_SESSION, {}];
|
|
116
|
+
if (command === 'getAppiumSessions')
|
|
117
|
+
return LISTING;
|
|
66
118
|
return 'about:blank';
|
|
67
119
|
}),
|
|
68
120
|
};
|
|
121
|
+
const credentials = { 'alice-token': 'alice', 'bob-token': 'bob' };
|
|
69
122
|
const verifier = new commandCaller_1.CommandCallerVerifier({
|
|
70
123
|
verifyKeyPair: sinon_1.default.stub().callsFake((_ak, token) => __awaiter(this, void 0, void 0, function* () {
|
|
71
|
-
return token
|
|
124
|
+
return credentials[token]
|
|
72
125
|
? {
|
|
73
|
-
row: { id: 'k', userId:
|
|
74
|
-
user: { id:
|
|
126
|
+
row: { id: 'k', userId: credentials[token], scopes: 'sessions', expiresAt: null },
|
|
127
|
+
user: { id: credentials[token], role: 'MEMBER', status: 'ACTIVE' },
|
|
75
128
|
}
|
|
76
129
|
: null;
|
|
77
130
|
})),
|
|
78
131
|
verifyBearer: sinon_1.default.stub().resolves(null),
|
|
79
132
|
});
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
133
|
+
const deps = {
|
|
134
|
+
enabled: () => enabled,
|
|
135
|
+
authDisabled: () => false,
|
|
136
|
+
verifier,
|
|
137
|
+
ownerOf: (id) => __awaiter(this, void 0, void 0, function* () { var _a; return (_a = OWNERS[id]) !== null && _a !== void 0 ? _a : null; }),
|
|
138
|
+
ownersOf: (ids) => __awaiter(this, void 0, void 0, function* () { return new Map(ids.map((id) => { var _a; return [id, (_a = OWNERS[id]) !== null && _a !== void 0 ? _a : null]; })); }),
|
|
139
|
+
logger: {
|
|
140
|
+
info: (m) => infos.push(m),
|
|
141
|
+
warn: (m) => warnings.push(m),
|
|
142
|
+
error: () => undefined,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
const createServer = http_1.default.createServer;
|
|
146
|
+
const forced = opts.upgrades === 'listener' ? sinon_1.default.stub(http_1.default, 'createServer') : undefined;
|
|
147
|
+
forced === null || forced === void 0 ? void 0 : forced.callsFake(((app) => {
|
|
148
|
+
const s = createServer(app);
|
|
149
|
+
if (typeof s.shouldUpgradeCallback === 'undefined') {
|
|
150
|
+
s.shouldUpgradeCallback = () => true;
|
|
151
|
+
}
|
|
152
|
+
return s;
|
|
153
|
+
}));
|
|
154
|
+
try {
|
|
155
|
+
server = yield baseDriver.server({
|
|
156
|
+
routeConfiguringFunction: baseDriver.routeConfiguringFunction(driver),
|
|
157
|
+
port: 0,
|
|
158
|
+
hostname: '127.0.0.1',
|
|
159
|
+
basePath: '/wd/hub',
|
|
160
|
+
// Appium hands plugins the raw CLI args; the base path here is normalised
|
|
161
|
+
// by registerCommandAuth itself.
|
|
162
|
+
cliArgs: { basePath: 'wd/hub/' },
|
|
163
|
+
serverUpdaters: [
|
|
164
|
+
(app, httpServer, cliArgs) => {
|
|
165
|
+
(0, registerCommandAuth_1.registerCommandAuth)(app, cliArgs, deps);
|
|
166
|
+
(0, registerCommandAuth_1.registerSessionUpgradeGuard)(httpServer, app, cliArgs, deps);
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
forced === null || forced === void 0 ? void 0 : forced.restore();
|
|
173
|
+
}
|
|
174
|
+
// Appium's main adds its BiDi handler right after server() resolves.
|
|
175
|
+
const bidi = new ws_1.WebSocketServer({ noServer: true });
|
|
176
|
+
bidi.on('connection', (ws, req) => {
|
|
177
|
+
bidiConnections.push(String(req.url));
|
|
178
|
+
ws.on('error', () => undefined);
|
|
179
|
+
});
|
|
180
|
+
yield booted().addWebSocketHandler('/wd/hub/bidi', bidi);
|
|
181
|
+
yield booted().addWebSocketHandler('/wd/hub/bidi/:sessionId', bidi);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function upgrade(urlPath, headers = {}) {
|
|
185
|
+
const { port } = booted().address();
|
|
186
|
+
return new Promise((resolve) => {
|
|
187
|
+
const ws = new ws_1.default(`ws://127.0.0.1:${port}${urlPath}`, { headers });
|
|
188
|
+
ws.on('open', () => {
|
|
189
|
+
resolve('open');
|
|
190
|
+
ws.terminate();
|
|
191
|
+
});
|
|
192
|
+
ws.on('unexpected-response', (req, res) => {
|
|
193
|
+
var _a;
|
|
194
|
+
resolve((_a = res.statusCode) !== null && _a !== void 0 ? _a : 0);
|
|
195
|
+
req.destroy();
|
|
103
196
|
});
|
|
197
|
+
ws.on('error', (err) => resolve(err.message));
|
|
104
198
|
});
|
|
105
199
|
}
|
|
106
200
|
// Appium's HTTP logger writes every request to stdout; quiet it here only.
|
|
@@ -130,6 +224,7 @@ describe("per-command auth in Appium 3's own server()", () => {
|
|
|
130
224
|
}
|
|
131
225
|
beforeEach(() => {
|
|
132
226
|
enabled = true;
|
|
227
|
+
infos = [];
|
|
133
228
|
warnings = [];
|
|
134
229
|
});
|
|
135
230
|
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -144,7 +239,7 @@ describe("per-command auth in Appium 3's own server()", () => {
|
|
|
144
239
|
}));
|
|
145
240
|
it("refuses a session command before Appium's route runs", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
146
241
|
yield boot();
|
|
147
|
-
const res = yield (0,
|
|
242
|
+
const res = yield (0, loopbackRequest_1.default)(booted()).get(`/wd/hub/session/${LIVE_SESSION}/url`);
|
|
148
243
|
(0, chai_1.expect)(res.status).to.equal(404);
|
|
149
244
|
(0, chai_1.expect)(res.text).to.equal(EXACT_UNKNOWN_SESSION);
|
|
150
245
|
(0, chai_1.expect)(commands).to.deep.equal([]);
|
|
@@ -152,7 +247,7 @@ describe("per-command auth in Appium 3's own server()", () => {
|
|
|
152
247
|
}));
|
|
153
248
|
it("lets the owner through to Appium's route and the driver", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
154
249
|
yield boot();
|
|
155
|
-
const res = yield (0,
|
|
250
|
+
const res = yield (0, loopbackRequest_1.default)(booted())
|
|
156
251
|
.get(`/wd/hub/session/${LIVE_SESSION}/url`)
|
|
157
252
|
.set({ 'x-xenon-access-key': 'ak', 'x-xenon-token': 'alice-token' });
|
|
158
253
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
@@ -161,7 +256,7 @@ describe("per-command auth in Appium 3's own server()", () => {
|
|
|
161
256
|
}));
|
|
162
257
|
it('lets createSession reach the driver without credentials', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
163
258
|
yield boot();
|
|
164
|
-
yield (0,
|
|
259
|
+
yield (0, loopbackRequest_1.default)(booted())
|
|
165
260
|
.post('/wd/hub/session')
|
|
166
261
|
.send({ capabilities: { alwaysMatch: { platformName: 'Fake' } } })
|
|
167
262
|
.expect(200);
|
|
@@ -170,7 +265,7 @@ describe("per-command auth in Appium 3's own server()", () => {
|
|
|
170
265
|
it("with the setting off, Appium gives its own unknown-session answer, which the refusal matches but for Appium's stack trace", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
171
266
|
enabled = false;
|
|
172
267
|
yield boot();
|
|
173
|
-
const res = yield (0,
|
|
268
|
+
const res = yield (0, loopbackRequest_1.default)(booted()).get('/wd/hub/session/no-such-session/url');
|
|
174
269
|
(0, chai_1.expect)(res.status).to.equal(404);
|
|
175
270
|
(0, chai_1.expect)(res.headers['content-type']).to.equal('application/json; charset=utf-8');
|
|
176
271
|
(0, chai_1.expect)(res.body.value.error).to.equal(commandAuth_1.UNKNOWN_SESSION_BODY.value.error);
|
|
@@ -181,4 +276,64 @@ describe("per-command auth in Appium 3's own server()", () => {
|
|
|
181
276
|
(0, chai_1.expect)(res.body.value.stacktrace).to.match(/^NoSuchDriverError: A session is either terminated or not started/);
|
|
182
277
|
(0, chai_1.expect)(warnings).to.deep.equal([]);
|
|
183
278
|
}));
|
|
279
|
+
describe("Appium's session listing (GET /appium/sessions)", () => {
|
|
280
|
+
it('answers a caller without credentials an empty list, though Appium produced the full one', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
281
|
+
yield boot();
|
|
282
|
+
const res = yield (0, loopbackRequest_1.default)(booted()).get('/wd/hub/appium/sessions');
|
|
283
|
+
(0, chai_1.expect)(res.status).to.equal(200);
|
|
284
|
+
(0, chai_1.expect)(res.headers['content-type']).to.equal('application/json; charset=utf-8');
|
|
285
|
+
(0, chai_1.expect)(res.text).to.equal('{"value":[]}');
|
|
286
|
+
(0, chai_1.expect)(commands).to.deep.equal(['getAppiumSessions']);
|
|
287
|
+
(0, chai_1.expect)(warnings).to.have.length(1);
|
|
288
|
+
}));
|
|
289
|
+
it('shows the owner only their own session', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
290
|
+
yield boot();
|
|
291
|
+
const res = yield (0, loopbackRequest_1.default)(booted())
|
|
292
|
+
.get('/wd/hub/appium/sessions')
|
|
293
|
+
.set({ 'x-xenon-access-key': 'ak', 'x-xenon-token': 'alice-token' });
|
|
294
|
+
(0, chai_1.expect)(res.body).to.deep.equal({ value: [LISTING[0]] });
|
|
295
|
+
(0, chai_1.expect)(infos.some((m) => m.includes('1 of 2'))).to.equal(true);
|
|
296
|
+
}));
|
|
297
|
+
it("with the setting off, answers Appium's listing untouched", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
298
|
+
enabled = false;
|
|
299
|
+
yield boot();
|
|
300
|
+
const res = yield (0, loopbackRequest_1.default)(booted()).get('/wd/hub/appium/sessions');
|
|
301
|
+
(0, chai_1.expect)(res.body).to.deep.equal({ value: LISTING });
|
|
302
|
+
}));
|
|
303
|
+
});
|
|
304
|
+
for (const upgrades of ['native', 'listener']) {
|
|
305
|
+
const how = upgrades === 'listener' || nativelyUsesUpgradeListener()
|
|
306
|
+
? 'with its upgrade listener'
|
|
307
|
+
: 'with its Express upgrade middleware';
|
|
308
|
+
describe(`the BiDi WebSocket, Appium dispatching upgrades ${how} (${upgrades})`, () => {
|
|
309
|
+
it('refuses a session upgrade without credentials with 404, before Appium attaches it', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
310
|
+
yield boot({ upgrades });
|
|
311
|
+
(0, chai_1.expect)(yield upgrade(`/wd/hub/bidi/${LIVE_SESSION}`)).to.equal(404);
|
|
312
|
+
(0, chai_1.expect)(bidiConnections).to.deep.equal([]);
|
|
313
|
+
(0, chai_1.expect)(warnings).to.have.length(1);
|
|
314
|
+
}));
|
|
315
|
+
it('refuses another user with 404', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
316
|
+
yield boot({ upgrades });
|
|
317
|
+
(0, chai_1.expect)(yield upgrade(`/wd/hub/bidi/${LIVE_SESSION}`, {
|
|
318
|
+
'x-xenon-access-key': 'ak',
|
|
319
|
+
'x-xenon-token': 'bob-token',
|
|
320
|
+
})).to.equal(404);
|
|
321
|
+
(0, chai_1.expect)(bidiConnections).to.deep.equal([]);
|
|
322
|
+
}));
|
|
323
|
+
it("lets the owner attach through Appium's own handler", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
324
|
+
yield boot({ upgrades });
|
|
325
|
+
(0, chai_1.expect)(yield upgrade(`/wd/hub/bidi/${LIVE_SESSION}`, {
|
|
326
|
+
'x-xenon-access-key': 'ak',
|
|
327
|
+
'x-xenon-token': 'alice-token',
|
|
328
|
+
})).to.equal('open');
|
|
329
|
+
(0, chai_1.expect)(bidiConnections).to.deep.equal([`/wd/hub/bidi/${LIVE_SESSION}`]);
|
|
330
|
+
}));
|
|
331
|
+
it('with the setting off, lets anyone attach, as Appium does', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
332
|
+
enabled = false;
|
|
333
|
+
yield boot({ upgrades });
|
|
334
|
+
(0, chai_1.expect)(yield upgrade(`/wd/hub/bidi/${LIVE_SESSION}`)).to.equal('open');
|
|
335
|
+
(0, chai_1.expect)(warnings).to.deep.equal([]);
|
|
336
|
+
}));
|
|
337
|
+
});
|
|
338
|
+
}
|
|
184
339
|
});
|
|
@@ -16,7 +16,7 @@ const chai_1 = require("chai");
|
|
|
16
16
|
const path_1 = __importDefault(require("path"));
|
|
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 commandAuth_1 = require("../../src/middleware/commandAuth");
|
|
21
21
|
const commandCaller_1 = require("../../src/middleware/commandCaller");
|
|
22
22
|
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
@@ -199,14 +199,14 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
199
199
|
describe('who may drive a session', () => {
|
|
200
200
|
it("lets the owner's key through to the route", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
201
201
|
const { app, ran } = yield lab();
|
|
202
|
-
const res = yield (0,
|
|
202
|
+
const res = yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('alice-token'));
|
|
203
203
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
204
204
|
(0, chai_1.expect)(ran).to.deep.equal(['getUrl']);
|
|
205
205
|
(0, chai_1.expect)(warnings).to.deep.equal([]);
|
|
206
206
|
}));
|
|
207
207
|
it("lets the owner's Bearer token through", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
208
208
|
const { app, ran } = yield lab();
|
|
209
|
-
yield (0,
|
|
209
|
+
yield (0, loopbackRequest_1.default)(app)
|
|
210
210
|
.get('/wd/hub/session/s-alice/url')
|
|
211
211
|
.set(bearer('alice-jwt'))
|
|
212
212
|
.expect(200);
|
|
@@ -214,13 +214,13 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
214
214
|
}));
|
|
215
215
|
it('refuses another user with the unknown-session answer, before the route runs', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
216
216
|
const { app, ran } = yield lab();
|
|
217
|
-
const res = yield (0,
|
|
217
|
+
const res = yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token'));
|
|
218
218
|
yield expectRefused(res);
|
|
219
219
|
(0, chai_1.expect)(ran).to.deep.equal([]);
|
|
220
220
|
}));
|
|
221
221
|
it('logs the refusal with the session and the caller, never the credential', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
222
222
|
const { app } = yield lab();
|
|
223
|
-
yield (0,
|
|
223
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token'));
|
|
224
224
|
(0, chai_1.expect)(warnings).to.have.length(1);
|
|
225
225
|
(0, chai_1.expect)(warnings[0]).to.include('s-alice');
|
|
226
226
|
(0, chai_1.expect)(warnings[0]).to.include('bob');
|
|
@@ -229,7 +229,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
229
229
|
}));
|
|
230
230
|
it('refuses a request with no credentials, without looking anything up', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
231
231
|
const { app, ran } = yield lab();
|
|
232
|
-
yield expectRefused(yield (0,
|
|
232
|
+
yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url'));
|
|
233
233
|
(0, chai_1.expect)(ran).to.deep.equal([]);
|
|
234
234
|
(0, chai_1.expect)(verifyKeyPair.called || verifyBearer.called).to.equal(false);
|
|
235
235
|
(0, chai_1.expect)(ownerOf.called).to.equal(false);
|
|
@@ -238,7 +238,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
238
238
|
}));
|
|
239
239
|
it('refuses credentials that do not verify, without looking up the owner', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
240
240
|
const { app } = yield lab();
|
|
241
|
-
const res = yield (0,
|
|
241
|
+
const res = yield (0, loopbackRequest_1.default)(app)
|
|
242
242
|
.get('/wd/hub/session/s-alice/url')
|
|
243
243
|
.set({ 'x-xenon-access-key': 'ak-alice', 'x-xenon-token': 'guessed' });
|
|
244
244
|
yield expectRefused(res);
|
|
@@ -248,34 +248,34 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
248
248
|
}));
|
|
249
249
|
it('lets an admin-scoped key drive a session it does not own', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
250
250
|
const { app, ran } = yield lab();
|
|
251
|
-
yield (0,
|
|
251
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('carol-admin')).expect(200);
|
|
252
252
|
(0, chai_1.expect)(ran).to.deep.equal(['getUrl']);
|
|
253
253
|
}));
|
|
254
254
|
it('lets a SUPER_ADMIN drive a session they do not own', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
255
255
|
const { app, ran } = yield lab();
|
|
256
|
-
yield (0,
|
|
256
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(bearer('root-jwt')).expect(200);
|
|
257
257
|
(0, chai_1.expect)(ran).to.deep.equal(['getUrl']);
|
|
258
258
|
}));
|
|
259
259
|
it("refuses an ADMIN's ordinary key on someone else's session", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
260
260
|
const { app, ran } = yield lab();
|
|
261
|
-
yield expectRefused(yield (0,
|
|
261
|
+
yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('carol-ordinary')));
|
|
262
262
|
(0, chai_1.expect)(ran).to.deep.equal([]);
|
|
263
263
|
}));
|
|
264
264
|
it('refuses everyone but override admins on a session nobody owns', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
265
265
|
const { app, ran } = yield lab();
|
|
266
|
-
yield expectRefused(yield (0,
|
|
267
|
-
yield expectRefused(yield (0,
|
|
266
|
+
yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-anon/url').set(as('alice-token')));
|
|
267
|
+
yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-anon/url').set(as('carol-ordinary')));
|
|
268
268
|
(0, chai_1.expect)(ran).to.deep.equal([]);
|
|
269
|
-
yield (0,
|
|
270
|
-
yield (0,
|
|
269
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-anon/url').set(as('carol-admin')).expect(200);
|
|
270
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-anon/url').set(bearer('root-jwt')).expect(200);
|
|
271
271
|
(0, chai_1.expect)(ran).to.deep.equal(['getUrl', 'getUrl']);
|
|
272
272
|
}));
|
|
273
273
|
it('answers a session the caller may not use exactly as one that does not exist', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
274
274
|
const { app } = yield lab();
|
|
275
|
-
const refused = yield (0,
|
|
275
|
+
const refused = yield (0, loopbackRequest_1.default)(app)
|
|
276
276
|
.get('/wd/hub/session/s-alice/url')
|
|
277
277
|
.set(as('bob-token'));
|
|
278
|
-
const missing = yield (0,
|
|
278
|
+
const missing = yield (0, loopbackRequest_1.default)(app)
|
|
279
279
|
.get('/wd/hub/session/no-such-session/url')
|
|
280
280
|
.set(as('bob-token'));
|
|
281
281
|
(0, chai_1.expect)(refused.status).to.equal(missing.status);
|
|
@@ -293,38 +293,38 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
293
293
|
for (const [method, url, name] of commands) {
|
|
294
294
|
it(`guards ${method.toUpperCase()} ${url.replace('/wd/hub', '')}`, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
295
295
|
const { app, ran } = yield lab();
|
|
296
|
-
yield expectRefused(yield (0,
|
|
296
|
+
yield expectRefused(yield (0, loopbackRequest_1.default)(app)[method](url).set(as('bob-token')).send({}));
|
|
297
297
|
(0, chai_1.expect)(ran).to.deep.equal([]);
|
|
298
|
-
yield (0,
|
|
298
|
+
yield (0, loopbackRequest_1.default)(app)[method](url).set(as('alice-token')).send({}).expect(200);
|
|
299
299
|
(0, chai_1.expect)(ran).to.deep.equal([name]);
|
|
300
300
|
}));
|
|
301
301
|
}
|
|
302
302
|
it('guards a path in another letter case, which the routes also match', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
303
303
|
const { app, ran } = yield lab();
|
|
304
|
-
yield expectRefused(yield (0,
|
|
304
|
+
yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/WD/HUB/SESSION/s-alice/url').set(as('bob-token')));
|
|
305
305
|
(0, chai_1.expect)(ran).to.deep.equal([]);
|
|
306
306
|
}));
|
|
307
307
|
});
|
|
308
308
|
describe('what it leaves alone', () => {
|
|
309
309
|
it('lets createSession through; it has its own credential checks', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
310
310
|
const { app, ran } = yield lab();
|
|
311
|
-
yield (0,
|
|
311
|
+
yield (0, loopbackRequest_1.default)(app).post('/wd/hub/session').send({ capabilities: {} }).expect(200);
|
|
312
312
|
(0, chai_1.expect)(ran).to.deep.equal(['createSession']);
|
|
313
313
|
(0, chai_1.expect)(ownerOf.called).to.equal(false);
|
|
314
314
|
}));
|
|
315
315
|
it('lets requests that are not session commands through', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
316
316
|
const { app, ran } = yield lab();
|
|
317
|
-
yield (0,
|
|
318
|
-
yield (0,
|
|
319
|
-
yield (0,
|
|
317
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/status').expect(200);
|
|
318
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/appium/sessions').expect(200);
|
|
319
|
+
yield (0, loopbackRequest_1.default)(app).get('/xenon/api/devices').expect(200);
|
|
320
320
|
(0, chai_1.expect)(ran).to.deep.equal(['getStatus', 'getAppiumSessions', 'xenonApi']);
|
|
321
321
|
(0, chai_1.expect)(verifyKeyPair.called || verifyBearer.called || ownerOf.called).to.equal(false);
|
|
322
322
|
}));
|
|
323
323
|
it('does nothing at all when the setting is off (the default)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
324
324
|
enabled = false;
|
|
325
325
|
const { app, ran } = yield lab();
|
|
326
|
-
yield (0,
|
|
327
|
-
yield (0,
|
|
326
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').expect(200);
|
|
327
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token')).expect(200);
|
|
328
328
|
(0, chai_1.expect)(ran).to.deep.equal(['getUrl', 'getUrl']);
|
|
329
329
|
(0, chai_1.expect)(verifyKeyPair.called || verifyBearer.called || ownerOf.called).to.equal(false);
|
|
330
330
|
(0, chai_1.expect)(warnings).to.deep.equal([]);
|
|
@@ -332,7 +332,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
332
332
|
it('does nothing when auth is disabled, even with the setting on', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
333
333
|
authDisabled = true;
|
|
334
334
|
const { app, ran } = yield lab();
|
|
335
|
-
yield (0,
|
|
335
|
+
yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').expect(200);
|
|
336
336
|
(0, chai_1.expect)(ran).to.deep.equal(['getUrl']);
|
|
337
337
|
(0, chai_1.expect)(verifyKeyPair.called || verifyBearer.called || ownerOf.called).to.equal(false);
|
|
338
338
|
}));
|
|
@@ -341,7 +341,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
341
341
|
it('answers 503 when the owner lookup fails, never letting the command through', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
342
342
|
ownerOf.rejects(new Error('database is locked'));
|
|
343
343
|
const { app, ran } = yield lab();
|
|
344
|
-
const res = yield (0,
|
|
344
|
+
const res = yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('alice-token'));
|
|
345
345
|
(0, chai_1.expect)(res.status).to.equal(503);
|
|
346
346
|
(0, chai_1.expect)(res.body).to.deep.equal(commandAuth_1.COMMAND_AUTH_UNAVAILABLE_BODY);
|
|
347
347
|
(0, chai_1.expect)(ran).to.deep.equal([]);
|
|
@@ -351,7 +351,7 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
351
351
|
it('answers 503 when the credential check fails', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
352
352
|
verifyKeyPair.rejects(new Error('database is locked'));
|
|
353
353
|
const { app, ran } = yield lab();
|
|
354
|
-
const res = yield (0,
|
|
354
|
+
const res = yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('alice-token'));
|
|
355
355
|
(0, chai_1.expect)(res.status).to.equal(503);
|
|
356
356
|
(0, chai_1.expect)(res.body).to.deep.equal(commandAuth_1.COMMAND_AUTH_UNAVAILABLE_BODY);
|
|
357
357
|
(0, chai_1.expect)(ran).to.deep.equal([]);
|
|
@@ -360,13 +360,13 @@ describe('per-command auth (XENON_REQUIRE_COMMAND_AUTH)', () => {
|
|
|
360
360
|
describe('base path', () => {
|
|
361
361
|
it("normalises the base path the way Appium does ('wd/hub/' → '/wd/hub')", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
362
362
|
const { app, ran } = yield lab({ basePath: 'wd/hub/' });
|
|
363
|
-
yield expectRefused(yield (0,
|
|
363
|
+
yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/wd/hub/session/s-alice/url').set(as('bob-token')));
|
|
364
364
|
(0, chai_1.expect)(ran).to.deep.equal([]);
|
|
365
365
|
}));
|
|
366
366
|
it("guards /session/:id under Appium 3's default empty base path", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
367
367
|
const { app, ran } = yield lab({ basePath: '', routePrefix: '' });
|
|
368
|
-
yield expectRefused(yield (0,
|
|
369
|
-
yield (0,
|
|
368
|
+
yield expectRefused(yield (0, loopbackRequest_1.default)(app).get('/session/s-alice/url').set(as('bob-token')));
|
|
369
|
+
yield (0, loopbackRequest_1.default)(app).post('/session').send({}).expect(200);
|
|
370
370
|
(0, chai_1.expect)(ran).to.deep.equal(['createSession']);
|
|
371
371
|
}));
|
|
372
372
|
});
|