@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
@@ -0,0 +1,179 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ require("reflect-metadata");
16
+ const chai_1 = require("chai");
17
+ const sinon_1 = __importDefault(require("sinon"));
18
+ const typedi_1 = require("typedi");
19
+ const CommandInterceptor_1 = require("../../src/interceptors/CommandInterceptor");
20
+ const HealEtalonService_1 = require("../../src/services/healing/HealEtalonService");
21
+ const SelfHealingSwitch_1 = require("../../src/services/settings/SelfHealingSwitch");
22
+ const SessionManager_1 = require("../../src/sessions/SessionManager");
23
+ const event_manager_1 = require("../../src/dashboard/event-manager");
24
+ const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
25
+ const waitFor_1 = require("../../src/services/autowait/waitFor");
26
+ const container_registration_1 = require("../helpers/container-registration");
27
+ const scratch_database_1 = require("../helpers/scratch-database");
28
+ /**
29
+ * After a findElement finds its element, Xenon learns the selector's
30
+ * fingerprint (the element's attributes, position and path), which the
31
+ * Resilio and Fuzzy XML tiers heal it with later, on the server, without
32
+ * calling the AI provider.
33
+ *
34
+ * Through 2.14 it learnt only behind the dashboard's per-command record
35
+ * (`isHub && enableDashboard && SESSION_MANAGER.isValidSession`): never with
36
+ * `enableDashboard` off (the default), and never on a node. Those servers
37
+ * healed without fingerprints, so a broken selector went on to OCR, Visual AI
38
+ * and the LLM. Learning now follows the self-healing switch alone, on every
39
+ * session this server drives. The record itself is unchanged.
40
+ */
41
+ describe('selector learning, on every session this server drives', () => {
42
+ (0, scratch_database_1.useScratchDatabase)();
43
+ let restoreContainer;
44
+ let etalons;
45
+ let learn;
46
+ let record;
47
+ beforeEach(() => {
48
+ restoreContainer = (0, container_registration_1.saveRegistrations)(HealEtalonService_1.HealEtalonService, SelfHealingSwitch_1.SelfHealingSwitch);
49
+ // The switch as at boot with nothing saved: it follows the options.
50
+ typedi_1.Container.set(SelfHealingSwitch_1.SelfHealingSwitch, new SelfHealingSwitch_1.SelfHealingSwitch());
51
+ etalons = {
52
+ getSignature: sinon_1.default.stub().resolves(null),
53
+ saveSignature: sinon_1.default.stub().resolves(),
54
+ };
55
+ typedi_1.Container.set(HealEtalonService_1.HealEtalonService, etalons);
56
+ learn = sinon_1.default.spy(CommandInterceptor_1.CommandInterceptor.prototype, 'triggerLearning');
57
+ record = sinon_1.default.stub(event_manager_1.DASHBORD_EVENT_MANAGER, 'afterSessionCommand').resolves();
58
+ });
59
+ afterEach(() => {
60
+ sinon_1.default.restore();
61
+ restoreContainer();
62
+ });
63
+ const options = (over = {}) => (Object.assign(Object.assign(Object.assign({}, IPluginArgs_1.DefaultPluginArgs), { enableDashboard: false }), over));
64
+ let sessions = 0;
65
+ /** A session's driver, with what learning reads from a found element. */
66
+ function sessionDriver(caps = {}) {
67
+ sessions += 1;
68
+ return {
69
+ sessionId: `learn-${sessions}-${Date.now()}`,
70
+ caps: Object.assign({ platformName: 'Android' }, caps),
71
+ // As UiAutomator2 answers: String(value), so "null" for an unset one.
72
+ getAttribute: (name, _id) => __awaiter(this, void 0, void 0, function* () { var _a; return String((_a = { 'resource-id': 'com.shop:id/pay', text: 'Pay now' }[name]) !== null && _a !== void 0 ? _a : null); }),
73
+ getElementRect: () => __awaiter(this, void 0, void 0, function* () { return ({ x: 40, y: 900, width: 300, height: 96 }); }),
74
+ getName: () => __awaiter(this, void 0, void 0, function* () { return 'android.widget.Button'; }),
75
+ getPageSource: () => __awaiter(this, void 0, void 0, function* () { return '<hierarchy><android.widget.Button text="Pay now" resource-id="com.shop:id/pay"/></hierarchy>'; }),
76
+ };
77
+ }
78
+ const PAY = ['id', 'com.shop:id/pay'];
79
+ const FOUND = { ELEMENT: 'el-1', 'element-6066-11e4-a52e-4f735466cecf': 'el-1' };
80
+ /** A findElement the driver answers with its element. */
81
+ function find(driver_1, pluginArgs_1, _a) {
82
+ return __awaiter(this, arguments, void 0, function* (driver, pluginArgs, { isHub, command = 'findElement' }) {
83
+ const response = command === 'findElements' ? [FOUND] : FOUND;
84
+ return typedi_1.Container.get(CommandInterceptor_1.CommandInterceptor).handle(() => __awaiter(this, void 0, void 0, function* () { return response; }), driver, command, [...PAY, driver.sessionId], pluginArgs, isHub);
85
+ });
86
+ }
87
+ /** Learning runs in the background, after the find has answered. */
88
+ function learnt() {
89
+ return __awaiter(this, void 0, void 0, function* () {
90
+ const result = yield (0, waitFor_1.waitFor)(() => __awaiter(this, void 0, void 0, function* () { return (etalons.saveSignature.called ? true : null); }), {
91
+ timeoutMs: 2000,
92
+ intervalMs: 5,
93
+ });
94
+ return 'value' in result;
95
+ });
96
+ }
97
+ for (const [where, isHub] of [
98
+ ['a standalone server', true],
99
+ ["a node running its hub's session", false],
100
+ ]) {
101
+ it(`learns a found selector on ${where} with enableDashboard off`, () => __awaiter(void 0, void 0, void 0, function* () {
102
+ const driver = sessionDriver();
103
+ // The case: nothing registers it, as nothing registered it before.
104
+ (0, chai_1.expect)(SessionManager_1.SESSION_MANAGER.isValidSession(driver.sessionId)).to.equal(false);
105
+ const answer = yield find(driver, options(), { isHub });
106
+ (0, chai_1.expect)(answer).to.deep.equal(FOUND);
107
+ (0, chai_1.expect)(learn.calledOnce, 'learning started').to.equal(true);
108
+ (0, chai_1.expect)(yield learnt(), 'a fingerprint was saved').to.equal(true);
109
+ (0, chai_1.expect)(etalons.saveSignature.firstCall.args.slice(0, 2)).to.deep.equal(PAY);
110
+ // A fingerprint that says which element it is, which Fuzzy XML can use.
111
+ (0, chai_1.expect)(etalons.saveSignature.firstCall.args[2].attributes).to.deep.include({
112
+ name: 'resource-id',
113
+ value: 'com.shop:id/pay',
114
+ });
115
+ // The dashboard's record is still the dashboard's: nothing was recorded.
116
+ (0, chai_1.expect)(record.called).to.equal(false);
117
+ }));
118
+ }
119
+ it('learns a selector autowait found', () => __awaiter(void 0, void 0, void 0, function* () {
120
+ const driver = sessionDriver();
121
+ const pluginArgs = options({
122
+ autowait: {
123
+ enabled: true,
124
+ timeoutMs: 500,
125
+ intervalBetweenAttemptsMs: 10,
126
+ excludeEnabledCheck: [],
127
+ },
128
+ });
129
+ yield find(driver, pluginArgs, { isHub: true });
130
+ (0, chai_1.expect)(learn.calledOnce).to.equal(true);
131
+ (0, chai_1.expect)(yield learnt()).to.equal(true);
132
+ }));
133
+ it('learns once, and still records, where the dashboard records the session', () => __awaiter(void 0, void 0, void 0, function* () {
134
+ const driver = sessionDriver();
135
+ sinon_1.default.stub(SessionManager_1.SESSION_MANAGER, 'isValidSession').returns(true);
136
+ yield find(driver, options({ enableDashboard: true }), { isHub: true });
137
+ (0, chai_1.expect)(record.calledOnce).to.equal(true);
138
+ (0, chai_1.expect)(learn.calledOnce).to.equal(true);
139
+ (0, chai_1.expect)(yield learnt()).to.equal(true);
140
+ }));
141
+ it('learns nothing with self-healing off', () => __awaiter(void 0, void 0, void 0, function* () {
142
+ yield find(sessionDriver(), options({ enableSelfHealing: false }), { isHub: true });
143
+ (0, chai_1.expect)(learn.called).to.equal(false);
144
+ }));
145
+ it('learns nothing from a session that turned its own healing off', () => __awaiter(void 0, void 0, void 0, function* () {
146
+ const driver = sessionDriver({ 'xe:options': { healingTiers: [] } });
147
+ yield find(driver, options(), { isHub: true });
148
+ (0, chai_1.expect)(learn.called).to.equal(false);
149
+ }));
150
+ it('still learns from a session that limits its tiers', () => __awaiter(void 0, void 0, void 0, function* () {
151
+ const driver = sessionDriver({ 'xe:options': { healingTiers: [3] } });
152
+ yield find(driver, options(), { isHub: true });
153
+ (0, chai_1.expect)(learn.calledOnce).to.equal(true);
154
+ }));
155
+ it('learns nothing from findElements, as before', () => __awaiter(void 0, void 0, void 0, function* () {
156
+ yield find(sessionDriver(), options(), { isHub: true, command: 'findElements' });
157
+ (0, chai_1.expect)(learn.called).to.equal(false);
158
+ }));
159
+ it('answers the find even when deciding whether to learn throws', () => __awaiter(void 0, void 0, void 0, function* () {
160
+ sinon_1.default.stub(SelfHealingSwitch_1.SelfHealingSwitch.prototype, 'isEnabled').throws(new Error('switch unreadable'));
161
+ const answer = yield find(sessionDriver(), options(), { isHub: true });
162
+ (0, chai_1.expect)(answer).to.deep.equal(FOUND);
163
+ (0, chai_1.expect)(learn.called).to.equal(false);
164
+ }));
165
+ it('answers the find even when learning fails', () => __awaiter(void 0, void 0, void 0, function* () {
166
+ etalons.getSignature.rejects(new Error('database is locked'));
167
+ const driver = sessionDriver();
168
+ const answer = yield find(driver, options(), { isHub: true });
169
+ (0, chai_1.expect)(answer).to.deep.equal(FOUND);
170
+ // The background pass ends, and the session may learn again.
171
+ const settled = yield (0, waitFor_1.waitFor)(() => __awaiter(void 0, void 0, void 0, function* () {
172
+ return typedi_1.Container.get(CommandInterceptor_1.CommandInterceptor).learningSessions.has(driver.sessionId)
173
+ ? null
174
+ : true;
175
+ }), { timeoutMs: 2000, intervalMs: 5 });
176
+ (0, chai_1.expect)('value' in settled).to.equal(true);
177
+ (0, chai_1.expect)(etalons.saveSignature.called).to.equal(false);
178
+ }));
179
+ });
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ require("reflect-metadata");
16
+ const chai_1 = require("chai");
17
+ const sinon_1 = __importDefault(require("sinon"));
18
+ const scratch_database_1 = require("../helpers/scratch-database");
19
+ const SelectorVerificationJob_1 = require("../../src/services/SelectorVerificationJob");
20
+ const HOUR = 60 * 60 * 1000;
21
+ const ELEMENT = '{"value":{"element-6066-11e4-a52e-4f735466cecf":"e-1"},"sessionId":"s"}';
22
+ const ELEMENTS = '{"value":[{"element-6066-11e4-a52e-4f735466cecf":"e-1"}],"sessionId":"s"}';
23
+ const EMPTY_LIST = '{"value":[],"sessionId":"s"}';
24
+ const NOT_FOUND = '{"value":{"error":"An element could not be located on the page"},"sessionId":"s"}';
25
+ /**
26
+ * A clean build is one where the selector was found without healing. Through
27
+ * 2.14 it was any build with a find of the selector that didn't heal, so a
28
+ * selector whose find failed outright in three builds was verified as fixed.
29
+ * The rows are the ones the dashboard writes (event-manager.afterSessionCommand):
30
+ * a failed find has `is_error` and an error body, an empty findElements has
31
+ * `{"value":[]}`, and the real SQL runs against a scratch database.
32
+ */
33
+ describe('SelectorVerificationJob counts only builds where the selector was found', function () {
34
+ this.timeout(90000);
35
+ const scratch = (0, scratch_database_1.useScratchDatabase)();
36
+ let socket;
37
+ const now = Date.now();
38
+ const fixedAt = new Date(now - 48 * HOUR);
39
+ const builds = ['cb-b1', 'cb-b2', 'cb-b3'];
40
+ before(() => __awaiter(this, void 0, void 0, function* () {
41
+ for (const b of builds) {
42
+ yield scratch.db.build.create({ data: { id: b, name: b } });
43
+ yield scratch.db.session.create({
44
+ data: {
45
+ id: `cb-s-${b}`,
46
+ build_id: b,
47
+ device_udid: 'cb-phone',
48
+ device_platform: 'android',
49
+ device_version: '14',
50
+ desired_capabilities: '{}',
51
+ session_capabilities: '{}',
52
+ node_id: 'localhost',
53
+ has_live_video: false,
54
+ },
55
+ });
56
+ }
57
+ }));
58
+ beforeEach(() => __awaiter(this, void 0, void 0, function* () {
59
+ yield scratch.db.selectorEvent.deleteMany();
60
+ yield scratch.db.selectorState.deleteMany();
61
+ yield scratch.db.sessionLog.deleteMany();
62
+ socket = { emitToDashboard: sinon_1.default.stub() };
63
+ yield scratch.db.selectorState.create({
64
+ data: {
65
+ original_strategy: 'xpath',
66
+ original_selector: '//login',
67
+ status: 'pending',
68
+ fixed_at: fixedAt,
69
+ clean_builds_count: 0,
70
+ },
71
+ });
72
+ }));
73
+ const find = (build, command, response, isError = false) => scratch.db.sessionLog.create({
74
+ data: {
75
+ session_id: `cb-s-${build}`,
76
+ command_name: command,
77
+ url: command === 'findElement' ? '/findElement' : '/findElements',
78
+ method: 'POST',
79
+ title: 'Find element',
80
+ response,
81
+ is_success: !isError,
82
+ is_error: isError,
83
+ is_healed: false,
84
+ original_strategy: 'xpath',
85
+ original_selector: '//login',
86
+ createdAt: new Date(now - HOUR),
87
+ },
88
+ });
89
+ const run = () => new SelectorVerificationJob_1.SelectorVerificationJob(scratch.db, socket).run();
90
+ const row = () => scratch.db.selectorState.findUniqueOrThrow({
91
+ where: {
92
+ original_strategy_original_selector: {
93
+ original_strategy: 'xpath',
94
+ original_selector: '//login',
95
+ },
96
+ },
97
+ });
98
+ it('does not count builds where the find failed outright', () => __awaiter(this, void 0, void 0, function* () {
99
+ for (const b of builds)
100
+ yield find(b, 'findElement', NOT_FOUND, true);
101
+ yield run();
102
+ const state = yield row();
103
+ (0, chai_1.expect)(state.status).to.equal('pending');
104
+ (0, chai_1.expect)(state.clean_builds_count).to.equal(0);
105
+ }));
106
+ it('does not count builds where findElements found nothing', () => __awaiter(this, void 0, void 0, function* () {
107
+ for (const b of builds)
108
+ yield find(b, 'findElements', EMPTY_LIST);
109
+ yield run();
110
+ const state = yield row();
111
+ (0, chai_1.expect)(state.status).to.equal('pending');
112
+ (0, chai_1.expect)(state.clean_builds_count).to.equal(0);
113
+ }));
114
+ it('verifies the selector after three builds that found it', () => __awaiter(this, void 0, void 0, function* () {
115
+ yield find('cb-b1', 'findElement', ELEMENT);
116
+ yield find('cb-b2', 'findElements', ELEMENTS);
117
+ yield find('cb-b3', 'findElement', ELEMENT);
118
+ yield run();
119
+ (0, chai_1.expect)((yield row()).status).to.equal('resolved');
120
+ }));
121
+ it('counts a build that found it once, even if another find of it failed', () => __awaiter(this, void 0, void 0, function* () {
122
+ yield find('cb-b1', 'findElement', NOT_FOUND, true);
123
+ yield find('cb-b1', 'findElement', ELEMENT);
124
+ yield find('cb-b2', 'findElement', NOT_FOUND, true);
125
+ yield run();
126
+ const state = yield row();
127
+ (0, chai_1.expect)(state.status).to.equal('pending');
128
+ (0, chai_1.expect)(state.clean_builds_count).to.equal(1);
129
+ }));
130
+ });
@@ -65,9 +65,9 @@ describe('SelectorVerificationJob.run', () => {
65
65
  },
66
66
  ]);
67
67
  prismaStub.$queryRaw.resolves([
68
- { build_id: 'b-1', healed: 0 },
69
- { build_id: 'b-2', healed: 0 },
70
- { build_id: 'b-3', healed: 0 },
68
+ { build_id: 'b-1', healed: 0, found: 1 },
69
+ { build_id: 'b-2', healed: 0, found: 1 },
70
+ { build_id: 'b-3', healed: 0, found: 1 },
71
71
  ]);
72
72
  prismaStub.selectorState.update.resolves({
73
73
  id: 'r-1',
@@ -99,8 +99,8 @@ describe('SelectorVerificationJob.run', () => {
99
99
  },
100
100
  ]);
101
101
  prismaStub.$queryRaw.resolves([
102
- { build_id: 'b-1', healed: 0 },
103
- { build_id: 'b-2', healed: 1 }, // not clean
102
+ { build_id: 'b-1', healed: 0, found: 1 },
103
+ { build_id: 'b-2', healed: 1, found: 1 }, // not clean
104
104
  ]);
105
105
  prismaStub.selectorState.update.resolves({
106
106
  id: 'r-1',
@@ -125,8 +125,8 @@ describe('SelectorVerificationJob.run', () => {
125
125
  },
126
126
  ]);
127
127
  prismaStub.$queryRaw.resolves([
128
- { build_id: 'b-1', healed: 0 },
129
- { build_id: 'b-2', healed: 1 },
128
+ { build_id: 'b-1', healed: 0, found: 1 },
129
+ { build_id: 'b-2', healed: 1, found: 1 },
130
130
  ]);
131
131
  yield job.run();
132
132
  (0, chai_1.expect)(prismaStub.selectorState.update.called).to.be.false;
@@ -183,9 +183,9 @@ describe('SelectorVerificationJob.run', () => {
183
183
  ]);
184
184
  prismaStub.$queryRaw.onFirstCall().rejects(new Error('boom'));
185
185
  prismaStub.$queryRaw.onSecondCall().resolves([
186
- { build_id: 'b-1', healed: 0 },
187
- { build_id: 'b-2', healed: 0 },
188
- { build_id: 'b-3', healed: 0 },
186
+ { build_id: 'b-1', healed: 0, found: 1 },
187
+ { build_id: 'b-2', healed: 0, found: 1 },
188
+ { build_id: 'b-3', healed: 0, found: 1 },
189
189
  ]);
190
190
  prismaStub.selectorState.update.resolves({
191
191
  id: 'r-2',
@@ -222,9 +222,9 @@ describe('SelectorVerificationJob.run', () => {
222
222
  },
223
223
  ]);
224
224
  prismaStub.$queryRaw.resolves([
225
- { build_id: 'b-1', healed: 0 },
226
- { build_id: 'b-2', healed: 0 },
227
- { build_id: 'b-3', healed: 0 },
225
+ { build_id: 'b-1', healed: 0, found: 1 },
226
+ { build_id: 'b-2', healed: 0, found: 1 },
227
+ { build_id: 'b-3', healed: 0, found: 1 },
228
228
  ]);
229
229
  prismaStub.selectorState.update.resolves({
230
230
  id: 'r-1',
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ require("reflect-metadata");
16
+ const chai_1 = require("chai");
17
+ const sinon_1 = __importDefault(require("sinon"));
18
+ const typedi_1 = require("typedi");
19
+ const plugin_1 = require("../../src/plugin");
20
+ const ServerManager_1 = require("../../src/services/ServerManager");
21
+ const PluginContext_1 = require("../../src/PluginContext");
22
+ const SelfHealingSwitch_1 = require("../../src/services/settings/SelfHealingSwitch");
23
+ const aiEngineSettings_1 = require("../../src/services/settings/aiEngineSettings");
24
+ const logger_1 = __importDefault(require("../../src/logger"));
25
+ /**
26
+ * The self-healing value saved on the Settings page, and the AI engine page's
27
+ * provider, model and base URL, are read once at boot, so a restart doesn't put
28
+ * the server back on the options it was started with. They have to be loaded
29
+ * once the database is ready (migrations ran), after the startup options were
30
+ * applied (the AI engine's are what a cleared value goes back to), and before
31
+ * the first route can answer or the first command arrive.
32
+ */
33
+ describe('loading the saved self-healing and AI engine settings, at boot', () => {
34
+ let statics;
35
+ beforeEach(() => {
36
+ statics = {
37
+ nodeId: plugin_1.XenonPlugin.NODE_ID,
38
+ port: plugin_1.XenonPlugin.port,
39
+ basePath: plugin_1.XenonPlugin.nodeBasePath,
40
+ };
41
+ });
42
+ afterEach(() => {
43
+ sinon_1.default.restore();
44
+ plugin_1.XenonPlugin.NODE_ID = statics.nodeId;
45
+ plugin_1.XenonPlugin.port = statics.port;
46
+ plugin_1.XenonPlugin.nodeBasePath = statics.basePath;
47
+ });
48
+ it('runs once the database is ready, before routes are registered', () => __awaiter(void 0, void 0, void 0, function* () {
49
+ const steps = [];
50
+ const step = (name) => () => {
51
+ steps.push(name);
52
+ return Promise.resolve();
53
+ };
54
+ const boot = ServerManager_1.ServerManager.prototype;
55
+ sinon_1.default.stub(boot, 'resolvePluginArgs').resolves({});
56
+ sinon_1.default.stub(logger_1.default, 'banner');
57
+ sinon_1.default.stub(PluginContext_1.PluginContext.prototype, 'setContext');
58
+ sinon_1.default.stub(boot, 'syncDatabaseAndAIConfig').callsFake(step('database config'));
59
+ sinon_1.default.stub(boot, 'initializeCoreSubsystems').callsFake(step('database and migrations'));
60
+ // The real one kills every go-ios process on the machine.
61
+ sinon_1.default.stub(boot, 'reapLeftoverGoIos').callsFake(step('reap'));
62
+ sinon_1.default.stub(SelfHealingSwitch_1.SelfHealingSwitch.prototype, 'load').callsFake(step('self-healing setting'));
63
+ sinon_1.default.stub(aiEngineSettings_1.AiEngineSettings.prototype, 'load').callsFake(step('AI engine settings'));
64
+ sinon_1.default.stub(boot, 'registerRoutes').callsFake(() => {
65
+ steps.push('routes');
66
+ throw new Error('boot stopped by the test');
67
+ });
68
+ yield typedi_1.Container.get(ServerManager_1.ServerManager)
69
+ .updateServer({}, null, { port: 4723 })
70
+ .then(() => chai_1.expect.fail('the stubbed routes step should have stopped the boot'), (err) => (0, chai_1.expect)(err.message).to.equal('boot stopped by the test'));
71
+ (0, chai_1.expect)(steps).to.deep.equal([
72
+ 'database config',
73
+ 'database and migrations',
74
+ 'reap',
75
+ 'self-healing setting',
76
+ 'AI engine settings',
77
+ 'routes',
78
+ ]);
79
+ }));
80
+ });