@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
@@ -0,0 +1,255 @@
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
+ const chai_1 = require("chai");
16
+ const express_1 = __importDefault(require("express"));
17
+ const sinon_1 = __importDefault(require("sinon"));
18
+ const loopbackRequest_1 = __importDefault(require("../helpers/loopbackRequest"));
19
+ const base_driver_1 = require("@appium/base-driver");
20
+ const sessionCreate_1 = require("../../src/gateway/sessionCreate");
21
+ const createHandoff_1 = require("../../src/gateway/createHandoff");
22
+ const hubSessionToken_1 = require("../../src/gateway/hubSessionToken");
23
+ const loopbackServer_1 = require("../helpers/loopbackServer");
24
+ /**
25
+ * `POST <basePath>/session` in the session gateway: allocation happens here,
26
+ * before Appium's route. A remote phone's session is created on its node and
27
+ * answered here; a local phone's allocation goes on to the plugin through the
28
+ * request's async context, and is given back if nothing takes it.
29
+ *
30
+ * Appium's route is stood in for by a plain route, and the plugin by a
31
+ * function that takes the handoff, the way XenonPlugin.createSession does.
32
+ */
33
+ describe('the session gateway’s create layer', () => {
34
+ const loopback = (0, loopbackServer_1.loopbackServers)();
35
+ const quiet = { info: () => undefined, warn: () => undefined, error: () => undefined };
36
+ let lifecycle;
37
+ let routeHits;
38
+ let pluginTakes;
39
+ let base;
40
+ const grant = { userId: 'alice', udid: 'phone-1', host: 'http://node:4725' };
41
+ const caps = () => ({
42
+ capabilities: { alwaysMatch: { platformName: 'Android' }, firstMatch: [{}] },
43
+ });
44
+ beforeEach(() => {
45
+ routeHits = [];
46
+ pluginTakes = true;
47
+ lifecycle = {
48
+ prepareSession: sinon_1.default.stub().resolves({ remote: false, device: { udid: 'phone-1' } }),
49
+ completeRemoteSession: sinon_1.default
50
+ .stub()
51
+ .resolves({ protocol: 'W3C', value: ['node-s1', { platformName: 'Android' }, 'W3C'] }),
52
+ releaseAllocation: sinon_1.default.stub().resolves(),
53
+ };
54
+ });
55
+ afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
56
+ sinon_1.default.restore();
57
+ yield loopback.closeAll();
58
+ }));
59
+ function serve() {
60
+ return __awaiter(this, arguments, void 0, function* (opts = {}) {
61
+ var _a;
62
+ const basePath = (_a = opts.basePath) !== null && _a !== void 0 ? _a : '/wd/hub';
63
+ const app = (0, express_1.default)();
64
+ app.use(express_1.default.json());
65
+ app.use(`${basePath}/session`, (0, sessionCreate_1.createSessionCreateLayer)({
66
+ lifecycle: () => lifecycle,
67
+ hubGrants: opts.hubGrants,
68
+ authDisabled: () => !!opts.authDisabled,
69
+ logger: quiet,
70
+ }));
71
+ if (opts.beforeRoute)
72
+ app.use(opts.beforeRoute);
73
+ // Appium's route, with the plugin inside it.
74
+ app.post(`${basePath}/session`, (req, res) => __awaiter(this, void 0, void 0, function* () {
75
+ var _a;
76
+ const hit = { headers: Object.assign({}, req.headers), body: req.body };
77
+ routeHits.push(hit);
78
+ yield new Promise((resolve) => setTimeout(resolve, 1));
79
+ if (pluginTakes)
80
+ hit.took = (_a = (0, createHandoff_1.currentCreateHandoff)()) === null || _a === void 0 ? void 0 : _a.take();
81
+ res.json({ value: { sessionId: 'local-s1', capabilities: {} } });
82
+ }));
83
+ app.all('*', (_req, res) => res.status(404).json({ value: { error: 'unknown command' } }));
84
+ const server = yield loopback.serve(app);
85
+ base = `http://127.0.0.1:${server.address().port}`;
86
+ return base;
87
+ });
88
+ }
89
+ describe('what it handles', () => {
90
+ it('only a POST of exactly the create path', () => __awaiter(void 0, void 0, void 0, function* () {
91
+ yield serve();
92
+ yield (0, loopbackRequest_1.default)(base).get('/wd/hub/session').expect(404);
93
+ yield (0, loopbackRequest_1.default)(base).post('/wd/hub/session/abc/url').send({}).expect(404);
94
+ yield (0, loopbackRequest_1.default)(base).post('/wd/hub/sessionx').send(caps()).expect(404);
95
+ (0, chai_1.expect)(lifecycle.prepareSession.called).to.equal(false);
96
+ }));
97
+ it('a body without a capabilities object goes straight on, for Appium to answer', () => __awaiter(void 0, void 0, void 0, function* () {
98
+ yield serve();
99
+ yield (0, loopbackRequest_1.default)(base).post('/wd/hub/session').send({ desiredCapabilities: {} }).expect(200);
100
+ yield (0, loopbackRequest_1.default)(base).post('/wd/hub/session').send({ capabilities: 'no' }).expect(200);
101
+ (0, chai_1.expect)(lifecycle.prepareSession.called).to.equal(false);
102
+ }));
103
+ it('matches the path as the router does: letter case, a trailing slash, any base path', () => __awaiter(void 0, void 0, void 0, function* () {
104
+ yield serve({ basePath: '' });
105
+ yield (0, loopbackRequest_1.default)(base).post('/SESSION/').send(caps()).expect(200);
106
+ (0, chai_1.expect)(lifecycle.prepareSession.calledOnce).to.equal(true);
107
+ }));
108
+ it('fills in an omitted firstMatch, as the plugin used to', () => __awaiter(void 0, void 0, void 0, function* () {
109
+ yield serve();
110
+ yield (0, loopbackRequest_1.default)(base)
111
+ .post('/wd/hub/session')
112
+ .send({ capabilities: { alwaysMatch: { platformName: 'Android' } } })
113
+ .expect(200);
114
+ (0, chai_1.expect)(lifecycle.prepareSession.firstCall.args[0].firstMatch).to.deep.equal([{}]);
115
+ (0, chai_1.expect)(routeHits[0].body.capabilities.firstMatch).to.deep.equal([{}]);
116
+ }));
117
+ });
118
+ describe('a remote phone', () => {
119
+ beforeEach(() => lifecycle.prepareSession.resolves({ remote: true }));
120
+ it('is created on its node and answered here, as Appium answers a new session', () => __awaiter(void 0, void 0, void 0, function* () {
121
+ yield serve();
122
+ const res = yield (0, loopbackRequest_1.default)(base).post('/wd/hub/session').send(caps());
123
+ (0, chai_1.expect)(res.status).to.equal(200);
124
+ (0, chai_1.expect)(res.headers['content-type']).to.equal('application/json; charset=utf-8');
125
+ (0, chai_1.expect)(res.body).to.deep.equal({
126
+ value: { capabilities: { platformName: 'Android' }, sessionId: 'node-s1' },
127
+ });
128
+ (0, chai_1.expect)(routeHits, 'Appium’s route').to.deep.equal([]);
129
+ (0, chai_1.expect)(lifecycle.completeRemoteSession.calledOnce).to.equal(true);
130
+ }));
131
+ it('a failure is answered as Appium answers the error', () => __awaiter(void 0, void 0, void 0, function* () {
132
+ lifecycle.completeRemoteSession.rejects(new base_driver_1.errors.SessionNotCreatedError('node said no'));
133
+ yield serve();
134
+ const res = yield (0, loopbackRequest_1.default)(base).post('/wd/hub/session').send(caps());
135
+ (0, chai_1.expect)(res.status).to.equal(500);
136
+ (0, chai_1.expect)(res.body.value.error).to.equal('session not created');
137
+ (0, chai_1.expect)(res.body.value.message).to.include('node said no');
138
+ (0, chai_1.expect)(routeHits).to.deep.equal([]);
139
+ }));
140
+ });
141
+ describe('a refusal before any phone is allocated', () => {
142
+ it('is answered as Appium answers the error, and Appium never sees the request', () => __awaiter(void 0, void 0, void 0, function* () {
143
+ lifecycle.prepareSession.rejects(new base_driver_1.errors.InvalidArgumentError('session rejected: no'));
144
+ yield serve();
145
+ const res = yield (0, loopbackRequest_1.default)(base).post('/wd/hub/session').send(caps());
146
+ (0, chai_1.expect)(res.status).to.equal(400);
147
+ (0, chai_1.expect)(res.body.value).to.include({ error: 'invalid argument' });
148
+ (0, chai_1.expect)(res.body.value.message).to.include('session rejected: no');
149
+ (0, chai_1.expect)(routeHits).to.deep.equal([]);
150
+ }));
151
+ it('a plain error is an unknown error', () => __awaiter(void 0, void 0, void 0, function* () {
152
+ lifecycle.prepareSession.rejects(new Error('No device matching request.'));
153
+ yield serve();
154
+ const res = yield (0, loopbackRequest_1.default)(base).post('/wd/hub/session').send(caps());
155
+ (0, chai_1.expect)(res.status).to.equal(500);
156
+ (0, chai_1.expect)(res.body.value.error).to.equal('unknown error');
157
+ }));
158
+ });
159
+ describe('a local phone', () => {
160
+ it('its allocation reaches the plugin through Appium’s route, and is not given back', () => __awaiter(void 0, void 0, void 0, function* () {
161
+ yield serve();
162
+ yield (0, loopbackRequest_1.default)(base).post('/wd/hub/session').send(caps()).expect(200);
163
+ (0, chai_1.expect)(routeHits[0].took).to.deep.equal({ remote: false, device: { udid: 'phone-1' } });
164
+ (0, chai_1.expect)(lifecycle.prepareSession.calledOnce).to.equal(true);
165
+ (0, chai_1.expect)(lifecycle.releaseAllocation.called).to.equal(false);
166
+ }));
167
+ it('is given back once when Appium answers without the plugin taking it', () => __awaiter(void 0, void 0, void 0, function* () {
168
+ pluginTakes = false;
169
+ yield serve();
170
+ yield (0, loopbackRequest_1.default)(base).post('/wd/hub/session').send(caps()).expect(200);
171
+ yield new Promise((resolve) => setImmediate(resolve));
172
+ (0, chai_1.expect)(lifecycle.releaseAllocation.callCount).to.equal(1);
173
+ (0, chai_1.expect)(lifecycle.releaseAllocation.firstCall.args[0].device.udid).to.equal('phone-1');
174
+ }));
175
+ it('is given back, and Appium never called, when the client left while it waited', () => __awaiter(void 0, void 0, void 0, function* () {
176
+ let allocated;
177
+ lifecycle.prepareSession.callsFake(() => new Promise((resolve) => {
178
+ allocated = () => resolve({ remote: false, device: { udid: 'phone-1' } });
179
+ }));
180
+ yield serve();
181
+ const req = (0, loopbackRequest_1.default)(base).post('/wd/hub/session').send(caps()).timeout(50);
182
+ yield req.then(() => undefined, () => undefined);
183
+ // The server sees the connection close, then the phone turns up.
184
+ yield new Promise((resolve) => setTimeout(resolve, 20));
185
+ allocated();
186
+ yield new Promise((resolve) => setTimeout(resolve, 20));
187
+ (0, chai_1.expect)(lifecycle.releaseAllocation.callCount).to.equal(1);
188
+ (0, chai_1.expect)(routeHits).to.deep.equal([]);
189
+ }));
190
+ });
191
+ describe('on a node: the hub’s token', () => {
192
+ it('a valid one is the grant the node allocates by; the header goes no further', () => __awaiter(void 0, void 0, void 0, function* () {
193
+ const verifyCreate = sinon_1.default.stub().resolves(grant);
194
+ yield serve({ hubGrants: { verifyCreate } });
195
+ yield (0, loopbackRequest_1.default)(base)
196
+ .post('/wd/hub/session')
197
+ .set(hubSessionToken_1.HUB_TOKEN_HEADER, 'good-token')
198
+ .send(caps())
199
+ .expect(200);
200
+ (0, chai_1.expect)(verifyCreate.calledOnceWith('good-token')).to.equal(true);
201
+ (0, chai_1.expect)(lifecycle.prepareSession.firstCall.args[1]).to.deep.equal({ hubGrant: grant });
202
+ (0, chai_1.expect)(routeHits[0].headers[hubSessionToken_1.HUB_TOKEN_HEADER]).to.equal(undefined);
203
+ }));
204
+ it('an invalid one is refused, and nothing is allocated', () => __awaiter(void 0, void 0, void 0, function* () {
205
+ yield serve({ hubGrants: { verifyCreate: () => __awaiter(void 0, void 0, void 0, function* () { return null; }) } });
206
+ const res = yield (0, loopbackRequest_1.default)(base)
207
+ .post('/wd/hub/session')
208
+ .set(hubSessionToken_1.HUB_TOKEN_HEADER, 'forged')
209
+ .send(caps());
210
+ (0, chai_1.expect)(res.status).to.equal(400);
211
+ (0, chai_1.expect)(res.body.value.error).to.equal('invalid argument');
212
+ (0, chai_1.expect)(res.body.value.message).to.include('session rejected');
213
+ (0, chai_1.expect)(lifecycle.prepareSession.called).to.equal(false);
214
+ (0, chai_1.expect)(routeHits).to.deep.equal([]);
215
+ }));
216
+ it('one that cannot be checked is a 503, and nothing is allocated', () => __awaiter(void 0, void 0, void 0, function* () {
217
+ yield serve({
218
+ hubGrants: {
219
+ verifyCreate: () => __awaiter(void 0, void 0, void 0, function* () {
220
+ throw new hubSessionToken_1.HubTokenUnavailableError('hub down');
221
+ }),
222
+ },
223
+ });
224
+ const res = yield (0, loopbackRequest_1.default)(base)
225
+ .post('/wd/hub/session')
226
+ .set(hubSessionToken_1.HUB_TOKEN_HEADER, 'token')
227
+ .send(caps());
228
+ (0, chai_1.expect)(res.status).to.equal(503);
229
+ (0, chai_1.expect)(res.body.value.error).to.equal('unknown error');
230
+ (0, chai_1.expect)(lifecycle.prepareSession.called).to.equal(false);
231
+ }));
232
+ it('with auth disabled it is not checked, only removed', () => __awaiter(void 0, void 0, void 0, function* () {
233
+ const verifyCreate = sinon_1.default.stub().resolves(null);
234
+ yield serve({ hubGrants: { verifyCreate }, authDisabled: true });
235
+ yield (0, loopbackRequest_1.default)(base)
236
+ .post('/wd/hub/session')
237
+ .set(hubSessionToken_1.HUB_TOKEN_HEADER, 'anything')
238
+ .send(caps())
239
+ .expect(200);
240
+ (0, chai_1.expect)(verifyCreate.called).to.equal(false);
241
+ (0, chai_1.expect)(lifecycle.prepareSession.firstCall.args[1]).to.deep.equal({});
242
+ (0, chai_1.expect)(routeHits[0].headers[hubSessionToken_1.HUB_TOKEN_HEADER]).to.equal(undefined);
243
+ }));
244
+ it('a hub, which checks no hub tokens, ignores and removes one', () => __awaiter(void 0, void 0, void 0, function* () {
245
+ yield serve();
246
+ yield (0, loopbackRequest_1.default)(base)
247
+ .post('/wd/hub/session')
248
+ .set(hubSessionToken_1.HUB_TOKEN_HEADER, 'anything')
249
+ .send(caps())
250
+ .expect(200);
251
+ (0, chai_1.expect)(lifecycle.prepareSession.firstCall.args[1]).to.deep.equal({});
252
+ (0, chai_1.expect)(routeHits[0].headers[hubSessionToken_1.HUB_TOKEN_HEADER]).to.equal(undefined);
253
+ }));
254
+ });
255
+ });
@@ -54,12 +54,15 @@ const PluginContext_1 = require("../../src/PluginContext");
54
54
  const ApiKeyService_1 = require("../../src/services/ApiKeyService");
55
55
  const UserService_1 = require("../../src/services/UserService");
56
56
  const JwtKeyService_1 = require("../../src/services/token/JwtKeyService");
57
+ const hubSessionToken_1 = require("../../src/gateway/hubSessionToken");
57
58
  const pendingSessions = __importStar(require("../../src/data-service/pending-sessions-service"));
58
59
  const deviceService = __importStar(require("../../src/data-service/device-service"));
59
60
  const deviceUtils = __importStar(require("../../src/device-utils"));
60
61
  const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
61
62
  const config_1 = require("../../src/config");
63
+ const prisma_1 = require("../../src/prisma");
62
64
  const container_registration_1 = require("../helpers/container-registration");
65
+ const loki_stores_1 = require("../helpers/loki-stores");
63
66
  /**
64
67
  * createSession end to end: the credentials a session presents in
65
68
  * `xe:options` (or its alias `xenon:options`) are read once, then taken out of
@@ -85,6 +88,10 @@ const caps = (always, firstMatch = [{}]) => ({
85
88
  });
86
89
  const leaked = (value) => SECRETS.filter((s) => JSON.stringify(value !== null && value !== void 0 ? value : null).includes(s));
87
90
  describe('createSession — credentials never reach the driver or storage', () => {
91
+ // The in-memory stores, also when this file runs on its own: without
92
+ // NODE_ENV=test the factory handed out the Prisma stores, which are the
93
+ // developer's ~/.cache/xenon/xenon.db.
94
+ (0, loki_stores_1.useLokiStores)();
88
95
  let svc;
89
96
  let device;
90
97
  let context;
@@ -125,7 +132,7 @@ describe('createSession — credentials never reach the driver or storage', () =
125
132
  context.pluginArgs = Object.assign({}, IPluginArgs_1.DefaultPluginArgs, { bindHostOrIp: '127.0.0.1' });
126
133
  context.nodeId = 'node-hub';
127
134
  device = { udid: 'u1', host: 'h1', platform: 'android', nodeId: 'node-hub', teamId: null };
128
- restore = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, UserService_1.UserService, JwtKeyService_1.JwtKeyService);
135
+ restore = (0, container_registration_1.saveRegistrations)(ApiKeyService_1.ApiKeyService, UserService_1.UserService, JwtKeyService_1.JwtKeyService, hubSessionToken_1.HubSessionTokenIssuer);
129
136
  typedi_1.Container.set(ApiKeyService_1.ApiKeyService, {
130
137
  verifyPair: sinon_1.default
131
138
  .stub()
@@ -142,6 +149,8 @@ describe('createSession — credentials never reach the driver or storage', () =
142
149
  return { sub: t.slice('jwt:'.length), teamId: null };
143
150
  }),
144
151
  });
152
+ // The owner's teams: none, and not read from the developer's database.
153
+ sinon_1.default.stub(prisma_1.prisma.teamMember, 'findMany').resolves([]);
145
154
  seen = {};
146
155
  sinon_1.default.stub(pendingSessions, 'addNewPendingSession').callsFake((c) => __awaiter(void 0, void 0, void 0, function* () {
147
156
  seen.pending = JSON.parse(JSON.stringify(c));
@@ -246,32 +255,39 @@ describe('createSession — credentials never reach the driver or storage', () =
246
255
  }));
247
256
  });
248
257
  describe('when the device is on another node', () => {
258
+ let grants;
249
259
  beforeEach(() => {
250
260
  sinon_1.default.stub(svc, 'finalizeSession').callsFake((...args) => __awaiter(void 0, void 0, void 0, function* () {
251
261
  seen.sessionResponse = { desired: JSON.parse(JSON.stringify(args[2])) };
252
262
  }));
253
- sinon_1.default.stub(svc, 'forwardSessionRequest').callsFake((_d, c) => __awaiter(void 0, void 0, void 0, function* () {
263
+ sinon_1.default.stub(svc, 'forwardSessionRequest').callsFake((_d, c, t) => __awaiter(void 0, void 0, void 0, function* () {
254
264
  seen.forwarded = JSON.parse(JSON.stringify(c));
265
+ seen.hubToken = t;
255
266
  return { protocol: 'W3C', value: ['sess-1', {}, 'W3C'] };
256
267
  }));
268
+ grants = sinon_1.default.stub().resolves('HUB-CREATE-TOKEN');
269
+ typedi_1.Container.set(hubSessionToken_1.HubSessionTokenIssuer, {
270
+ createTokenFor: grants,
271
+ forget: () => undefined,
272
+ });
257
273
  });
258
- it('a peer Xenon node gets them, since it authenticates the session itself', () => __awaiter(void 0, void 0, void 0, function* () {
274
+ it('a peer Xenon node never gets them: it gets the hub’s token for the verified owner', () => __awaiter(void 0, void 0, void 0, function* () {
259
275
  device.nodeId = 'node-peer';
260
276
  yield create(caps({ 'xe:options': { accessKey: KEY, token: TOKEN, sessionToken: JWT } }));
261
- (0, chai_1.expect)(seen.forwarded.alwaysMatch['xe:options']).to.deep.equal({
262
- accessKey: KEY,
263
- token: TOKEN,
264
- sessionToken: JWT,
265
- });
277
+ (0, chai_1.expect)(leaked(seen.forwarded), 'forwarded').to.deep.equal([]);
278
+ (0, chai_1.expect)(seen.hubToken).to.equal('HUB-CREATE-TOKEN');
279
+ (0, chai_1.expect)(grants.firstCall.args[0]).to.deep.equal({ userId: 'usr_key', udid: 'u1', host: 'h1' });
266
280
  for (const where of ['pending', 'sent', 'sessionResponse']) {
267
281
  (0, chai_1.expect)(leaked(seen[where]), where).to.deep.equal([]);
268
282
  }
269
283
  }));
270
- it('a cloud provider never does', () => __awaiter(void 0, void 0, void 0, function* () {
284
+ it('a cloud provider gets neither', () => __awaiter(void 0, void 0, void 0, function* () {
271
285
  device.nodeId = undefined;
272
286
  device.cloud = true;
273
287
  yield create(caps({ 'xe:options': { accessKey: KEY, token: TOKEN, sessionToken: JWT } }));
274
288
  (0, chai_1.expect)(leaked(seen.forwarded)).to.deep.equal([]);
289
+ (0, chai_1.expect)(seen.hubToken).to.equal(null);
290
+ (0, chai_1.expect)(grants.called).to.equal(false);
275
291
  }));
276
292
  });
277
293
  });
@@ -100,50 +100,3 @@ describe('takeSessionCredentials', () => {
100
100
  (0, chai_1.expect)(() => (0, sessionCredentials_1.takeSessionCredentials)({ alwaysMatch: null, firstMatch: 'x' })).to.not.throw();
101
101
  });
102
102
  });
103
- describe('capsWithCredentials — the copy a peer Xenon node gets', () => {
104
- // A peer node re-runs createSession: it authenticates and attributes the
105
- // session and checks the lease itself, so it needs what the hub read. It
106
- // takes the credentials out again before its own driver sees them.
107
- const take = (caps) => ({ caps, credentials: (0, sessionCredentials_1.takeSessionCredentials)(caps) });
108
- it('puts every credential back, so the node reads the same ones the hub did', () => {
109
- const { caps, credentials } = take({
110
- alwaysMatch: {
111
- 'xenon:options': { leaseId: 'lse_1', accessKey: 'ak_old', token: 'tk_old' },
112
- 'xe:options': { token: SECRETS.token, sessionToken: SECRETS.sessionToken },
113
- },
114
- firstMatch: [{}],
115
- });
116
- const forwarded = (0, sessionCredentials_1.capsWithCredentials)(caps, credentials);
117
- (0, chai_1.expect)((0, sessionCredentials_1.takeSessionCredentials)(JSON.parse(JSON.stringify(forwarded)))).to.deep.equal(credentials);
118
- });
119
- it('writes them into the namespace that wins, in the bucket it is in', () => {
120
- const { caps, credentials } = take({
121
- alwaysMatch: { 'xenon:options': { leaseId: 'lse_1', leaseToken: SECRETS.leaseToken } },
122
- firstMatch: [{ 'xe:options': { accessKey: SECRETS.accessKey, token: SECRETS.token } }],
123
- });
124
- const forwarded = (0, sessionCredentials_1.capsWithCredentials)(caps, credentials);
125
- (0, chai_1.expect)(forwarded.firstMatch[0]['xe:options']).to.deep.equal({
126
- accessKey: SECRETS.accessKey,
127
- token: SECRETS.token,
128
- leaseToken: SECRETS.leaseToken,
129
- });
130
- (0, chai_1.expect)(forwarded.alwaysMatch['xenon:options']).to.deep.equal({ leaseId: 'lse_1' });
131
- });
132
- it('does not modify the caps it copies', () => {
133
- const { caps, credentials } = take({
134
- alwaysMatch: { 'xe:options': Object.assign({ leaseId: 'lse_1' }, SECRETS) },
135
- firstMatch: [{}],
136
- });
137
- const before = JSON.stringify(caps);
138
- (0, sessionCredentials_1.capsWithCredentials)(caps, credentials);
139
- (0, chai_1.expect)(JSON.stringify(caps)).to.equal(before);
140
- (0, chai_1.expect)(leaks(caps)).to.deep.equal([]);
141
- });
142
- it('returns the caps unchanged when there is nothing to put back', () => {
143
- const { caps, credentials } = take({
144
- alwaysMatch: { 'xe:options': { leaseId: 'lse_1' } },
145
- firstMatch: [{}],
146
- });
147
- (0, chai_1.expect)((0, sessionCredentials_1.capsWithCredentials)(caps, credentials)).to.equal(caps);
148
- });
149
- });