@xenon-device-management/xenon 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/README.md +2 -1
  2. package/lib/package.json +1 -2
  3. package/lib/public/assets/{AnnotationOverlay-BmTPCAds.js → AnnotationOverlay-C-Pw_hvw.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-B5eB7f8B.js → ApiKeyGate-DmYQ0oSk.js} +1 -1
  5. package/lib/public/assets/BugReportButton-n8T1dFUp.js +10 -0
  6. package/lib/public/assets/{DeviceMosaicView-9DUoZnQi.js → DeviceMosaicView-D-rGOBbJ.js} +1 -1
  7. package/lib/public/assets/{EmptyState-lvyNiXev.js → EmptyState-DExETevp.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BduMPM9K.js → FieldGroup-CCdFlZm6.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-BSqX6I94.js → FilterMenu-DiUSOSL-.js} +1 -1
  10. package/lib/public/assets/{Menu-Ba5cT7jZ.js → Menu-ZOfpH6gh.js} +1 -1
  11. package/lib/public/assets/{Modal-CceoMY7D.js → Modal-CZNtoB-1.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-6T9dOidc.js → RecordingPage-BKp6h8Jl.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-qj6FdZXR.js → RecordingsPage-Bf881FX4.js} +1 -1
  14. package/lib/public/assets/{SegmentedControl-CSRuZ8TR.js → SegmentedControl-EUoyizvW.js} +1 -1
  15. package/lib/public/assets/{SettingCard-Bp1kz4By.js → SettingCard-ClT4hf4_.js} +1 -1
  16. package/lib/public/assets/{Table-LV7zFyZg.js → Table-ucR4bnmc.js} +1 -1
  17. package/lib/public/assets/{activity-D17TUloi.js → activity-K-hdylwq.js} +1 -1
  18. package/lib/public/assets/{ai-settings-qvbhY89P.js → ai-settings-BLhdwI3c.js} +1 -1
  19. package/lib/public/assets/{api-keys-BfpvdG7G.js → api-keys-BbiN7rBI.js} +1 -1
  20. package/lib/public/assets/{apps-Dbnzd0fW.js → apps-Dez1petz.js} +1 -1
  21. package/lib/public/assets/{arrow-left-DD1VjtC7.js → arrow-left-UjmFHLAJ.js} +1 -1
  22. package/lib/public/assets/{arrow-right-D0pnM3ma.js → arrow-right-BVyEigbu.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-BIC8JOV6.js → arrow-up-right-B-ZlBof3.js} +1 -1
  24. package/lib/public/assets/{auth-shell-DWHPXSnI.js → auth-shell-DAQjAgUv.js} +2 -2
  25. package/lib/public/assets/{builds-page-ClCNBUv_.js → builds-page-CmIPa5JK.js} +1 -1
  26. package/lib/public/assets/{button-xtAhYR-f.js → button-Dd-IhMC1.js} +1 -1
  27. package/lib/public/assets/{calendar-CupwJ7Bm.js → calendar-CAIeJwa5.js} +1 -1
  28. package/lib/public/assets/{check-CyuEoKkd.js → check-C4Jrjg8f.js} +1 -1
  29. package/lib/public/assets/{chevron-left-DgX-F9xK.js → chevron-left-BGzlf-4i.js} +1 -1
  30. package/lib/public/assets/{chevron-right-BKJgJnU9.js → chevron-right-_wYqDjDC.js} +1 -1
  31. package/lib/public/assets/{circle-check-CZdg8mMa.js → circle-check-Ckn00yMB.js} +1 -1
  32. package/lib/public/assets/{circle-x-CZH6t9z-.js → circle-x-pdVkomLa.js} +1 -1
  33. package/lib/public/assets/{clock-Cd-t7xpe.js → clock-B8vmwkHj.js} +1 -1
  34. package/lib/public/assets/{copy-C6zerX3I.js → copy-BAqnFI-F.js} +1 -1
  35. package/lib/public/assets/{device-explorer-B1pI6b_M.js → device-explorer-CYq_4F_N.js} +3 -3
  36. package/lib/public/assets/device-explorer-D2AqrrLN.css +1 -0
  37. package/lib/public/assets/{download-x9WP7mcL.js → download-B6UEAp_1.js} +1 -1
  38. package/lib/public/assets/{forgot-password-B0TEH4N-.js → forgot-password-CLqNvmrN.js} +1 -1
  39. package/lib/public/assets/{funnel-Cb40MfFN.js → funnel-Da90BBbT.js} +1 -1
  40. package/lib/public/assets/{index-BR1wTIPC.js → index-BuIIcmGp.js} +2 -2
  41. package/lib/public/assets/{index-BvH_ekRy.js → index-MX7IzqyW.js} +1 -1
  42. package/lib/public/assets/{input-DejcCiTI.js → input-Bq9aY-We.js} +1 -1
  43. package/lib/public/assets/{keycodes-Mavx5iGg.js → keycodes-CIhXIJyP.js} +1 -1
  44. package/lib/public/assets/{layers-BKqYLiHE.js → layers-bXjpswQI.js} +1 -1
  45. package/lib/public/assets/{lock-LFsFhc17.js → lock-pJ72y5Fy.js} +1 -1
  46. package/lib/public/assets/{login-x2pkzt-t.js → login-UOcHALV2.js} +1 -1
  47. package/lib/public/assets/{maintenance-settings-CYafcoV-.js → maintenance-settings-XnJgp5yb.js} +1 -1
  48. package/lib/public/assets/{monitor-_7_uJ8d9.js → monitor-QkAhI1Jx.js} +1 -1
  49. package/lib/public/assets/{mouse-pointer-2-BfLuURKN.js → mouse-pointer-2-B42Da0m4.js} +1 -1
  50. package/lib/public/assets/{network-Z_zBhpsD.js → network-CKVqcyp6.js} +1 -1
  51. package/lib/public/assets/{overview-BY5uv08S.js → overview-BORGkN3N.js} +1 -1
  52. package/lib/public/assets/{page-header-CP2UkykG.js → page-header-CKWFOjOI.js} +1 -1
  53. package/lib/public/assets/{plus-BTD0dnQ0.js → plus-BpFKVdC8.js} +1 -1
  54. package/lib/public/assets/{profile-page-BhJZuCS7.js → profile-page-CcbKBicY.js} +1 -1
  55. package/lib/public/assets/{reset-password-CMN0SO81.js → reset-password-BA0W9bxl.js} +1 -1
  56. package/lib/public/assets/{runbook-page-CBpPX9_5.js → runbook-page-Cp7ZBq3s.js} +1 -1
  57. package/lib/public/assets/{select-Uc9E5pye.js → select-DIZWIoVR.js} +1 -1
  58. package/lib/public/assets/{selector-detail-page-BSyBnRuU.js → selector-detail-page-C_4YuvAp.js} +1 -1
  59. package/lib/public/assets/{selector-health-DzqxNkzm.js → selector-health-CXjD_Xth.js} +1 -1
  60. package/lib/public/assets/{selector-health-page-CyPLjoD1.js → selector-health-page-CTqIHeoG.js} +1 -1
  61. package/lib/public/assets/{session-detail-page-BZQZN1P-.js → session-detail-page-B0KYkk8z.js} +1 -1
  62. package/lib/public/assets/{settings-CkSWiZq8.js → settings-BcRBKUXd.js} +1 -1
  63. package/lib/public/assets/{status-pill-outline-BFYni2qo.js → status-pill-outline-Dh2PXSf_.js} +1 -1
  64. package/lib/public/assets/{tablet-BSd-xSZK.js → tablet-_sXqZztt.js} +1 -1
  65. package/lib/public/assets/{teams-PpoEQz31.js → teams-DGHT8MKm.js} +1 -1
  66. package/lib/public/assets/{trash-2-BorJv5_u.js → trash-2-BV88cV3Z.js} +1 -1
  67. package/lib/public/assets/{upload-C3i7sAZl.js → upload-CIDH9wkj.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-CeZQ3EsH.js → use-password-reset-mode-DYGuSNwl.js} +1 -1
  69. package/lib/public/assets/{users-CEJ3fC78.js → users-7yviqflc.js} +1 -1
  70. package/lib/public/assets/{users-Drd7ifJx.js → users-BUE7MoTq.js} +1 -1
  71. package/lib/public/assets/{video-off-CNzn6bPE.js → video-off-BTRxtlGo.js} +1 -1
  72. package/lib/public/assets/{webhook-settings-BFqX6QXV.js → webhook-settings-DMKD9Pgm.js} +1 -1
  73. package/lib/public/assets/{zap-4yN8Jdfw.js → zap-CWE94qcK.js} +1 -1
  74. package/lib/public/index.html +1 -1
  75. package/lib/src/InternalHttpClient.js +5 -1
  76. package/lib/src/XenonCapabilityManager.js +15 -1
  77. package/lib/src/app/appiumBasePath.js +32 -0
  78. package/lib/src/app/index.js +12 -0
  79. package/lib/src/app/insertBeforeRoutes.js +24 -4
  80. package/lib/src/app/registerCommandAuth.js +152 -33
  81. package/lib/src/app/routers/control.js +57 -13
  82. package/lib/src/app/routers/grid.js +28 -8
  83. package/lib/src/app/ws/h264StreamWs.js +13 -8
  84. package/lib/src/app/ws/logcatWs.js +12 -7
  85. package/lib/src/app/ws/upgradeRouter.js +215 -0
  86. package/lib/src/dashboard/event-manager.js +7 -6
  87. package/lib/src/data-service/device-service.js +152 -49
  88. package/lib/src/data-service/device-store.js +81 -10
  89. package/lib/src/data-service/deviceClaims.js +98 -0
  90. package/lib/src/data-service/deviceFieldOwners.js +32 -10
  91. package/lib/src/data-service/prisma-store.js +94 -31
  92. package/lib/src/device-managers/AndroidDeviceManager.js +13 -15
  93. package/lib/src/device-managers/HealthMonitorService.js +17 -2
  94. package/lib/src/device-managers/IOSDeviceManager.js +5 -1
  95. package/lib/src/device-managers/NodeDevices.js +7 -2
  96. package/lib/src/device-managers/android/AndroidH264StreamService.js +3 -3
  97. package/lib/src/device-managers/android/AndroidStreamService.js +6 -5
  98. package/lib/src/device-managers/android/previewHold.js +2 -2
  99. package/lib/src/device-managers/ios/IOSDiscoveryService.js +19 -8
  100. package/lib/src/device-managers/ios/IOSStreamService.js +158 -38
  101. package/lib/src/device-managers/localDeviceHosts.js +86 -0
  102. package/lib/src/device-managers/ownDeviceRow.js +38 -0
  103. package/lib/src/device-utils.js +78 -24
  104. package/lib/src/gateway/createHandoff.js +73 -0
  105. package/lib/src/gateway/defaultGateway.js +80 -0
  106. package/lib/src/gateway/forwardToNode.js +147 -0
  107. package/lib/src/gateway/hubSessionToken.js +356 -0
  108. package/lib/src/gateway/internalCall.js +90 -0
  109. package/lib/src/gateway/nodeSessionStatus.js +140 -0
  110. package/lib/src/gateway/nodeWebDriverUrl.js +139 -0
  111. package/lib/src/gateway/sessionCreate.js +136 -0
  112. package/lib/src/gateway/sessionGateway.js +201 -0
  113. package/lib/src/gateway/sessionLocator.js +100 -0
  114. package/lib/src/generated/client/edge.js +7 -4
  115. package/lib/src/generated/client/index-browser.js +4 -1
  116. package/lib/src/generated/client/index.d.ts +112 -1
  117. package/lib/src/generated/client/index.js +7 -4
  118. package/lib/src/generated/client/package.json +1 -1
  119. package/lib/src/generated/client/schema.prisma +9 -0
  120. package/lib/src/generated/client/wasm.js +4 -1
  121. package/lib/src/helpers/index.js +5 -1
  122. package/lib/src/logger.js +5 -0
  123. package/lib/src/middleware/authMiddleware.js +80 -0
  124. package/lib/src/middleware/commandAuth.js +31 -17
  125. package/lib/src/middleware/csrfMiddleware.js +3 -2
  126. package/lib/src/middleware/sessionListingFilter.js +153 -0
  127. package/lib/src/middleware/sessionUpgradeGuard.js +203 -0
  128. package/lib/src/plugin.js +27 -1
  129. package/lib/src/prisma.js +2 -1
  130. package/lib/src/services/DeviceReconciler.js +5 -2
  131. package/lib/src/services/OrphanSweeper.js +12 -19
  132. package/lib/src/services/PortAllocator.js +75 -16
  133. package/lib/src/services/ServerManager.js +50 -30
  134. package/lib/src/services/SessionLifecycleService.js +335 -76
  135. package/lib/src/services/ShutdownCoordinator.js +7 -1
  136. package/lib/src/services/SocketServer.js +11 -2
  137. package/lib/src/services/device-access/LiveSessionOwners.js +53 -0
  138. package/lib/src/services/device-access/SessionOwnerResolver.js +74 -0
  139. package/lib/src/services/lease/LeaseOrphanSweeper.js +32 -6
  140. package/lib/src/services/lease/LeaseService.js +19 -5
  141. package/lib/src/services/session/nodeCreateCaps.js +55 -0
  142. package/lib/src/services/session/sessionCredentials.js +7 -59
  143. package/lib/src/sessions/CloudSession.js +12 -0
  144. package/lib/src/sessions/LocalSession.js +52 -26
  145. package/lib/src/sessions/RemoteSession.js +116 -9
  146. package/lib/src/sessions/SessionManager.js +49 -6
  147. package/lib/src/sessions/appiumUmbrella.js +32 -0
  148. package/lib/test/helpers/appium-umbrella.js +84 -0
  149. package/lib/test/helpers/fake-device-table.js +97 -0
  150. package/lib/test/helpers/loki-stores.js +41 -0
  151. package/lib/test/helpers/loopbackRequest.js +77 -0
  152. package/lib/test/helpers/scratch-database.js +100 -0
  153. package/lib/test/helpers/scratch-port-leases.js +119 -0
  154. package/lib/test/helpers/test-container.js +35 -27
  155. package/lib/test/integration/androidDevices.spec.js +5 -4
  156. package/lib/test/integration/ios/01iOSSimulator.spec.js +6 -5
  157. package/lib/test/integration/ios/02iOSDevices.spec.js +3 -2
  158. package/lib/test/integration/prisma-device-store-add-devices.spec.js +14 -4
  159. package/lib/test/unit/AndroidDeviceManager.spec.js +8 -1
  160. package/lib/test/unit/DeviceModel.spec.js +9 -9
  161. package/lib/test/unit/HealingCascade.test.js +5 -1
  162. package/lib/test/unit/OrphanSweeper.test.js +17 -13
  163. package/lib/test/unit/SessionHeartbeatWrite.test.js +6 -7
  164. package/lib/test/unit/StreamTicketService.spec.js +4 -1
  165. package/lib/test/unit/TokenEndpoint.spec.js +17 -3
  166. package/lib/test/unit/aggregate-hotspots-session-scope.spec.js +5 -5
  167. package/lib/test/unit/android-h264-service.spec.js +6 -0
  168. package/lib/test/unit/android-stream-stall-eof.spec.js +10 -0
  169. package/lib/test/unit/apps-team-visibility.spec.js +16 -16
  170. package/lib/test/unit/auth-me-auth-disabled.test.js +7 -4
  171. package/lib/test/unit/authMiddleware.appTicket.spec.js +5 -5
  172. package/lib/test/unit/authMiddleware.bearer.spec.js +4 -1
  173. package/lib/test/unit/authMiddleware.mcpAudience.spec.js +4 -1
  174. package/lib/test/unit/authMiddleware.streamTicket.spec.js +19 -7
  175. package/lib/test/unit/authMiddleware.test.js +3 -0
  176. package/lib/test/unit/bug-report/route.spec.js +9 -9
  177. package/lib/test/unit/cliargs-admin-only.spec.js +3 -3
  178. package/lib/test/unit/clipboard-route.spec.js +18 -8
  179. package/lib/test/unit/command-auth-appium-server.spec.js +187 -32
  180. package/lib/test/unit/command-auth.spec.js +31 -31
  181. package/lib/test/unit/control-device-lookup.spec.js +9 -9
  182. package/lib/test/unit/control-forward-decision.spec.js +115 -0
  183. package/lib/test/unit/control-lookup-failure.spec.js +3 -3
  184. package/lib/test/unit/create-error-message.spec.js +43 -0
  185. package/lib/test/unit/create-handoff.spec.js +86 -0
  186. package/lib/test/unit/dashboard-events-team-scope.spec.js +6 -3
  187. package/lib/test/unit/device-access-guard.spec.js +28 -28
  188. package/lib/test/unit/device-service.spec.js +4 -0
  189. package/lib/test/unit/device-store-clear-at-boot.spec.js +48 -0
  190. package/lib/test/unit/device-sync-lost-update.spec.js +17 -89
  191. package/lib/test/unit/device-team-guard.spec.js +20 -20
  192. package/lib/test/unit/device-utils.spec.js +8 -0
  193. package/lib/test/unit/extract-team-cap.spec.js +65 -0
  194. package/lib/test/unit/grid-admin-only.spec.js +3 -3
  195. package/lib/test/unit/healing-tier-gate.spec.js +5 -1
  196. package/lib/test/unit/health-monitor-own-phones.spec.js +102 -0
  197. package/lib/test/unit/health-monitor-stream-lookup.spec.js +11 -1
  198. package/lib/test/unit/helpers.spec.js +27 -0
  199. package/lib/test/unit/hub-boot-sweep-recovery.spec.js +192 -0
  200. package/lib/test/unit/hub-create-once.spec.js +190 -0
  201. package/lib/test/unit/hub-create-session-umbrella.spec.js +160 -0
  202. package/lib/test/unit/hub-gateway-create-appium-server.spec.js +591 -0
  203. package/lib/test/unit/hub-node-control-forward.spec.js +291 -0
  204. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +594 -0
  205. package/lib/test/unit/hub-node-race.spec.js +401 -0
  206. package/lib/test/unit/hub-node-same-host.spec.js +356 -0
  207. package/lib/test/unit/hub-register-removal.spec.js +165 -0
  208. package/lib/test/unit/hub-remote-session-heartbeat.spec.js +277 -0
  209. package/lib/test/unit/hub-routed-session-recovery.spec.js +151 -0
  210. package/lib/test/unit/hub-session-token.spec.js +265 -0
  211. package/lib/test/unit/insert-before-routes.spec.js +5 -5
  212. package/lib/test/unit/install-repository-app-team.spec.js +2 -2
  213. package/lib/test/unit/interceptor/MitmProxyHost.spec.js +23 -10
  214. package/lib/test/unit/internal-call.spec.js +181 -0
  215. package/lib/test/unit/ios-discovery-same-host.spec.js +193 -0
  216. package/lib/test/unit/ios-stream-appium-tunnel.spec.js +317 -0
  217. package/lib/test/unit/ios-stream-port-leases.spec.js +299 -0
  218. package/lib/test/unit/ios-wda-status-identity.spec.js +84 -0
  219. package/lib/test/unit/is-xenon-running.spec.js +69 -0
  220. package/lib/test/unit/lease/LeaseOrphanSweeper.spec.js +2 -2
  221. package/lib/test/unit/lease/LeaseService.spec.js +28 -1
  222. package/lib/test/unit/lease/lease-reap-held-phone.spec.js +154 -0
  223. package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +92 -0
  224. package/lib/test/unit/lease/lease-team-visibility.spec.js +2 -2
  225. package/lib/test/unit/lease/lease-token-log-filter.spec.js +242 -64
  226. package/lib/test/unit/lease/lease-token-oracle.spec.js +20 -7
  227. package/lib/test/unit/lease/session-lease-proof.spec.js +8 -3
  228. package/lib/test/unit/logcat-ws.spec.js +4 -1
  229. package/lib/test/unit/loopback-request.spec.js +81 -0
  230. package/lib/test/unit/node-create-caps.spec.js +54 -0
  231. package/lib/test/unit/node-gateway-create-appium-server.spec.js +354 -0
  232. package/lib/test/unit/node-hub-session-owner.spec.js +223 -0
  233. package/lib/test/unit/omni-vision.spec.js +7 -3
  234. package/lib/test/unit/plugin.spec.js +45 -4
  235. package/lib/test/unit/port-allocator-os-probe.spec.js +151 -0
  236. package/lib/test/unit/profile-router.test.js +11 -11
  237. package/lib/test/unit/queue-requester.spec.js +5 -0
  238. package/lib/test/unit/queue-team-scope-routes.spec.js +17 -16
  239. package/lib/test/unit/queue-visibility.spec.js +1 -1
  240. package/lib/test/unit/recording-downloads-visibility.spec.js +2 -2
  241. package/lib/test/unit/recording-instrumentation.spec.js +5 -0
  242. package/lib/test/unit/recording-source-ended.spec.js +5 -0
  243. package/lib/test/unit/recording-store.spec.js +9 -3
  244. package/lib/test/unit/recordings-library-routes.spec.js +51 -51
  245. package/lib/test/unit/recordings-write-visibility.spec.js +2 -2
  246. package/lib/test/unit/remote-session-create.spec.js +130 -0
  247. package/lib/test/unit/reservation-team-visibility.spec.js +5 -5
  248. package/lib/test/unit/reset-link.test.js +14 -14
  249. package/lib/test/unit/session-app-resolution.spec.js +14 -10
  250. package/lib/test/unit/session-assets.spec.js +9 -9
  251. package/lib/test/unit/session-attribution-wiring.spec.js +29 -2
  252. package/lib/test/unit/session-create-layer.spec.js +255 -0
  253. package/lib/test/unit/session-credential-strip.spec.js +25 -9
  254. package/lib/test/unit/session-credentials.spec.js +0 -47
  255. package/lib/test/unit/session-listing-auth.spec.js +345 -0
  256. package/lib/test/unit/session-locator.spec.js +250 -0
  257. package/lib/test/unit/session-node-calls.spec.js +174 -0
  258. package/lib/test/unit/session-owner-resolver.spec.js +107 -0
  259. package/lib/test/unit/session-recovery-routing.spec.js +118 -0
  260. package/lib/test/unit/session-upgrade-guard.spec.js +571 -0
  261. package/lib/test/unit/shutdown-drain-remote.spec.js +58 -0
  262. package/lib/test/unit/socketServer.bearer.spec.js +3 -1
  263. package/lib/test/unit/stream-leave-route.spec.js +15 -7
  264. package/lib/test/unit/stream-own-device-row.spec.js +164 -0
  265. package/lib/test/unit/stream-stop-ownership.spec.js +13 -5
  266. package/lib/test/unit/stream-stop-recording.spec.js +13 -5
  267. package/lib/test/unit/stream-ticket-identity.spec.js +15 -5
  268. package/lib/test/unit/upgrade-router.spec.js +446 -0
  269. package/lib/test/unit/users-router.test.js +11 -8
  270. package/lib/test/unit/verify-credential.spec.js +4 -1
  271. package/lib/test/unit/webdriver-info-route.spec.js +64 -0
  272. package/lib/test/unit/ws-upgrade-routing-appium-server.spec.js +500 -0
  273. package/lib/tsconfig.tsbuildinfo +1 -1
  274. package/package.json +1 -2
  275. package/prisma/migrations/20260929120000_device_claims/migration.sql +8 -0
  276. package/prisma/schema.prisma +9 -0
  277. package/lib/public/assets/BugReportButton-DKYsOdGV.js +0 -10
  278. package/lib/public/assets/device-explorer-DV8rWRxL.css +0 -1
  279. package/lib/src/proxy/wd-command-proxy.js +0 -221
@@ -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
  const grid_1 = __importDefault(require("../../src/app/routers/grid"));
21
21
  const authMiddleware_1 = require("../../src/middleware/authMiddleware");
@@ -120,24 +120,24 @@ describe('the session queue is team-scoped: own teams + a count', () => {
120
120
  afterEach(() => sinon_1.default.restore());
121
121
  describe('a member in team A', () => {
122
122
  it("GET /queue shows the request for her team's phone and her teammate's, nothing else", () => __awaiter(void 0, void 0, void 0, function* () {
123
- const r = yield (0, supertest_1.default)(alice()).get('/queue');
123
+ const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue');
124
124
  (0, chai_1.expect)(r.status).to.equal(200);
125
125
  (0, chai_1.expect)(ids(r.body)).to.deep.equal(['targets-a', 'untargeted-a']);
126
126
  (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('bob-secret-release');
127
127
  (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('bob-targeted');
128
128
  }));
129
129
  it('GET /queue never returns who asked', () => __awaiter(void 0, void 0, void 0, function* () {
130
- const r = yield (0, supertest_1.default)(alice()).get('/queue');
130
+ const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue');
131
131
  for (const shown of r.body)
132
132
  (0, chai_1.expect)(shown).to.not.have.property(REQUESTER);
133
133
  }));
134
134
  it('GET /queue/length is the whole queue, the number every caller gets', () => __awaiter(void 0, void 0, void 0, function* () {
135
- const r = yield (0, supertest_1.default)(alice()).get('/queue/length');
135
+ const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue/length');
136
136
  (0, chai_1.expect)(r.status).to.equal(200);
137
137
  (0, chai_1.expect)(r.body).to.equal(4);
138
138
  }));
139
139
  it('GET /queue/summary agrees with /queue/length, and counts what she cannot see', () => __awaiter(void 0, void 0, void 0, function* () {
140
- const r = yield (0, supertest_1.default)(alice()).get('/queue/summary');
140
+ const r = yield (0, loopbackRequest_1.default)(alice()).get('/queue/summary');
141
141
  (0, chai_1.expect)(r.status).to.equal(200);
142
142
  (0, chai_1.expect)(r.body.total).to.equal(4);
143
143
  (0, chai_1.expect)(r.body.otherCount).to.equal(2);
@@ -145,17 +145,17 @@ describe('the session queue is team-scoped: own teams + a count', () => {
145
145
  (0, chai_1.expect)(byPlatform.reduce((n, p) => n + p.count, 0)).to.equal(4);
146
146
  }));
147
147
  it("GET /queue/status answers for another team's request exactly as for an unknown id", () => __awaiter(void 0, void 0, void 0, function* () {
148
- const unknown = yield (0, supertest_1.default)(alice()).get('/queue/status/no-such-request');
148
+ const unknown = yield (0, loopbackRequest_1.default)(alice()).get('/queue/status/no-such-request');
149
149
  (0, chai_1.expect)(unknown.status).to.equal(404);
150
150
  for (const hidden of ['targets-b', 'untargeted-b']) {
151
- const r = yield (0, supertest_1.default)(alice()).get(`/queue/status/${hidden}`);
151
+ const r = yield (0, loopbackRequest_1.default)(alice()).get(`/queue/status/${hidden}`);
152
152
  (0, chai_1.expect)(r.status, hidden).to.equal(404);
153
153
  (0, chai_1.expect)(r.body, hidden).to.deep.equal(unknown.body);
154
154
  }
155
155
  }));
156
156
  it('GET /queue/status still answers for the requests she can see', () => __awaiter(void 0, void 0, void 0, function* () {
157
157
  for (const shown of ['targets-a', 'untargeted-a']) {
158
- const r = yield (0, supertest_1.default)(alice()).get(`/queue/status/${shown}`);
158
+ const r = yield (0, loopbackRequest_1.default)(alice()).get(`/queue/status/${shown}`);
159
159
  (0, chai_1.expect)(r.status, shown).to.equal(200);
160
160
  (0, chai_1.expect)(r.body.platform, shown).to.equal('android');
161
161
  }
@@ -163,25 +163,25 @@ describe('the session queue is team-scoped: own teams + a count', () => {
163
163
  });
164
164
  describe('an admin', () => {
165
165
  it('GET /queue shows all four', () => __awaiter(void 0, void 0, void 0, function* () {
166
- const r = yield (0, supertest_1.default)(admin()).get('/queue');
166
+ const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue');
167
167
  (0, chai_1.expect)(r.status).to.equal(200);
168
168
  (0, chai_1.expect)(ids(r.body)).to.deep.equal(['targets-a', 'targets-b', 'untargeted-a', 'untargeted-b']);
169
169
  for (const shown of r.body)
170
170
  (0, chai_1.expect)(shown).to.not.have.property(REQUESTER);
171
171
  }));
172
172
  it('with no lookup of phones or teams', () => __awaiter(void 0, void 0, void 0, function* () {
173
- yield (0, supertest_1.default)(admin()).get('/queue');
173
+ yield (0, loopbackRequest_1.default)(admin()).get('/queue');
174
174
  (0, chai_1.expect)(prisma_1.prisma.device.findMany.called).to.equal(false);
175
175
  (0, chai_1.expect)(prisma_1.prisma.teamMember.findMany.called).to.equal(false);
176
176
  }));
177
177
  it('GET /queue/summary counts nothing as hidden', () => __awaiter(void 0, void 0, void 0, function* () {
178
- const r = yield (0, supertest_1.default)(admin()).get('/queue/summary');
178
+ const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue/summary');
179
179
  (0, chai_1.expect)(r.body.total).to.equal(4);
180
180
  (0, chai_1.expect)(r.body.otherCount).to.equal(0);
181
181
  }));
182
182
  it('GET /queue/length and /queue/status answer as before', () => __awaiter(void 0, void 0, void 0, function* () {
183
- (0, chai_1.expect)((yield (0, supertest_1.default)(admin()).get('/queue/length')).body).to.equal(4);
184
- const r = yield (0, supertest_1.default)(admin()).get('/queue/status/untargeted-b');
183
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(admin()).get('/queue/length')).body).to.equal(4);
184
+ const r = yield (0, loopbackRequest_1.default)(admin()).get('/queue/status/untargeted-b');
185
185
  (0, chai_1.expect)(r.status).to.equal(200);
186
186
  }));
187
187
  });
@@ -195,13 +195,14 @@ describe('the session queue is team-scoped: own teams + a count', () => {
195
195
  ['an admin', admin],
196
196
  ]) {
197
197
  it(`are never returned to ${who}`, () => __awaiter(void 0, void 0, void 0, function* () {
198
- const r = yield (0, supertest_1.default)(caller()).get('/queue');
198
+ const r = yield (0, loopbackRequest_1.default)(caller()).get('/queue');
199
199
  const shown = r.body.find((x) => x.capability_id === 'untargeted-a');
200
200
  (0, chai_1.expect)(shown, 'the request itself is shown').to.not.equal(undefined);
201
201
  (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('amy-api-token');
202
202
  (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('amy.session.jwt');
203
- // What isn't a secret still reads as before.
204
- (0, chai_1.expect)(shown['xe:options'].accessKey).to.equal('ak_amy');
203
+ // Nor the access key: half of the pair, and it names Amy's key.
204
+ (0, chai_1.expect)(JSON.stringify(r.body)).to.not.include('ak_amy');
205
+ // What isn't a credential still reads as before.
205
206
  (0, chai_1.expect)(shown['xenon:options'].sessionName).to.equal('amy-login');
206
207
  }));
207
208
  }
@@ -102,7 +102,7 @@ describe('queue visibility', () => {
102
102
  (0, chai_1.expect)(yield seen([untargeted, targeted], alice)).to.deep.equal([false, false]);
103
103
  }));
104
104
  it('a credential narrowed to her team, whatever teams its user is in', () => __awaiter(void 0, void 0, void 0, function* () {
105
- // A team-bound key, a session token's teamId claim, or xenon:team.
105
+ // A team-bound key, a session token's teamId claim, or xe:options.team.
106
106
  (0, chai_1.expect)(yield seen([req({ userId: 'bob', teamId: 'team-a' })], alice)).to.deep.equal([true]);
107
107
  }));
108
108
  it("not a teammate's request narrowed to another team", () => __awaiter(void 0, void 0, void 0, function* () {
@@ -49,7 +49,7 @@ require("reflect-metadata");
49
49
  const chai_1 = require("chai");
50
50
  const sinon_1 = __importDefault(require("sinon"));
51
51
  const express_1 = __importDefault(require("express"));
52
- const supertest_1 = __importDefault(require("supertest"));
52
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
53
53
  const fs = __importStar(require("fs"));
54
54
  const os = __importStar(require("os"));
55
55
  const path = __importStar(require("path"));
@@ -104,7 +104,7 @@ function binary(res, cb) {
104
104
  }
105
105
  function get(caller_1, url_1) {
106
106
  return __awaiter(this, arguments, void 0, function* (caller, url, headers = {}) {
107
- let req = (0, supertest_1.default)(buildApp(caller)).get(`/xenon/api/recordings/${GROUP}/${url}`);
107
+ let req = (0, loopbackRequest_1.default)(buildApp(caller)).get(`/xenon/api/recordings/${GROUP}/${url}`);
108
108
  for (const [k, v] of Object.entries(headers))
109
109
  req = req.set(k, v);
110
110
  const res = yield req.buffer(true).parse(binary);
@@ -21,6 +21,7 @@ const path_1 = __importDefault(require("path"));
21
21
  const api_1 = require("@opentelemetry/api");
22
22
  const attributes_1 = require("../../src/services/telemetry/attributes");
23
23
  const artifact_store_1 = require("../helpers/artifact-store");
24
+ const loki_stores_1 = require("../helpers/loki-stores");
24
25
  // Stub metrics + trace BEFORE the module loads so the module-level
25
26
  // ensureOtelInstruments() picks up our mocks. Inline import after stubbing.
26
27
  function makeMockMeter() {
@@ -56,6 +57,10 @@ function makeMockSpan() {
56
57
  };
57
58
  }
58
59
  describe('RecordingOrchestrator instrumentation', () => {
60
+ // The in-memory stores, also when this file runs on its own: without
61
+ // NODE_ENV=test the factory handed out the Prisma stores, which are the
62
+ // developer's ~/.cache/xenon/xenon.db.
63
+ (0, loki_stores_1.useLokiStores)();
59
64
  // RecordingOrchestrator resolves ARTIFACT_STORE from the container at runtime.
60
65
  (0, artifact_store_1.useArtifactStore)();
61
66
  let getMeterStub;
@@ -21,6 +21,7 @@ const path_1 = __importDefault(require("path"));
21
21
  const RecordingOrchestrator_1 = require("../../src/services/recording/RecordingOrchestrator");
22
22
  const concurrency_gate_1 = require("../../src/services/recording/concurrency-gate");
23
23
  const artifact_store_1 = require("../helpers/artifact-store");
24
+ const loki_stores_1 = require("../helpers/loki-stores");
24
25
  // Issue #203. When the ffmpeg process for a recording exits on its own nothing
25
26
  // told the orchestrator, so the row sat at RECORDING — no ended_at, no duration,
26
27
  // no size — until a human clicked Stop. Observed live during the #200 cable-pull
@@ -69,6 +70,10 @@ function makeOrch(row, overrides = {}) {
69
70
  return { orch, store, gate, videoPipeline, eventMgr, unblockDeviceFn, probeDurationMsFn };
70
71
  }
71
72
  describe('RecordingOrchestrator: ffmpeg exiting on its own', () => {
73
+ // The in-memory stores, also when this file runs on its own: without
74
+ // NODE_ENV=test the factory handed out the Prisma stores, which are the
75
+ // developer's ~/.cache/xenon/xenon.db.
76
+ (0, loki_stores_1.useLokiStores)();
72
77
  // compositeOutputPath and per-device paths resolve through ArtifactStore.
73
78
  (0, artifact_store_1.useArtifactStore)();
74
79
  afterEach(() => sinon_1.default.restore());
@@ -13,12 +13,18 @@ require("reflect-metadata");
13
13
  const chai_1 = require("chai");
14
14
  const recording_store_1 = require("../../src/services/recording/recording-store");
15
15
  const prisma_1 = require("../../src/prisma");
16
+ const scratch_database_1 = require("../helpers/scratch-database");
16
17
  describe('RecordingStore (Prisma round-trip)', () => {
18
+ // Its own migrated database: the round trip wrote these rows to the
19
+ // developer's ~/.cache/xenon/xenon.db, and failed where it lacked the tables.
20
+ const scratch = (0, scratch_database_1.useScratchDatabase)();
17
21
  const store = new recording_store_1.RecordingStore();
22
+ // Through the scratch client itself: the helper's own afterEach, which
23
+ // runs first, has already put `prisma` back on the server's database.
18
24
  afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
19
- yield prisma_1.prisma.annotation.deleteMany({ where: { recording_id: { contains: 'test-' } } });
20
- yield prisma_1.prisma.bookmark.deleteMany({ where: { recording_id: { contains: 'test-' } } });
21
- yield prisma_1.prisma.recording.deleteMany({ where: { group_id: { startsWith: 'test-' } } });
25
+ yield scratch.db.annotation.deleteMany({ where: { recording_id: { contains: 'test-' } } });
26
+ yield scratch.db.bookmark.deleteMany({ where: { recording_id: { contains: 'test-' } } });
27
+ yield scratch.db.recording.deleteMany({ where: { group_id: { startsWith: 'test-' } } });
22
28
  }));
23
29
  it('creates a recording row with status=RECORDING', () => __awaiter(void 0, void 0, void 0, function* () {
24
30
  const rec = yield store.create({
@@ -49,7 +49,7 @@ require("reflect-metadata");
49
49
  const chai_1 = require("chai");
50
50
  const sinon_1 = __importDefault(require("sinon"));
51
51
  const express_1 = __importDefault(require("express"));
52
- const supertest_1 = __importDefault(require("supertest"));
52
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
53
53
  const fs = __importStar(require("fs"));
54
54
  const os = __importStar(require("os"));
55
55
  const path = __importStar(require("path"));
@@ -198,7 +198,7 @@ describe('recordings library routes', () => {
198
198
  rec({ id: 'b', group_id: 'g2', started_at: new Date(T0 + 1000) }),
199
199
  rec({ id: 'c', group_id: 'g3', device_udid: 'HIDDEN', started_by: 'usr_carol' }),
200
200
  ];
201
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
201
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
202
202
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
203
203
  (0, chai_1.expect)(res.body.recordings.map((s) => s.groupId)).to.deep.equal(['g2', 'g1']);
204
204
  (0, chai_1.expect)(res.body.recordings[0].phones[0].name).to.equal('Galaxy S9+');
@@ -211,15 +211,15 @@ describe('recordings library routes', () => {
211
211
  rec({ id: 'a', group_id: 'g1' }),
212
212
  rec({ id: 'b', group_id: 'g2', device_udid: 'U2', started_at: new Date(T0 + 1000) }),
213
213
  ];
214
- const one = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings?limit=1');
214
+ const one = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings?limit=1');
215
215
  (0, chai_1.expect)(one.body.recordings.map((s) => s.groupId)).to.deep.equal(['g2']);
216
- const two = yield (0, supertest_1.default)(buildApp(alice)).get(`/xenon/api/recordings?limit=1&cursor=${encodeURIComponent(one.body.nextCursor)}`);
216
+ const two = yield (0, loopbackRequest_1.default)(buildApp(alice)).get(`/xenon/api/recordings?limit=1&cursor=${encodeURIComponent(one.body.nextCursor)}`);
217
217
  (0, chai_1.expect)(two.body.recordings.map((s) => s.groupId)).to.deep.equal(['g1']);
218
- const byPhone = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings?udid=U2');
218
+ const byPhone = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings?udid=U2');
219
219
  (0, chai_1.expect)(byPhone.body.recordings.map((s) => s.groupId)).to.deep.equal(['g2']);
220
220
  }));
221
221
  it('answers 400 for a bad query', () => __awaiter(void 0, void 0, void 0, function* () {
222
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings?since=nope');
222
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings?since=nope');
223
223
  (0, chai_1.expect)(res.status).to.equal(400);
224
224
  (0, chai_1.expect)(res.body.error).to.equal('since must be an ISO time');
225
225
  }));
@@ -238,7 +238,7 @@ describe('recordings library routes', () => {
238
238
  started_at: new Date(now - 40 * 24 * 60 * 60 * 1000),
239
239
  }),
240
240
  ];
241
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
241
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
242
242
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
243
243
  (0, chai_1.expect)(res.body.facets.phones).to.deep.equal([
244
244
  { udid: 'U1', name: 'Galaxy S9+', count: 1 },
@@ -250,7 +250,7 @@ describe('recordings library routes', () => {
250
250
  }));
251
251
  it('names the synthetic user of an auth-disabled server "Auth disabled"', () => __awaiter(void 0, void 0, void 0, function* () {
252
252
  rows = [rec({ started_by: 'auth-disabled' })];
253
- const res = yield (0, supertest_1.default)(buildApp(admin)).get('/xenon/api/recordings');
253
+ const res = yield (0, loopbackRequest_1.default)(buildApp(admin)).get('/xenon/api/recordings');
254
254
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
255
255
  (0, chai_1.expect)(res.body.recordings[0].startedBy).to.deep.equal({
256
256
  id: 'auth-disabled',
@@ -266,7 +266,7 @@ describe('recordings library routes', () => {
266
266
  ctx.pluginArgs = Object.assign(Object.assign({}, original), { recordingCleanupDays: 7, recordingCleanupMaxCount: 20 });
267
267
  try {
268
268
  rows = [rec()];
269
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
269
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
270
270
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
271
271
  (0, chai_1.expect)(res.body.retention).to.deep.equal({ days: 7, maxCount: 20 });
272
272
  }
@@ -296,7 +296,7 @@ describe('recordings library routes', () => {
296
296
  ],
297
297
  }),
298
298
  ];
299
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
299
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
300
300
  (0, chai_1.expect)(res.status).to.equal(200);
301
301
  (0, chai_1.expect)(res.body.summary.groupId).to.equal('g1');
302
302
  (0, chai_1.expect)(res.body.recordings).to.have.length(1);
@@ -312,9 +312,9 @@ describe('recordings library routes', () => {
312
312
  });
313
313
  }));
314
314
  it('answers 404 for a group that does not exist or is not visible', () => __awaiter(void 0, void 0, void 0, function* () {
315
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(admin)).get('/xenon/api/recordings/nope')).status).to.equal(404);
315
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(admin)).get('/xenon/api/recordings/nope')).status).to.equal(404);
316
316
  rows = [rec({ device_udid: 'HIDDEN' })];
317
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
317
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
318
318
  }));
319
319
  it('sorts bookmarks and marks by timecode across every recording in the group', () => __awaiter(void 0, void 0, void 0, function* () {
320
320
  rows = [
@@ -357,7 +357,7 @@ describe('recordings library routes', () => {
357
357
  ],
358
358
  }),
359
359
  ];
360
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
360
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
361
361
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
362
362
  (0, chai_1.expect)(res.body.bookmarks.map((b) => b.id)).to.deep.equal(['b1', 'b2']);
363
363
  (0, chai_1.expect)(res.body.annotations.map((a) => a.id)).to.deep.equal(['a1', 'a2']);
@@ -388,8 +388,8 @@ describe('recordings library routes', () => {
388
388
  });
389
389
  /** hasComposite as the list, then the detail, reports it. */
390
390
  const reported = (who) => __awaiter(void 0, void 0, void 0, function* () {
391
- const list = yield (0, supertest_1.default)(buildApp(who)).get('/xenon/api/recordings');
392
- const detail = yield (0, supertest_1.default)(buildApp(who)).get('/xenon/api/recordings/g1');
391
+ const list = yield (0, loopbackRequest_1.default)(buildApp(who)).get('/xenon/api/recordings');
392
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(who)).get('/xenon/api/recordings/g1');
393
393
  (0, chai_1.expect)(list.status, JSON.stringify(list.body)).to.equal(200);
394
394
  (0, chai_1.expect)(detail.status, JSON.stringify(detail.body)).to.equal(200);
395
395
  return [list.body.recordings[0].hasComposite, detail.body.summary.hasComposite];
@@ -419,7 +419,7 @@ describe('recordings library routes', () => {
419
419
  });
420
420
  });
421
421
  const enabled = (who) => __awaiter(void 0, void 0, void 0, function* () {
422
- const res = yield (0, supertest_1.default)(buildApp(who)).get('/xenon/api/recordings/active');
422
+ const res = yield (0, loopbackRequest_1.default)(buildApp(who)).get('/xenon/api/recordings/active');
423
423
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
424
424
  return res.body.groups.map((g) => [g.groupId, g.compositeEnabled]);
425
425
  });
@@ -435,7 +435,7 @@ describe('recordings library routes', () => {
435
435
  devices.set('U2', { name: 'Team B phone', platform: 'android' });
436
436
  visible = new Set(['U1']);
437
437
  rows = rows.map((r) => (Object.assign(Object.assign({}, r), { annotations: [{ id: `a-${r.id}`, t_ms: 1, kind: 'rect', color: '#f00', payload: '{}' }] })));
438
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/active');
438
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/active');
439
439
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
440
440
  const [group] = res.body.groups;
441
441
  (0, chai_1.expect)(group.recordings.map((r) => r.udid)).to.deep.equal(['U1']);
@@ -452,14 +452,14 @@ describe('recordings library routes', () => {
452
452
  });
453
453
  }),
454
454
  });
455
- const res = yield (0, supertest_1.default)(buildApp(admin)).get('/xenon/api/recordings/active');
455
+ const res = yield (0, loopbackRequest_1.default)(buildApp(admin)).get('/xenon/api/recordings/active');
456
456
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
457
457
  (0, chai_1.expect)(res.body.groups[0].recordings.map((r) => r.udid)).to.deep.equal(['U1', 'U2']);
458
458
  }));
459
459
  });
460
460
  });
461
461
  describe('DELETE /recordings/:groupId', () => {
462
- const del = (who) => (0, supertest_1.default)(buildApp(who)).delete('/xenon/api/recordings/g1');
462
+ const del = (who) => (0, loopbackRequest_1.default)(buildApp(who)).delete('/xenon/api/recordings/g1');
463
463
  it('lets the person who recorded it delete it: rows, then files', () => __awaiter(void 0, void 0, void 0, function* () {
464
464
  rows = [
465
465
  rec(),
@@ -596,20 +596,20 @@ describe('recordings library routes', () => {
596
596
  const unplugged = (over = {}) => rec(Object.assign({ id: 'r2', device_udid: 'GONE', started_at: new Date(T0 + 500) }, over));
597
597
  it('still lists it, and opens it with every phone', () => __awaiter(void 0, void 0, void 0, function* () {
598
598
  rows = [rec({ group_id: 'gone', id: 'solo', device_udid: 'GONE' }), rec(), unplugged()];
599
- const list = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
599
+ const list = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
600
600
  (0, chai_1.expect)(list.status, JSON.stringify(list.body)).to.equal(200);
601
601
  (0, chai_1.expect)(list.body.recordings.map((s) => s.groupId).sort()).to.deep.equal(['g1', 'gone']);
602
602
  const g1 = list.body.recordings.find((s) => s.groupId === 'g1');
603
603
  (0, chai_1.expect)(g1.phones.map((p) => p.udid)).to.deep.equal(['U1', 'GONE']);
604
- const detail = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/gone');
604
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/gone');
605
605
  (0, chai_1.expect)(detail.status, JSON.stringify(detail.body)).to.equal(200);
606
606
  (0, chai_1.expect)(detail.body.summary.phones.map((p) => p.udid)).to.deep.equal(['GONE']);
607
- const both = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
607
+ const both = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
608
608
  (0, chai_1.expect)(both.body.summary.phones.map((p) => p.udid)).to.deep.equal(['U1', 'GONE']);
609
609
  }));
610
610
  it('can still delete it', () => __awaiter(void 0, void 0, void 0, function* () {
611
611
  rows = [rec({ device_udid: 'GONE' })];
612
- const res = yield (0, supertest_1.default)(buildApp(alice)).delete('/xenon/api/recordings/g1');
612
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).delete('/xenon/api/recordings/g1');
613
613
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(204);
614
614
  (0, chai_1.expect)(store.deleteGroupRows.calledOnceWith('g1')).to.equal(true);
615
615
  }));
@@ -617,7 +617,7 @@ describe('recordings library routes', () => {
617
617
  const file = path.join(dir, 'r2.mp4');
618
618
  fs.writeFileSync(file, 'x');
619
619
  rows = [rec(), unplugged({ file_path: file })];
620
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
620
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
621
621
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
622
622
  }));
623
623
  it('is the person who started it, not someone who added a phone later', () => __awaiter(void 0, void 0, void 0, function* () {
@@ -630,14 +630,14 @@ describe('recordings library routes', () => {
630
630
  unplugged(),
631
631
  rec({ id: 'r3', started_by: 'usr_bob', started_at: new Date(T0 + 900) }),
632
632
  ];
633
- const asAlice = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
633
+ const asAlice = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
634
634
  (0, chai_1.expect)(asAlice.body.summary.phones.map((p) => p.recordingId).sort()).to.deep.equal([
635
635
  'r0',
636
636
  'r2',
637
637
  'r3',
638
638
  'rn',
639
639
  ]);
640
- const asBob = yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
640
+ const asBob = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
641
641
  (0, chai_1.expect)(asBob.body.summary.phones.map((p) => p.recordingId).sort()).to.deep.equal([
642
642
  'r0',
643
643
  'r3',
@@ -650,9 +650,9 @@ describe('recordings library routes', () => {
650
650
  const file = path.join(dir, 'r2.mp4');
651
651
  fs.writeFileSync(file, 'x');
652
652
  rows = [rec(), unplugged({ started_by: 'usr_bob', file_path: file })];
653
- const detail = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
653
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
654
654
  (0, chai_1.expect)(detail.body.summary.phones.map((p) => p.recordingId)).to.deep.equal(['r1']);
655
- const src = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
655
+ const src = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
656
656
  (0, chai_1.expect)(src.status).to.equal(404);
657
657
  }));
658
658
  // The exemption is for a phone that is gone, not for one that moved to
@@ -662,13 +662,13 @@ describe('recordings library routes', () => {
662
662
  fs.writeFileSync(file, 'x');
663
663
  devices.set('OTHER', { name: 'Team B phone', platform: 'android' });
664
664
  rows = [rec(), rec({ id: 'r2', device_udid: 'OTHER', file_path: file })];
665
- const list = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
665
+ const list = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings');
666
666
  (0, chai_1.expect)(list.status, JSON.stringify(list.body)).to.equal(200);
667
667
  (0, chai_1.expect)(list.body.recordings[0].phones.map((p) => p.udid)).to.deep.equal(['U1']);
668
- const detail = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
668
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1');
669
669
  (0, chai_1.expect)(detail.body.summary.phones.map((p) => p.udid)).to.deep.equal(['U1']);
670
670
  (0, chai_1.expect)(detail.body.recordings.map((r) => r.id)).to.deep.equal(['r1']);
671
- const src = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
671
+ const src = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r2');
672
672
  (0, chai_1.expect)(src.status).to.equal(404);
673
673
  }));
674
674
  // The Delete button shows for the group's owner. A member who only added
@@ -679,23 +679,23 @@ describe('recordings library routes', () => {
679
679
  rec({ device_udid: 'HIDDEN' }),
680
680
  rec({ id: 'r2', started_by: 'usr_bob', started_at: new Date(T0 + 500) }),
681
681
  ];
682
- const list = yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
682
+ const list = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
683
683
  (0, chai_1.expect)(list.body.recordings[0].phones.map((p) => p.recordingId)).to.deep.equal(['r2']);
684
684
  (0, chai_1.expect)(list.body.recordings[0].startedBy).to.deep.equal({ id: 'usr_alice', name: 'Alice' });
685
- const detail = yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
685
+ const detail = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1');
686
686
  (0, chai_1.expect)(detail.body.summary.startedBy).to.deep.equal({ id: 'usr_alice', name: 'Alice' });
687
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(403);
687
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(403);
688
688
  }));
689
689
  it('does not open it to another member: 404 everywhere', () => __awaiter(void 0, void 0, void 0, function* () {
690
690
  const file = path.join(dir, 'r1.mp4');
691
691
  fs.writeFileSync(file, 'x');
692
692
  rows = [rec({ device_udid: 'GONE', file_path: file })];
693
- const list = yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
693
+ const list = yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings');
694
694
  (0, chai_1.expect)(list.body.recordings).to.deep.equal([]);
695
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
696
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1'))
695
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1')).status).to.equal(404);
696
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1'))
697
697
  .status).to.equal(404);
698
- (0, chai_1.expect)((yield (0, supertest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(404);
698
+ (0, chai_1.expect)((yield (0, loopbackRequest_1.default)(buildApp(bob)).delete('/xenon/api/recordings/g1')).status).to.equal(404);
699
699
  (0, chai_1.expect)(store.deleteGroupRows.called).to.equal(false);
700
700
  }));
701
701
  });
@@ -738,7 +738,7 @@ describe('recordings library routes', () => {
738
738
  { id: 'r-a', udid: 'U1', status: 'STOPPED', durationMs: 1000, sizeBytes: 10 },
739
739
  { id: 'r-b', udid: 'U2', status: 'STOPPED', durationMs: 2000, sizeBytes: 20 },
740
740
  ]));
741
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
741
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
742
742
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
743
743
  (0, chai_1.expect)(res.body.groupId).to.equal('g1');
744
744
  (0, chai_1.expect)(res.body.recordings.map((r) => r.id)).to.deep.equal(['r-a']);
@@ -752,7 +752,7 @@ describe('recordings library routes', () => {
752
752
  { id: 'r-a', udid: 'U1', status: 'STOPPED' },
753
753
  { id: 'r-b', udid: 'U2', status: 'STOPPED' },
754
754
  ]));
755
- const res = yield (0, supertest_1.default)(buildApp(admin)).post('/xenon/api/recordings/g1/stop');
755
+ const res = yield (0, loopbackRequest_1.default)(buildApp(admin)).post('/xenon/api/recordings/g1/stop');
756
756
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
757
757
  (0, chai_1.expect)(res.body.recordings.map((r) => r.id).sort()).to.deep.equal(['r-a', 'r-b']);
758
758
  }));
@@ -773,7 +773,7 @@ describe('recordings library routes', () => {
773
773
  { id: 'r-a', udid: 'U1', status: 'STOPPED' },
774
774
  { id: 'r-b', udid: 'U2', status: 'STOPPED' },
775
775
  ]));
776
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
776
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
777
777
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
778
778
  (0, chai_1.expect)(res.body.recordings.map((r) => r.id).sort()).to.deep.equal(['r-a', 'r-b']);
779
779
  }));
@@ -786,21 +786,21 @@ describe('recordings library routes', () => {
786
786
  { id: 'r-a', udid: 'U1', status: 'STOPPED' },
787
787
  { id: 'r-b', udid: 'U2', status: 'STOPPED' },
788
788
  ]));
789
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
789
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
790
790
  (0, chai_1.expect)(res.body.recordings.map((r) => r.id)).to.deep.equal(['r-a']);
791
791
  }));
792
792
  it('still 500s when the orchestrator throws, without ever filtering', () => __awaiter(void 0, void 0, void 0, function* () {
793
793
  twoTeamRows();
794
794
  visible = new Set(['U1']);
795
795
  stop.rejects(new Error('ffmpeg exploded'));
796
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
796
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
797
797
  (0, chai_1.expect)(res.status).to.equal(500);
798
798
  (0, chai_1.expect)(res.body).to.deep.equal({ error: 'internal', message: 'ffmpeg exploded' });
799
799
  }));
800
800
  it('a group with no visible phones is not found, and is not stopped', () => __awaiter(void 0, void 0, void 0, function* () {
801
801
  twoTeamRows();
802
802
  visible = new Set(); // neither U1 nor U2 is on alice's team
803
- const res = yield (0, supertest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
803
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).post('/xenon/api/recordings/g1/stop');
804
804
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(404);
805
805
  (0, chai_1.expect)(res.body).to.deep.equal({ error: 'not_found' });
806
806
  (0, chai_1.expect)(stop.called).to.equal(false);
@@ -820,7 +820,7 @@ describe('recordings library routes', () => {
820
820
  typedi_1.Container.set(annotation_render_1.AnnotationRenderService, { renderForRecording: render });
821
821
  });
822
822
  afterEach(() => restoreRender());
823
- const get = (who, id) => (0, supertest_1.default)(buildApp(who))
823
+ const get = (who, id) => (0, loopbackRequest_1.default)(buildApp(who))
824
824
  .get(`/xenon/api/recordings/g1/exports/annotated.mp4?recordingId=${id}`)
825
825
  .buffer(true)
826
826
  .parse((r, cb) => {
@@ -865,7 +865,7 @@ describe('recordings library routes', () => {
865
865
  const file = path.join(dir, 'r1.mp4');
866
866
  fs.writeFileSync(file, Buffer.from('0123456789'));
867
867
  rows = [rec({ file_path: file })];
868
- const res = yield (0, supertest_1.default)(buildApp(alice))
868
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice))
869
869
  .get('/xenon/api/recordings/g1/source.mp4?recordingId=r1')
870
870
  .set('Range', 'bytes=2-5')
871
871
  .buffer(true)
@@ -891,7 +891,7 @@ describe('recordings library routes', () => {
891
891
  rec({ file_path: visibleFile }),
892
892
  rec({ id: 'r2', device_udid: 'GONE', file_path: file }),
893
893
  ];
894
- const get = (who, id) => (0, supertest_1.default)(buildApp(who)).get(`/xenon/api/recordings/g1/source.mp4?recordingId=${id}`);
894
+ const get = (who, id) => (0, loopbackRequest_1.default)(buildApp(who)).get(`/xenon/api/recordings/g1/source.mp4?recordingId=${id}`);
895
895
  (0, chai_1.expect)((yield get(bob, 'r1')).status).to.equal(200);
896
896
  (0, chai_1.expect)(store.findVideo.calledWith('r1')).to.equal(true);
897
897
  (0, chai_1.expect)(store.listGroupStarts.called, 'a visible phone needs no owner').to.equal(false);
@@ -908,9 +908,9 @@ describe('recordings library routes', () => {
908
908
  fs.writeFileSync(file, Buffer.from('0123456789'));
909
909
  rows = [rec({ file_path: file })];
910
910
  const url = '/xenon/api/recordings/g1/source.mp4?recordingId=r1&download=1';
911
- const fileEtag = (yield (0, supertest_1.default)(buildApp(alice)).get(url)).headers.etag;
911
+ const fileEtag = (yield (0, loopbackRequest_1.default)(buildApp(alice)).get(url)).headers.etag;
912
912
  (0, chai_1.expect)(fileEtag, "the file's own ETag").to.be.a('string');
913
- const res = yield (0, supertest_1.default)(buildApp(alice)).get(url).set('Range', 'bytes=9999-10000');
913
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get(url).set('Range', 'bytes=9999-10000');
914
914
  (0, chai_1.expect)(res.status).to.equal(416);
915
915
  (0, chai_1.expect)(res.headers['content-type']).to.match(/^application\/json/);
916
916
  (0, chai_1.expect)(res.headers['content-range']).to.equal('bytes */10');
@@ -922,13 +922,13 @@ describe('recordings library routes', () => {
922
922
  const file = path.join(dir, 'r1.mp4');
923
923
  fs.writeFileSync(file, 'x');
924
924
  rows = [rec({ file_path: file })];
925
- const res = yield (0, supertest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1&download=1');
925
+ const res = yield (0, loopbackRequest_1.default)(buildApp(alice)).get('/xenon/api/recordings/g1/source.mp4?recordingId=r1&download=1');
926
926
  (0, chai_1.expect)(res.status).to.equal(200);
927
927
  (0, chai_1.expect)(res.headers['content-disposition']).to.match(/attachment; filename="U1\.mp4"/);
928
928
  }));
929
929
  it('answers 400 without a recording, and 404 for another group, an invisible phone or a missing file', () => __awaiter(void 0, void 0, void 0, function* () {
930
930
  rows = [rec(), rec({ id: 'rx', group_id: 'g2' }), rec({ id: 'rh', device_udid: 'HIDDEN' })];
931
- const get = (q) => (0, supertest_1.default)(buildApp(bob)).get(`/xenon/api/recordings/g1/source.mp4${q}`);
931
+ const get = (q) => (0, loopbackRequest_1.default)(buildApp(bob)).get(`/xenon/api/recordings/g1/source.mp4${q}`);
932
932
  (0, chai_1.expect)((yield get('')).status).to.equal(400);
933
933
  (0, chai_1.expect)((yield get('?recordingId=rx')).status).to.equal(404);
934
934
  (0, chai_1.expect)((yield get('?recordingId=rh')).status).to.equal(404);
@@ -49,7 +49,7 @@ require("reflect-metadata");
49
49
  const chai_1 = require("chai");
50
50
  const sinon_1 = __importDefault(require("sinon"));
51
51
  const express_1 = __importDefault(require("express"));
52
- const supertest_1 = __importDefault(require("supertest"));
52
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
53
53
  const typedi_1 = require("typedi");
54
54
  const recordings_1 = __importDefault(require("../../src/app/routers/recordings"));
55
55
  const RecordingOrchestrator_1 = require("../../src/services/recording/RecordingOrchestrator");
@@ -164,7 +164,7 @@ describe('recording write routes: team visibility', () => {
164
164
  const alice = { userId: 'usr_alice', teamIds: ['t1'] };
165
165
  const bob = { userId: 'usr_bob', teamIds: ['t1'] };
166
166
  const admin = { userId: 'usr_root', role: 'ADMIN' };
167
- const post = (who, url, body = {}) => (0, supertest_1.default)(buildApp(who)).post(`/xenon/api/recordings${url}`).send(body);
167
+ const post = (who, url, body = {}) => (0, loopbackRequest_1.default)(buildApp(who)).post(`/xenon/api/recordings${url}`).send(body);
168
168
  const notFound = (res) => {
169
169
  (0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(404);
170
170
  (0, chai_1.expect)(res.body).to.deep.equal({ error: 'not_found' });