@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
|
@@ -21,10 +21,12 @@ const prisma_store_1 = require("../../src/data-service/prisma-store");
|
|
|
21
21
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
22
22
|
const device_service_1 = require("../../src/data-service/device-service");
|
|
23
23
|
const device_utils_1 = require("../../src/device-utils");
|
|
24
|
+
const localDeviceHosts_1 = require("../../src/device-managers/localDeviceHosts");
|
|
24
25
|
const device_managers_1 = require("../../src/device-managers");
|
|
25
26
|
const SocketServer_1 = require("../../src/services/SocketServer");
|
|
26
27
|
const NotificationService_1 = require("../../src/services/NotificationService");
|
|
27
28
|
const container_registration_1 = require("../helpers/container-registration");
|
|
29
|
+
const fake_device_table_1 = require("../helpers/fake-device-table");
|
|
28
30
|
/**
|
|
29
31
|
* The periodic device sync reads every row, runs discovery (seconds of adb or
|
|
30
32
|
* simctl), then writes each phone back. A write that lands in between (a
|
|
@@ -38,81 +40,7 @@ const container_registration_1 = require("../helpers/container-registration");
|
|
|
38
40
|
*/
|
|
39
41
|
const HOST = 'http://127.0.0.1:4723';
|
|
40
42
|
const BIND = '127.0.0.1';
|
|
41
|
-
|
|
42
|
-
const rows = new Map();
|
|
43
|
-
const key = (udid, host) => `${udid}|${host}`;
|
|
44
|
-
const matches = (row, where) => {
|
|
45
|
-
if (!where)
|
|
46
|
-
return true;
|
|
47
|
-
return Object.entries(where).every(([field, cond]) => {
|
|
48
|
-
var _a;
|
|
49
|
-
if (field === 'OR')
|
|
50
|
-
return cond.some((w) => matches(row, w));
|
|
51
|
-
if (field === 'AND')
|
|
52
|
-
return cond.every((w) => matches(row, w));
|
|
53
|
-
if (cond && typeof cond === 'object' && 'in' in cond)
|
|
54
|
-
return cond.in.includes(row[field]);
|
|
55
|
-
if (cond && typeof cond === 'object' && 'contains' in cond)
|
|
56
|
-
return String((_a = row[field]) !== null && _a !== void 0 ? _a : '').includes(cond.contains);
|
|
57
|
-
return row[field] === cond;
|
|
58
|
-
});
|
|
59
|
-
};
|
|
60
|
-
const write = (row, data) => {
|
|
61
|
-
for (const [field, value] of Object.entries(data))
|
|
62
|
-
if (value !== undefined)
|
|
63
|
-
row[field] = value;
|
|
64
|
-
};
|
|
65
|
-
const defaults = () => ({
|
|
66
|
-
busy: false,
|
|
67
|
-
userBlocked: false,
|
|
68
|
-
offline: false,
|
|
69
|
-
session_id: null,
|
|
70
|
-
teamId: null,
|
|
71
|
-
sessionProgress: '',
|
|
72
|
-
sessionStartTime: 0,
|
|
73
|
-
totalUtilizationTimeMilliSec: 0,
|
|
74
|
-
});
|
|
75
|
-
for (const r of seed)
|
|
76
|
-
rows.set(key(r.udid, r.host), Object.assign(Object.assign({}, defaults()), r));
|
|
77
|
-
return {
|
|
78
|
-
row: (udid) => [...rows.values()].find((r) => r.udid === udid),
|
|
79
|
-
delegate: {
|
|
80
|
-
findMany: (...args_1) => __awaiter(this, [...args_1], void 0, function* (args = {}) { return [...rows.values()].filter((r) => matches(r, args.where)).map((r) => (Object.assign({}, r))); }),
|
|
81
|
-
findUnique: (_a) => __awaiter(this, [_a], void 0, function* ({ where }) {
|
|
82
|
-
const r = rows.get(key(where.udid_host.udid, where.udid_host.host));
|
|
83
|
-
return r ? Object.assign({}, r) : null;
|
|
84
|
-
}),
|
|
85
|
-
upsert: (_a) => __awaiter(this, [_a], void 0, function* ({ where, create, update, }) {
|
|
86
|
-
const k = key(where.udid_host.udid, where.udid_host.host);
|
|
87
|
-
const found = rows.get(k);
|
|
88
|
-
if (found) {
|
|
89
|
-
write(found, update);
|
|
90
|
-
return Object.assign({}, found);
|
|
91
|
-
}
|
|
92
|
-
const created = defaults();
|
|
93
|
-
write(created, create);
|
|
94
|
-
rows.set(k, created);
|
|
95
|
-
return Object.assign({}, created);
|
|
96
|
-
}),
|
|
97
|
-
updateMany: (_a) => __awaiter(this, [_a], void 0, function* ({ where, data }) {
|
|
98
|
-
let count = 0;
|
|
99
|
-
for (const r of rows.values()) {
|
|
100
|
-
if (matches(r, where)) {
|
|
101
|
-
write(r, data);
|
|
102
|
-
count++;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return { count };
|
|
106
|
-
}),
|
|
107
|
-
deleteMany: (_a) => __awaiter(this, [_a], void 0, function* ({ where }) {
|
|
108
|
-
for (const [k, r] of rows)
|
|
109
|
-
if (matches(r, where))
|
|
110
|
-
rows.delete(k);
|
|
111
|
-
return { count: 0 };
|
|
112
|
-
}),
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
}
|
|
43
|
+
const LOCAL = (0, localDeviceHosts_1.localDeviceHosts)({ bindHostOrIp: BIND }, 4723);
|
|
116
44
|
/** What AndroidDeviceManager returns for a phone it already knows (~158-166). */
|
|
117
45
|
const androidEcho = (known) => (Object.assign(Object.assign({}, known), { state: 'device', busy: known.busy || false }));
|
|
118
46
|
/** What IOSDiscoveryService.getSimulators returns for every simulator, known or not. */
|
|
@@ -136,7 +64,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
136
64
|
let restoreContainer;
|
|
137
65
|
let savedStore;
|
|
138
66
|
const seed = (rows) => {
|
|
139
|
-
table = fakeDeviceTable(rows);
|
|
67
|
+
table = (0, fake_device_table_1.fakeDeviceTable)(rows);
|
|
140
68
|
for (const m of Object.keys(table.delegate)) {
|
|
141
69
|
sinon_1.default.stub(prisma_1.prisma.device, m).callsFake(table.delegate[m]);
|
|
142
70
|
}
|
|
@@ -178,7 +106,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
178
106
|
yield (0, device_service_1.blockDevice)('s9', HOST, 'manual_user-1_s9');
|
|
179
107
|
return known.map(androidEcho);
|
|
180
108
|
});
|
|
181
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
109
|
+
yield (0, device_utils_1.updateDeviceList)(LOCAL);
|
|
182
110
|
(0, chai_1.expect)(table.row('s9')).to.include({ busy: true, session_id: 'manual_user-1_s9' });
|
|
183
111
|
}));
|
|
184
112
|
it('keeps a session lock taken while discovery ran, so the phone is not handed out twice', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -190,7 +118,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
190
118
|
(0, chai_1.expect)(locked === null || locked === void 0 ? void 0 : locked.udid).to.equal('s9');
|
|
191
119
|
return known.map(androidEcho);
|
|
192
120
|
});
|
|
193
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
121
|
+
yield (0, device_utils_1.updateDeviceList)(LOCAL);
|
|
194
122
|
(0, chai_1.expect)(table.row('s9')).to.include({ busy: true });
|
|
195
123
|
const second = yield device_store_1.DeviceStoreFactory.getStore().findAndLockDevice({
|
|
196
124
|
platform: 'android',
|
|
@@ -203,7 +131,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
203
131
|
yield device_store_1.DeviceStoreFactory.getStore().updateDevice('s9', HOST, { teamId: 'team-b' });
|
|
204
132
|
return known.map(androidEcho);
|
|
205
133
|
});
|
|
206
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
134
|
+
yield (0, device_utils_1.updateDeviceList)(LOCAL);
|
|
207
135
|
(0, chai_1.expect)(table.row('s9')).to.include({ teamId: 'team-b' });
|
|
208
136
|
}));
|
|
209
137
|
it("keeps a stream's port written while discovery ran", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -212,7 +140,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
212
140
|
yield device_store_1.DeviceStoreFactory.getStore().updateDevice('s9', HOST, { mjpegServerPort: 9200 });
|
|
213
141
|
return known.map(androidEcho);
|
|
214
142
|
});
|
|
215
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
143
|
+
yield (0, device_utils_1.updateDeviceList)(LOCAL);
|
|
216
144
|
(0, chai_1.expect)(table.row('s9')).to.include({ mjpegServerPort: 9200 });
|
|
217
145
|
}));
|
|
218
146
|
it('leaves a simulator in a session busy, though discovery always reports it free', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -232,7 +160,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
232
160
|
},
|
|
233
161
|
]);
|
|
234
162
|
discover = () => __awaiter(void 0, void 0, void 0, function* () { return [simulatorAsDiscovered()]; });
|
|
235
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
163
|
+
yield (0, device_utils_1.updateDeviceList)(LOCAL);
|
|
236
164
|
(0, chai_1.expect)(table.row('sim-1')).to.include({
|
|
237
165
|
busy: true,
|
|
238
166
|
session_id: 'sess-1',
|
|
@@ -268,7 +196,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
268
196
|
Object.assign(Object.assign({}, known.find((d) => d.udid === 'iphone')), { ip: '10.0.0.9' }),
|
|
269
197
|
];
|
|
270
198
|
});
|
|
271
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
199
|
+
yield (0, device_utils_1.updateDeviceList)(LOCAL);
|
|
272
200
|
(0, chai_1.expect)(table.row('sim-1')).to.include({ state: 'Booted', wdaLocalPort: 8101 });
|
|
273
201
|
(0, chai_1.expect)(table.row('iphone')).to.include({ ip: '10.0.0.9' });
|
|
274
202
|
}));
|
|
@@ -276,7 +204,7 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
276
204
|
seed([S9]);
|
|
277
205
|
const pixel = Object.assign(Object.assign({}, S9), { udid: 'pixel', name: 'Pixel 8' });
|
|
278
206
|
discover = (known) => __awaiter(void 0, void 0, void 0, function* () { return [...known.map(androidEcho), pixel]; });
|
|
279
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
207
|
+
yield (0, device_utils_1.updateDeviceList)(LOCAL);
|
|
280
208
|
(0, chai_1.expect)(emits.filter(([e]) => e === 'device_added').map(([, d]) => d.udid)).to.deep.equal([
|
|
281
209
|
'pixel',
|
|
282
210
|
]);
|
|
@@ -297,16 +225,16 @@ describe('Device sync keeps writes made while it ran (lost update)', () => {
|
|
|
297
225
|
});
|
|
298
226
|
(0, chai_1.expect)(emits.filter(([e]) => e === 'device_added')).to.have.length(0);
|
|
299
227
|
}));
|
|
300
|
-
it("the hub
|
|
228
|
+
it("the hub takes a node's report of its phone busy as the node's, not a session of its own", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
301
229
|
seed([Object.assign(Object.assign({}, S9), { host: 'http://10.0.0.2:4723' })]);
|
|
302
|
-
yield (0, device_service_1.addNewDevice)([Object.assign(Object.assign({}, S9), { host: 'http://10.0.0.2:4723', busy: true, session_id: 'node-sess' })], undefined, {
|
|
303
|
-
(0, chai_1.expect)(table.row('s9')).to.include({ busy: true, session_id:
|
|
230
|
+
yield (0, device_service_1.addNewDevice)([Object.assign(Object.assign({}, S9), { host: 'http://10.0.0.2:4723', busy: true, session_id: 'node-sess' })], undefined, { nodeReport: true });
|
|
231
|
+
(0, chai_1.expect)(table.row('s9')).to.include({ busy: true, nodeBusy: true, session_id: null });
|
|
304
232
|
(0, chai_1.expect)(emits.filter(([e]) => e === 'device_added')).to.have.length(0);
|
|
305
233
|
}));
|
|
306
234
|
});
|
|
307
235
|
describe('Device sync keeps writes made while it ran, on the Loki test store', () => {
|
|
308
236
|
const LOKI_HOST = 'http://loki-sync-test:4723';
|
|
309
|
-
const
|
|
237
|
+
const LOKI_LOCAL = (0, localDeviceHosts_1.localDeviceHosts)({ bindHostOrIp: 'loki-sync-test' }, 4723);
|
|
310
238
|
const UDIDS = ['loki-s9', 'loki-sim', 'loki-pixel'];
|
|
311
239
|
let emits;
|
|
312
240
|
let discover;
|
|
@@ -358,7 +286,7 @@ describe('Device sync keeps writes made while it ran, on the Loki test store', (
|
|
|
358
286
|
yield (0, device_service_1.blockDevice)('loki-s9', LOKI_HOST, 'manual_user-1_loki-s9');
|
|
359
287
|
return known.filter((d) => d.host === LOKI_HOST).map(androidEcho);
|
|
360
288
|
});
|
|
361
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
289
|
+
yield (0, device_utils_1.updateDeviceList)(LOKI_LOCAL);
|
|
362
290
|
(0, chai_1.expect)(yield row('loki-s9')).to.include({ busy: true, session_id: 'manual_user-1_loki-s9' });
|
|
363
291
|
}));
|
|
364
292
|
it('leaves a simulator in a session busy, and still takes its new state', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
@@ -371,7 +299,7 @@ describe('Device sync keeps writes made while it ran, on the Loki test store', (
|
|
|
371
299
|
simulatorAsDiscovered({ udid: 'loki-sim', host: LOKI_HOST, state: 'Booted' }),
|
|
372
300
|
];
|
|
373
301
|
});
|
|
374
|
-
yield (0, device_utils_1.updateDeviceList)(
|
|
302
|
+
yield (0, device_utils_1.updateDeviceList)(LOKI_LOCAL);
|
|
375
303
|
(0, chai_1.expect)(yield row('loki-sim')).to.include({
|
|
376
304
|
busy: true,
|
|
377
305
|
session_id: 'sess-1',
|
|
@@ -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
|
+
});
|