@xenon-device-management/xenon 2.13.1 → 2.14.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 (223) hide show
  1. package/README.md +11 -5
  2. package/lib/package.json +1 -1
  3. package/lib/public/assets/{AnnotationOverlay-Cvpkk4r9.js → AnnotationOverlay-B_VNtKBH.js} +1 -1
  4. package/lib/public/assets/{ApiKeyGate-Bhn5ZtVM.js → ApiKeyGate-BwuaMjkX.js} +1 -1
  5. package/lib/public/assets/{BugReportButton-BA6aEjTC.js → BugReportButton-BAt0_pzq.js} +1 -1
  6. package/lib/public/assets/DeviceMosaicView-BYaQHhTf.js +2 -0
  7. package/lib/public/assets/{EmptyState-CgzbuUvO.js → EmptyState-BSjhk_IV.js} +1 -1
  8. package/lib/public/assets/{FieldGroup-BHc_5qNc.js → FieldGroup-Cb5QZsE1.js} +1 -1
  9. package/lib/public/assets/{FilterMenu-D7OMGuRc.js → FilterMenu-BAaSlWbW.js} +1 -1
  10. package/lib/public/assets/{Menu-C173JJ0l.js → Menu-JGGYrSQQ.js} +1 -1
  11. package/lib/public/assets/{Modal-ChvVUI3B.js → Modal-CFCp3n9d.js} +1 -1
  12. package/lib/public/assets/{RecordingPage-Dhw4d_O6.js → RecordingPage-CbJj_EBT.js} +1 -1
  13. package/lib/public/assets/{RecordingsPage-z97ftGK-.js → RecordingsPage-BJeYnygR.js} +2 -2
  14. package/lib/public/assets/{SegmentedControl-DbXhBGZC.js → SegmentedControl-Bg42Atjy.js} +1 -1
  15. package/lib/public/assets/{SettingCard-C908GgiI.js → SettingCard-Njz1pUIJ.js} +1 -1
  16. package/lib/public/assets/{Table-B1DB_ZN-.js → Table-BcTm5xV2.js} +1 -1
  17. package/lib/public/assets/{activity-Css0xNvp.js → activity-DOVdsfMQ.js} +1 -1
  18. package/lib/public/assets/{ai-settings-BMQK3-yN.js → ai-settings-CsuJroRf.js} +2 -2
  19. package/lib/public/assets/{api-keys-CbJ42eDQ.js → api-keys-BKnNSTw9.js} +1 -1
  20. package/lib/public/assets/{apps-DW1pw2re.js → apps-D0SC6uw6.js} +2 -2
  21. package/lib/public/assets/{arrow-left-Dpog9L0F.js → arrow-left-MMbTmgoE.js} +1 -1
  22. package/lib/public/assets/{arrow-right-CsBIlSIw.js → arrow-right-BMfnGmxW.js} +1 -1
  23. package/lib/public/assets/{arrow-up-right-z1tRQE4R.js → arrow-up-right-DuAftMWB.js} +1 -1
  24. package/lib/public/assets/{auth-shell-mXiX5vNk.js → auth-shell-nZC2JNDO.js} +2 -2
  25. package/lib/public/assets/{builds-page-BcFn23bE.js → builds-page-hDCgTwkS.js} +1 -1
  26. package/lib/public/assets/{button-Cq_zHETv.js → button-Dda4irn_.js} +1 -1
  27. package/lib/public/assets/{calendar-CKxABX9q.js → calendar-BMr0Q62v.js} +1 -1
  28. package/lib/public/assets/{check-C7xu-hGU.js → check-CZ9iw9-e.js} +1 -1
  29. package/lib/public/assets/{chevron-right-BiqfdCGA.js → chevron-right-BRYJJVC9.js} +1 -1
  30. package/lib/public/assets/{circle-check-CyBjjPxR.js → circle-check-CqAJjQ5O.js} +1 -1
  31. package/lib/public/assets/{circle-x-6gMRcx6z.js → circle-x-DlDJtnVz.js} +1 -1
  32. package/lib/public/assets/{clock-U1rDTT1h.js → clock-CpuDKysU.js} +1 -1
  33. package/lib/public/assets/{copy-BIM0ZX0L.js → copy-CAQydNFe.js} +1 -1
  34. package/lib/public/assets/device-explorer-CdO6_nrn.js +282 -0
  35. package/lib/public/assets/{download-DDBRXnAZ.js → download-C9XG6lLv.js} +1 -1
  36. package/lib/public/assets/{forgot-password-Dpg7yxM8.js → forgot-password-CGnkRgE9.js} +1 -1
  37. package/lib/public/assets/h264Stream-VQBphYhg.js +26 -0
  38. package/lib/public/assets/{index-BGBqohfQ.js → index-BbKEXUFH.js} +5 -5
  39. package/lib/public/assets/{index-BTYMAqNW.css → index-Bxoxt9sV.css} +1 -1
  40. package/lib/public/assets/{index-BQ2luT7L.js → index-DJGc1M2f.js} +1 -1
  41. package/lib/public/assets/{input-BvB1cjHE.js → input-Cm0tmSoc.js} +1 -1
  42. package/lib/public/assets/{line-chart-m78VggnW.js → line-chart-D2DyJCml.js} +1 -1
  43. package/lib/public/assets/{list-checks-cY7o1QTL.js → list-checks-UWf1GY99.js} +1 -1
  44. package/lib/public/assets/{lock-loz8txQm.js → lock-CCJv9m_I.js} +1 -1
  45. package/lib/public/assets/{login-BYOvz4lx.js → login-f8vOVW-U.js} +1 -1
  46. package/lib/public/assets/maintenance-settings-C1PrkuAH.js +6 -0
  47. package/lib/public/assets/{monitor-C4HPHb6s.js → monitor-B2jDgwm7.js} +1 -1
  48. package/lib/public/assets/{mouse-pointer-2-BkdKtCzi.js → mouse-pointer-2-DLI1RtzU.js} +1 -1
  49. package/lib/public/assets/{network-C5e0MIWo.js → network-ebT86qoi.js} +1 -1
  50. package/lib/public/assets/{overview-tlu8ijGd.js → overview-C0aykVrd.js} +1 -1
  51. package/lib/public/assets/{page-header-FXPlo6az.js → page-header-BjzgWz-T.js} +1 -1
  52. package/lib/public/assets/{play-CWdk0_Sy.js → play-DLG9t8FU.js} +1 -1
  53. package/lib/public/assets/{plus-B2xCnntV.js → plus-nBFSMu_X.js} +1 -1
  54. package/lib/public/assets/{profile-page-D080qzr4.js → profile-page-CSl0UYlv.js} +1 -1
  55. package/lib/public/assets/{reset-password-D3u4Mbvn.js → reset-password-LeOuHqmB.js} +1 -1
  56. package/lib/public/assets/{runbook-page-hPRZMy7s.js → runbook-page-CMMeQml9.js} +1 -1
  57. package/lib/public/assets/{select-12RThAXc.js → select-88jHcl-Z.js} +1 -1
  58. package/lib/public/assets/{selector-detail-redirect-uQwvV7EQ.js → selector-detail-redirect-BbXKJjZM.js} +1 -1
  59. package/lib/public/assets/{selector-health-page-CThoxDFO.js → selector-health-page-B4rA1Xkv.js} +2 -2
  60. package/lib/public/assets/session-detail-page-COL_RZxw.js +4 -0
  61. package/lib/public/assets/settings-GF6ZMSQ6.js +6 -0
  62. package/lib/public/assets/{stat-tile-B02WEte8.js → stat-tile-BV98e_Sv.js} +1 -1
  63. package/lib/public/assets/{tablet-Dj30VGZl.js → tablet-BiAsnFDj.js} +1 -1
  64. package/lib/public/assets/{teams-uL4fjTDR.js → teams-B4-jrr-1.js} +1 -1
  65. package/lib/public/assets/{trash-2-B54e-TfY.js → trash-2-C5RSuX0e.js} +1 -1
  66. package/lib/public/assets/{upload-B3k8WCSP.js → upload-BG7_rMXK.js} +1 -1
  67. package/lib/public/assets/{use-builds-data-BFn90NH3.js → use-builds-data-J-riQYyC.js} +1 -1
  68. package/lib/public/assets/{use-password-reset-mode-DL2TmWZ0.js → use-password-reset-mode-C8-eH-UG.js} +1 -1
  69. package/lib/public/assets/{users-CqBuxMlX.js → users-CFba5TSy.js} +1 -1
  70. package/lib/public/assets/{users-DjQSucKb.js → users-Cp0beJm3.js} +1 -1
  71. package/lib/public/assets/{video-off-T0rlt0GA.js → video-off-DDsEISjV.js} +1 -1
  72. package/lib/public/assets/webhook-settings-BGu32vSB.js +1 -0
  73. package/lib/public/assets/{zap-gBHqutZV.js → zap-l4VaXzq8.js} +1 -1
  74. package/lib/public/index.html +2 -2
  75. package/lib/schema.json +19 -46
  76. package/lib/src/XenonCapabilityManager.js +22 -5
  77. package/lib/src/app/openapi/control.yaml +71 -20
  78. package/lib/src/app/openapi/grid.yaml +23 -5
  79. package/lib/src/app/openapi/platform.yaml +136 -28
  80. package/lib/src/app/routers/control.js +30 -5
  81. package/lib/src/app/routers/dashboard.js +29 -1
  82. package/lib/src/app/routers/grid.js +32 -10
  83. package/lib/src/app/routers/streamStartConflict.js +3 -0
  84. package/lib/src/app/routers/streamType.js +7 -2
  85. package/lib/src/app/routers/webhook.js +8 -4
  86. package/lib/src/config.js +50 -5
  87. package/lib/src/dashboard/commands.js +185 -141
  88. package/lib/src/dashboard/event-manager.js +39 -6
  89. package/lib/src/data-service/appleFamily.js +43 -0
  90. package/lib/src/data-service/db.js +11 -0
  91. package/lib/src/data-service/device-service.js +46 -1
  92. package/lib/src/data-service/device-store.js +66 -3
  93. package/lib/src/data-service/deviceClaims.js +37 -0
  94. package/lib/src/data-service/deviceFieldOwners.js +25 -11
  95. package/lib/src/data-service/deviceSettings.js +79 -0
  96. package/lib/src/data-service/prisma-store.js +152 -9
  97. package/lib/src/data-service/web-config-service.js +43 -92
  98. package/lib/src/device-managers/AndroidDeviceManager.js +9 -31
  99. package/lib/src/device-managers/ios/WDAClient.js +44 -43
  100. package/lib/src/device-managers/shellCommands.js +96 -0
  101. package/lib/src/device-utils.js +143 -37
  102. package/lib/src/generated/client/edge.js +17 -3
  103. package/lib/src/generated/client/index-browser.js +14 -0
  104. package/lib/src/generated/client/index.d.ts +2054 -379
  105. package/lib/src/generated/client/index.js +17 -3
  106. package/lib/src/generated/client/package.json +1 -1
  107. package/lib/src/generated/client/schema.prisma +35 -7
  108. package/lib/src/generated/client/wasm.js +14 -0
  109. package/lib/src/interceptors/CommandInterceptor.js +29 -8
  110. package/lib/src/interceptors/xenonScripts.js +55 -0
  111. package/lib/src/interfaces/IPluginArgs.js +1 -2
  112. package/lib/src/middleware/deviceAccessGuard.js +63 -9
  113. package/lib/src/plugin.js +15 -1
  114. package/lib/src/prisma.js +1 -1
  115. package/lib/src/scripts/database-check.js +124 -0
  116. package/lib/src/services/AIService.js +99 -0
  117. package/lib/src/services/CleanupService.js +6 -1
  118. package/lib/src/services/InterceptorService.js +70 -13
  119. package/lib/src/services/NetworkConditioningService.js +85 -88
  120. package/lib/src/services/NotificationService.js +36 -29
  121. package/lib/src/services/OrphanSweeper.js +5 -0
  122. package/lib/src/services/PortAllocator.js +5 -0
  123. package/lib/src/services/ServerManager.js +97 -8
  124. package/lib/src/services/SessionLifecycleService.js +63 -53
  125. package/lib/src/services/ShutdownCoordinator.js +33 -0
  126. package/lib/src/services/TeamService.js +20 -3
  127. package/lib/src/services/device-access/SessionOwnerResolver.js +26 -0
  128. package/lib/src/services/device-access/deviceAccessPolicy.js +38 -6
  129. package/lib/src/services/device-access/leaseHold.js +26 -0
  130. package/lib/src/services/device-access/ticketActorAccess.js +6 -0
  131. package/lib/src/services/healing/HealingOrchestrator.js +32 -1
  132. package/lib/src/services/interceptor/AndroidProxyAdapter.js +18 -0
  133. package/lib/src/services/interceptor/interceptorOptions.js +44 -0
  134. package/lib/src/services/lease/activeLeases.js +37 -0
  135. package/lib/src/services/network/AndroidRadios.js +71 -0
  136. package/lib/src/services/network/PhoneNetworkLedger.js +184 -0
  137. package/lib/src/services/network/PhoneNetworkRestore.js +330 -0
  138. package/lib/src/services/network/adbForPhone.js +60 -0
  139. package/lib/src/services/network/phoneNetworkLock.js +20 -0
  140. package/lib/src/services/network/xenonProxy.js +107 -0
  141. package/lib/src/services/omni-vision/OmniVisionService.js +53 -43
  142. package/lib/src/services/omni-vision/VisionAssertionService.js +28 -25
  143. package/lib/src/services/omni-vision/ocrTextMatch.js +105 -0
  144. package/lib/src/services/omni-vision/screenScale.js +68 -0
  145. package/lib/src/services/recording/busy-precheck.js +24 -3
  146. package/lib/src/services/recording/concurrency-gate.js +12 -3
  147. package/lib/src/services/settings/SelfHealingSwitch.js +70 -0
  148. package/lib/src/services/settings/labSettings.js +149 -0
  149. package/lib/src/services/webhookEvents.js +173 -0
  150. package/lib/test/helpers/fake-adb.js +58 -0
  151. package/lib/test/helpers/fake-device-table.js +63 -24
  152. package/lib/test/helpers/scratch-database.js +1 -0
  153. package/lib/test/unit/TeamService.test.js +3 -0
  154. package/lib/test/unit/XenonCapabilityManager.spec.js +4 -2
  155. package/lib/test/unit/apple-family.spec.js +36 -0
  156. package/lib/test/unit/cleanup-settings-db.spec.js +103 -0
  157. package/lib/test/unit/cleanup-settings.spec.js +244 -0
  158. package/lib/test/unit/dashboard-api-paths.spec.js +101 -0
  159. package/lib/test/unit/dashboard-commands.spec.js +249 -0
  160. package/lib/test/unit/dashboard-events-team-scope.spec.js +9 -6
  161. package/lib/test/unit/database-check.spec.js +108 -0
  162. package/lib/test/unit/default-plugin-args.spec.js +33 -0
  163. package/lib/test/unit/device-access-lease.spec.js +270 -0
  164. package/lib/test/unit/device-offline-webhook.spec.js +81 -0
  165. package/lib/test/unit/device-setting-columns.spec.js +66 -0
  166. package/lib/test/unit/device-settings-loki.spec.js +87 -0
  167. package/lib/test/unit/device-settings-survive.spec.js +555 -0
  168. package/lib/test/unit/device-sync-lost-update.spec.js +5 -0
  169. package/lib/test/unit/emulator-boot.spec.js +91 -0
  170. package/lib/test/unit/hub-node-gateway-appium-server.spec.js +48 -0
  171. package/lib/test/unit/hub-node-same-host.spec.js +5 -0
  172. package/lib/test/unit/iphone-ipad-only.spec.js +132 -0
  173. package/lib/test/unit/json-logging-option.spec.js +74 -0
  174. package/lib/test/unit/lab-settings.spec.js +218 -0
  175. package/lib/test/unit/lease/allocate-lease-branch.spec.js +22 -0
  176. package/lib/test/unit/lease/session-lease-proof.spec.js +59 -0
  177. package/lib/test/unit/lease-held-phone.spec.js +196 -0
  178. package/lib/test/unit/max-sessions.spec.js +240 -0
  179. package/lib/test/unit/ocr-text-match.spec.js +44 -0
  180. package/lib/test/unit/omni-vision-analyze-screen.spec.js +72 -0
  181. package/lib/test/unit/omni-vision-ios-points.spec.js +161 -0
  182. package/lib/test/unit/omni-vision-tap-text.spec.js +129 -0
  183. package/lib/test/unit/omni-vision.spec.js +1 -1
  184. package/lib/test/unit/partial-config-file.spec.js +114 -0
  185. package/lib/test/unit/phone-network/boot-clean-up.spec.js +64 -0
  186. package/lib/test/unit/phone-network/interceptor-options.spec.js +77 -0
  187. package/lib/test/unit/phone-network/interceptor-proxy-restore.spec.js +145 -0
  188. package/lib/test/unit/phone-network/network-conditioning.spec.js +145 -0
  189. package/lib/test/unit/phone-network/phone-network-restore.spec.js +246 -0
  190. package/lib/test/unit/phone-network/session-end-paths.spec.js +182 -0
  191. package/lib/test/unit/phone-network/session-start.spec.js +110 -0
  192. package/lib/test/unit/phone-network/xenon-proxy.spec.js +69 -0
  193. package/lib/test/unit/plugin.spec.js +3 -3
  194. package/lib/test/unit/recording-options.spec.js +181 -0
  195. package/lib/test/unit/schema-required-args.spec.js +21 -16
  196. package/lib/test/unit/self-healing-boot.spec.js +75 -0
  197. package/lib/test/unit/self-healing-setting.spec.js +272 -0
  198. package/lib/test/unit/session-failed-webhook.spec.js +170 -0
  199. package/lib/test/unit/shell-commands.spec.js +133 -0
  200. package/lib/test/unit/socketServer.scopedEmit.spec.js +2 -0
  201. package/lib/test/unit/stream-start-pipelines.spec.js +210 -0
  202. package/lib/test/unit/stream-status-type.spec.js +4 -0
  203. package/lib/test/unit/vision-assertion.spec.js +118 -0
  204. package/lib/test/unit/wda-client-shell.spec.js +133 -0
  205. package/lib/test/unit/web-config-service.spec.js +79 -0
  206. package/lib/test/unit/webhook-delivery.spec.js +24 -0
  207. package/lib/test/unit/webhook-payloads.spec.js +273 -0
  208. package/lib/test/unit/xenon-execute-scripts.spec.js +138 -0
  209. package/lib/tsconfig.tsbuildinfo +1 -1
  210. package/lib/web/src/components/webhook-settings/webhookEventVariables.json +30 -0
  211. package/package.json +1 -1
  212. package/prisma/migrations/20261004140000_keep_device_settings/migration.sql +19 -0
  213. package/prisma/schema.prisma +35 -7
  214. package/schema.json +19 -46
  215. package/scripts/generate-types-from-schema.js +7 -2
  216. package/scripts/lib/always-present-plugin-args.js +42 -0
  217. package/lib/public/assets/DeviceMosaicView-ChhHnpFQ.js +0 -2
  218. package/lib/public/assets/device-explorer-DSpAA_t9.js +0 -279
  219. package/lib/public/assets/maintenance-settings-BxbA7SkB.js +0 -6
  220. package/lib/public/assets/recording-group-store-B75435xR.js +0 -26
  221. package/lib/public/assets/session-detail-page-Bc02YjMm.js +0 -4
  222. package/lib/public/assets/settings-C1VIluuT.js +0 -6
  223. package/lib/public/assets/webhook-settings-DAHgnJyZ.js +0 -1
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ require("reflect-metadata");
16
+ const chai_1 = require("chai");
17
+ const sinon_1 = __importDefault(require("sinon"));
18
+ const fs_1 = __importDefault(require("fs"));
19
+ const net_1 = __importDefault(require("net"));
20
+ const os_1 = __importDefault(require("os"));
21
+ const path_1 = __importDefault(require("path"));
22
+ const typedi_1 = require("typedi");
23
+ const InterceptorService_1 = require("../../../src/services/InterceptorService");
24
+ const CertManager_1 = require("../../../src/services/interceptor/CertManager");
25
+ const MitmProxyHost_1 = require("../../../src/services/interceptor/MitmProxyHost");
26
+ const PortAllocator_1 = require("../../../src/services/PortAllocator");
27
+ const PhoneNetworkLedger_1 = require("../../../src/services/network/PhoneNetworkLedger");
28
+ const config_1 = require("../../../src/config");
29
+ const container_registration_1 = require("../../helpers/container-registration");
30
+ const scratch_database_1 = require("../../helpers/scratch-database");
31
+ const fake_adb_1 = require("../../helpers/fake-adb");
32
+ const PROXY_PORT = 11150;
33
+ const phone = { udid: 'R5CT', platform: 'android', deviceType: 'real', host: 'h' };
34
+ const ON = { enabled: true, mocks: [], includeHosts: [], excludeHosts: [] };
35
+ /**
36
+ * The interceptor points the whole phone at its proxy. When the session ends
37
+ * it puts back the proxy the phone had (Xenon used to write ":0" over a lab's
38
+ * own proxy), and records the change while it lasts so a restart can undo it.
39
+ */
40
+ describe('InterceptorService and the phone proxy', function () {
41
+ this.timeout(60000);
42
+ (0, scratch_database_1.useScratchDatabase)();
43
+ let adb;
44
+ (0, fake_adb_1.useFakeAdb)(() => adb);
45
+ let restoreRegs;
46
+ let assetsBefore;
47
+ let assetsDir;
48
+ let ledger;
49
+ let range;
50
+ beforeEach(() => __awaiter(this, void 0, void 0, function* () {
51
+ adb = (0, fake_adb_1.fakeAdb)({ 'shell settings get global http_proxy': 'proxy.corp:3128' });
52
+ restoreRegs = (0, container_registration_1.saveRegistrations)(PortAllocator_1.PortAllocator);
53
+ range = [11100, 11199];
54
+ typedi_1.Container.set(PortAllocator_1.PortAllocator, {
55
+ acquire: () => __awaiter(this, void 0, void 0, function* () { return PROXY_PORT; }),
56
+ releaseForUdid: () => __awaiter(this, void 0, void 0, function* () { return undefined; }),
57
+ rangeOf: () => range,
58
+ });
59
+ sinon_1.default.stub(CertManager_1.CertManager.prototype, 'ensure').resolves({
60
+ certPath: '/nonexistent/ca.pem',
61
+ keyPath: '/nonexistent/ca.key',
62
+ subjectHash: 'abc',
63
+ });
64
+ sinon_1.default.stub(CertManager_1.CertManager.prototype, 'androidCertFilename').returns('abc.0');
65
+ sinon_1.default.stub(MitmProxyHost_1.MitmProxyHost.prototype, 'start').resolves();
66
+ sinon_1.default.stub(MitmProxyHost_1.MitmProxyHost.prototype, 'stop').resolves();
67
+ assetsBefore = config_1.config.sessionAssetsPath;
68
+ assetsDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'xenon-icp-'));
69
+ (0, config_1.updateConfig)({ sessionAssetsPath: assetsDir });
70
+ ledger = new PhoneNetworkLedger_1.PhoneNetworkLedger();
71
+ for (const entry of yield ledger.list())
72
+ yield ledger.forget(entry.sessionId);
73
+ }));
74
+ afterEach(() => {
75
+ sinon_1.default.restore();
76
+ restoreRegs();
77
+ (0, config_1.updateConfig)({ sessionAssetsPath: assetsBefore });
78
+ fs_1.default.rmSync(assetsDir, { recursive: true, force: true });
79
+ });
80
+ it('reads the proxy the phone had and writes its own down before setting it', () => __awaiter(this, void 0, void 0, function* () {
81
+ const svc = new InterceptorService_1.InterceptorService();
82
+ yield svc.start('s1', phone, ON);
83
+ const cmds = adb.commands('R5CT');
84
+ const read = cmds.indexOf('shell settings get global http_proxy');
85
+ const set = cmds.indexOf(`shell settings put global http_proxy 127.0.0.1:${PROXY_PORT}`);
86
+ (0, chai_1.expect)(read, cmds.join('\n')).to.be.greaterThan(-1);
87
+ (0, chai_1.expect)(set).to.be.greaterThan(read);
88
+ const [entry] = yield ledger.list();
89
+ (0, chai_1.expect)(entry).to.include({ sessionId: 's1', udid: 'R5CT' });
90
+ (0, chai_1.expect)(entry.proxy).to.deep.equal({
91
+ set: `127.0.0.1:${PROXY_PORT}`,
92
+ previous: 'proxy.corp:3128',
93
+ reversePort: PROXY_PORT,
94
+ });
95
+ yield svc.stop('s1');
96
+ }));
97
+ it("puts the phone's own proxy back at the end, and saves the capture", () => __awaiter(this, void 0, void 0, function* () {
98
+ const svc = new InterceptorService_1.InterceptorService();
99
+ yield svc.start('s1', phone, ON);
100
+ adb.calls.length = 0;
101
+ yield svc.stop('s1');
102
+ (0, chai_1.expect)(adb.commands('R5CT')).to.include('shell settings put global http_proxy proxy.corp:3128');
103
+ (0, chai_1.expect)(adb.commands('R5CT')).not.to.include('shell settings put global http_proxy :0');
104
+ (0, chai_1.expect)(yield ledger.list()).to.deep.equal([]);
105
+ (0, chai_1.expect)(fs_1.default.existsSync(path_1.default.join(assetsDir, 's1', 'interceptor', 'requests.json'))).to.equal(true);
106
+ }));
107
+ it('clears the proxy at the end when the phone had none', () => __awaiter(this, void 0, void 0, function* () {
108
+ adb.answers['shell settings get global http_proxy'] = ':0';
109
+ const svc = new InterceptorService_1.InterceptorService();
110
+ yield svc.start('s1', phone, ON);
111
+ adb.calls.length = 0;
112
+ yield svc.stop('s1');
113
+ (0, chai_1.expect)(adb.commands('R5CT')).to.include('shell settings put global http_proxy :0');
114
+ }));
115
+ it("doesn't put back a proxy an earlier run left pointing at a dead capture port", () => __awaiter(this, void 0, void 0, function* () {
116
+ const closed = net_1.default.createServer();
117
+ yield new Promise((r) => closed.listen(0, '127.0.0.1', () => r()));
118
+ const dead = closed.address().port;
119
+ yield new Promise((r) => closed.close(() => r()));
120
+ range = [Math.min(dead, PROXY_PORT), Math.max(dead, PROXY_PORT)];
121
+ adb.answers['shell settings get global http_proxy'] = `127.0.0.1:${dead}`;
122
+ const svc = new InterceptorService_1.InterceptorService();
123
+ yield svc.start('s1', phone, ON);
124
+ adb.calls.length = 0;
125
+ yield svc.stop('s1');
126
+ (0, chai_1.expect)(adb.commands('R5CT')).to.include('shell settings put global http_proxy :0');
127
+ (0, chai_1.expect)(adb.commands('R5CT')).not.to.include(`shell settings put global http_proxy 127.0.0.1:${dead}`);
128
+ }));
129
+ it('keeps the record when the proxy could not be put back', () => __awaiter(this, void 0, void 0, function* () {
130
+ const svc = new InterceptorService_1.InterceptorService();
131
+ yield svc.start('s1', phone, ON);
132
+ adb.answers['shell settings put global http_proxy proxy.corp:3128'] = new Error('offline');
133
+ yield svc.stop('s1');
134
+ (0, chai_1.expect)((yield ledger.list()).map((e) => e.sessionId)).to.deep.equal(['s1']);
135
+ (0, chai_1.expect)(svc.isActive('s1')).to.equal(false);
136
+ }));
137
+ it('stops once, however many times it is asked', () => __awaiter(this, void 0, void 0, function* () {
138
+ const svc = new InterceptorService_1.InterceptorService();
139
+ yield svc.start('s1', phone, ON);
140
+ adb.calls.length = 0;
141
+ yield Promise.all([svc.stop('s1'), svc.stop('s1')]);
142
+ const puts = adb.commands('R5CT').filter((c) => c.startsWith('shell settings put'));
143
+ (0, chai_1.expect)(puts).to.deep.equal(['shell settings put global http_proxy proxy.corp:3128']);
144
+ }));
145
+ });
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ require("reflect-metadata");
16
+ const chai_1 = require("chai");
17
+ const sinon_1 = __importDefault(require("sinon"));
18
+ const NetworkConditioningService_1 = require("../../../src/services/NetworkConditioningService");
19
+ const PhoneNetworkLedger_1 = require("../../../src/services/network/PhoneNetworkLedger");
20
+ const scratch_database_1 = require("../../helpers/scratch-database");
21
+ const fake_adb_1 = require("../../helpers/fake-adb");
22
+ // The live module object, so a spy sees a call made through any import of it.
23
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
24
+ const childProcess = require('child_process');
25
+ const phone = (udid, extra = {}) => (Object.assign({ udid, platform: 'android', deviceType: 'real', host: 'http://127.0.0.1:4723' }, extra));
26
+ const RADIOS_ON = {
27
+ 'shell settings get global wifi_on': '1',
28
+ 'shell settings get global mobile_data': '1',
29
+ };
30
+ describe('NetworkConditioningService', function () {
31
+ this.timeout(60000);
32
+ (0, scratch_database_1.useScratchDatabase)();
33
+ let adb;
34
+ (0, fake_adb_1.useFakeAdb)(() => adb);
35
+ let ledger;
36
+ beforeEach(() => __awaiter(this, void 0, void 0, function* () {
37
+ adb = (0, fake_adb_1.fakeAdb)(Object.assign({}, RADIOS_ON));
38
+ ledger = new PhoneNetworkLedger_1.PhoneNetworkLedger();
39
+ for (const entry of yield ledger.list())
40
+ yield ledger.forget(entry.sessionId);
41
+ }));
42
+ afterEach(() => sinon_1.default.restore());
43
+ describe('Offline on an Android phone', () => {
44
+ it('turns mobile data and Wi-Fi off through the resolved adb, never a shell', () => __awaiter(this, void 0, void 0, function* () {
45
+ const exec = sinon_1.default.spy(childProcess, 'exec');
46
+ const execFile = sinon_1.default.spy(childProcess, 'execFile');
47
+ const svc = new NetworkConditioningService_1.NetworkConditioningService();
48
+ yield svc.applyProfile('s1', phone('R5CT'), 'Offline');
49
+ (0, chai_1.expect)(adb.commands('R5CT')).to.deep.equal([
50
+ 'shell settings get global wifi_on',
51
+ 'shell settings get global mobile_data',
52
+ 'shell svc data disable',
53
+ 'shell svc wifi disable',
54
+ ]);
55
+ (0, chai_1.expect)(adb.calls.every((args) => args[0] === '-s' && args[1] === 'R5CT')).to.equal(true);
56
+ (0, chai_1.expect)(exec.called, 'child_process.exec').to.equal(false);
57
+ (0, chai_1.expect)(execFile.called, 'child_process.execFile').to.equal(false);
58
+ }));
59
+ it('writes down what it changed before changing it, so a restart can undo it', () => __awaiter(this, void 0, void 0, function* () {
60
+ adb.answers['shell settings get global mobile_data'] = '0';
61
+ yield new NetworkConditioningService_1.NetworkConditioningService().applyProfile('s1', phone('R5CT'), 'Offline');
62
+ const entries = yield ledger.list();
63
+ (0, chai_1.expect)(entries).to.have.length(1);
64
+ (0, chai_1.expect)(entries[0]).to.include({ sessionId: 's1', udid: 'R5CT' });
65
+ (0, chai_1.expect)(entries[0].offline).to.deep.equal({ wifiOn: true, dataOn: false });
66
+ }));
67
+ it('puts back what was on, and leaves off what was off', () => __awaiter(this, void 0, void 0, function* () {
68
+ adb.answers['shell settings get global mobile_data'] = '0';
69
+ const svc = new NetworkConditioningService_1.NetworkConditioningService();
70
+ yield svc.applyProfile('s1', phone('R5CT'), 'Offline');
71
+ adb.calls.length = 0;
72
+ yield svc.reset('s1');
73
+ (0, chai_1.expect)(adb.commands('R5CT')).to.deep.equal(['shell svc wifi enable']);
74
+ (0, chai_1.expect)(yield ledger.list()).to.deep.equal([]);
75
+ (0, chai_1.expect)(svc.has('s1')).to.equal(false);
76
+ }));
77
+ it('turns both back on when it could not read what they were', () => __awaiter(this, void 0, void 0, function* () {
78
+ adb.answers['shell settings get global wifi_on'] = new Error('device offline');
79
+ adb.answers['shell settings get global mobile_data'] = 'null';
80
+ const svc = new NetworkConditioningService_1.NetworkConditioningService();
81
+ yield svc.applyProfile('s1', phone('R5CT'), 'Offline');
82
+ adb.calls.length = 0;
83
+ yield svc.reset('s1');
84
+ (0, chai_1.expect)(adb.commands('R5CT')).to.deep.equal([
85
+ 'shell svc data enable',
86
+ 'shell svc wifi enable',
87
+ ]);
88
+ }));
89
+ it('keeps the record when the phone cannot be reached at the end', () => __awaiter(this, void 0, void 0, function* () {
90
+ const svc = new NetworkConditioningService_1.NetworkConditioningService();
91
+ yield svc.applyProfile('s1', phone('R5CT'), 'Offline');
92
+ adb.answers['shell svc data enable'] = new Error("device 'R5CT' not found");
93
+ yield svc.reset('s1');
94
+ const entries = yield ledger.list();
95
+ (0, chai_1.expect)(entries.map((e) => e.sessionId)).to.deep.equal(['s1']);
96
+ (0, chai_1.expect)(svc.has('s1')).to.equal(false);
97
+ }));
98
+ it('undoes a session once, however many times it is asked', () => __awaiter(this, void 0, void 0, function* () {
99
+ const svc = new NetworkConditioningService_1.NetworkConditioningService();
100
+ yield svc.applyProfile('s1', phone('R5CT'), 'Offline');
101
+ adb.calls.length = 0;
102
+ yield Promise.all([svc.reset('s1'), svc.reset('s1')]);
103
+ yield svc.reset('s1');
104
+ (0, chai_1.expect)(adb.commands('R5CT')).to.deep.equal([
105
+ 'shell svc data enable',
106
+ 'shell svc wifi enable',
107
+ ]);
108
+ }));
109
+ });
110
+ it("changes nothing on the phone for 3G, at the start or the end; it's a delay", () => __awaiter(this, void 0, void 0, function* () {
111
+ const svc = new NetworkConditioningService_1.NetworkConditioningService();
112
+ yield svc.applyProfile('s1', phone('R5CT'), '3G');
113
+ (0, chai_1.expect)(svc.getLatency('s1')).to.equal(100);
114
+ yield svc.reset('s1');
115
+ (0, chai_1.expect)(adb.calls).to.deep.equal([]);
116
+ (0, chai_1.expect)(svc.getLatency('s1')).to.equal(0);
117
+ (0, chai_1.expect)(yield ledger.list()).to.deep.equal([]);
118
+ }));
119
+ it('turns the network on for Normal, and has nothing to undo at the end', () => __awaiter(this, void 0, void 0, function* () {
120
+ const svc = new NetworkConditioningService_1.NetworkConditioningService();
121
+ yield svc.applyProfile('s1', phone('R5CT'), 'Normal');
122
+ (0, chai_1.expect)(adb.commands('R5CT')).to.deep.equal(['shell svc data enable', 'shell svc wifi enable']);
123
+ adb.calls.length = 0;
124
+ yield svc.reset('s1');
125
+ (0, chai_1.expect)(adb.calls).to.deep.equal([]);
126
+ (0, chai_1.expect)(yield ledger.list()).to.deep.equal([]);
127
+ }));
128
+ it("doesn't run xcrun for an iOS simulator, and says once that only the delay applies", () => __awaiter(this, void 0, void 0, function* () {
129
+ const exec = sinon_1.default.spy(childProcess, 'exec');
130
+ const execFile = sinon_1.default.spy(childProcess, 'execFile');
131
+ const svc = new NetworkConditioningService_1.NetworkConditioningService();
132
+ const said = sinon_1.default.spy(svc.log, 'info');
133
+ const sim = { udid: 'SIM-1', platform: 'ios', deviceType: 'simulator' };
134
+ yield svc.applyProfile('s1', sim, 'Offline');
135
+ yield svc.applyProfile('s2', Object.assign(Object.assign({}, sim), { udid: 'SIM-2' }), 'Edge');
136
+ yield svc.reset('s1');
137
+ yield svc.reset('s2');
138
+ (0, chai_1.expect)(exec.called).to.equal(false);
139
+ (0, chai_1.expect)(execFile.called).to.equal(false);
140
+ (0, chai_1.expect)(adb.calls).to.deep.equal([]);
141
+ const notes = said.args.filter((a) => /only .*delay/i.test(String(a[0])));
142
+ (0, chai_1.expect)(notes, said.args.map((a) => a[0]).join('\n')).to.have.length(1);
143
+ (0, chai_1.expect)(yield ledger.list()).to.deep.equal([]);
144
+ }));
145
+ });
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ require("reflect-metadata");
16
+ const chai_1 = require("chai");
17
+ const sinon_1 = __importDefault(require("sinon"));
18
+ const net_1 = __importDefault(require("net"));
19
+ const typedi_1 = require("typedi");
20
+ const PhoneNetworkRestore_1 = require("../../../src/services/network/PhoneNetworkRestore");
21
+ const PhoneNetworkLedger_1 = require("../../../src/services/network/PhoneNetworkLedger");
22
+ const NetworkConditioningService_1 = require("../../../src/services/NetworkConditioningService");
23
+ const InterceptorService_1 = require("../../../src/services/InterceptorService");
24
+ const PortAllocator_1 = require("../../../src/services/PortAllocator");
25
+ const prisma_1 = require("../../../src/prisma");
26
+ const container_registration_1 = require("../../helpers/container-registration");
27
+ const scratch_database_1 = require("../../helpers/scratch-database");
28
+ const fake_adb_1 = require("../../helpers/fake-adb");
29
+ const DAY = 24 * 60 * 60 * 1000;
30
+ function deferred() {
31
+ let resolve;
32
+ const promise = new Promise((r) => (resolve = r));
33
+ return { promise, resolve };
34
+ }
35
+ /** Stand-ins for the two services that change a phone's network for a session. */
36
+ function fakeServices(live) {
37
+ const net = {
38
+ has: (id) => live.net.has(id),
39
+ sessionIds: () => [...live.net],
40
+ reset: sinon_1.default.stub().callsFake((id) => __awaiter(this, void 0, void 0, function* () { return void live.net.delete(id); })),
41
+ };
42
+ const icp = {
43
+ isActive: (id) => live.icp.has(id),
44
+ sessionIds: () => [...live.icp],
45
+ stop: sinon_1.default.stub().callsFake((id) => __awaiter(this, void 0, void 0, function* () { return void live.icp.delete(id); })),
46
+ };
47
+ return { net, icp };
48
+ }
49
+ describe('PhoneNetworkRestore', function () {
50
+ this.timeout(60000);
51
+ (0, scratch_database_1.useScratchDatabase)();
52
+ let adb;
53
+ (0, fake_adb_1.useFakeAdb)(() => adb);
54
+ let restoreRegs;
55
+ let live;
56
+ let fakes;
57
+ let ledger;
58
+ let range;
59
+ beforeEach(() => __awaiter(this, void 0, void 0, function* () {
60
+ adb = (0, fake_adb_1.fakeAdb)();
61
+ restoreRegs = (0, container_registration_1.saveRegistrations)(NetworkConditioningService_1.NetworkConditioningService, InterceptorService_1.InterceptorService, PortAllocator_1.PortAllocator, PhoneNetworkLedger_1.PhoneNetworkLedger);
62
+ live = { net: new Set(), icp: new Set() };
63
+ fakes = fakeServices(live);
64
+ typedi_1.Container.set(NetworkConditioningService_1.NetworkConditioningService, fakes.net);
65
+ typedi_1.Container.set(InterceptorService_1.InterceptorService, fakes.icp);
66
+ range = [11100, 11199];
67
+ typedi_1.Container.set(PortAllocator_1.PortAllocator, { rangeOf: () => range });
68
+ ledger = new PhoneNetworkLedger_1.PhoneNetworkLedger();
69
+ typedi_1.Container.set(PhoneNetworkLedger_1.PhoneNetworkLedger, ledger);
70
+ for (const entry of yield ledger.list())
71
+ yield ledger.forget(entry.sessionId);
72
+ }));
73
+ afterEach(() => {
74
+ sinon_1.default.restore();
75
+ restoreRegs();
76
+ });
77
+ describe('restoreSession', () => {
78
+ it('resets the profile and stops the interceptor of a session', () => __awaiter(this, void 0, void 0, function* () {
79
+ live.net.add('s1');
80
+ live.icp.add('s1');
81
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().restoreSession('s1', 'test');
82
+ (0, chai_1.expect)(fakes.net.reset.calledOnceWith('s1')).to.equal(true);
83
+ (0, chai_1.expect)(fakes.icp.stop.calledOnceWith('s1')).to.equal(true);
84
+ }));
85
+ it('does nothing for a session that changed nothing, or no session', () => __awaiter(this, void 0, void 0, function* () {
86
+ const restore = new PhoneNetworkRestore_1.PhoneNetworkRestore();
87
+ yield restore.restoreSession('s-plain', 'test');
88
+ yield restore.restoreSession(undefined, 'test');
89
+ yield restore.restoreSession(null, 'test');
90
+ (0, chai_1.expect)(fakes.net.reset.called).to.equal(false);
91
+ (0, chai_1.expect)(fakes.icp.stop.called).to.equal(false);
92
+ }));
93
+ it('runs once when two endings race, and both wait until it is done', () => __awaiter(this, void 0, void 0, function* () {
94
+ live.net.add('s1');
95
+ const gate = deferred();
96
+ fakes.net.reset.callsFake((id) => __awaiter(this, void 0, void 0, function* () {
97
+ yield gate.promise;
98
+ live.net.delete(id);
99
+ }));
100
+ const restore = new PhoneNetworkRestore_1.PhoneNetworkRestore();
101
+ let firstDone = false;
102
+ let secondDone = false;
103
+ const first = restore.restoreSession('s1', 'deleted').then(() => (firstDone = true));
104
+ const second = restore.restoreSession('s1', 'idle').then(() => (secondDone = true));
105
+ yield new Promise((r) => setImmediate(r));
106
+ (0, chai_1.expect)(firstDone || secondDone, 'nobody may go on before the network is back').to.equal(false);
107
+ gate.resolve();
108
+ yield Promise.all([first, second]);
109
+ yield restore.restoreSession('s1', 'later');
110
+ (0, chai_1.expect)(fakes.net.reset.callCount).to.equal(1);
111
+ }));
112
+ it("never throws: one service's failure doesn't stop the other's restore", () => __awaiter(this, void 0, void 0, function* () {
113
+ live.net.add('s1');
114
+ live.icp.add('s1');
115
+ fakes.net.reset.rejects(new Error('boom'));
116
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().restoreSession('s1', 'test');
117
+ (0, chai_1.expect)(fakes.icp.stop.calledOnceWith('s1')).to.equal(true);
118
+ }));
119
+ });
120
+ it('restoreAll restores every session either service still has', () => __awaiter(this, void 0, void 0, function* () {
121
+ live.net.add('a');
122
+ live.icp.add('a');
123
+ live.icp.add('b');
124
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().restoreAll('shutdown');
125
+ (0, chai_1.expect)(fakes.net.reset.args.map((a) => a[0])).to.deep.equal(['a']);
126
+ (0, chai_1.expect)(fakes.icp.stop.args.map((a) => a[0]).sort()).to.deep.equal(['a', 'b']);
127
+ }));
128
+ describe('restoreLeftovers: what an ended run wrote down', () => {
129
+ beforeEach(() => {
130
+ adb.answers['P1 shell settings get global http_proxy'] = '127.0.0.1:11150';
131
+ });
132
+ it('turns back on what was on, and puts back the proxy it set', () => __awaiter(this, void 0, void 0, function* () {
133
+ yield ledger.note('old', 'P1', { offline: { wifiOn: true, dataOn: false } });
134
+ yield ledger.note('old', 'P1', {
135
+ proxy: { set: '127.0.0.1:11150', previous: null, reversePort: 11150 },
136
+ });
137
+ const done = yield new PhoneNetworkRestore_1.PhoneNetworkRestore().restoreLeftovers({});
138
+ (0, chai_1.expect)(done).to.equal(1);
139
+ (0, chai_1.expect)(adb.commands('P1')).to.deep.equal([
140
+ 'shell svc wifi enable',
141
+ 'shell settings get global http_proxy',
142
+ 'shell settings put global http_proxy :0',
143
+ 'reverse --remove tcp:11150',
144
+ ]);
145
+ (0, chai_1.expect)(yield ledger.list()).to.deep.equal([]);
146
+ }));
147
+ it("puts back the phone's own proxy when it had one", () => __awaiter(this, void 0, void 0, function* () {
148
+ yield ledger.note('old', 'P1', {
149
+ proxy: { set: '127.0.0.1:11150', previous: 'proxy.corp:3128' },
150
+ });
151
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().restoreLeftovers({});
152
+ (0, chai_1.expect)(adb.commands('P1')).to.include('shell settings put global http_proxy proxy.corp:3128');
153
+ }));
154
+ it('leaves a proxy alone that someone changed since, and forgets it', () => __awaiter(this, void 0, void 0, function* () {
155
+ adb.answers['P1 shell settings get global http_proxy'] = 'proxy.corp:3128';
156
+ yield ledger.note('old', 'P1', { proxy: { set: '127.0.0.1:11150', previous: null } });
157
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().restoreLeftovers({});
158
+ (0, chai_1.expect)(adb.commands('P1')).to.deep.equal(['shell settings get global http_proxy']);
159
+ (0, chai_1.expect)(yield ledger.list()).to.deep.equal([]);
160
+ }));
161
+ it('skips a session this server still runs', () => __awaiter(this, void 0, void 0, function* () {
162
+ live.net.add('running');
163
+ yield ledger.note('running', 'P1', { offline: { wifiOn: true, dataOn: true } });
164
+ (0, chai_1.expect)(yield new PhoneNetworkRestore_1.PhoneNetworkRestore().restoreLeftovers({})).to.equal(0);
165
+ (0, chai_1.expect)(adb.calls).to.deep.equal([]);
166
+ (0, chai_1.expect)((yield ledger.list()).map((e) => e.sessionId)).to.deep.equal(['running']);
167
+ }));
168
+ it('touches only the phone it is asked about', () => __awaiter(this, void 0, void 0, function* () {
169
+ yield ledger.note('old-1', 'P1', { offline: { wifiOn: true, dataOn: true } });
170
+ yield ledger.note('old-2', 'P2', { offline: { wifiOn: true, dataOn: true } });
171
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().restoreLeftovers({ udid: 'P2' });
172
+ (0, chai_1.expect)(adb.commands('P1')).to.deep.equal([]);
173
+ (0, chai_1.expect)(adb.commands('P2')).to.deep.equal(['shell svc data enable', 'shell svc wifi enable']);
174
+ (0, chai_1.expect)((yield ledger.list()).map((e) => e.sessionId)).to.deep.equal(['old-1']);
175
+ }));
176
+ it('keeps what it could not undo for next time, and gives up after a week', () => __awaiter(this, void 0, void 0, function* () {
177
+ var _a;
178
+ adb.answers['P1 shell svc data enable'] = new Error("device 'P1' not found");
179
+ adb.answers['P9 shell svc data enable'] = new Error("device 'P9' not found");
180
+ yield ledger.note('recent', 'P1', { offline: { wifiOn: true, dataOn: true } });
181
+ yield ledger.note('ancient', 'P9', { offline: { wifiOn: true, dataOn: true } });
182
+ const row = yield prisma_1.prisma.webConfig.findUnique({ where: { name: 'phone-network:ancient' } });
183
+ (0, chai_1.expect)(row, 'the record').to.not.equal(null);
184
+ const value = JSON.parse((_a = row === null || row === void 0 ? void 0 : row.value) !== null && _a !== void 0 ? _a : '{}');
185
+ yield prisma_1.prisma.webConfig.update({
186
+ where: { name: 'phone-network:ancient' },
187
+ data: { value: JSON.stringify(Object.assign(Object.assign({}, value), { at: Date.now() - 8 * DAY })) },
188
+ });
189
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().restoreLeftovers({});
190
+ (0, chai_1.expect)((yield ledger.list()).map((e) => e.sessionId)).to.deep.equal(['recent']);
191
+ }));
192
+ });
193
+ describe('cleanUpAtBoot: a proxy left pointing at a dead capture port', () => {
194
+ let server;
195
+ let livePort;
196
+ let deadPort;
197
+ beforeEach(() => __awaiter(this, void 0, void 0, function* () {
198
+ server = net_1.default.createServer((s) => s.destroy());
199
+ yield new Promise((r) => server.listen(0, '127.0.0.1', () => r()));
200
+ livePort = server.address().port;
201
+ const closed = net_1.default.createServer();
202
+ yield new Promise((r) => closed.listen(0, '127.0.0.1', () => r()));
203
+ deadPort = closed.address().port;
204
+ yield new Promise((r) => closed.close(() => r()));
205
+ range = [Math.min(livePort, deadPort), Math.max(livePort, deadPort)];
206
+ }));
207
+ afterEach(() => __awaiter(this, void 0, void 0, function* () {
208
+ yield new Promise((r) => server.close(() => r()));
209
+ }));
210
+ it("clears only a proxy that points at this machine's capture ports where nothing answers", () => __awaiter(this, void 0, void 0, function* () {
211
+ adb.answers['DEAD shell settings get global http_proxy'] = `127.0.0.1:${deadPort}`;
212
+ adb.answers['EMU shell settings get global http_proxy'] = `10.0.2.2:${deadPort}`;
213
+ adb.answers['LIVE shell settings get global http_proxy'] = `127.0.0.1:${livePort}`;
214
+ adb.answers['CORP shell settings get global http_proxy'] = 'proxy.corp:3128';
215
+ adb.answers['NONE shell settings get global http_proxy'] = ':0';
216
+ adb.answers['FAR shell settings get global http_proxy'] = '127.0.0.1:1';
217
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().cleanUpAtBoot(['DEAD', 'EMU', 'LIVE', 'CORP', 'NONE', 'FAR']);
218
+ (0, chai_1.expect)(adb.commands('DEAD')).to.deep.equal([
219
+ 'shell settings get global http_proxy',
220
+ 'shell settings put global http_proxy :0',
221
+ `reverse --remove tcp:${deadPort}`,
222
+ ]);
223
+ (0, chai_1.expect)(adb.commands('EMU')).to.deep.equal([
224
+ 'shell settings get global http_proxy',
225
+ 'shell settings put global http_proxy :0',
226
+ ]);
227
+ for (const kept of ['LIVE', 'CORP', 'NONE', 'FAR']) {
228
+ (0, chai_1.expect)(adb.commands(kept), kept).to.deep.equal(['shell settings get global http_proxy']);
229
+ }
230
+ }));
231
+ it('undoes what the ledger holds first, then checks the phones', () => __awaiter(this, void 0, void 0, function* () {
232
+ yield ledger.note('old', 'P1', { offline: { wifiOn: true, dataOn: true } });
233
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().cleanUpAtBoot([]);
234
+ (0, chai_1.expect)(adb.commands('P1')).to.deep.equal(['shell svc data enable', 'shell svc wifi enable']);
235
+ }));
236
+ it('never throws, even when a phone cannot be read', () => __awaiter(this, void 0, void 0, function* () {
237
+ adb.answers['shell settings get global http_proxy'] = new Error('unauthorized');
238
+ yield new PhoneNetworkRestore_1.PhoneNetworkRestore().cleanUpAtBoot(['X']);
239
+ }));
240
+ });
241
+ it('can be built by the container', () => {
242
+ restoreRegs();
243
+ restoreRegs = (0, container_registration_1.saveRegistrations)(PhoneNetworkRestore_1.PhoneNetworkRestore);
244
+ (0, chai_1.expect)(() => typedi_1.Container.get(PhoneNetworkRestore_1.PhoneNetworkRestore)).not.to.throw();
245
+ });
246
+ });