@xenon-device-management/xenon 2.13.2 → 2.15.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.
Files changed (350) hide show
  1. package/README.md +17 -8
  2. package/lib/package.json +5 -5
  3. package/lib/public/assets/{AnnotationOverlay-BXruClDk.js → AnnotationOverlay-Btq3dZRI.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-qTS4LPLC.js → ApiKeyGate-C9OoAgg_.js} +1 -1
  5. package/lib/public/assets/DeviceMosaicView-DBC_xczB.js +2 -0
  6. package/lib/public/assets/{EmptyState-Cr17PdDv.js → EmptyState-DqWU2-vM.js} +1 -1
  7. package/lib/public/assets/{FieldGroup-Bvn08svv.js → FieldGroup-CveQ3xrA.js} +1 -1
  8. package/lib/public/assets/{FilterMenu-DDSO8aDf.js → FilterMenu-BfKDSfHz.js} +1 -1
  9. package/lib/public/assets/{Menu-B-Qn2X3T.js → Menu-CtdCybzA.js} +1 -1
  10. package/lib/public/assets/{Modal-Cg9V5JYU.js → Modal-Bsvj1tm2.js} +1 -1
  11. package/lib/public/assets/{RecordingPage-BTUs9VTW.js → RecordingPage-CRofOiyx.js} +1 -1
  12. package/lib/public/assets/{RecordingsPage-DYcfxITK.js → RecordingsPage-jNQHb2IR.js} +2 -2
  13. package/lib/public/assets/{SegmentedControl-Zy7suhT5.js → SegmentedControl-C8HtTha-.js} +1 -1
  14. package/lib/public/assets/{SettingCard-CWYmycoe.js → SettingCard-D8K1LZNo.js} +1 -1
  15. package/lib/public/assets/{Table-io9fwa98.js → Table-zNTX92Pn.js} +1 -1
  16. package/lib/public/assets/{activity-Bdf_uIWl.js → activity-CJ8LwZq9.js} +1 -1
  17. package/lib/public/assets/ai-settings-CgxQZqrc.js +16 -0
  18. package/lib/public/assets/{api-keys-CPqtVpm3.js → api-keys-ECpo-zTo.js} +1 -1
  19. package/lib/public/assets/{apps-BgZan0H9.js → apps-BjGymyf3.js} +2 -2
  20. package/lib/public/assets/{arrow-left-B27ckB31.js → arrow-left-BWfCbeHP.js} +1 -1
  21. package/lib/public/assets/{arrow-right-DOF8ITCz.js → arrow-right-DC7UGf_n.js} +1 -1
  22. package/lib/public/assets/{arrow-up-right-eFG5KTFS.js → arrow-up-right-BOjwybDk.js} +1 -1
  23. package/lib/public/assets/{auth-shell-DlC3_27j.js → auth-shell-CxMTo9aH.js} +2 -2
  24. package/lib/public/assets/{builds-page-BuXpPfKo.js → builds-page-DOCm4HkA.js} +1 -1
  25. package/lib/public/assets/{button-fIW8owMI.js → button-Cg08-EMT.js} +1 -1
  26. package/lib/public/assets/{calendar-WNjmrF-a.js → calendar-CA1ikLJs.js} +1 -1
  27. package/lib/public/assets/{check-BdTh1Sq7.js → check-BMTaH72W.js} +1 -1
  28. package/lib/public/assets/{chevron-right-B4BKlsqk.js → chevron-right-B2QOv9Nx.js} +1 -1
  29. package/lib/public/assets/{circle-check-zJU80_EF.js → circle-check-CYXBoUqT.js} +1 -1
  30. package/lib/public/assets/{circle-x-5Uwquo0h.js → circle-x-DTwnSAnl.js} +1 -1
  31. package/lib/public/assets/{clock-BtUprDsZ.js → clock-C8_HViQP.js} +1 -1
  32. package/lib/public/assets/{copy-DGSbZI3u.js → copy-Be7LNMXV.js} +1 -1
  33. package/lib/public/assets/derive-UnLRp4r3.js +1 -0
  34. package/lib/public/assets/device-explorer-BOqtfGXz.js +282 -0
  35. package/lib/public/assets/{download-WG6l7BgD.js → download-B56lwxWP.js} +1 -1
  36. package/lib/public/assets/forgot-password-CCU6bzLn.js +1 -0
  37. package/lib/public/assets/h264Stream-C-OtmIuk.js +26 -0
  38. package/lib/public/assets/index-B9KQ6LsY.js +15 -0
  39. package/lib/public/assets/{index-DI1fGIyj.js → index-BOxe43p1.js} +5 -5
  40. package/lib/public/assets/{index-HDQVe1gi.js → index-CfxsUslp.js} +1 -1
  41. package/lib/public/assets/{input-BjDRKCea.js → input-CZHFjbSg.js} +1 -1
  42. package/lib/public/assets/{line-chart-C0zdgd0v.js → line-chart-Gl_Dz3X_.js} +1 -1
  43. package/lib/public/assets/{list-checks-BdW5CjM8.js → list-checks-CCgjeyGY.js} +1 -1
  44. package/lib/public/assets/{lock-Dy3L4I8E.js → lock-CkOwgDcx.js} +1 -1
  45. package/lib/public/assets/{login-BHbY9CLG.js → login-D5cc4w7M.js} +1 -1
  46. package/lib/public/assets/maintenance-settings-t7qr3fvX.js +6 -0
  47. package/lib/public/assets/{monitor-8xjkeHHi.js → monitor-DEJixKQO.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-CXdKMXQ8.js → mouse-pointer-2-CtOrk4M8.js} +1 -1
  49. package/lib/public/assets/{network-Cj8JOKON.js → network-Dii9qdvk.js} +1 -1
  50. package/lib/public/assets/{overview-CTg7F0im.js → overview-rolAACrO.js} +1 -1
  51. package/lib/public/assets/{page-header--aTyJXq6.js → page-header-COQZxJfR.js} +1 -1
  52. package/lib/public/assets/{play-B-FodgHV.js → play-DrN1Se_R.js} +1 -1
  53. package/lib/public/assets/{plus-CsIk10Cq.js → plus-5aq-9uRx.js} +1 -1
  54. package/lib/public/assets/{profile-page-DkFKkMfl.js → profile-page-6i5kY6kF.js} +1 -1
  55. package/lib/public/assets/{reset-password-I2-zqLlI.js → reset-password-sp2IBrc7.js} +1 -1
  56. package/lib/public/assets/runbook-page-DJDjeopU.js +253 -0
  57. package/lib/public/assets/{select-DvCSv25z.js → select-Bpyn7rWO.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-BLVfMJs_.js → selector-detail-redirect-BH74vmKv.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-oqadRokn.js → selector-health-page-Cw_g218H.js} +2 -2
  60. package/lib/public/assets/session-detail-page-AZJE6XXE.js +4 -0
  61. package/lib/public/assets/settings-C3uyGhyc.js +6 -0
  62. package/lib/public/assets/{settings-Dep_3kbA.css → settings-K8UHWMdI.css} +1 -1
  63. package/lib/public/assets/{stat-tile-DV9Ix-4O.js → stat-tile-xVqAKIlI.js} +1 -1
  64. package/lib/public/assets/{tablet-6b_SEN2X.js → tablet-BxUScAeO.js} +1 -1
  65. package/lib/public/assets/{teams-BjZyfuHh.js → teams-B5ljGkY2.js} +1 -1
  66. package/lib/public/assets/{trash-2-CTZpPSdd.js → trash-2-B701i8sy.js} +1 -1
  67. package/lib/public/assets/{upload-P3XzwQUb.js → upload-C2gr44RS.js} +1 -1
  68. package/lib/public/assets/{use-builds-data-1cOCMf-z.js → use-builds-data-NeQOFEoy.js} +1 -1
  69. package/lib/public/assets/{use-password-reset-mode-DgF9w088.js → use-password-reset-mode-DEzJq5ef.js} +1 -1
  70. package/lib/public/assets/{users-DMF9Mk5H.js → users-D_vv_yz4.js} +1 -1
  71. package/lib/public/assets/{users-CiNTKUiz.js → users-MY6eNe6s.js} +1 -1
  72. package/lib/public/assets/{video-off-CHp1KU_T.js → video-off-OK1lNZQn.js} +1 -1
  73. package/lib/public/assets/webhook-settings-BU-C9lBf.js +1 -0
  74. package/lib/public/assets/{zap-DRz0tDq5.js → zap-DO9ry1iz.js} +1 -1
  75. package/lib/public/index.html +1 -1
  76. package/lib/schema.json +23 -25
  77. package/lib/src/XenonCapabilityManager.js +22 -5
  78. package/lib/src/app/dashboardPluginLink.js +117 -0
  79. package/lib/src/app/index.js +13 -34
  80. package/lib/src/app/openapi/control.yaml +73 -20
  81. package/lib/src/app/openapi/grid.yaml +31 -7
  82. package/lib/src/app/openapi/identity.yaml +103 -31
  83. package/lib/src/app/openapi/platform.yaml +165 -39
  84. package/lib/src/app/openapi/sessions.yaml +10 -3
  85. package/lib/src/app/routers/apikeys.js +14 -2
  86. package/lib/src/app/routers/auth.js +29 -5
  87. package/lib/src/app/routers/control.js +20 -2
  88. package/lib/src/app/routers/dashboard.js +69 -26
  89. package/lib/src/app/routers/grid.js +37 -13
  90. package/lib/src/app/routers/profile.js +21 -0
  91. package/lib/src/app/routers/streamType.js +7 -2
  92. package/lib/src/app/routers/users.js +10 -5
  93. package/lib/src/app/routers/webhook.js +8 -4
  94. package/lib/src/app/swagger.js +2 -0
  95. package/lib/src/app/ws/h264StreamWs.js +10 -1
  96. package/lib/src/config.js +51 -5
  97. package/lib/src/dashboard/commands.js +185 -141
  98. package/lib/src/dashboard/event-manager.js +88 -67
  99. package/lib/src/dashboard/services/failure-analysis-service.js +136 -45
  100. package/lib/src/dashboard/sessionCommandSummary.js +39 -0
  101. package/lib/src/data-service/appleFamily.js +43 -0
  102. package/lib/src/data-service/db.js +11 -0
  103. package/lib/src/data-service/device-service.js +6 -1
  104. package/lib/src/data-service/device-store.js +76 -3
  105. package/lib/src/data-service/deviceClaims.js +37 -0
  106. package/lib/src/data-service/deviceFieldOwners.js +25 -11
  107. package/lib/src/data-service/deviceSettings.js +79 -0
  108. package/lib/src/data-service/prisma-store.js +169 -15
  109. package/lib/src/data-service/web-config-service.js +50 -92
  110. package/lib/src/device-managers/AndroidDeviceManager.js +9 -31
  111. package/lib/src/device-managers/android/AndroidH264StreamService.js +69 -20
  112. package/lib/src/device-managers/android/H264Multiplexer.js +38 -4
  113. package/lib/src/device-managers/ios/WDAClient.js +44 -43
  114. package/lib/src/device-managers/shellCommands.js +96 -0
  115. package/lib/src/device-utils.js +114 -40
  116. package/lib/src/gateway/defaultGateway.js +1 -1
  117. package/lib/src/gateway/forwardToNode.js +2 -0
  118. package/lib/src/gateway/healReport.js +87 -0
  119. package/lib/src/gateway/sessionGateway.js +12 -2
  120. package/lib/src/generated/client/edge.js +18 -3
  121. package/lib/src/generated/client/index-browser.js +15 -0
  122. package/lib/src/generated/client/index.d.ts +2086 -379
  123. package/lib/src/generated/client/index.js +18 -3
  124. package/lib/src/generated/client/package.json +1 -1
  125. package/lib/src/generated/client/schema.prisma +38 -7
  126. package/lib/src/generated/client/wasm.js +15 -0
  127. package/lib/src/interceptors/CommandInterceptor.js +271 -188
  128. package/lib/src/interceptors/xenonScripts.js +55 -0
  129. package/lib/src/interfaces/IPluginArgs.js +1 -2
  130. package/lib/src/middleware/authMiddleware.js +15 -3
  131. package/lib/src/middleware/commandCaller.js +3 -3
  132. package/lib/src/middleware/verifyCredential.js +58 -1
  133. package/lib/src/plugin.js +23 -4
  134. package/lib/src/prisma.js +1 -1
  135. package/lib/src/scripts/database-check.js +124 -0
  136. package/lib/src/services/AICommandService.js +9 -2
  137. package/lib/src/services/AIService.js +281 -32
  138. package/lib/src/services/CleanupService.js +6 -1
  139. package/lib/src/services/EmailService.js +29 -0
  140. package/lib/src/services/InterceptorService.js +70 -13
  141. package/lib/src/services/NetworkConditioningService.js +85 -88
  142. package/lib/src/services/NotificationService.js +36 -29
  143. package/lib/src/services/OrphanSweeper.js +7 -0
  144. package/lib/src/services/PortAllocator.js +5 -0
  145. package/lib/src/services/SelectorVerificationJob.js +15 -4
  146. package/lib/src/services/ServerManager.js +105 -9
  147. package/lib/src/services/SessionLifecycleService.js +133 -75
  148. package/lib/src/services/ShutdownCoordinator.js +33 -0
  149. package/lib/src/services/SocketServer.js +19 -3
  150. package/lib/src/services/TeamService.js +20 -3
  151. package/lib/src/services/bug-report/video-slice.js +4 -1
  152. package/lib/src/services/device-access/leaseOverride.js +7 -5
  153. package/lib/src/services/healing/FuzzyXmlHealingProvider.js +51 -0
  154. package/lib/src/services/healing/HealEtalonService.js +3 -2
  155. package/lib/src/services/healing/HealingOrchestrator.js +85 -71
  156. package/lib/src/services/healing/OcrHealingProvider.js +41 -64
  157. package/lib/src/services/healing/ResilioTreeHealingProvider.js +41 -50
  158. package/lib/src/services/healing/VisualAiHealingProvider.js +1 -1
  159. package/lib/src/services/healing/fingerprintIdentity.js +126 -0
  160. package/lib/src/services/healing/resilioPath.js +274 -0
  161. package/lib/src/services/interceptor/AndroidProxyAdapter.js +18 -0
  162. package/lib/src/services/interceptor/interceptorOptions.js +44 -0
  163. package/lib/src/services/logcat/SessionDeviceLogs.js +271 -0
  164. package/lib/src/services/logcat/deviceLogBook.js +163 -0
  165. package/lib/src/services/network/AndroidRadios.js +71 -0
  166. package/lib/src/services/network/PhoneNetworkLedger.js +184 -0
  167. package/lib/src/services/network/PhoneNetworkRestore.js +330 -0
  168. package/lib/src/services/network/adbForPhone.js +60 -0
  169. package/lib/src/services/network/phoneNetworkLock.js +20 -0
  170. package/lib/src/services/network/xenonProxy.js +107 -0
  171. package/lib/src/services/ocr/ocrData.js +118 -0
  172. package/lib/src/services/ocr/vendor/LICENSE-tessdata.txt +202 -0
  173. package/lib/src/services/ocr/vendor/eng.traineddata.gz +0 -0
  174. package/lib/src/services/omni-vision/OmniVisionService.js +132 -95
  175. package/lib/src/services/omni-vision/VisionAssertionService.js +28 -25
  176. package/lib/src/services/omni-vision/ocrTextMatch.js +105 -0
  177. package/lib/src/services/omni-vision/screenScale.js +68 -0
  178. package/lib/src/services/passwordResetLink.js +50 -9
  179. package/lib/src/services/passwordResetMode.js +3 -1
  180. package/lib/src/services/publicUrl.js +45 -0
  181. package/lib/src/services/recording/concurrency-gate.js +12 -3
  182. package/lib/src/services/session/sessionIdentity.js +5 -3
  183. package/lib/src/services/session/shutdownReason.js +26 -0
  184. package/lib/src/services/settings/SelfHealingSwitch.js +70 -0
  185. package/lib/src/services/settings/aiEngineSettings.js +219 -0
  186. package/lib/src/services/settings/labSettings.js +149 -0
  187. package/lib/src/services/token/mintedLifetime.js +51 -0
  188. package/lib/src/services/webhookEvents.js +173 -0
  189. package/lib/src/sessions/SessionManager.js +8 -2
  190. package/lib/src/sessions/sessionMemory.js +25 -0
  191. package/lib/test/helpers/fake-adb.js +58 -0
  192. package/lib/test/helpers/fake-ai-provider.js +188 -0
  193. package/lib/test/helpers/fake-device-table.js +63 -24
  194. package/lib/test/helpers/loki-stores.js +33 -0
  195. package/lib/test/helpers/page-sources.js +164 -0
  196. package/lib/test/helpers/scratch-database.js +58 -13
  197. package/lib/test/integration/audit-ingest.spec.js +10 -10
  198. package/lib/test/integration/auth-flow.spec.js +9 -14
  199. package/lib/test/integration/auth-rate-limit.spec.js +6 -3
  200. package/lib/test/integration/bug-report-route.spec.js +22 -2
  201. package/lib/test/integration/forgot-password-rate-limit.spec.js +6 -3
  202. package/lib/test/integration/forgot-password.spec.js +17 -18
  203. package/lib/test/integration/legacy-deprecation-headers.spec.js +5 -2
  204. package/lib/test/integration/migration.spec.js +6 -1
  205. package/lib/test/integration/node-pair-auth-rest.spec.js +5 -9
  206. package/lib/test/integration/ports-router.spec.js +4 -4
  207. package/lib/test/integration/prisma-device-store-add-devices.spec.js +2 -3
  208. package/lib/test/integration/prisma-device-store-team-filter.spec.js +2 -5
  209. package/lib/test/integration/profile-tokens.spec.js +10 -16
  210. package/lib/test/integration/recordings-router.spec.js +16 -16
  211. package/lib/test/integration/reset-revokes-sessions.spec.js +4 -9
  212. package/lib/test/integration/role-matrix.spec.js +4 -9
  213. package/lib/test/integration/sdk-leases-router.spec.js +9 -9
  214. package/lib/test/integration/sdk-version.spec.js +2 -2
  215. package/lib/test/integration/selector-health-endpoint.spec.js +5 -5
  216. package/lib/test/integration/team-membership-migration.spec.js +73 -12
  217. package/lib/test/integration/team-visibility-apps.spec.js +15 -22
  218. package/lib/test/integration/team-visibility-control.spec.js +96 -69
  219. package/lib/test/integration/team-visibility-dashboard.spec.js +7 -23
  220. package/lib/test/integration/team-visibility-grid.spec.js +16 -15
  221. package/lib/test/integration/team-visibility-leases.spec.js +8 -11
  222. package/lib/test/integration/team-visibility-recordings.spec.js +6 -19
  223. package/lib/test/integration/team-visibility-sessions.spec.js +6 -19
  224. package/lib/test/integration/users-flow.spec.js +7 -12
  225. package/lib/test/integration/users-lockout.spec.js +5 -3
  226. package/lib/test/unit/EmailService.test.js +66 -0
  227. package/lib/test/unit/ResilioTreeHealingProvider.test.js +46 -19
  228. package/lib/test/unit/TeamService.test.js +3 -0
  229. package/lib/test/unit/XenonCapabilityManager.spec.js +4 -2
  230. package/lib/test/unit/ai-call-time-limit.spec.js +130 -0
  231. package/lib/test/unit/ai-engine-settings.spec.js +338 -0
  232. package/lib/test/unit/ai-provider-switch.spec.js +130 -0
  233. package/lib/test/unit/ai-rate-limit.spec.js +126 -0
  234. package/lib/test/unit/android-h264-viewers.spec.js +272 -0
  235. package/lib/test/unit/android-preview-hold.spec.js +3 -2
  236. package/lib/test/unit/apikeys-router.spec.js +84 -0
  237. package/lib/test/unit/apple-family.spec.js +36 -0
  238. package/lib/test/unit/authMiddleware.bearer.spec.js +9 -0
  239. package/lib/test/unit/authMiddleware.credentialLifetime.spec.js +149 -0
  240. package/lib/test/unit/bug-report/video-slice.spec.js +19 -0
  241. package/lib/test/unit/cleanup-settings-db.spec.js +103 -0
  242. package/lib/test/unit/cleanup-settings.spec.js +244 -0
  243. package/lib/test/unit/dashboard-api-paths.spec.js +101 -0
  244. package/lib/test/unit/dashboard-commands.spec.js +249 -0
  245. package/lib/test/unit/dashboard-events-device-logs.spec.js +207 -0
  246. package/lib/test/unit/dashboard-events-team-scope.spec.js +9 -6
  247. package/lib/test/unit/dashboard-plugin-link.spec.js +213 -0
  248. package/lib/test/unit/database-check.spec.js +108 -0
  249. package/lib/test/unit/default-plugin-args.spec.js +33 -0
  250. package/lib/test/unit/device-log-book.spec.js +180 -0
  251. package/lib/test/unit/device-offline-webhook.spec.js +81 -0
  252. package/lib/test/unit/device-setting-columns.spec.js +66 -0
  253. package/lib/test/unit/device-settings-loki.spec.js +87 -0
  254. package/lib/test/unit/device-settings-survive.spec.js +555 -0
  255. package/lib/test/unit/device-sync-lost-update.spec.js +5 -0
  256. package/lib/test/unit/emulator-boot.spec.js +91 -0
  257. package/lib/test/unit/enable-dashboard-option.spec.js +57 -0
  258. package/lib/test/unit/event-log-session-data.spec.js +244 -0
  259. package/lib/test/unit/failure-analysis.spec.js +182 -0
  260. package/lib/test/unit/failure-categories.spec.js +128 -0
  261. package/lib/test/unit/fingerprint-identity.spec.js +433 -0
  262. package/lib/test/unit/h264-multiplexer.spec.js +46 -0
  263. package/lib/test/unit/heal-report.spec.js +71 -0
  264. package/lib/test/unit/healing-instrumentation.spec.js +6 -1
  265. package/lib/test/unit/healing-tier-gate.spec.js +73 -29
  266. package/lib/test/unit/healing-tiers-every-session.spec.js +219 -0
  267. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +219 -0
  268. package/lib/test/unit/hub-node-same-host.spec.js +5 -0
  269. package/lib/test/unit/interceptor-visual-elements.spec.js +192 -0
  270. package/lib/test/unit/iphone-ipad-only.spec.js +132 -0
  271. package/lib/test/unit/json-logging-option.spec.js +74 -0
  272. package/lib/test/unit/lab-settings.spec.js +218 -0
  273. package/lib/test/unit/lease/lease-override.spec.js +23 -6
  274. package/lib/test/unit/lease/session-lease-proof.spec.js +16 -4
  275. package/lib/test/unit/max-sessions.spec.js +240 -0
  276. package/lib/test/unit/ocr-healing-tier-real-ocr.spec.js +113 -0
  277. package/lib/test/unit/ocr-healing-tier.spec.js +122 -0
  278. package/lib/test/unit/ocr-offline.spec.js +213 -0
  279. package/lib/test/unit/ocr-text-match.spec.js +44 -0
  280. package/lib/test/unit/omni-virtual-element-store.spec.js +301 -0
  281. package/lib/test/unit/omni-vision-analyze-screen.spec.js +72 -0
  282. package/lib/test/unit/omni-vision-ios-points.spec.js +159 -0
  283. package/lib/test/unit/omni-vision-tap-text.spec.js +129 -0
  284. package/lib/test/unit/omni-vision.spec.js +6 -8
  285. package/lib/test/unit/package-engines.spec.js +27 -0
  286. package/lib/test/unit/partial-config-file.spec.js +14 -0
  287. package/lib/test/unit/passwordResetMode.test.js +10 -2
  288. package/lib/test/unit/phone-network/boot-clean-up.spec.js +64 -0
  289. package/lib/test/unit/phone-network/interceptor-options.spec.js +77 -0
  290. package/lib/test/unit/phone-network/interceptor-proxy-restore.spec.js +145 -0
  291. package/lib/test/unit/phone-network/network-conditioning.spec.js +145 -0
  292. package/lib/test/unit/phone-network/phone-network-restore.spec.js +246 -0
  293. package/lib/test/unit/phone-network/session-end-paths.spec.js +182 -0
  294. package/lib/test/unit/phone-network/session-start.spec.js +110 -0
  295. package/lib/test/unit/phone-network/xenon-proxy.spec.js +69 -0
  296. package/lib/test/unit/plugin.spec.js +3 -3
  297. package/lib/test/unit/profile-router.test.js +122 -1
  298. package/lib/test/unit/queue-requester.spec.js +1 -1
  299. package/lib/test/unit/recording-options.spec.js +181 -0
  300. package/lib/test/unit/reset-link-address.spec.js +266 -0
  301. package/lib/test/unit/reset-link.test.js +10 -4
  302. package/lib/test/unit/resilio-path-healing.spec.js +270 -0
  303. package/lib/test/unit/selector-learning-every-session.spec.js +179 -0
  304. package/lib/test/unit/selector-verification-clean-build.spec.js +130 -0
  305. package/lib/test/unit/selector-verification-job.spec.js +13 -13
  306. package/lib/test/unit/self-healing-boot.spec.js +80 -0
  307. package/lib/test/unit/self-healing-setting.spec.js +273 -0
  308. package/lib/test/unit/session-app-resolution.spec.js +5 -1
  309. package/lib/test/unit/session-attribution-wiring.spec.js +41 -13
  310. package/lib/test/unit/session-create-credential-owner.spec.js +242 -0
  311. package/lib/test/unit/session-credential-strip.spec.js +1 -1
  312. package/lib/test/unit/session-device-logs.spec.js +347 -0
  313. package/lib/test/unit/session-end-failure-analysis.spec.js +133 -0
  314. package/lib/test/unit/session-failed-webhook.spec.js +170 -0
  315. package/lib/test/unit/session-log-indexes.spec.js +64 -3
  316. package/lib/test/unit/shell-commands.spec.js +133 -0
  317. package/lib/test/unit/socketServer.scopedEmit.spec.js +2 -0
  318. package/lib/test/unit/socketServer.sessionCookie.spec.js +2 -1
  319. package/lib/test/unit/stream-start-pipelines.spec.js +264 -0
  320. package/lib/test/unit/stream-status-type.spec.js +4 -0
  321. package/lib/test/unit/token-mint-limits.spec.js +165 -0
  322. package/lib/test/unit/unexpected-shutdown-reason.spec.js +163 -0
  323. package/lib/test/unit/verify-credential.spec.js +33 -0
  324. package/lib/test/unit/vision-assertion.spec.js +119 -0
  325. package/lib/test/unit/wda-client-shell.spec.js +133 -0
  326. package/lib/test/unit/web-config-service.spec.js +98 -0
  327. package/lib/test/unit/webhook-delivery.spec.js +24 -0
  328. package/lib/test/unit/webhook-payloads.spec.js +273 -0
  329. package/lib/test/unit/xenon-execute-scripts.spec.js +138 -0
  330. package/lib/tsconfig.tsbuildinfo +1 -1
  331. package/lib/web/src/components/webhook-settings/webhookEventVariables.json +30 -0
  332. package/package.json +5 -5
  333. package/prisma/migrations/20261004140000_keep_device_settings/migration.sql +19 -0
  334. package/prisma/migrations/20261004174413_locator_etalon_path/migration.sql +2 -0
  335. package/prisma/schema.prisma +38 -7
  336. package/schema.json +23 -25
  337. package/scripts/generate-types-from-schema.js +1 -2
  338. package/scripts/lib/always-present-plugin-args.js +4 -1
  339. package/lib/public/assets/BugReportButton-BoGhlI7F.js +0 -15
  340. package/lib/public/assets/DeviceMosaicView-8J0oYqMM.js +0 -2
  341. package/lib/public/assets/ai-settings-Bce5SJob.js +0 -21
  342. package/lib/public/assets/device-explorer-C-KnhMHI.js +0 -279
  343. package/lib/public/assets/forgot-password-DnudqFFo.js +0 -1
  344. package/lib/public/assets/maintenance-settings-zDVlOMVK.js +0 -6
  345. package/lib/public/assets/recording-group-store-WVM3hoXn.js +0 -26
  346. package/lib/public/assets/runbook-page-DJTVCJX7.js +0 -124
  347. package/lib/public/assets/session-detail-page-CGn55Pvl.js +0 -4
  348. package/lib/public/assets/settings-MsVU2ukE.js +0 -6
  349. package/lib/public/assets/webhook-settings-DiS8geIm.js +0 -1
  350. /package/lib/public/assets/{BugReportButton-CPBjfiv4.css → index-CPBjfiv4.css} +0 -0
@@ -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 supertest_1 = __importDefault(require("supertest"));
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 PasswordResetService_1 = require("../../src/services/PasswordResetService");
23
23
  const prisma_1 = require("../../src/prisma");
24
+ const scratch_database_1 = require("../helpers/scratch-database");
24
25
  describe('forgot-password flow (integration)', function () {
25
26
  this.timeout(30000);
27
+ (0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
26
28
  let user;
27
29
  const email = 'forgot-it@xenon.local';
28
30
  before(() => __awaiter(this, void 0, void 0, function* () {
29
- yield prisma_1.prisma.passwordResetToken.deleteMany({ where: { user: { email } } });
30
- yield prisma_1.prisma.userSession.deleteMany({ where: { user: { email } } });
31
- yield prisma_1.prisma.user.deleteMany({ where: { email } });
32
31
  user = yield typedi_1.Container.get(UserService_1.UserService).createUser({
33
32
  email,
34
33
  name: 'Forgot IT',
@@ -36,42 +35,41 @@ describe('forgot-password flow (integration)', function () {
36
35
  role: 'ADMIN',
37
36
  });
38
37
  }));
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
38
  function buildApp() {
45
39
  const app = (0, express_1.default)();
46
40
  app.use(express_1.default.json());
47
41
  app.use('/auth', (0, auth_1.authPublicRouter)());
48
42
  return app;
49
43
  }
44
+ // A POST with the token in the body: since #265 the token stays out of
45
+ // request URLs, which the server log records. The old GET /check/:token is
46
+ // gone, so it answered 404 to every token, valid or not.
47
+ const check = (app, token) => (0, loopbackRequest_1.default)(app).post('/auth/reset-password/check').send({ token });
50
48
  it('full flow: forgot -> DB token -> reset -> login with new password', () => __awaiter(this, void 0, void 0, function* () {
51
49
  const app = buildApp();
52
50
  // Generic 204 even with wrong email
53
- const r1 = yield (0, supertest_1.default)(app).post('/auth/forgot-password').send({ email: 'no@no.com' });
51
+ const r1 = yield (0, loopbackRequest_1.default)(app).post('/auth/forgot-password').send({ email: 'no@no.com' });
54
52
  (0, chai_1.expect)(r1.status).to.equal(204);
55
- const r2 = yield (0, supertest_1.default)(app).post('/auth/forgot-password').send({ email });
53
+ const r2 = yield (0, loopbackRequest_1.default)(app).post('/auth/forgot-password').send({ email });
56
54
  (0, chai_1.expect)(r2.status).to.equal(204);
57
55
  // Generate a token via the service directly so we have the raw value to reset with
58
56
  const { raw } = yield typedi_1.Container.get(PasswordResetService_1.PasswordResetService).createToken(user.id);
59
- const check = yield (0, supertest_1.default)(app).get(`/auth/reset-password/check/${raw}`);
60
- (0, chai_1.expect)(check.status).to.equal(200);
61
- const reset = yield (0, supertest_1.default)(app)
57
+ const valid = yield check(app, raw);
58
+ (0, chai_1.expect)(valid.status).to.equal(200);
59
+ const reset = yield (0, loopbackRequest_1.default)(app)
62
60
  .post('/auth/reset-password')
63
61
  .send({ token: raw, newPassword: 'NewPassw0rd1' });
64
62
  (0, chai_1.expect)(reset.status).to.equal(204);
65
63
  // Token now consumed
66
- const recheck = yield (0, supertest_1.default)(app).get(`/auth/reset-password/check/${raw}`);
64
+ const recheck = yield check(app, raw);
67
65
  (0, chai_1.expect)(recheck.status).to.equal(404);
68
66
  // Login with new password works
69
- const login = yield (0, supertest_1.default)(app)
67
+ const login = yield (0, loopbackRequest_1.default)(app)
70
68
  .post('/auth/login')
71
69
  .send({ email, password: 'NewPassw0rd1' });
72
70
  (0, chai_1.expect)(login.status).to.equal(204);
73
71
  // Login with old password fails
74
- const oldLogin = yield (0, supertest_1.default)(app)
72
+ const oldLogin = yield (0, loopbackRequest_1.default)(app)
75
73
  .post('/auth/login')
76
74
  .send({ email, password: 'old-password-1' });
77
75
  (0, chai_1.expect)(oldLogin.status).to.equal(401);
@@ -83,7 +81,8 @@ describe('forgot-password flow (integration)', function () {
83
81
  data: { expiresAt: new Date(Date.now() - 1000) },
84
82
  });
85
83
  const app = buildApp();
86
- const r = yield (0, supertest_1.default)(app).get(`/auth/reset-password/check/${raw}`);
84
+ const r = yield check(app, raw);
87
85
  (0, chai_1.expect)(r.status).to.equal(404);
86
+ (0, chai_1.expect)(r.body).to.deep.equal({ error: 'invalid or expired token' });
88
87
  }));
89
88
  });
@@ -48,8 +48,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
48
48
  require("reflect-metadata");
49
49
  const chai_1 = require("chai");
50
50
  const express_1 = __importDefault(require("express"));
51
- const supertest_1 = __importDefault(require("supertest"));
51
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
52
+ const scratch_database_1 = require("../helpers/scratch-database");
52
53
  describe('legacy /reservation headers', () => {
54
+ // The route reads the phones' reservations from the database.
55
+ (0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
53
56
  it('GET /reservation returns Deprecation + Sunset + Link headers', () => __awaiter(void 0, void 0, void 0, function* () {
54
57
  const router = (yield Promise.resolve().then(() => __importStar(require('../../src/app/routers/reservation')))).default;
55
58
  const app = (0, express_1.default)();
@@ -60,7 +63,7 @@ describe('legacy /reservation headers', () => {
60
63
  next();
61
64
  });
62
65
  app.use('/reservation', router);
63
- const res = yield (0, supertest_1.default)(app).get('/reservation');
66
+ const res = yield (0, loopbackRequest_1.default)(app).get('/reservation');
64
67
  (0, chai_1.expect)(res.headers['deprecation']).to.equal('true');
65
68
  (0, chai_1.expect)(res.headers['sunset']).to.match(/^\d{4}-\d{2}-\d{2}T/);
66
69
  (0, chai_1.expect)(res.headers['link']).to.include('rel="alternate"');
@@ -13,15 +13,20 @@ require("reflect-metadata");
13
13
  const chai_1 = require("chai");
14
14
  const bootstrap_1 = require("../../src/services/identity/bootstrap");
15
15
  const prisma_1 = require("../../src/prisma");
16
+ const scratch_database_1 = require("../helpers/scratch-database");
16
17
  describe('bootstrapIdentity idempotency (integration)', function () {
17
18
  this.timeout(30000);
19
+ (0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
20
+ // On a fresh database, so the first run has a super-admin to create. On a
21
+ // developer's populated one both runs returned at once and proved nothing.
18
22
  it('running twice does not create duplicate users', () => __awaiter(this, void 0, void 0, function* () {
19
23
  const before = yield prisma_1.prisma.user.count();
24
+ (0, chai_1.expect)(before, 'a fresh database').to.equal(0);
20
25
  yield (0, bootstrap_1.bootstrapIdentity)();
21
26
  const after1 = yield prisma_1.prisma.user.count();
27
+ (0, chai_1.expect)(after1, 'the bootstrap super-admin').to.equal(1);
22
28
  yield (0, bootstrap_1.bootstrapIdentity)();
23
29
  const after2 = yield prisma_1.prisma.user.count();
24
30
  (0, chai_1.expect)(after2).to.equal(after1);
25
- (0, chai_1.expect)(after1).to.be.greaterThanOrEqual(before);
26
31
  }));
27
32
  });
@@ -15,15 +15,16 @@ 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 supertest_1 = __importDefault(require("supertest"));
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");
22
22
  const UserService_1 = require("../../src/services/UserService");
23
23
  const ApiKeyService_1 = require("../../src/services/ApiKeyService");
24
- const prisma_1 = require("../../src/prisma");
24
+ const scratch_database_1 = require("../helpers/scratch-database");
25
25
  describe('node pair auth — REST /register (integration)', function () {
26
26
  this.timeout(60000);
27
+ (0, scratch_database_1.useScratchDatabase)({ wholeSuite: true });
27
28
  let nodeUser;
28
29
  let rawToken;
29
30
  before(() => __awaiter(this, void 0, void 0, function* () {
@@ -41,11 +42,6 @@ describe('node pair auth — REST /register (integration)', function () {
41
42
  });
42
43
  rawToken = tok.raw;
43
44
  }));
44
- after(() => __awaiter(this, void 0, void 0, function* () {
45
- yield prisma_1.prisma.apiKey.deleteMany({ where: { userId: nodeUser.id } });
46
- yield prisma_1.prisma.userSession.deleteMany({ where: { userId: nodeUser.id } });
47
- yield prisma_1.prisma.user.delete({ where: { id: nodeUser.id } });
48
- }));
49
45
  function buildApp() {
50
46
  const app = (0, express_1.default)();
51
47
  app.use(express_1.default.json());
@@ -54,7 +50,7 @@ describe('node pair auth — REST /register (integration)', function () {
54
50
  return app;
55
51
  }
56
52
  it('pair (accessKey, token) → not 401/403', () => __awaiter(this, void 0, void 0, function* () {
57
- const r = yield (0, supertest_1.default)(buildApp())
53
+ const r = yield (0, loopbackRequest_1.default)(buildApp())
58
54
  .post('/register')
59
55
  .query({ type: 'add' })
60
56
  .set('x-xenon-access-key', nodeUser.accessKey)
@@ -64,7 +60,7 @@ describe('node pair auth — REST /register (integration)', function () {
64
60
  (0, chai_1.expect)(r.status).to.not.equal(403);
65
61
  }));
66
62
  it('no auth headers → 401', () => __awaiter(this, void 0, void 0, function* () {
67
- const r = yield (0, supertest_1.default)(buildApp())
63
+ const r = yield (0, loopbackRequest_1.default)(buildApp())
68
64
  .post('/register')
69
65
  .query({ type: 'add' })
70
66
  .send([]);
@@ -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 = __importDefault(require("express"));
51
- const supertest_1 = __importDefault(require("supertest"));
51
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
52
52
  const sinon_1 = __importDefault(require("sinon"));
53
53
  describe('POST /xenon/api/ports/allocate', () => {
54
54
  let app;
@@ -62,7 +62,7 @@ describe('POST /xenon/api/ports/allocate', () => {
62
62
  app.use('/xenon/api/ports', router.makeRouter({ allocator: { allocate: allocateStub } }));
63
63
  }));
64
64
  it('returns 200 with the allocated ports', () => __awaiter(void 0, void 0, void 0, function* () {
65
- const res = yield (0, supertest_1.default)(app)
65
+ const res = yield (0, loopbackRequest_1.default)(app)
66
66
  .post('/xenon/api/ports/allocate')
67
67
  .send({ udid: 'u1', host: 'h1', purposes: ['systemPort', 'chromedriverPort'], durationMs: 60000 });
68
68
  (0, chai_1.expect)(res.status).to.equal(200);
@@ -70,14 +70,14 @@ describe('POST /xenon/api/ports/allocate', () => {
70
70
  (0, chai_1.expect)(allocateStub.calledOnce).to.equal(true);
71
71
  }));
72
72
  it('returns 400 when purposes is missing or empty', () => __awaiter(void 0, void 0, void 0, function* () {
73
- const res = yield (0, supertest_1.default)(app)
73
+ const res = yield (0, loopbackRequest_1.default)(app)
74
74
  .post('/xenon/api/ports/allocate')
75
75
  .send({ udid: 'u1', host: 'h1', durationMs: 60000 });
76
76
  (0, chai_1.expect)(res.status).to.equal(400);
77
77
  }));
78
78
  it('returns 500 on allocator failure', () => __awaiter(void 0, void 0, void 0, function* () {
79
79
  allocateStub.rejects(new Error('boom'));
80
- const res = yield (0, supertest_1.default)(app)
80
+ const res = yield (0, loopbackRequest_1.default)(app)
81
81
  .post('/xenon/api/ports/allocate')
82
82
  .send({ udid: 'u1', host: 'h1', purposes: ['systemPort'], durationMs: 60000 });
83
83
  (0, chai_1.expect)(res.status).to.equal(500);
@@ -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 supertest_1 = __importDefault(require("supertest"));
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 prisma_1 = require("../../src/prisma");
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, supertest_1.default)(app)
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, supertest_1.default)(app).get('/profile/tokens').set('Cookie', cookie);
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, supertest_1.default)(app).get('/profile/access-key').set('Cookie', cookie);
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, supertest_1.default)(app)
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, supertest_1.default)(app).post('/profile/access-key/rotate').set('Cookie', cookie);
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, supertest_1.default)(app)
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, supertest_1.default)(app)
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 supertest_1 = __importDefault(require("supertest"));
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, supertest_1.default)(makeApp())
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, supertest_1.default)(makeApp())
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, supertest_1.default)(makeApp())
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, supertest_1.default)(app).post('/xenon/api/recordings').send({}).expect(400);
113
- yield (0, supertest_1.default)(app).post('/xenon/api/recordings').send({ udids: [] }).expect(400);
114
- yield (0, supertest_1.default)(app).post('/xenon/api/recordings').send({ udids: [123] }).expect(400);
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, supertest_1.default)(makeApp()).post('/xenon/api/recordings/g-1/stop');
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, supertest_1.default)(makeApp())
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, supertest_1.default)(makeApp())
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, supertest_1.default)(makeBearerApp())
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, supertest_1.default)(makeBearerApp())
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, supertest_1.default)(makeHeaderPairApp())
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, supertest_1.default)(makeHeaderPairApp())
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, supertest_1.default)(makeApp())
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, supertest_1.default)(makeApp())
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 supertest_1 = __importDefault(require("supertest"));
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, supertest_1.default)(app)
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 supertest_1 = __importDefault(require("supertest"));
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 prisma_1 = require("../../src/prisma");
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, supertest_1.default)(app)[c.method.toLowerCase()](c.path).set('Cookie', users[role].cookie);
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 supertest_1 = __importDefault(require("supertest"));
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, supertest_1.default)(app)
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, supertest_1.default)(app)
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, supertest_1.default)(bearerApp)
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, supertest_1.default)(app)
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, supertest_1.default)(app)
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, supertest_1.default)(app).post('/xenon/api/sdk/leases/lse_1/heartbeat');
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, supertest_1.default)(app)
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, supertest_1.default)(app)
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);