@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
|
@@ -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 fs_1 = __importDefault(require("fs"));
|
|
20
20
|
const os_1 = __importDefault(require("os"));
|
|
21
21
|
const path_1 = __importDefault(require("path"));
|
|
@@ -29,17 +29,17 @@ const app_service_1 = require("../../src/dashboard/services/app-service");
|
|
|
29
29
|
const prisma_1 = require("../../src/prisma");
|
|
30
30
|
const seedUser_1 = require("../helpers/seedUser");
|
|
31
31
|
const container_registration_1 = require("../helpers/container-registration");
|
|
32
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
32
33
|
/**
|
|
33
34
|
* Uploaded apps follow the device team rule, run as real queries: the list
|
|
34
35
|
* builds a Prisma filter (`OR: [{ teamId: null }, { teamId: { in } }]`, with an
|
|
35
36
|
* empty `in` for a member in no team) that a stub would only echo back.
|
|
36
37
|
*
|
|
37
|
-
*
|
|
38
|
-
* DATABASE_URL=file:/tmp/x.db npx prisma migrate deploy
|
|
39
|
-
* DATABASE_URL=file:/tmp/x.db npx mocha test/integration/team-visibility-apps.spec.ts
|
|
38
|
+
* On a scratch database of its own, so `npx mocha <file>` is enough.
|
|
40
39
|
*/
|
|
41
40
|
describe('team visibility on uploaded apps (integration)', function () {
|
|
42
41
|
this.timeout(60000);
|
|
42
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
43
43
|
let sa;
|
|
44
44
|
let alice;
|
|
45
45
|
let loner;
|
|
@@ -87,17 +87,10 @@ describe('team visibility on uploaded apps (integration)', function () {
|
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
89
|
}));
|
|
90
|
-
after(() =>
|
|
91
|
-
yield prisma_1.prisma.app.deleteMany({ where: { id: { in: Object.values(A) } } });
|
|
92
|
-
yield prisma_1.prisma.teamMember.deleteMany({ where: { teamId: { in: [teamA.id, teamB.id] } } });
|
|
93
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamA.id } }).catch(() => undefined);
|
|
94
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamB.id } }).catch(() => undefined);
|
|
95
|
-
yield sa.cleanup();
|
|
96
|
-
yield alice.cleanup();
|
|
97
|
-
yield loner.cleanup();
|
|
90
|
+
after(() => {
|
|
98
91
|
restore();
|
|
99
92
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
100
|
-
})
|
|
93
|
+
});
|
|
101
94
|
function app() {
|
|
102
95
|
const a = (0, express_1.default)();
|
|
103
96
|
a.use(express_1.default.json());
|
|
@@ -106,13 +99,13 @@ describe('team visibility on uploaded apps (integration)', function () {
|
|
|
106
99
|
return a;
|
|
107
100
|
}
|
|
108
101
|
const ids = (cookie) => __awaiter(this, void 0, void 0, function* () {
|
|
109
|
-
return (yield (0,
|
|
102
|
+
return (yield (0, loopbackRequest_1.default)(app()).get('/apps').set('Cookie', cookie)).body
|
|
110
103
|
.map((r) => r.id)
|
|
111
104
|
.filter((id) => Object.values(A).includes(id));
|
|
112
105
|
});
|
|
113
106
|
const UNKNOWN = { error: 'App not found' };
|
|
114
107
|
it("lists a member's shared and own-team apps, with the team's name", () => __awaiter(this, void 0, void 0, function* () {
|
|
115
|
-
const res = yield (0,
|
|
108
|
+
const res = yield (0, loopbackRequest_1.default)(app()).get('/apps').set('Cookie', alice.cookie);
|
|
116
109
|
(0, chai_1.expect)(res.status).to.equal(200);
|
|
117
110
|
const mine = res.body.filter((r) => Object.values(A).includes(r.id));
|
|
118
111
|
(0, chai_1.expect)(mine.map((r) => r.id)).to.have.members([A.shared, A.a]);
|
|
@@ -127,20 +120,20 @@ describe('team visibility on uploaded apps (integration)', function () {
|
|
|
127
120
|
(0, chai_1.expect)(yield ids(sa.cookie)).to.have.members(Object.values(A));
|
|
128
121
|
}));
|
|
129
122
|
it("answers another team's app exactly as an unknown id", () => __awaiter(this, void 0, void 0, function* () {
|
|
130
|
-
const hidden = yield (0,
|
|
131
|
-
const unknown = yield (0,
|
|
123
|
+
const hidden = yield (0, loopbackRequest_1.default)(app()).get(`/apps/${A.b}/download`).set('Cookie', alice.cookie);
|
|
124
|
+
const unknown = yield (0, loopbackRequest_1.default)(app())
|
|
132
125
|
.get(`/apps/nope-${stamp}/download`)
|
|
133
126
|
.set('Cookie', alice.cookie);
|
|
134
127
|
(0, chai_1.expect)(hidden.status).to.equal(404);
|
|
135
128
|
(0, chai_1.expect)(hidden.body).to.deep.equal(UNKNOWN);
|
|
136
129
|
(0, chai_1.expect)(unknown.status).to.equal(404);
|
|
137
130
|
(0, chai_1.expect)(unknown.body).to.deep.equal(UNKNOWN);
|
|
138
|
-
const own = yield (0,
|
|
131
|
+
const own = yield (0, loopbackRequest_1.default)(app()).get(`/apps/${A.a}/download`).set('Cookie', alice.cookie);
|
|
139
132
|
(0, chai_1.expect)(own.status).to.equal(200);
|
|
140
133
|
}));
|
|
141
134
|
it('a download ticket serves its app once, with no login', () => __awaiter(this, void 0, void 0, function* () {
|
|
142
135
|
const t = yield typedi_1.Container.get(AppDownloadTicketService_1.AppDownloadTicketService).mint(A.b);
|
|
143
|
-
const first = yield (0,
|
|
136
|
+
const first = yield (0, loopbackRequest_1.default)(app())
|
|
144
137
|
.get(`/apps/${A.b}/download`)
|
|
145
138
|
.query({ ticket: t })
|
|
146
139
|
.buffer(true)
|
|
@@ -151,16 +144,16 @@ describe('team visibility on uploaded apps (integration)', function () {
|
|
|
151
144
|
});
|
|
152
145
|
(0, chai_1.expect)(first.status).to.equal(200);
|
|
153
146
|
(0, chai_1.expect)(Buffer.compare(first.body, bytesOf(A.b))).to.equal(0);
|
|
154
|
-
const again = yield (0,
|
|
147
|
+
const again = yield (0, loopbackRequest_1.default)(app()).get(`/apps/${A.b}/download`).query({ ticket: t });
|
|
155
148
|
(0, chai_1.expect)(again.status).to.equal(401);
|
|
156
149
|
}));
|
|
157
150
|
it('moving an app to a team changes who sees it', () => __awaiter(this, void 0, void 0, function* () {
|
|
158
|
-
const put = (teamId) => (0,
|
|
151
|
+
const put = (teamId) => (0, loopbackRequest_1.default)(app()).put(`/apps/${A.b}/team`).set('Cookie', sa.cookie).send({ teamId });
|
|
159
152
|
(0, chai_1.expect)((yield put(teamA.id)).body).to.deep.equal({ ok: true, updated: 1 });
|
|
160
153
|
(0, chai_1.expect)(yield ids(alice.cookie)).to.include(A.b);
|
|
161
154
|
(0, chai_1.expect)((yield put(teamB.id)).status).to.equal(200);
|
|
162
155
|
(0, chai_1.expect)(yield ids(alice.cookie)).to.not.include(A.b);
|
|
163
|
-
const member = yield (0,
|
|
156
|
+
const member = yield (0, loopbackRequest_1.default)(app())
|
|
164
157
|
.put(`/apps/${A.b}/team`)
|
|
165
158
|
.set('Cookie', alice.cookie)
|
|
166
159
|
.send({ teamId: teamA.id });
|
|
@@ -16,7 +16,7 @@ require("reflect-metadata");
|
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
17
|
const express_1 = __importDefault(require("express"));
|
|
18
18
|
const cors_1 = __importDefault(require("cors"));
|
|
19
|
-
const
|
|
19
|
+
const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
|
|
20
20
|
const fs_1 = __importDefault(require("fs"));
|
|
21
21
|
const os_1 = __importDefault(require("os"));
|
|
22
22
|
const path_1 = __importDefault(require("path"));
|
|
@@ -31,6 +31,10 @@ const prisma_1 = require("../../src/prisma");
|
|
|
31
31
|
const seedUser_1 = require("../helpers/seedUser");
|
|
32
32
|
const container_registration_1 = require("../helpers/container-registration");
|
|
33
33
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
34
|
+
const db_1 = require("../../src/data-service/db");
|
|
35
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
36
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
37
|
+
const own_node_id_1 = require("../helpers/own-node-id");
|
|
34
38
|
/**
|
|
35
39
|
* Teams are a device boundary on /control, end to end: the real auth
|
|
36
40
|
* middleware computes req.auth.teamIds from TeamMember rows, and the real
|
|
@@ -88,23 +92,37 @@ const METHODS_BY_ACTION = CONTROL_ROUTES.reduce((acc, { method, action }) => {
|
|
|
88
92
|
}, new Map());
|
|
89
93
|
describe('team boundary on /control (integration)', function () {
|
|
90
94
|
this.timeout(60000);
|
|
95
|
+
// Hermetic, so it runs in `npm run test:all` and on its own alike: users,
|
|
96
|
+
// teams and sessions go to a scratch database, never ~/.cache/xenon/xenon.db,
|
|
97
|
+
// and the phones to the in-memory device store. Nothing here stubs `prisma`
|
|
98
|
+
// itself, so the scratch database stands in for the whole suite.
|
|
99
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
100
|
+
(0, loki_stores_1.useLokiStores)();
|
|
101
|
+
// The fixture phones are this server's own; see DEVICE_HOST.
|
|
102
|
+
(0, own_node_id_1.useOwnNodeId)();
|
|
91
103
|
let sa;
|
|
92
104
|
let alice;
|
|
93
|
-
let teamA;
|
|
94
|
-
let teamB;
|
|
95
105
|
let keyDir;
|
|
106
|
+
let deviceRows;
|
|
96
107
|
const restores = [];
|
|
97
108
|
const stamp = Date.now();
|
|
98
109
|
const SHARED_UDID = `tb-shared-${stamp}`;
|
|
99
110
|
const TEAM_A_UDID = `tb-team-a-${stamp}`;
|
|
100
111
|
const TEAM_B_UDID = `tb-team-b-${stamp}`;
|
|
101
|
-
//
|
|
102
|
-
//
|
|
112
|
+
// Team B's phone on a node. /control sends a node's phone's actions on to
|
|
113
|
+
// the node, or refuses them naming it (nodePhoneControl.ts), so the team
|
|
114
|
+
// guard has to have hidden it before that gate runs.
|
|
115
|
+
const TEAM_B_NODE_UDID = `tb-node-b-${stamp}`;
|
|
116
|
+
// The same length as the hidden udids, so an answer that echoes the path
|
|
117
|
+
// (Express's "Cannot GET …") has the same length for each.
|
|
103
118
|
const UNKNOWN_UDID = `tb-nobody-${stamp}`;
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
119
|
+
// Whose a phone is comes from its row: its nodeId, else its exact host
|
|
120
|
+
// (isOtherServersPhone). A row with neither of this server's is another
|
|
121
|
+
// server's phone, and /control sends its requests on to that host. So the
|
|
122
|
+
// fixtures carry this server's node id, and /control runs them here with
|
|
123
|
+
// the fake manager below. Port 1 has nothing listening, so a request sent
|
|
124
|
+
// on by mistake fails at once instead of reaching a real server here.
|
|
125
|
+
const DEVICE_HOST = 'http://127.0.0.1:1';
|
|
108
126
|
before(() => __awaiter(this, void 0, void 0, function* () {
|
|
109
127
|
keyDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-team-boundary-'));
|
|
110
128
|
const keys = new JwtKeyService_1.JwtKeyService();
|
|
@@ -112,21 +130,28 @@ describe('team boundary on /control (integration)', function () {
|
|
|
112
130
|
restores.push((0, container_registration_1.saveRegistrations)(JwtKeyService_1.JwtKeyService, device_managers_1.XenonManager));
|
|
113
131
|
typedi_1.Container.set(JwtKeyService_1.JwtKeyService, keys);
|
|
114
132
|
typedi_1.Container.set(device_managers_1.XenonManager, { deviceInstances: () => __awaiter(this, void 0, void 0, function* () { return [new IOSDeviceManager()]; }) });
|
|
133
|
+
// The scratch database goes with the suite, and every row seeded here
|
|
134
|
+
// with it.
|
|
115
135
|
sa = yield (0, seedUser_1.seedUser)('SUPER_ADMIN', { name: 'TB SA' });
|
|
116
136
|
alice = yield (0, seedUser_1.seedUser)('MEMBER', { name: 'Alice (team A)' });
|
|
117
|
-
teamA = yield typedi_1.Container.get(TeamService_1.TeamService).create(`tb-team-a-${stamp}`);
|
|
118
|
-
teamB = yield typedi_1.Container.get(TeamService_1.TeamService).create(`tb-team-b-${stamp}`);
|
|
137
|
+
const teamA = yield typedi_1.Container.get(TeamService_1.TeamService).create(`tb-team-a-${stamp}`);
|
|
138
|
+
const teamB = yield typedi_1.Container.get(TeamService_1.TeamService).create(`tb-team-b-${stamp}`);
|
|
119
139
|
yield typedi_1.Container.get(TeamService_1.TeamService).addMember(teamA.id, alice.user.id);
|
|
140
|
+
// The in-memory store is one collection for the whole process. Held here,
|
|
141
|
+
// so `after` removes these rows whatever useLokiStores has put back by then.
|
|
142
|
+
deviceRows = yield db_1.XenonDatabase.DeviceModel;
|
|
120
143
|
const store = device_store_1.DeviceStoreFactory.getStore();
|
|
121
|
-
for (const [udid, teamId] of [
|
|
122
|
-
[SHARED_UDID, null],
|
|
123
|
-
[TEAM_A_UDID, teamA.id],
|
|
124
|
-
[TEAM_B_UDID, teamB.id],
|
|
144
|
+
for (const [udid, teamId, nodeId] of [
|
|
145
|
+
[SHARED_UDID, null, own_node_id_1.OWN_NODE_ID],
|
|
146
|
+
[TEAM_A_UDID, teamA.id, own_node_id_1.OWN_NODE_ID],
|
|
147
|
+
[TEAM_B_UDID, teamB.id, own_node_id_1.OWN_NODE_ID],
|
|
148
|
+
[TEAM_B_NODE_UDID, teamB.id, 'tb-another-node'],
|
|
125
149
|
]) {
|
|
126
150
|
const row = yield prisma_1.prisma.device.create({
|
|
127
151
|
data: {
|
|
128
152
|
udid,
|
|
129
153
|
host: DEVICE_HOST,
|
|
154
|
+
nodeId,
|
|
130
155
|
name: udid,
|
|
131
156
|
platform: 'ios',
|
|
132
157
|
// Known screen size, so stream/ticket's after-response size fetch
|
|
@@ -139,19 +164,14 @@ describe('team boundary on /control (integration)', function () {
|
|
|
139
164
|
yield store.addDevices([row]);
|
|
140
165
|
}
|
|
141
166
|
}));
|
|
142
|
-
after(() =>
|
|
143
|
-
|
|
144
|
-
|
|
167
|
+
after(() => {
|
|
168
|
+
deviceRows === null || deviceRows === void 0 ? void 0 : deviceRows.findAndRemove({
|
|
169
|
+
udid: { $in: [SHARED_UDID, TEAM_A_UDID, TEAM_B_UDID, TEAM_B_NODE_UDID] },
|
|
145
170
|
});
|
|
146
|
-
yield prisma_1.prisma.teamMember.deleteMany({ where: { teamId: { in: [teamA.id, teamB.id] } } });
|
|
147
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamA.id } }).catch(() => undefined);
|
|
148
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamB.id } }).catch(() => undefined);
|
|
149
|
-
yield sa.cleanup();
|
|
150
|
-
yield alice.cleanup();
|
|
151
171
|
for (const restore of restores.reverse())
|
|
152
172
|
restore();
|
|
153
173
|
fs_1.default.rmSync(keyDir, { recursive: true, force: true });
|
|
154
|
-
})
|
|
174
|
+
});
|
|
155
175
|
/**
|
|
156
176
|
* The /xenon/api stack as src/app/index.ts builds it, as far as /control
|
|
157
177
|
* goes: cors, auth, the control router, then `tail`.
|
|
@@ -181,8 +201,8 @@ describe('team boundary on /control (integration)', function () {
|
|
|
181
201
|
}
|
|
182
202
|
function send(who, method, udid, action, tail = 'api') {
|
|
183
203
|
const url = `/xenon/api/control/${encodeURIComponent(udid)}/${action}`;
|
|
184
|
-
const req = (0,
|
|
185
|
-
return req.set('Cookie', who.cookie)
|
|
204
|
+
const req = (0, loopbackRequest_1.default)(buildApp(tail))[method](url);
|
|
205
|
+
return req.set('Cookie', who.cookie);
|
|
186
206
|
}
|
|
187
207
|
/** What a caller can observe of an answer, with the requested udid masked out. */
|
|
188
208
|
function observed(res, udid) {
|
|
@@ -195,12 +215,12 @@ describe('team boundary on /control (integration)', function () {
|
|
|
195
215
|
allow: res.headers['allow'],
|
|
196
216
|
};
|
|
197
217
|
}
|
|
198
|
-
function expectSameAnswer(method, action, tail) {
|
|
218
|
+
function expectSameAnswer(udid, method, action, tail) {
|
|
199
219
|
return __awaiter(this, void 0, void 0, function* () {
|
|
200
220
|
var _a;
|
|
201
|
-
const hidden = yield send(alice, method,
|
|
221
|
+
const hidden = yield send(alice, method, udid, action, tail);
|
|
202
222
|
const unknown = yield send(alice, method, UNKNOWN_UDID, action, tail);
|
|
203
|
-
(0, chai_1.expect)(observed(hidden,
|
|
223
|
+
(0, chai_1.expect)(observed(hidden, udid)).to.deep.equal(observed(unknown, UNKNOWN_UDID));
|
|
204
224
|
// The guard's placeholder never reaches the caller, in a body or a header.
|
|
205
225
|
(0, chai_1.expect)((_a = hidden.text) !== null && _a !== void 0 ? _a : '', 'body').to.not.include(controlDevice_1.HIDDEN_DEVICE_UDID);
|
|
206
226
|
(0, chai_1.expect)(JSON.stringify(hidden.headers), 'headers').to.not.include(controlDevice_1.HIDDEN_DEVICE_UDID);
|
|
@@ -274,51 +294,58 @@ describe('team boundary on /control (integration)', function () {
|
|
|
274
294
|
// has this phone. Hold every request to that: another team's phone has to
|
|
275
295
|
// get the same status, body, content type, length and Allow header as a
|
|
276
296
|
// udid that does not exist, whether a route handles the request or not.
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
'echo-url': 'a later layer that echoes req.url',
|
|
290
|
-
};
|
|
291
|
-
for (const tail of ['api', 'express', 'echo-url']) {
|
|
292
|
-
describe(`where no route handles the request (${TAIL_LABEL[tail]})`, () => {
|
|
293
|
-
for (const [method, action] of [
|
|
294
|
-
['get', 'bogus'],
|
|
295
|
-
['post', 'bogus'],
|
|
296
|
-
['post', 'stream/bogus'],
|
|
297
|
-
['get', ''],
|
|
298
|
-
]) {
|
|
299
|
-
it(`${method.toUpperCase()} ${action || '(no action)'}`, () => __awaiter(this, void 0, void 0, function* () {
|
|
300
|
-
yield expectSameAnswer(method, action, tail);
|
|
297
|
+
// On this server or on a node: a node's phone must not get the gate's
|
|
298
|
+
// forward or its refusal naming the node.
|
|
299
|
+
for (const [label, hiddenUdid] of [
|
|
300
|
+
["another team's phone", TEAM_B_UDID],
|
|
301
|
+
["another team's phone on a node", TEAM_B_NODE_UDID],
|
|
302
|
+
]) {
|
|
303
|
+
describe(`${label} answers exactly like an unknown udid`, () => {
|
|
304
|
+
describe('on every route', () => {
|
|
305
|
+
for (const { method, action } of CONTROL_ROUTES) {
|
|
306
|
+
it(`${method.toUpperCase()} ${action}`, () => __awaiter(this, void 0, void 0, function* () {
|
|
307
|
+
const res = yield expectSameAnswer(hiddenUdid, method, action, 'api');
|
|
308
|
+
(0, chai_1.expect)(res.status, 'status').to.equal(404);
|
|
301
309
|
}));
|
|
302
310
|
}
|
|
311
|
+
});
|
|
312
|
+
const TAIL_LABEL = {
|
|
313
|
+
api: "the API's JSON 404",
|
|
314
|
+
express: "Express's own 404",
|
|
315
|
+
'echo-url': 'a later layer that echoes req.url',
|
|
316
|
+
};
|
|
317
|
+
for (const tail of ['api', 'express', 'echo-url']) {
|
|
318
|
+
describe(`where no route handles the request (${TAIL_LABEL[tail]})`, () => {
|
|
319
|
+
for (const [method, action] of [
|
|
320
|
+
['get', 'bogus'],
|
|
321
|
+
['post', 'bogus'],
|
|
322
|
+
['post', 'stream/bogus'],
|
|
323
|
+
['get', ''],
|
|
324
|
+
]) {
|
|
325
|
+
it(`${method.toUpperCase()} ${action || '(no action)'}`, () => __awaiter(this, void 0, void 0, function* () {
|
|
326
|
+
yield expectSameAnswer(hiddenUdid, method, action, tail);
|
|
327
|
+
}));
|
|
328
|
+
}
|
|
329
|
+
for (const [action, methods] of METHODS_BY_ACTION) {
|
|
330
|
+
const wrong = methods.includes('get') ? 'post' : 'get';
|
|
331
|
+
if (methods.includes(wrong))
|
|
332
|
+
continue;
|
|
333
|
+
it(`${wrong.toUpperCase()} ${action} (the wrong method)`, () => __awaiter(this, void 0, void 0, function* () {
|
|
334
|
+
yield expectSameAnswer(hiddenUdid, wrong, action, tail);
|
|
335
|
+
}));
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
describe('OPTIONS', () => {
|
|
303
340
|
for (const [action, methods] of METHODS_BY_ACTION) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
341
|
+
it(`OPTIONS ${action}`, () => __awaiter(this, void 0, void 0, function* () {
|
|
342
|
+
const res = yield expectSameAnswer(hiddenUdid, 'options', action, 'api');
|
|
343
|
+
(0, chai_1.expect)(res.headers['allow'], 'allow').to.be.a('string');
|
|
344
|
+
for (const m of methods)
|
|
345
|
+
(0, chai_1.expect)(res.headers['allow']).to.include(m.toUpperCase());
|
|
309
346
|
}));
|
|
310
347
|
}
|
|
311
348
|
});
|
|
312
|
-
}
|
|
313
|
-
describe('OPTIONS', () => {
|
|
314
|
-
for (const [action, methods] of METHODS_BY_ACTION) {
|
|
315
|
-
it(`OPTIONS ${action}`, () => __awaiter(this, void 0, void 0, function* () {
|
|
316
|
-
const res = yield expectSameAnswer('options', action, 'api');
|
|
317
|
-
(0, chai_1.expect)(res.headers['allow'], 'allow').to.be.a('string');
|
|
318
|
-
for (const m of methods)
|
|
319
|
-
(0, chai_1.expect)(res.headers['allow']).to.include(m.toUpperCase());
|
|
320
|
-
}));
|
|
321
|
-
}
|
|
322
349
|
});
|
|
323
|
-
}
|
|
350
|
+
}
|
|
324
351
|
});
|
|
@@ -15,15 +15,17 @@ 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 dashboard_1 = __importDefault(require("../../src/app/routers/dashboard"));
|
|
21
21
|
const typedi_1 = require("typedi");
|
|
22
22
|
const TeamService_1 = require("../../src/services/TeamService");
|
|
23
23
|
const prisma_1 = require("../../src/prisma");
|
|
24
24
|
const seedUser_1 = require("../helpers/seedUser");
|
|
25
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
25
26
|
describe('team visibility on /dashboard reads (integration)', function () {
|
|
26
27
|
this.timeout(60000);
|
|
28
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
27
29
|
let sa;
|
|
28
30
|
let aliceMember;
|
|
29
31
|
let teamA;
|
|
@@ -94,24 +96,6 @@ describe('team visibility on /dashboard reads (integration)', function () {
|
|
|
94
96
|
data: Object.assign({ id: `sess-b-${stamp}`, device_udid: TEAM_B_UDID, build_id: buildTeamB.id }, baseSession),
|
|
95
97
|
});
|
|
96
98
|
}));
|
|
97
|
-
after(() => __awaiter(this, void 0, void 0, function* () {
|
|
98
|
-
yield prisma_1.prisma.session.deleteMany({
|
|
99
|
-
where: { id: { in: [sessionShared.id, sessionTeamA.id, sessionTeamB.id] } },
|
|
100
|
-
});
|
|
101
|
-
yield prisma_1.prisma.build.deleteMany({
|
|
102
|
-
where: { id: { in: [buildShared.id, buildTeamA.id, buildTeamB.id] } },
|
|
103
|
-
});
|
|
104
|
-
yield prisma_1.prisma.device.deleteMany({
|
|
105
|
-
where: { udid: { in: [SHARED_UDID, TEAM_A_UDID, TEAM_B_UDID] } },
|
|
106
|
-
});
|
|
107
|
-
yield prisma_1.prisma.teamMember.deleteMany({
|
|
108
|
-
where: { teamId: { in: [teamA.id, teamB.id] } },
|
|
109
|
-
});
|
|
110
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamA.id } }).catch(() => undefined);
|
|
111
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamB.id } }).catch(() => undefined);
|
|
112
|
-
yield sa.cleanup();
|
|
113
|
-
yield aliceMember.cleanup();
|
|
114
|
-
}));
|
|
115
99
|
function buildApp() {
|
|
116
100
|
const app = (0, express_1.default)();
|
|
117
101
|
app.use(express_1.default.json());
|
|
@@ -121,13 +105,13 @@ describe('team visibility on /dashboard reads (integration)', function () {
|
|
|
121
105
|
}
|
|
122
106
|
it('SA sees all three sessions; Alice sees only shared + team-A', () => __awaiter(this, void 0, void 0, function* () {
|
|
123
107
|
const app = buildApp();
|
|
124
|
-
const saList = yield (0,
|
|
108
|
+
const saList = yield (0, loopbackRequest_1.default)(app).get('/session').set('Cookie', sa.cookie);
|
|
125
109
|
(0, chai_1.expect)(saList.status).to.equal(200);
|
|
126
110
|
const saIds = saList.body.map((s) => s.id);
|
|
127
111
|
(0, chai_1.expect)(saIds).to.include(sessionShared.id);
|
|
128
112
|
(0, chai_1.expect)(saIds).to.include(sessionTeamA.id);
|
|
129
113
|
(0, chai_1.expect)(saIds).to.include(sessionTeamB.id);
|
|
130
|
-
const aliceList = yield (0,
|
|
114
|
+
const aliceList = yield (0, loopbackRequest_1.default)(app).get('/session').set('Cookie', aliceMember.cookie);
|
|
131
115
|
(0, chai_1.expect)(aliceList.status).to.equal(200);
|
|
132
116
|
const aliceIds = aliceList.body.map((s) => s.id);
|
|
133
117
|
(0, chai_1.expect)(aliceIds).to.include(sessionShared.id);
|
|
@@ -135,13 +119,13 @@ describe('team visibility on /dashboard reads (integration)', function () {
|
|
|
135
119
|
(0, chai_1.expect)(aliceIds).to.not.include(sessionTeamB.id);
|
|
136
120
|
}));
|
|
137
121
|
it('Alice GET /session/:teamB-id → 404', () => __awaiter(this, void 0, void 0, function* () {
|
|
138
|
-
const r = yield (0,
|
|
122
|
+
const r = yield (0, loopbackRequest_1.default)(buildApp())
|
|
139
123
|
.get(`/session/${sessionTeamB.id}`)
|
|
140
124
|
.set('Cookie', aliceMember.cookie);
|
|
141
125
|
(0, chai_1.expect)(r.status).to.equal(404);
|
|
142
126
|
}));
|
|
143
127
|
it('Alice GET /build → does not include team-B-only build', () => __awaiter(this, void 0, void 0, function* () {
|
|
144
|
-
const r = yield (0,
|
|
128
|
+
const r = yield (0, loopbackRequest_1.default)(buildApp()).get('/build').set('Cookie', aliceMember.cookie);
|
|
145
129
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
146
130
|
const ids = r.body.map((b) => b.id);
|
|
147
131
|
(0, chai_1.expect)(ids).to.include(buildShared.id);
|
|
@@ -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 authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
20
20
|
const grid_1 = __importDefault(require("../../src/app/routers/grid"));
|
|
21
21
|
const typedi_1 = require("typedi");
|
|
@@ -23,8 +23,11 @@ const TeamService_1 = require("../../src/services/TeamService");
|
|
|
23
23
|
const prisma_1 = require("../../src/prisma");
|
|
24
24
|
const seedUser_1 = require("../helpers/seedUser");
|
|
25
25
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
26
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
27
|
+
const prisma_store_1 = require("../../src/data-service/prisma-store");
|
|
26
28
|
describe('team visibility on /grid/devices (integration)', function () {
|
|
27
29
|
this.timeout(60000);
|
|
30
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
28
31
|
let sa;
|
|
29
32
|
let aliceMember;
|
|
30
33
|
let bobUnaffiliated;
|
|
@@ -43,6 +46,9 @@ describe('team visibility on /grid/devices (integration)', function () {
|
|
|
43
46
|
teamB = yield typedi_1.Container.get(TeamService_1.TeamService).create(`p4a-team-b-${stamp}`);
|
|
44
47
|
// Alice on team A only.
|
|
45
48
|
yield typedi_1.Container.get(TeamService_1.TeamService).addMember(teamA.id, aliceMember.user.id);
|
|
49
|
+
// /devices lists the store grid.ts took when it was imported: Prisma under
|
|
50
|
+
// `npx mocha <file>`, Loki under `test:all` (NODE_ENV=test). So each phone
|
|
51
|
+
// is a Device row and is added to that store.
|
|
46
52
|
store = device_store_1.DeviceStoreFactory.getStore();
|
|
47
53
|
// Three test devices. Use minimal field set; the schema may have
|
|
48
54
|
// additional optional fields we don't need to populate for this test.
|
|
@@ -77,18 +83,13 @@ describe('team visibility on /grid/devices (integration)', function () {
|
|
|
77
83
|
});
|
|
78
84
|
yield store.addDevices([teamBDev]);
|
|
79
85
|
}));
|
|
86
|
+
// Loki is one collection for the whole process: take the phones back out.
|
|
87
|
+
// A Prisma store's rows went with the scratch database.
|
|
80
88
|
after(() => __awaiter(this, void 0, void 0, function* () {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
where: { teamId: { in: [teamA.id, teamB.id] } },
|
|
86
|
-
});
|
|
87
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamA.id } }).catch(() => undefined);
|
|
88
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamB.id } }).catch(() => undefined);
|
|
89
|
-
yield sa.cleanup();
|
|
90
|
-
yield aliceMember.cleanup();
|
|
91
|
-
yield bobUnaffiliated.cleanup();
|
|
89
|
+
if (store instanceof prisma_store_1.PrismaDeviceStore)
|
|
90
|
+
return;
|
|
91
|
+
for (const udid of [SHARED_UDID, TEAM_A_UDID, TEAM_B_UDID])
|
|
92
|
+
yield store.removeDevices({ udid });
|
|
92
93
|
}));
|
|
93
94
|
function buildApp() {
|
|
94
95
|
const app = (0, express_1.default)();
|
|
@@ -101,7 +102,7 @@ describe('team visibility on /grid/devices (integration)', function () {
|
|
|
101
102
|
return rows.map((r) => r.udid);
|
|
102
103
|
}
|
|
103
104
|
it('SUPER_ADMIN sees all three test devices', () => __awaiter(this, void 0, void 0, function* () {
|
|
104
|
-
const r = yield (0,
|
|
105
|
+
const r = yield (0, loopbackRequest_1.default)(buildApp()).get('/devices').set('Cookie', sa.cookie);
|
|
105
106
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
106
107
|
const got = udids(r.body);
|
|
107
108
|
(0, chai_1.expect)(got).to.include(SHARED_UDID);
|
|
@@ -109,7 +110,7 @@ describe('team visibility on /grid/devices (integration)', function () {
|
|
|
109
110
|
(0, chai_1.expect)(got).to.include(TEAM_B_UDID);
|
|
110
111
|
}));
|
|
111
112
|
it('Alice (team A member) sees shared + team A; not team B', () => __awaiter(this, void 0, void 0, function* () {
|
|
112
|
-
const r = yield (0,
|
|
113
|
+
const r = yield (0, loopbackRequest_1.default)(buildApp()).get('/devices').set('Cookie', aliceMember.cookie);
|
|
113
114
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
114
115
|
const got = udids(r.body);
|
|
115
116
|
(0, chai_1.expect)(got).to.include(SHARED_UDID);
|
|
@@ -117,7 +118,7 @@ describe('team visibility on /grid/devices (integration)', function () {
|
|
|
117
118
|
(0, chai_1.expect)(got).to.not.include(TEAM_B_UDID);
|
|
118
119
|
}));
|
|
119
120
|
it('Bob (no team) sees only the shared device', () => __awaiter(this, void 0, void 0, function* () {
|
|
120
|
-
const r = yield (0,
|
|
121
|
+
const r = yield (0, loopbackRequest_1.default)(buildApp()).get('/devices').set('Cookie', bobUnaffiliated.cookie);
|
|
121
122
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
122
123
|
const got = udids(r.body);
|
|
123
124
|
(0, chai_1.expect)(got).to.include(SHARED_UDID);
|
|
@@ -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 typedi_1 = require("typedi");
|
|
20
20
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
21
21
|
const sdk_leases_1 = require("../../src/app/routers/sdk-leases");
|
|
@@ -24,6 +24,8 @@ const TeamService_1 = require("../../src/services/TeamService");
|
|
|
24
24
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
25
25
|
const prisma_1 = require("../../src/prisma");
|
|
26
26
|
const seedUser_1 = require("../helpers/seedUser");
|
|
27
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
28
|
+
const loki_stores_1 = require("../helpers/loki-stores");
|
|
27
29
|
/**
|
|
28
30
|
* SDK leases against the production store: the real auth middleware computes
|
|
29
31
|
* the member's teams, and the Prisma store's match (a SQL where clause, not
|
|
@@ -32,6 +34,10 @@ const seedUser_1 = require("../helpers/seedUser");
|
|
|
32
34
|
*/
|
|
33
35
|
describe('team boundary on SDK leases (integration)', function () {
|
|
34
36
|
this.timeout(60000);
|
|
37
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
38
|
+
// The Prisma store this suite is about, under `test:all` too, whose
|
|
39
|
+
// NODE_ENV would hand the factory a Loki one.
|
|
40
|
+
(0, loki_stores_1.usePrismaStores)();
|
|
35
41
|
let sa;
|
|
36
42
|
let alice;
|
|
37
43
|
let teamA;
|
|
@@ -63,15 +69,6 @@ describe('team boundary on SDK leases (integration)', function () {
|
|
|
63
69
|
yield prisma_1.prisma.lease.deleteMany({ where: { deviceUdid: { in: UDIDS } } });
|
|
64
70
|
yield prisma_1.prisma.device.updateMany({ where: { udid: { in: UDIDS } }, data: { busy: false } });
|
|
65
71
|
}));
|
|
66
|
-
after(() => __awaiter(this, void 0, void 0, function* () {
|
|
67
|
-
yield prisma_1.prisma.lease.deleteMany({ where: { deviceUdid: { in: UDIDS } } });
|
|
68
|
-
yield prisma_1.prisma.device.deleteMany({ where: { udid: { in: UDIDS } } });
|
|
69
|
-
yield prisma_1.prisma.teamMember.deleteMany({ where: { teamId: { in: [teamA.id, teamB.id] } } });
|
|
70
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamA.id } }).catch(() => undefined);
|
|
71
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamB.id } }).catch(() => undefined);
|
|
72
|
-
yield sa.cleanup();
|
|
73
|
-
yield alice.cleanup();
|
|
74
|
-
}));
|
|
75
72
|
function lease(who, filters) {
|
|
76
73
|
const svc = new LeaseService_1.LeaseService(prisma_1.prisma, device_store_1.DeviceStoreFactory.getStore(), {
|
|
77
74
|
allocate: () => __awaiter(this, void 0, void 0, function* () { return ({ systemPort: 9001, chromedriverPort: 9002, mjpegServerPort: 9003 }); }),
|
|
@@ -80,7 +77,7 @@ describe('team boundary on SDK leases (integration)', function () {
|
|
|
80
77
|
app.use(express_1.default.json());
|
|
81
78
|
app.use(authMiddleware_1.authMiddleware);
|
|
82
79
|
app.use('/sdk/leases', (0, sdk_leases_1.makeRouter)({ leaseService: svc }));
|
|
83
|
-
return (0,
|
|
80
|
+
return (0, loopbackRequest_1.default)(app)
|
|
84
81
|
.post('/sdk/leases')
|
|
85
82
|
.set('Cookie', who.cookie)
|
|
86
83
|
.send({ filters: Object.assign({ platform: 'android', filterByHost: HOST }, filters) });
|
|
@@ -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 authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
20
20
|
const recordings_1 = __importDefault(require("../../src/app/routers/recordings"));
|
|
21
21
|
const typedi_1 = require("typedi");
|
|
@@ -23,8 +23,10 @@ const TeamService_1 = require("../../src/services/TeamService");
|
|
|
23
23
|
const prisma_1 = require("../../src/prisma");
|
|
24
24
|
const seedUser_1 = require("../helpers/seedUser");
|
|
25
25
|
const artifact_store_1 = require("../helpers/artifact-store");
|
|
26
|
+
const scratch_database_1 = require("../helpers/scratch-database");
|
|
26
27
|
describe('team visibility on /recordings reads (integration)', function () {
|
|
27
28
|
this.timeout(60000);
|
|
29
|
+
(0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
|
|
28
30
|
// GET /recordings/:groupId resolves ARTIFACT_STORE from the container, which
|
|
29
31
|
// ServerManager registers at boot. Without it every allowed read is a 500.
|
|
30
32
|
(0, artifact_store_1.useArtifactStore)();
|
|
@@ -90,21 +92,6 @@ describe('team visibility on /recordings reads (integration)', function () {
|
|
|
90
92
|
data: Object.assign({ id: `rec-b-${stamp}`, group_id: GROUP_TEAM_B, device_udid: TEAM_B_UDID }, baseRecording),
|
|
91
93
|
});
|
|
92
94
|
}));
|
|
93
|
-
after(() => __awaiter(this, void 0, void 0, function* () {
|
|
94
|
-
yield prisma_1.prisma.recording.deleteMany({
|
|
95
|
-
where: { group_id: { in: [GROUP_SHARED, GROUP_TEAM_A, GROUP_TEAM_B] } },
|
|
96
|
-
});
|
|
97
|
-
yield prisma_1.prisma.device.deleteMany({
|
|
98
|
-
where: { udid: { in: [SHARED_UDID, TEAM_A_UDID, TEAM_B_UDID] } },
|
|
99
|
-
});
|
|
100
|
-
yield prisma_1.prisma.teamMember.deleteMany({
|
|
101
|
-
where: { teamId: { in: [teamA.id, teamB.id] } },
|
|
102
|
-
});
|
|
103
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamA.id } }).catch(() => undefined);
|
|
104
|
-
yield prisma_1.prisma.team.delete({ where: { id: teamB.id } }).catch(() => undefined);
|
|
105
|
-
yield sa.cleanup();
|
|
106
|
-
yield aliceMember.cleanup();
|
|
107
|
-
}));
|
|
108
95
|
function buildApp() {
|
|
109
96
|
const app = (0, express_1.default)();
|
|
110
97
|
app.use(express_1.default.json());
|
|
@@ -113,19 +100,19 @@ describe('team visibility on /recordings reads (integration)', function () {
|
|
|
113
100
|
return app;
|
|
114
101
|
}
|
|
115
102
|
it('Alice GET /recordings/:teamBGroup → 404', () => __awaiter(this, void 0, void 0, function* () {
|
|
116
|
-
const r = yield (0,
|
|
103
|
+
const r = yield (0, loopbackRequest_1.default)(buildApp())
|
|
117
104
|
.get(`/recordings/${GROUP_TEAM_B}`)
|
|
118
105
|
.set('Cookie', aliceMember.cookie);
|
|
119
106
|
(0, chai_1.expect)(r.status).to.equal(404);
|
|
120
107
|
}));
|
|
121
108
|
it('SA GET /recordings/:teamBGroup → 200 (admin sees all)', () => __awaiter(this, void 0, void 0, function* () {
|
|
122
|
-
const r = yield (0,
|
|
109
|
+
const r = yield (0, loopbackRequest_1.default)(buildApp())
|
|
123
110
|
.get(`/recordings/${GROUP_TEAM_B}`)
|
|
124
111
|
.set('Cookie', sa.cookie);
|
|
125
112
|
(0, chai_1.expect)(r.status).to.equal(200);
|
|
126
113
|
}));
|
|
127
114
|
it('Alice GET /recordings/:teamAGroup → 200 (member of team A)', () => __awaiter(this, void 0, void 0, function* () {
|
|
128
|
-
const r = yield (0,
|
|
115
|
+
const r = yield (0, loopbackRequest_1.default)(buildApp())
|
|
129
116
|
.get(`/recordings/${GROUP_TEAM_A}`)
|
|
130
117
|
.set('Cookie', aliceMember.cookie);
|
|
131
118
|
(0, chai_1.expect)(r.status).to.equal(200);
|