@xenon-device-management/xenon 2.14.0 → 2.16.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 +44 -29
- package/lib/package.json +6 -5
- package/lib/public/assets/{AnnotationOverlay-B_VNtKBH.js → AnnotationOverlay-2W4JvEyZ.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-BwuaMjkX.js → ApiKeyGate-GmckX_7r.js} +1 -1
- package/lib/public/assets/DeviceMosaicView-DqODzkyo.js +2 -0
- package/lib/public/assets/{EmptyState-BSjhk_IV.js → EmptyState-7hETKEWb.js} +1 -1
- package/lib/public/assets/{FieldGroup-Cb5QZsE1.js → FieldGroup-GOgssFEP.js} +1 -1
- package/lib/public/assets/{FilterMenu-BAaSlWbW.js → FilterMenu-Yvf6RGMd.js} +1 -1
- package/lib/public/assets/{Menu-JGGYrSQQ.js → Menu-KrPnfdsf.js} +1 -1
- package/lib/public/assets/{Modal-CFCp3n9d.js → Modal-C6WjZFeI.js} +1 -1
- package/lib/public/assets/{RecordingPage-CbJj_EBT.js → RecordingPage-CSRIhZEh.js} +1 -1
- package/lib/public/assets/{RecordingsPage-BJeYnygR.js → RecordingsPage-CB8ST3nE.js} +1 -1
- package/lib/public/assets/{SegmentedControl-Bg42Atjy.js → SegmentedControl-C9o90KId.js} +1 -1
- package/lib/public/assets/{SettingCard-Njz1pUIJ.js → SettingCard-CPm3w9l2.js} +1 -1
- package/lib/public/assets/{Table-BcTm5xV2.js → Table-DwIgERRt.js} +1 -1
- package/lib/public/assets/{activity-DOVdsfMQ.js → activity-DrLbHifn.js} +1 -1
- package/lib/public/assets/ai-settings-DU_Ls06P.js +16 -0
- package/lib/public/assets/{api-keys-BKnNSTw9.js → api-keys-CWYdEzJ_.js} +1 -1
- package/lib/public/assets/{apps-D0SC6uw6.js → apps-BqNVdln7.js} +1 -1
- package/lib/public/assets/{arrow-left-MMbTmgoE.js → arrow-left-BtB0_Jg_.js} +1 -1
- package/lib/public/assets/{arrow-right-BMfnGmxW.js → arrow-right-C8NhdtyN.js} +1 -1
- package/lib/public/assets/{arrow-up-right-DuAftMWB.js → arrow-up-right-gMg0ffvv.js} +1 -1
- package/lib/public/assets/{auth-shell-nZC2JNDO.js → auth-shell-CiYZ3N4M.js} +2 -2
- package/lib/public/assets/{builds-page-hDCgTwkS.js → builds-page-EaaqDmLN.js} +1 -1
- package/lib/public/assets/{button-Dda4irn_.js → button-DDau4Axv.js} +1 -1
- package/lib/public/assets/{calendar-BMr0Q62v.js → calendar-DQ83B1ed.js} +1 -1
- package/lib/public/assets/{check-CZ9iw9-e.js → check-K1ovTQam.js} +1 -1
- package/lib/public/assets/{chevron-right-BRYJJVC9.js → chevron-right-Biq00pMt.js} +1 -1
- package/lib/public/assets/{circle-check-CqAJjQ5O.js → circle-check-CBv88cH7.js} +1 -1
- package/lib/public/assets/{circle-x-DlDJtnVz.js → circle-x-Bmltailn.js} +1 -1
- package/lib/public/assets/{clock-CpuDKysU.js → clock-BPrld-3h.js} +1 -1
- package/lib/public/assets/{copy-CAQydNFe.js → copy-t0QB14JI.js} +1 -1
- package/lib/public/assets/derive-UnLRp4r3.js +1 -0
- package/lib/public/assets/device-explorer-BgGZ8AvV.js +282 -0
- package/lib/public/assets/{download-C9XG6lLv.js → download-BroHxNOh.js} +1 -1
- package/lib/public/assets/forgot-password-Bjmf49i6.js +1 -0
- package/lib/public/assets/{h264Stream-VQBphYhg.js → h264Stream-DeD__6wa.js} +6 -6
- package/lib/public/assets/{index-DJGc1M2f.js → index-72oF89LJ.js} +1 -1
- package/lib/public/assets/index-B0JeuZbg.js +15 -0
- package/lib/public/assets/index-B0gQkEg0.js +219 -0
- package/lib/public/assets/{input-Cm0tmSoc.js → input-DIc4MSC7.js} +1 -1
- package/lib/public/assets/{line-chart-D2DyJCml.js → line-chart-BcWm2vW8.js} +1 -1
- package/lib/public/assets/{list-checks-UWf1GY99.js → list-checks-B06IJJad.js} +1 -1
- package/lib/public/assets/{lock-CCJv9m_I.js → lock-pVLvnN30.js} +1 -1
- package/lib/public/assets/{login-f8vOVW-U.js → login-CN77vy--.js} +1 -1
- package/lib/public/assets/{maintenance-settings-C1PrkuAH.js → maintenance-settings-CSdzQSkY.js} +1 -1
- package/lib/public/assets/{monitor-B2jDgwm7.js → monitor-BtrN5IkI.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-DLI1RtzU.js → mouse-pointer-2-CNsQfo6w.js} +1 -1
- package/lib/public/assets/{network-ebT86qoi.js → network-BSfA8ZxG.js} +1 -1
- package/lib/public/assets/{overview-C0aykVrd.js → overview-BfTebCK5.js} +1 -1
- package/lib/public/assets/{page-header-BjzgWz-T.js → page-header-D8KUX6Qg.js} +1 -1
- package/lib/public/assets/{play-DLG9t8FU.js → play-Cvc8xzic.js} +1 -1
- package/lib/public/assets/{plus-nBFSMu_X.js → plus-C9Zj84UF.js} +1 -1
- package/lib/public/assets/{profile-page-CSl0UYlv.js → profile-page-Bi27STgs.js} +1 -1
- package/lib/public/assets/{reset-password-LeOuHqmB.js → reset-password-CPsOuwSu.js} +1 -1
- package/lib/public/assets/runbook-page-DEQ-DBOX.js +304 -0
- package/lib/public/assets/{select-88jHcl-Z.js → select-BWpnhWXj.js} +1 -1
- package/lib/public/assets/{selector-detail-redirect-BbXKJjZM.js → selector-detail-redirect-BefSCSpe.js} +1 -1
- package/lib/public/assets/{selector-health-page-B4rA1Xkv.js → selector-health-page-MSk96Zz7.js} +1 -1
- package/lib/public/assets/session-detail-page-DPyBNef_.js +4 -0
- package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
- package/lib/public/assets/{settings-GF6ZMSQ6.js → settings-V6x-cXwy.js} +1 -1
- package/lib/public/assets/{stat-tile-BV98e_Sv.js → stat-tile-Duex-GZD.js} +1 -1
- package/lib/public/assets/{tablet-BiAsnFDj.js → tablet-BBcoqBmL.js} +1 -1
- package/lib/public/assets/{teams-B4-jrr-1.js → teams-ts8p5rEh.js} +1 -1
- package/lib/public/assets/{trash-2-C5RSuX0e.js → trash-2-CZ2ew6AZ.js} +1 -1
- package/lib/public/assets/{upload-BG7_rMXK.js → upload-Dv99y_le.js} +1 -1
- package/lib/public/assets/{use-builds-data-J-riQYyC.js → use-builds-data-DrYC8JCF.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-C8-eH-UG.js → use-password-reset-mode-C7QkBNkP.js} +1 -1
- package/lib/public/assets/{users-CFba5TSy.js → users-D7tKMESH.js} +1 -1
- package/lib/public/assets/{users-Cp0beJm3.js → users-gJ5Y01yo.js} +1 -1
- package/lib/public/assets/{video-off-DDsEISjV.js → video-off-CqZVuZGs.js} +1 -1
- package/lib/public/assets/{webhook-settings-BGu32vSB.js → webhook-settings-DI5DSW99.js} +1 -1
- package/lib/public/assets/{zap-l4VaXzq8.js → zap-ClcHFxfo.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/schema.json +16 -12
- package/lib/src/InternalHttpClient.js +27 -5
- package/lib/src/XenonCapabilityManager.js +10 -3
- package/lib/src/app/dashboardPluginLink.js +117 -0
- package/lib/src/app/index.js +13 -34
- package/lib/src/app/openapi/control.yaml +6 -4
- package/lib/src/app/openapi/grid.yaml +8 -2
- package/lib/src/app/openapi/identity.yaml +103 -31
- package/lib/src/app/openapi/platform.yaml +33 -15
- package/lib/src/app/openapi/sessions.yaml +12 -3
- package/lib/src/app/routers/apikeys.js +14 -2
- package/lib/src/app/routers/auth.js +31 -6
- package/lib/src/app/routers/control.js +13 -5
- package/lib/src/app/routers/dashboard.js +42 -27
- package/lib/src/app/routers/grid.js +5 -3
- package/lib/src/app/routers/profile.js +21 -0
- package/lib/src/app/routers/users.js +12 -6
- package/lib/src/app/swagger.js +2 -0
- package/lib/src/app/ws/h264StreamWs.js +10 -1
- package/lib/src/app/ws/nodeSocketRelay.js +77 -11
- package/lib/src/config.js +1 -0
- package/lib/src/dashboard/commandLogFields.js +42 -0
- package/lib/src/dashboard/commands.js +5 -5
- package/lib/src/dashboard/event-manager.js +64 -204
- package/lib/src/dashboard/services/failure-analysis-service.js +118 -105
- package/lib/src/dashboard/services/failureCategories.js +264 -0
- package/lib/src/dashboard/sessionCommandSummary.js +39 -0
- package/lib/src/data-service/device-store.js +14 -1
- package/lib/src/data-service/prisma-store.js +19 -7
- package/lib/src/data-service/web-config-service.js +7 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +61 -23
- package/lib/src/device-managers/IOSDeviceManager.js +10 -0
- package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
- package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
- package/lib/src/device-utils.js +29 -3
- package/lib/src/gateway/defaultGateway.js +1 -1
- package/lib/src/gateway/forwardToNode.js +6 -11
- package/lib/src/gateway/healReport.js +87 -0
- package/lib/src/gateway/hubSessionToken.js +27 -4
- package/lib/src/gateway/nodeWebDriverUrl.js +3 -5
- package/lib/src/gateway/sessionGateway.js +12 -2
- package/lib/src/generated/client/edge.js +4 -3
- package/lib/src/generated/client/index-browser.js +1 -0
- package/lib/src/generated/client/index.d.ts +32 -0
- package/lib/src/generated/client/index.js +4 -3
- package/lib/src/generated/client/package.json +1 -1
- package/lib/src/generated/client/schema.prisma +3 -0
- package/lib/src/generated/client/wasm.js +1 -0
- package/lib/src/helpers/outboundProxy.js +219 -0
- package/lib/src/index.js +21 -0
- package/lib/src/interceptors/CommandInterceptor.js +256 -196
- package/lib/src/interceptors/xenonScripts.js +28 -7
- package/lib/src/logger.js +3 -1
- package/lib/src/middleware/authMiddleware.js +15 -3
- package/lib/src/middleware/commandCaller.js +3 -3
- package/lib/src/middleware/verifyCredential.js +58 -1
- package/lib/src/plugin.js +16 -6
- package/lib/src/scripts/database-check.js +3 -3
- package/lib/src/scripts/initialize-database.js +35 -33
- package/lib/src/scripts/run-migrations.js +1079 -27
- package/lib/src/services/AICommandService.js +9 -2
- package/lib/src/services/AIService.js +192 -42
- package/lib/src/services/ApiKeyService.js +16 -5
- package/lib/src/services/EmailService.js +29 -0
- package/lib/src/services/InterceptorService.js +6 -2
- package/lib/src/services/OrphanSweeper.js +8 -0
- package/lib/src/services/SelectorStateService.js +11 -7
- package/lib/src/services/SelectorVerificationJob.js +21 -6
- package/lib/src/services/ServerManager.js +11 -1
- package/lib/src/services/SessionLifecycleService.js +118 -44
- package/lib/src/services/SocketClient.js +11 -0
- package/lib/src/services/SocketServer.js +380 -45
- package/lib/src/services/TeamService.js +5 -1
- package/lib/src/services/TracingService.js +171 -60
- package/lib/src/services/UserService.js +14 -3
- package/lib/src/services/UserSessionService.js +16 -4
- package/lib/src/services/bug-report/video-slice.js +4 -1
- package/lib/src/services/device-access/leaseOverride.js +7 -5
- package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
- package/lib/src/services/healing/HealEtalonService.js +3 -2
- package/lib/src/services/healing/HealingOrchestrator.js +57 -72
- package/lib/src/services/healing/OcrHealingProvider.js +41 -64
- package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
- package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
- package/lib/src/services/healing/fingerprintIdentity.js +126 -0
- package/lib/src/services/healing/resilioPath.js +274 -0
- package/lib/src/services/identity/identityChanges.js +41 -0
- package/lib/src/services/logcat/SessionDeviceLogs.js +358 -0
- package/lib/src/services/logcat/deviceLogBook.js +164 -0
- package/lib/src/services/logcat/iosDriverLog.js +178 -0
- package/lib/src/services/ocr/ocrData.js +118 -0
- package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
- package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
- package/lib/src/services/omni-vision/OmniVisionService.js +83 -56
- package/lib/src/services/passwordResetLink.js +50 -9
- package/lib/src/services/passwordResetMode.js +3 -1
- package/lib/src/services/publicUrl.js +45 -0
- package/lib/src/services/selector-health/SelectorVisibilityResolver.js +121 -0
- package/lib/src/services/session/sessionIdentity.js +5 -3
- package/lib/src/services/session/shutdownReason.js +26 -0
- package/lib/src/services/settings/aiEngineSettings.js +219 -0
- package/lib/src/services/token/JwtKeyService.js +8 -2
- package/lib/src/services/token/mcpTokenTtl.js +27 -0
- package/lib/src/services/token/mintedLifetime.js +51 -0
- package/lib/src/sessions/LocalSession.js +29 -1
- package/lib/src/sessions/RemoteSession.js +12 -2
- package/lib/src/sessions/SessionManager.js +4 -1
- package/lib/src/sessions/sessionMemory.js +25 -0
- package/lib/test/helpers/driver-log.js +38 -0
- package/lib/test/helpers/fake-ai-provider.js +188 -0
- package/lib/test/helpers/fake-http-proxy.js +106 -0
- package/lib/test/helpers/loki-stores.js +33 -0
- package/lib/test/helpers/otlp-probe.js +107 -0
- package/lib/test/helpers/page-sources.js +164 -0
- package/lib/test/helpers/scratch-database.js +57 -13
- package/lib/test/integration/androidDevices.spec.js +1 -1
- package/lib/test/integration/audit-ingest.spec.js +10 -10
- package/lib/test/integration/auth-flow.spec.js +9 -14
- package/lib/test/integration/auth-rate-limit.spec.js +6 -3
- package/lib/test/integration/bug-report-route.spec.js +22 -2
- package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
- package/lib/test/integration/forgot-password.spec.js +17 -18
- package/lib/test/integration/ios/01iOSSimulator.spec.js +1 -1
- package/lib/test/integration/ios/02iOSDevices.spec.js +1 -1
- package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
- package/lib/test/integration/migration.spec.js +6 -1
- package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
- package/lib/test/integration/ports-router.spec.js +4 -4
- package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
- package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
- package/lib/test/integration/profile-tokens.spec.js +10 -16
- package/lib/test/integration/recordings-router.spec.js +16 -16
- package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
- package/lib/test/integration/role-matrix.spec.js +4 -9
- package/lib/test/integration/sdk-leases-router.spec.js +9 -9
- package/lib/test/integration/sdk-version.spec.js +2 -2
- package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
- package/lib/test/integration/selector-state-flow.spec.js +5 -5
- package/lib/test/integration/team-membership-migration.spec.js +73 -12
- package/lib/test/integration/team-visibility-apps.spec.js +15 -22
- package/lib/test/integration/team-visibility-control.spec.js +96 -69
- package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
- package/lib/test/integration/team-visibility-grid.spec.js +16 -15
- package/lib/test/integration/team-visibility-leases.spec.js +8 -11
- package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
- package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
- package/lib/test/integration/users-flow.spec.js +7 -12
- package/lib/test/integration/users-lockout.spec.js +5 -3
- package/lib/test/unit/EmailService.test.js +66 -0
- package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
- package/lib/test/unit/XenonCapabilityManager.spec.js +23 -0
- package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
- package/lib/test/unit/ai-engine-settings.spec.js +338 -0
- package/lib/test/unit/ai-provider-switch.spec.js +130 -0
- package/lib/test/unit/ai-rate-limit.spec.js +126 -0
- package/lib/test/unit/android-adb-retry.spec.js +112 -0
- package/lib/test/unit/android-h264-viewers.spec.js +272 -0
- package/lib/test/unit/android-preview-hold.spec.js +3 -2
- package/lib/test/unit/apikeys-router.spec.js +84 -0
- package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
- package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
- package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
- package/lib/test/unit/command-log-network-capture.spec.js +191 -0
- package/lib/test/unit/dashboard-events-device-logs.spec.js +255 -0
- package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
- package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
- package/lib/test/unit/database-check.spec.js +1 -0
- package/lib/test/unit/device-log-book.spec.js +180 -0
- package/lib/test/unit/device-utils.spec.js +13 -4
- package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
- package/lib/test/unit/event-log-session-data.spec.js +251 -0
- package/lib/test/unit/failure-analysis.spec.js +196 -0
- package/lib/test/unit/failure-categories.spec.js +130 -0
- package/lib/test/unit/failure-categorization.spec.js +393 -0
- package/lib/test/unit/fingerprint-identity.spec.js +433 -0
- package/lib/test/unit/h264-multiplexer.spec.js +46 -0
- package/lib/test/unit/heal-report.spec.js +71 -0
- package/lib/test/unit/healing-instrumentation.spec.js +6 -1
- package/lib/test/unit/healing-tier-gate.spec.js +74 -29
- package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
- package/lib/test/unit/hub-node-gateway-appium-server.spec.js +243 -1
- package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
- package/lib/test/unit/ios-driver-log.spec.js +168 -0
- package/lib/test/unit/ios-simulator-session-readiness.spec.js +187 -0
- package/lib/test/unit/ios-simulator-session-teardown.spec.js +158 -0
- package/lib/test/unit/lease/lease-override.spec.js +23 -6
- package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
- package/lib/test/unit/logcat-ws.spec.js +78 -37
- package/lib/test/unit/logger-json-levels.spec.js +44 -0
- package/lib/test/unit/mcp-token-ttl.spec.js +38 -0
- package/lib/test/unit/observability-example-loki.spec.js +25 -0
- package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
- package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
- package/lib/test/unit/ocr-offline.spec.js +213 -0
- package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
- package/lib/test/unit/omni-vision-ios-points.spec.js +0 -2
- package/lib/test/unit/omni-vision.spec.js +5 -7
- package/lib/test/unit/openapi-for-site.spec.js +80 -0
- package/lib/test/unit/outbound-proxy.spec.js +320 -0
- package/lib/test/unit/package-engines.spec.js +27 -0
- package/lib/test/unit/passwordResetMode.test.js +10 -2
- package/lib/test/unit/port-exhaustion.spec.js +2 -1
- package/lib/test/unit/profile-router.test.js +122 -1
- package/lib/test/unit/proxy-option.spec.js +358 -0
- package/lib/test/unit/queue-requester.spec.js +1 -1
- package/lib/test/unit/reset-link-address.spec.js +266 -0
- package/lib/test/unit/reset-link.test.js +10 -4
- package/lib/test/unit/resilio-path-healing.spec.js +270 -0
- package/lib/test/unit/run-migrations-database.spec.js +807 -0
- package/lib/test/unit/run-migrations-fail-fast.test.js +41 -27
- package/lib/test/unit/run-migrations.test.js +864 -0
- package/lib/test/unit/selector-health-actions.spec.js +3 -1
- package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
- package/lib/test/unit/selector-state-service.spec.js +72 -23
- package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
- package/lib/test/unit/selector-verification-job.spec.js +28 -19
- package/lib/test/unit/selector-verification-missed-regression.spec.js +2 -2
- package/lib/test/unit/selector-visibility-resolver.spec.js +209 -0
- package/lib/test/unit/self-healing-boot.spec.js +10 -5
- package/lib/test/unit/self-healing-setting.spec.js +7 -6
- package/lib/test/unit/session-app-resolution.spec.js +5 -1
- package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
- package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
- package/lib/test/unit/session-credential-strip.spec.js +1 -1
- package/lib/test/unit/session-device-logs-ios.spec.js +180 -0
- package/lib/test/unit/session-device-logs.spec.js +356 -0
- package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
- package/lib/test/unit/session-log-indexes.spec.js +64 -3
- package/lib/test/unit/socket-team-scope.integration.spec.js +749 -0
- package/lib/test/unit/socketServer.scopedEmit.spec.js +237 -5
- package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
- package/lib/test/unit/stream-start-pipelines.spec.js +72 -18
- package/lib/test/unit/token-mint-limits.spec.js +165 -0
- package/lib/test/unit/tracing-export-routing.spec.js +137 -0
- package/lib/test/unit/tracing-session-spans.spec.js +337 -0
- package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
- package/lib/test/unit/verify-credential.spec.js +33 -0
- package/lib/test/unit/vision-assertion.spec.js +3 -2
- package/lib/test/unit/web-config-service.spec.js +19 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -5
- package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
- package/prisma/schema.prisma +3 -0
- package/schema.json +16 -12
- package/scripts/dev/readme-screenshots.js +276 -23
- package/scripts/dev/screenshot-fixtures/logcat.js +157 -0
- package/scripts/dev/screenshot-fixtures/phone-screen.html +469 -0
- package/scripts/dev/screenshot-fixtures/session.js +242 -0
- package/scripts/export-openapi.js +32 -0
- package/scripts/lib/openapi-for-site.js +79 -0
- package/lib/public/assets/BugReportButton-BAt0_pzq.js +0 -15
- package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +0 -2
- package/lib/public/assets/ai-settings-CsuJroRf.js +0 -21
- package/lib/public/assets/device-explorer-CdO6_nrn.js +0 -282
- package/lib/public/assets/forgot-password-CGnkRgE9.js +0 -1
- package/lib/public/assets/index-BbKEXUFH.js +0 -219
- package/lib/public/assets/runbook-page-CMMeQml9.js +0 -124
- package/lib/public/assets/session-detail-page-COL_RZxw.js +0 -4
- package/lib/src/scripts/prepare-prisma.js +0 -89
- /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
|
@@ -13,6 +13,7 @@ require("reflect-metadata");
|
|
|
13
13
|
const chai_1 = require("chai");
|
|
14
14
|
const prisma_store_1 = require("../../src/data-service/prisma-store");
|
|
15
15
|
const prisma_1 = require("../../src/prisma");
|
|
16
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
16
17
|
/**
|
|
17
18
|
* PrismaDeviceStore.addDevices, run as real queries.
|
|
18
19
|
*
|
|
@@ -23,6 +24,7 @@ const prisma_1 = require("../../src/prisma");
|
|
|
23
24
|
*/
|
|
24
25
|
describe('PrismaDeviceStore.addDevices (integration)', function () {
|
|
25
26
|
this.timeout(60000);
|
|
27
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
26
28
|
const store = new prisma_store_1.PrismaDeviceStore();
|
|
27
29
|
const stamp = Date.now();
|
|
28
30
|
const HOST = `http://add-devices-${stamp}:4723`;
|
|
@@ -46,9 +48,6 @@ describe('PrismaDeviceStore.addDevices (integration)', function () {
|
|
|
46
48
|
},
|
|
47
49
|
});
|
|
48
50
|
}));
|
|
49
|
-
after(() => __awaiter(this, void 0, void 0, function* () {
|
|
50
|
-
yield prisma_1.prisma.device.deleteMany({ where: { udid: { in: UDIDS } } });
|
|
51
|
-
}));
|
|
52
51
|
it('refreshes a known phone’s discovery columns but leaves its session alone', () => __awaiter(this, void 0, void 0, function* () {
|
|
53
52
|
const added = yield store.addDevices([phone(KNOWN, { sdk: '11', sessionStartTime: 0 })]);
|
|
54
53
|
(0, chai_1.expect)(added).to.deep.equal([]);
|
|
@@ -15,6 +15,7 @@ const typedi_1 = require("typedi");
|
|
|
15
15
|
const prisma_store_1 = require("../../src/data-service/prisma-store");
|
|
16
16
|
const TeamService_1 = require("../../src/services/TeamService");
|
|
17
17
|
const prisma_1 = require("../../src/prisma");
|
|
18
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
18
19
|
/**
|
|
19
20
|
* The team filter on the Prisma device store, run as a real query.
|
|
20
21
|
*
|
|
@@ -25,6 +26,7 @@ const prisma_1 = require("../../src/prisma");
|
|
|
25
26
|
*/
|
|
26
27
|
describe('PrismaDeviceStore team filter (integration)', function () {
|
|
27
28
|
this.timeout(60000);
|
|
29
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
28
30
|
const store = new prisma_store_1.PrismaDeviceStore();
|
|
29
31
|
let teamA;
|
|
30
32
|
let teamB;
|
|
@@ -51,11 +53,6 @@ describe('PrismaDeviceStore team filter (integration)', function () {
|
|
|
51
53
|
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
52
54
|
yield prisma_1.prisma.device.updateMany({ where: { udid: { in: UDIDS } }, data: { busy: false } });
|
|
53
55
|
}));
|
|
54
|
-
after(() => __awaiter(this, void 0, void 0, function* () {
|
|
55
|
-
yield prisma_1.prisma.device.deleteMany({ where: { udid: { in: UDIDS } } });
|
|
56
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamA.id } }).catch(() => undefined);
|
|
57
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamB.id } }).catch(() => undefined);
|
|
58
|
-
}));
|
|
59
56
|
const udidsOf = (rows) => rows.map((r) => r.udid).sort();
|
|
60
57
|
const busy = (udid) => __awaiter(this, void 0, void 0, function* () { var _a; return !!((_a = (yield prisma_1.prisma.device.findFirst({ where: { udid, host: HOST } }))) === null || _a === void 0 ? void 0 : _a.busy); });
|
|
61
58
|
it('getDevices with one team returns the shared phone and that team’s', () => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -15,20 +15,19 @@ 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 profile_1 = require("../../src/app/routers/profile");
|
|
20
20
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
21
21
|
const typedi_1 = require("typedi");
|
|
22
22
|
const UserService_1 = require("../../src/services/UserService");
|
|
23
23
|
const UserSessionService_1 = require("../../src/services/UserSessionService");
|
|
24
|
-
const
|
|
24
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
25
25
|
describe('profile/tokens (integration)', function () {
|
|
26
26
|
this.timeout(30000);
|
|
27
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
27
28
|
let user;
|
|
28
29
|
let cookie;
|
|
29
30
|
before(() => __awaiter(this, void 0, void 0, function* () {
|
|
30
|
-
yield prisma_1.prisma.userSession.deleteMany({ where: { user: { email: 'tokens-it@xenon.local' } } });
|
|
31
|
-
yield prisma_1.prisma.user.deleteMany({ where: { email: 'tokens-it@xenon.local' } });
|
|
32
31
|
user = yield typedi_1.Container.get(UserService_1.UserService).createUser({
|
|
33
32
|
email: 'tokens-it@xenon.local',
|
|
34
33
|
name: 'Tokens IT',
|
|
@@ -38,40 +37,35 @@ describe('profile/tokens (integration)', function () {
|
|
|
38
37
|
const session = yield typedi_1.Container.get(UserSessionService_1.UserSessionService).create(user.id);
|
|
39
38
|
cookie = `xenon_dashboard_session=${session.id}`;
|
|
40
39
|
}));
|
|
41
|
-
after(() => __awaiter(this, void 0, void 0, function* () {
|
|
42
|
-
yield prisma_1.prisma.apiKey.deleteMany({ where: { userId: user.id } });
|
|
43
|
-
yield prisma_1.prisma.userSession.deleteMany({ where: { userId: user.id } });
|
|
44
|
-
yield prisma_1.prisma.user.delete({ where: { id: user.id } });
|
|
45
|
-
}));
|
|
46
40
|
it('creates and lists a token; rotating accessKey keeps the token valid', () => __awaiter(this, void 0, void 0, function* () {
|
|
47
41
|
const app = (0, express_1.default)();
|
|
48
42
|
app.use(express_1.default.json());
|
|
49
43
|
app.use(authMiddleware_1.authMiddleware);
|
|
50
44
|
app.use('/profile', (0, profile_1.profileRouter)());
|
|
51
|
-
const created = yield (0,
|
|
45
|
+
const created = yield (0, loopbackRequest_1.default)(app)
|
|
52
46
|
.post('/profile/tokens')
|
|
53
47
|
.set('Cookie', cookie)
|
|
54
48
|
.send({ name: 'CI' });
|
|
55
49
|
(0, chai_1.expect)(created.status).to.equal(201);
|
|
56
50
|
const token = created.body.token;
|
|
57
|
-
const list = yield (0,
|
|
51
|
+
const list = yield (0, loopbackRequest_1.default)(app).get('/profile/tokens').set('Cookie', cookie);
|
|
58
52
|
(0, chai_1.expect)(list.body.length).to.equal(1);
|
|
59
|
-
const me = yield (0,
|
|
53
|
+
const me = yield (0, loopbackRequest_1.default)(app).get('/profile/access-key').set('Cookie', cookie);
|
|
60
54
|
const ak = me.body.accessKey;
|
|
61
|
-
const headerCheck = yield (0,
|
|
55
|
+
const headerCheck = yield (0, loopbackRequest_1.default)(app)
|
|
62
56
|
.get('/profile/tokens')
|
|
63
57
|
.set('x-xenon-access-key', ak)
|
|
64
58
|
.set('x-xenon-token', token);
|
|
65
59
|
(0, chai_1.expect)(headerCheck.status).to.equal(200);
|
|
66
|
-
const rotated = yield (0,
|
|
60
|
+
const rotated = yield (0, loopbackRequest_1.default)(app).post('/profile/access-key/rotate').set('Cookie', cookie);
|
|
67
61
|
const newAk = rotated.body.accessKey;
|
|
68
62
|
(0, chai_1.expect)(newAk).to.not.equal(ak);
|
|
69
|
-
const afterRotate = yield (0,
|
|
63
|
+
const afterRotate = yield (0, loopbackRequest_1.default)(app)
|
|
70
64
|
.get('/profile/tokens')
|
|
71
65
|
.set('x-xenon-access-key', newAk)
|
|
72
66
|
.set('x-xenon-token', token);
|
|
73
67
|
(0, chai_1.expect)(afterRotate.status).to.equal(200);
|
|
74
|
-
const oldFails = yield (0,
|
|
68
|
+
const oldFails = yield (0, loopbackRequest_1.default)(app)
|
|
75
69
|
.get('/profile/tokens')
|
|
76
70
|
.set('x-xenon-access-key', ak)
|
|
77
71
|
.set('x-xenon-token', token);
|
|
@@ -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 recordings_1 = __importDefault(require("../../src/app/routers/recordings"));
|
|
22
22
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
@@ -76,7 +76,7 @@ describe('Recordings router (integration)', () => {
|
|
|
76
76
|
startedAt: new Date(),
|
|
77
77
|
compositeEnabled: false,
|
|
78
78
|
});
|
|
79
|
-
const r = yield (0,
|
|
79
|
+
const r = yield (0, loopbackRequest_1.default)(makeApp())
|
|
80
80
|
.post('/xenon/api/recordings')
|
|
81
81
|
.send({ udids: ['U1'] });
|
|
82
82
|
(0, chai_1.expect)(r.status).to.equal(202);
|
|
@@ -88,7 +88,7 @@ describe('Recordings router (integration)', () => {
|
|
|
88
88
|
.rejects(new RecordingOrchestrator_1.RecordingError('device_busy', [
|
|
89
89
|
{ udid: 'U2', reason: 'automation', sessionId: 'sess-x' },
|
|
90
90
|
]));
|
|
91
|
-
const r = yield (0,
|
|
91
|
+
const r = yield (0, loopbackRequest_1.default)(makeApp())
|
|
92
92
|
.post('/xenon/api/recordings')
|
|
93
93
|
.send({ udids: ['U1', 'U2'] });
|
|
94
94
|
(0, chai_1.expect)(r.status).to.equal(409);
|
|
@@ -101,7 +101,7 @@ describe('Recordings router (integration)', () => {
|
|
|
101
101
|
sinon_1.default
|
|
102
102
|
.stub(typedi_1.Container.get(RecordingOrchestrator_1.RecordingOrchestrator), 'start')
|
|
103
103
|
.rejects(new RecordingOrchestrator_1.RecordingError('concurrency_cap', undefined, 4, 4));
|
|
104
|
-
const r = yield (0,
|
|
104
|
+
const r = yield (0, loopbackRequest_1.default)(makeApp())
|
|
105
105
|
.post('/xenon/api/recordings')
|
|
106
106
|
.send({ udids: ['U1'] });
|
|
107
107
|
(0, chai_1.expect)(r.status).to.equal(409);
|
|
@@ -109,16 +109,16 @@ describe('Recordings router (integration)', () => {
|
|
|
109
109
|
}));
|
|
110
110
|
it('POST /recordings: 400 when udids missing or empty', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
111
111
|
const app = makeApp();
|
|
112
|
-
yield (0,
|
|
113
|
-
yield (0,
|
|
114
|
-
yield (0,
|
|
112
|
+
yield (0, loopbackRequest_1.default)(app).post('/xenon/api/recordings').send({}).expect(400);
|
|
113
|
+
yield (0, loopbackRequest_1.default)(app).post('/xenon/api/recordings').send({ udids: [] }).expect(400);
|
|
114
|
+
yield (0, loopbackRequest_1.default)(app).post('/xenon/api/recordings').send({ udids: [123] }).expect(400);
|
|
115
115
|
}));
|
|
116
116
|
it('POST /recordings/:groupId/stop: 200', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
117
|
sinon_1.default.stub(typedi_1.Container.get(RecordingOrchestrator_1.RecordingOrchestrator), 'stop').resolves({
|
|
118
118
|
groupId: 'g-1',
|
|
119
119
|
recordings: [{ id: 'r-1', udid: 'U1', status: 'STOPPED', durationMs: 5000, sizeBytes: 1024 }],
|
|
120
120
|
});
|
|
121
|
-
const r = yield (0,
|
|
121
|
+
const r = yield (0, loopbackRequest_1.default)(makeApp()).post('/xenon/api/recordings/g-1/stop');
|
|
122
122
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
123
123
|
(0, chai_1.expect)(r.body.recordings[0].status).to.equal('STOPPED');
|
|
124
124
|
}));
|
|
@@ -127,11 +127,11 @@ describe('Recordings router (integration)', () => {
|
|
|
127
127
|
sinon_1.default
|
|
128
128
|
.stub(typedi_1.Container.get(RecordingOrchestrator_1.RecordingOrchestrator), 'addBookmark')
|
|
129
129
|
.resolves({ id: 'bm-1', label: 'bug here' });
|
|
130
|
-
const ok = yield (0,
|
|
130
|
+
const ok = yield (0, loopbackRequest_1.default)(makeApp())
|
|
131
131
|
.post('/xenon/api/recordings/g-1/bookmark')
|
|
132
132
|
.send({ recordingId: 'r-1', timecodeMs: 1000, label: 'bug here' });
|
|
133
133
|
(0, chai_1.expect)(ok.status).to.equal(201);
|
|
134
|
-
const bad = yield (0,
|
|
134
|
+
const bad = yield (0, loopbackRequest_1.default)(makeApp())
|
|
135
135
|
.post('/xenon/api/recordings/g-1/bookmark')
|
|
136
136
|
.send({ timecodeMs: 1000 });
|
|
137
137
|
(0, chai_1.expect)(bad.status).to.equal(400);
|
|
@@ -166,7 +166,7 @@ describe('Recordings router (integration)', () => {
|
|
|
166
166
|
startedAt: new Date(),
|
|
167
167
|
compositeEnabled: false,
|
|
168
168
|
});
|
|
169
|
-
const r = yield (0,
|
|
169
|
+
const r = yield (0, loopbackRequest_1.default)(makeBearerApp())
|
|
170
170
|
.post('/xenon/api/recordings')
|
|
171
171
|
.send({ udids: ['U1'] });
|
|
172
172
|
(0, chai_1.expect)(r.status).to.equal(202);
|
|
@@ -176,7 +176,7 @@ describe('Recordings router (integration)', () => {
|
|
|
176
176
|
groupId: 'g-1',
|
|
177
177
|
recordings: [{ id: 'r-2', udid: 'U2', status: 'RECORDING' }],
|
|
178
178
|
});
|
|
179
|
-
const r = yield (0,
|
|
179
|
+
const r = yield (0, loopbackRequest_1.default)(makeBearerApp())
|
|
180
180
|
.post('/xenon/api/recordings/g-1/add-device')
|
|
181
181
|
.send({ udid: 'U2' });
|
|
182
182
|
(0, chai_1.expect)(r.status).to.equal(201);
|
|
@@ -215,7 +215,7 @@ describe('Recordings router (integration)', () => {
|
|
|
215
215
|
startedAt: new Date(),
|
|
216
216
|
compositeEnabled: false,
|
|
217
217
|
});
|
|
218
|
-
const r = yield (0,
|
|
218
|
+
const r = yield (0, loopbackRequest_1.default)(makeHeaderPairApp())
|
|
219
219
|
.post('/xenon/api/recordings')
|
|
220
220
|
.send({ udids: ['U1'] });
|
|
221
221
|
(0, chai_1.expect)(r.status).to.equal(202);
|
|
@@ -227,7 +227,7 @@ describe('Recordings router (integration)', () => {
|
|
|
227
227
|
groupId: 'g-1',
|
|
228
228
|
recordings: [{ id: 'r-2', udid: 'U2', status: 'RECORDING' }],
|
|
229
229
|
});
|
|
230
|
-
const r = yield (0,
|
|
230
|
+
const r = yield (0, loopbackRequest_1.default)(makeHeaderPairApp())
|
|
231
231
|
.post('/xenon/api/recordings/g-1/add-device')
|
|
232
232
|
.send({ udid: 'U2' });
|
|
233
233
|
(0, chai_1.expect)(r.status).to.equal(201);
|
|
@@ -239,7 +239,7 @@ describe('Recordings router (integration)', () => {
|
|
|
239
239
|
sinon_1.default
|
|
240
240
|
.stub(typedi_1.Container.get(RecordingOrchestrator_1.RecordingOrchestrator), 'addAnnotation')
|
|
241
241
|
.resolves({ id: 'an-1' });
|
|
242
|
-
const ok = yield (0,
|
|
242
|
+
const ok = yield (0, loopbackRequest_1.default)(makeApp())
|
|
243
243
|
.post('/xenon/api/recordings/g-1/annotation')
|
|
244
244
|
.send({
|
|
245
245
|
recordingId: 'r-1',
|
|
@@ -249,7 +249,7 @@ describe('Recordings router (integration)', () => {
|
|
|
249
249
|
color: '#ff0000',
|
|
250
250
|
});
|
|
251
251
|
(0, chai_1.expect)(ok.status).to.equal(201);
|
|
252
|
-
const bad = yield (0,
|
|
252
|
+
const bad = yield (0, loopbackRequest_1.default)(makeApp())
|
|
253
253
|
.post('/xenon/api/recordings/g-1/annotation')
|
|
254
254
|
.send({ shape: 'RECT' });
|
|
255
255
|
(0, chai_1.expect)(bad.status).to.equal(400);
|
|
@@ -15,20 +15,20 @@ 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 auth_1 = require("../../src/app/routers/auth");
|
|
20
20
|
const typedi_1 = require("typedi");
|
|
21
21
|
const UserService_1 = require("../../src/services/UserService");
|
|
22
22
|
const UserSessionService_1 = require("../../src/services/UserSessionService");
|
|
23
23
|
const PasswordResetService_1 = require("../../src/services/PasswordResetService");
|
|
24
24
|
const prisma_1 = require("../../src/prisma");
|
|
25
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
25
26
|
describe('reset-password revokes all sessions', function () {
|
|
26
27
|
this.timeout(30000);
|
|
28
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
27
29
|
const email = 'reset-revokes-it@xenon.local';
|
|
28
30
|
let user;
|
|
29
31
|
before(() => __awaiter(this, void 0, void 0, function* () {
|
|
30
|
-
yield prisma_1.prisma.userSession.deleteMany({ where: { user: { email } } });
|
|
31
|
-
yield prisma_1.prisma.user.deleteMany({ where: { email } });
|
|
32
32
|
user = yield typedi_1.Container.get(UserService_1.UserService).createUser({
|
|
33
33
|
email,
|
|
34
34
|
name: 'Reset Revokes IT',
|
|
@@ -36,11 +36,6 @@ describe('reset-password revokes all sessions', function () {
|
|
|
36
36
|
role: 'MEMBER',
|
|
37
37
|
});
|
|
38
38
|
}));
|
|
39
|
-
after(() => __awaiter(this, void 0, void 0, function* () {
|
|
40
|
-
yield prisma_1.prisma.passwordResetToken.deleteMany({ where: { userId: user.id } });
|
|
41
|
-
yield prisma_1.prisma.userSession.deleteMany({ where: { userId: user.id } });
|
|
42
|
-
yield prisma_1.prisma.user.delete({ where: { id: user.id } });
|
|
43
|
-
}));
|
|
44
39
|
it('deletes every UserSession for the resetting user', () => __awaiter(this, void 0, void 0, function* () {
|
|
45
40
|
const sessions = [
|
|
46
41
|
yield typedi_1.Container.get(UserSessionService_1.UserSessionService).create(user.id),
|
|
@@ -51,7 +46,7 @@ describe('reset-password revokes all sessions', function () {
|
|
|
51
46
|
const app = (0, express_1.default)();
|
|
52
47
|
app.use(express_1.default.json());
|
|
53
48
|
app.use('/auth', (0, auth_1.authPublicRouter)());
|
|
54
|
-
const r = yield (0,
|
|
49
|
+
const r = yield (0, loopbackRequest_1.default)(app)
|
|
55
50
|
.post('/auth/reset-password')
|
|
56
51
|
.send({ token: raw, newPassword: 'BrandNewPw1' });
|
|
57
52
|
(0, chai_1.expect)(r.status).to.equal(204);
|
|
@@ -15,13 +15,13 @@ 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 authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
20
20
|
const apikeys_1 = require("../../src/app/routers/apikeys");
|
|
21
21
|
const teams_1 = require("../../src/app/routers/teams");
|
|
22
22
|
const processes_1 = require("../../src/app/routers/processes");
|
|
23
23
|
const users_1 = require("../../src/app/routers/users");
|
|
24
|
-
const
|
|
24
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
25
25
|
const seedUser_1 = require("../helpers/seedUser");
|
|
26
26
|
// scopesForRole now grants ADMIN the full 'admin,devices,sessions,read' set on
|
|
27
27
|
// cookie sessions (the user themselves logged in via /login). Token-default
|
|
@@ -84,6 +84,7 @@ function buildApp() {
|
|
|
84
84
|
}
|
|
85
85
|
describe('role matrix (integration)', function () {
|
|
86
86
|
this.timeout(60000);
|
|
87
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
87
88
|
let users;
|
|
88
89
|
before(() => __awaiter(this, void 0, void 0, function* () {
|
|
89
90
|
users = {
|
|
@@ -92,18 +93,12 @@ describe('role matrix (integration)', function () {
|
|
|
92
93
|
MEMBER: yield (0, seedUser_1.seedUser)('MEMBER'),
|
|
93
94
|
};
|
|
94
95
|
}));
|
|
95
|
-
after(() => __awaiter(this, void 0, void 0, function* () {
|
|
96
|
-
yield users.SUPER_ADMIN.cleanup();
|
|
97
|
-
yield users.ADMIN.cleanup();
|
|
98
|
-
yield users.MEMBER.cleanup();
|
|
99
|
-
yield prisma_1.prisma.user.deleteMany({ where: { email: { contains: 'rmcase-' } } });
|
|
100
|
-
}));
|
|
101
96
|
CASES.forEach((c) => {
|
|
102
97
|
['SUPER_ADMIN', 'ADMIN', 'MEMBER'].forEach((role) => {
|
|
103
98
|
it(`${role} ${c.method} ${c.path} → expect ${c.expect[role]}`, () => __awaiter(this, void 0, void 0, function* () {
|
|
104
99
|
const app = buildApp();
|
|
105
100
|
const expected = c.expect[role];
|
|
106
|
-
const reqBuilder = (0,
|
|
101
|
+
const reqBuilder = (0, loopbackRequest_1.default)(app)[c.method.toLowerCase()](c.path).set('Cookie', users[role].cookie);
|
|
107
102
|
const body = typeof c.body === 'function' ? c.body() : c.body;
|
|
108
103
|
const r = body ? yield reqBuilder.send(body) : yield reqBuilder;
|
|
109
104
|
if (expected >= 200 && expected < 300) {
|
|
@@ -15,7 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
require("reflect-metadata");
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const express_1 = __importDefault(require("express"));
|
|
18
|
-
const
|
|
18
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
19
19
|
const sinon_1 = __importDefault(require("sinon"));
|
|
20
20
|
// Static imports (recordings-router.spec idiom): the previous extensionless
|
|
21
21
|
// dynamic `await import(...)` fails to resolve when Node's native ESM loader
|
|
@@ -53,7 +53,7 @@ describe('POST /xenon/api/sdk/leases', () => {
|
|
|
53
53
|
allocatedPorts: { systemPort: 9001 },
|
|
54
54
|
appiumCapabilities: { 'appium:udid': 'u1', 'xe:options': { leaseId: 'lse_1' } },
|
|
55
55
|
});
|
|
56
|
-
const res = yield (0,
|
|
56
|
+
const res = yield (0, loopbackRequest_1.default)(app)
|
|
57
57
|
.post('/xenon/api/sdk/leases')
|
|
58
58
|
.send({ filters: { platform: 'android' }, durationMs: 60000, heartbeatSeconds: 30 });
|
|
59
59
|
(0, chai_1.expect)(res.status).to.equal(201);
|
|
@@ -62,7 +62,7 @@ describe('POST /xenon/api/sdk/leases', () => {
|
|
|
62
62
|
}));
|
|
63
63
|
it('records the api-key principal as actor/team on create', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
64
64
|
leaseSvc.create.resolves({ leaseId: 'lse_1', leaseToken: 't'.repeat(64) });
|
|
65
|
-
yield (0,
|
|
65
|
+
yield (0, loopbackRequest_1.default)(app)
|
|
66
66
|
.post('/xenon/api/sdk/leases')
|
|
67
67
|
.send({ filters: { platform: 'android' } });
|
|
68
68
|
(0, chai_1.expect)(leaseSvc.create.calledOnce).to.equal(true);
|
|
@@ -91,7 +91,7 @@ describe('POST /xenon/api/sdk/leases', () => {
|
|
|
91
91
|
});
|
|
92
92
|
bearerApp.use('/xenon/api/sdk/leases', (0, sdk_leases_1.makeRouter)({ leaseService: leaseSvc }));
|
|
93
93
|
leaseSvc.create.resolves({ leaseId: 'lse_2', leaseToken: 't'.repeat(64) });
|
|
94
|
-
const res = yield (0,
|
|
94
|
+
const res = yield (0, loopbackRequest_1.default)(bearerApp)
|
|
95
95
|
.post('/xenon/api/sdk/leases')
|
|
96
96
|
.send({ filters: { platform: 'android' } });
|
|
97
97
|
(0, chai_1.expect)(res.status).to.equal(201);
|
|
@@ -103,7 +103,7 @@ describe('POST /xenon/api/sdk/leases', () => {
|
|
|
103
103
|
}));
|
|
104
104
|
it('returns 404 when no device matches', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
105
105
|
leaseSvc.create.rejects(new LeaseService_1.NoMatchingDevice('no device'));
|
|
106
|
-
const res = yield (0,
|
|
106
|
+
const res = yield (0, loopbackRequest_1.default)(app)
|
|
107
107
|
.post('/xenon/api/sdk/leases')
|
|
108
108
|
.send({ filters: { platform: 'android', udid: 'absent' }, durationMs: 60000 });
|
|
109
109
|
(0, chai_1.expect)(res.status).to.equal(404);
|
|
@@ -111,23 +111,23 @@ describe('POST /xenon/api/sdk/leases', () => {
|
|
|
111
111
|
}));
|
|
112
112
|
it('heartbeat: 200 with valid token; 403 without', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
113
113
|
leaseSvc.heartbeat.resolves({ heartbeatedAt: Date.now(), expiresAt: Date.now() + 60000 });
|
|
114
|
-
const res1 = yield (0,
|
|
114
|
+
const res1 = yield (0, loopbackRequest_1.default)(app)
|
|
115
115
|
.post('/xenon/api/sdk/leases/lse_1/heartbeat')
|
|
116
116
|
.set('x-xenon-lease-token', 'cleartext');
|
|
117
117
|
(0, chai_1.expect)(res1.status).to.equal(200);
|
|
118
|
-
const res2 = yield (0,
|
|
118
|
+
const res2 = yield (0, loopbackRequest_1.default)(app).post('/xenon/api/sdk/leases/lse_1/heartbeat');
|
|
119
119
|
(0, chai_1.expect)(res2.status).to.equal(403);
|
|
120
120
|
}));
|
|
121
121
|
it('heartbeat: 403 on token mismatch from service', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
122
122
|
leaseSvc.heartbeat.rejects(new LeaseService_1.LeaseTokenMismatch());
|
|
123
|
-
const res = yield (0,
|
|
123
|
+
const res = yield (0, loopbackRequest_1.default)(app)
|
|
124
124
|
.post('/xenon/api/sdk/leases/lse_1/heartbeat')
|
|
125
125
|
.set('x-xenon-lease-token', 'wrong');
|
|
126
126
|
(0, chai_1.expect)(res.status).to.equal(403);
|
|
127
127
|
}));
|
|
128
128
|
it('heartbeat: 410 Gone on expired lease', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
129
129
|
leaseSvc.heartbeat.rejects(new LeaseService_1.LeaseGone('expired'));
|
|
130
|
-
const res = yield (0,
|
|
130
|
+
const res = yield (0, loopbackRequest_1.default)(app)
|
|
131
131
|
.post('/xenon/api/sdk/leases/lse_1/heartbeat')
|
|
132
132
|
.set('x-xenon-lease-token', 'whatever');
|
|
133
133
|
(0, chai_1.expect)(res.status).to.equal(410);
|
|
@@ -48,7 +48,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
48
48
|
require("reflect-metadata");
|
|
49
49
|
const chai_1 = require("chai");
|
|
50
50
|
const express_1 = __importStar(require("express"));
|
|
51
|
-
const
|
|
51
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
52
52
|
describe('GET /xenon/api/sdk/version', () => {
|
|
53
53
|
it('returns pluginVersion + supports array', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
54
|
const register = (yield Promise.resolve().then(() => __importStar(require('../../src/app/routers/sdk-version')))).default;
|
|
@@ -61,7 +61,7 @@ describe('GET /xenon/api/sdk/version', () => {
|
|
|
61
61
|
const apiRouter = (0, express_1.Router)();
|
|
62
62
|
register(apiRouter);
|
|
63
63
|
app.use('/xenon/api', apiRouter);
|
|
64
|
-
const res = yield (0,
|
|
64
|
+
const res = yield (0, loopbackRequest_1.default)(app).get('/xenon/api/sdk/version');
|
|
65
65
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
66
66
|
(0, chai_1.expect)(res.body.pluginVersion).to.be.a('string');
|
|
67
67
|
(0, chai_1.expect)(res.body.supports).to.include.members(['leases', 'ports', 'heartbeat']);
|
|
@@ -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 dashboard_1 = require("../../src/app/routers/dashboard");
|
|
21
21
|
const roleGuard_1 = require("../../src/middleware/roleGuard");
|
|
22
22
|
const prisma_1 = require("../../src/prisma");
|
|
@@ -209,23 +209,23 @@ describe('GET /healing/selector-health — MEMBER guard (router-level)', () => {
|
|
|
209
209
|
});
|
|
210
210
|
afterEach(() => sinon_1.default.restore());
|
|
211
211
|
it('401s when unauthenticated', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
212
|
-
const r = yield (0,
|
|
212
|
+
const r = yield (0, loopbackRequest_1.default)(makeApp()).get('/xenon/api/healing/selector-health');
|
|
213
213
|
(0, chai_1.expect)(r.status).to.equal(401);
|
|
214
214
|
}));
|
|
215
215
|
it('403s for a role below MEMBER', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
216
|
-
const r = yield (0,
|
|
216
|
+
const r = yield (0, loopbackRequest_1.default)(makeApp())
|
|
217
217
|
.get('/xenon/api/healing/selector-health')
|
|
218
218
|
.set('x-test-role', 'BOGUS');
|
|
219
219
|
(0, chai_1.expect)(r.status).to.equal(403);
|
|
220
220
|
}));
|
|
221
221
|
it('200s for MEMBER', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
222
|
-
const r = yield (0,
|
|
222
|
+
const r = yield (0, loopbackRequest_1.default)(makeApp())
|
|
223
223
|
.get('/xenon/api/healing/selector-health')
|
|
224
224
|
.set('x-test-role', 'MEMBER');
|
|
225
225
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
226
226
|
}));
|
|
227
227
|
it('200s for ADMIN', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
228
|
-
const r = yield (0,
|
|
228
|
+
const r = yield (0, loopbackRequest_1.default)(makeApp())
|
|
229
229
|
.get('/xenon/api/healing/selector-health')
|
|
230
230
|
.set('x-test-role', 'ADMIN');
|
|
231
231
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
@@ -105,7 +105,7 @@ describe('Selector state full flow (integration)', function () {
|
|
|
105
105
|
stdio: 'pipe',
|
|
106
106
|
});
|
|
107
107
|
prisma = new client_1.PrismaClient({ datasources: { db: { url } } });
|
|
108
|
-
socketStub = {
|
|
108
|
+
socketStub = { emitToDashboardForSelector: sinon_1.default.stub().resolves() };
|
|
109
109
|
stateService = new SelectorStateService_1.SelectorStateService(prisma, socketStub);
|
|
110
110
|
verifier = new SelectorVerificationJob_1.SelectorVerificationJob(prisma, socketStub);
|
|
111
111
|
}));
|
|
@@ -116,7 +116,7 @@ describe('Selector state full flow (integration)', function () {
|
|
|
116
116
|
fs.unlinkSync(dbPath);
|
|
117
117
|
}));
|
|
118
118
|
beforeEach(() => __awaiter(this, void 0, void 0, function* () {
|
|
119
|
-
socketStub.
|
|
119
|
+
socketStub.emitToDashboardForSelector.resetHistory();
|
|
120
120
|
// SessionLog → Session → Build — delete children before parents.
|
|
121
121
|
yield prisma.sessionLog.deleteMany({});
|
|
122
122
|
yield prisma.session.deleteMany({});
|
|
@@ -156,7 +156,7 @@ describe('Selector state full flow (integration)', function () {
|
|
|
156
156
|
});
|
|
157
157
|
(0, chai_1.expect)(fixed.status).to.equal('pending');
|
|
158
158
|
(0, chai_1.expect)(fixed.fixed_at).to.be.instanceOf(Date);
|
|
159
|
-
(0, chai_1.expect)(socketStub.
|
|
159
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.lastCall.args[0]).to.equal(SocketEvents_1.SocketEvents.SELECTOR_FIXED);
|
|
160
160
|
// Three distinct CI builds, each with one un-healed findElement on the tuple.
|
|
161
161
|
for (const buildId of ['b-1', 'b-2', 'b-3']) {
|
|
162
162
|
const sid = `s-${buildId}`;
|
|
@@ -190,7 +190,7 @@ describe('Selector state full flow (integration)', function () {
|
|
|
190
190
|
(0, chai_1.expect)(final.status).to.equal('resolved');
|
|
191
191
|
(0, chai_1.expect)(final.clean_builds_count).to.equal(3);
|
|
192
192
|
(0, chai_1.expect)(final.resolved_at).to.be.instanceOf(Date);
|
|
193
|
-
const resolvedEmits = socketStub.
|
|
193
|
+
const resolvedEmits = socketStub.emitToDashboardForSelector
|
|
194
194
|
.getCalls()
|
|
195
195
|
.filter((c) => c.args[0] === SocketEvents_1.SocketEvents.SELECTOR_RESOLVED);
|
|
196
196
|
(0, chai_1.expect)(resolvedEmits.length).to.equal(1);
|
|
@@ -226,6 +226,6 @@ describe('Selector state full flow (integration)', function () {
|
|
|
226
226
|
(0, chai_1.expect)(after.regression_count).to.equal(1);
|
|
227
227
|
(0, chai_1.expect)(after.resolved_at).to.be.null;
|
|
228
228
|
(0, chai_1.expect)(after.fixed_at).to.be.null;
|
|
229
|
-
(0, chai_1.expect)(socketStub.
|
|
229
|
+
(0, chai_1.expect)(socketStub.emitToDashboardForSelector.lastCall.args[0]).to.equal(SocketEvents_1.SocketEvents.SELECTOR_REGRESSED);
|
|
230
230
|
}));
|
|
231
231
|
});
|
|
@@ -8,28 +8,89 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
11
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
15
|
require("reflect-metadata");
|
|
13
16
|
const chai_1 = require("chai");
|
|
17
|
+
const child_process_1 = require("child_process");
|
|
18
|
+
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const os_1 = __importDefault(require("os"));
|
|
20
|
+
const path_1 = __importDefault(require("path"));
|
|
14
21
|
const typedi_1 = require("typedi");
|
|
22
|
+
const client_1 = require("../../src/generated/client");
|
|
15
23
|
const UserService_1 = require("../../src/services/UserService");
|
|
16
24
|
const TeamService_1 = require("../../src/services/TeamService");
|
|
17
25
|
const prisma_1 = require("../../src/prisma");
|
|
26
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
27
|
+
const ROOT = path_1.default.resolve(__dirname, '../..');
|
|
28
|
+
const MIGRATIONS = path_1.default.join(ROOT, 'prisma', 'migrations');
|
|
29
|
+
/** The migration that backfills TeamMember from ApiKey.teamId. */
|
|
30
|
+
const BACKFILL = '20260430001747_phase_3_team_members';
|
|
31
|
+
/**
|
|
32
|
+
* `prisma migrate deploy` into `url` of the repository's migrations that
|
|
33
|
+
* `include` names, from a copy of the schema in `dir`. Run again with more
|
|
34
|
+
* of them, it applies only the ones not yet applied.
|
|
35
|
+
*/
|
|
36
|
+
function deploy(dir, url, include) {
|
|
37
|
+
const target = path_1.default.join(dir, 'migrations');
|
|
38
|
+
fs_1.default.mkdirSync(target, { recursive: true });
|
|
39
|
+
fs_1.default.copyFileSync(path_1.default.join(MIGRATIONS, 'migration_lock.toml'), path_1.default.join(target, 'migration_lock.toml'));
|
|
40
|
+
for (const name of fs_1.default.readdirSync(MIGRATIONS)) {
|
|
41
|
+
const sql = path_1.default.join(MIGRATIONS, name, 'migration.sql');
|
|
42
|
+
if (!include(name) || !fs_1.default.existsSync(sql))
|
|
43
|
+
continue;
|
|
44
|
+
fs_1.default.mkdirSync(path_1.default.join(target, name), { recursive: true });
|
|
45
|
+
fs_1.default.copyFileSync(sql, path_1.default.join(target, name, 'migration.sql'));
|
|
46
|
+
}
|
|
47
|
+
fs_1.default.copyFileSync(path_1.default.join(ROOT, 'prisma', 'schema.prisma'), path_1.default.join(dir, 'schema.prisma'));
|
|
48
|
+
(0, child_process_1.execSync)(`npx prisma migrate deploy --schema "${path_1.default.join(dir, 'schema.prisma')}"`, {
|
|
49
|
+
cwd: ROOT,
|
|
50
|
+
env: Object.assign(Object.assign({}, process.env), { DATABASE_URL: url }),
|
|
51
|
+
stdio: 'pipe',
|
|
52
|
+
});
|
|
53
|
+
}
|
|
18
54
|
describe('team-membership migration backfill (integration)', function () {
|
|
19
55
|
this.timeout(60000);
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
56
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
57
|
+
// Through 2.14 this read the developer's own database, and returned early
|
|
58
|
+
// unless an old Legacy Admin happened to be in it. It runs the migration
|
|
59
|
+
// itself now, on a database migrated up to it and seeded as such a lab was.
|
|
60
|
+
it('backfills TeamMember from team keys, and gives Legacy Admin no membership', () => __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-team-backfill-'));
|
|
62
|
+
const url = `file:${path_1.default.join(dir, 'xenon.db')}`;
|
|
63
|
+
try {
|
|
64
|
+
deploy(dir, url, (name) => name < BACKFILL);
|
|
65
|
+
const before = new client_1.PrismaClient({ datasources: { db: { url } } });
|
|
66
|
+
try {
|
|
67
|
+
yield before.$executeRawUnsafe('INSERT INTO "Team" ("id", "name") VALUES (?, ?)', 'team-1', 'Team 1');
|
|
68
|
+
for (const [id, email] of [
|
|
69
|
+
['legacy', 'legacy-admin@xenon.local'],
|
|
70
|
+
['alice', 'alice@xenon.local'],
|
|
71
|
+
]) {
|
|
72
|
+
yield before.$executeRawUnsafe(`INSERT INTO "User" ("id", "email", "name", "passwordHash", "accessKey", "updatedAt")
|
|
73
|
+
VALUES (?, ?, ?, 'x', ?, CURRENT_TIMESTAMP)`, id, email, id, `ak-${id}`);
|
|
74
|
+
yield before.$executeRawUnsafe(`INSERT INTO "ApiKey" ("id", "name", "keyHash", "scopes", "teamId", "userId")
|
|
75
|
+
VALUES (?, ?, ?, 'devices', 'team-1', ?)`, `key-${id}`, id, `hash-${id}`, id);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
yield before.$disconnect();
|
|
80
|
+
}
|
|
81
|
+
deploy(dir, url, () => true);
|
|
82
|
+
const after = new client_1.PrismaClient({ datasources: { db: { url } } });
|
|
83
|
+
try {
|
|
84
|
+
const members = yield after.teamMember.findMany({ select: { teamId: true, userId: true } });
|
|
85
|
+
(0, chai_1.expect)(members).to.deep.equal([{ teamId: 'team-1', userId: 'alice' }]);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
yield after.$disconnect();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
30
93
|
}
|
|
31
|
-
const polluted = yield prisma_1.prisma.teamMember.count({ where: { userId: legacy.id } });
|
|
32
|
-
(0, chai_1.expect)(polluted).to.equal(0);
|
|
33
94
|
}));
|
|
34
95
|
it('addMember / removeMember round-trip an arbitrary user', () => __awaiter(this, void 0, void 0, function* () {
|
|
35
96
|
const u = yield typedi_1.Container.get(UserService_1.UserService).createUser({
|