@xenon-device-management/xenon 2.1.0 → 2.2.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.
- package/lib/package.json +3 -3
- package/lib/public/assets/{AnnotationOverlay-C-Pw_hvw.js → AnnotationOverlay-XpewX5nn.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-DmYQ0oSk.js → ApiKeyGate-BQDv3ZCC.js} +1 -1
- package/lib/public/assets/{BugReportButton-n8T1dFUp.js → BugReportButton-C7OmS61B.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-D-rGOBbJ.js → DeviceMosaicView-wl3l2Zlt.js} +1 -1
- package/lib/public/assets/{EmptyState-DExETevp.js → EmptyState-fPxujqqE.js} +1 -1
- package/lib/public/assets/{FieldGroup-CCdFlZm6.js → FieldGroup-5bCutWrs.js} +1 -1
- package/lib/public/assets/{FilterMenu-DiUSOSL-.js → FilterMenu-owsBiDT1.js} +1 -1
- package/lib/public/assets/{Menu-ZOfpH6gh.js → Menu-DFhqzPSt.js} +1 -1
- package/lib/public/assets/{Modal-CZNtoB-1.js → Modal-Bw-a-E-e.js} +1 -1
- package/lib/public/assets/{RecordingPage-BKp6h8Jl.js → RecordingPage-WtByf5bC.js} +1 -1
- package/lib/public/assets/{RecordingsPage-Bf881FX4.js → RecordingsPage-FpWWFNgT.js} +1 -1
- package/lib/public/assets/{SegmentedControl-EUoyizvW.js → SegmentedControl-Jzn9c7o8.js} +1 -1
- package/lib/public/assets/{SettingCard-ClT4hf4_.js → SettingCard-BR7nOhpT.js} +1 -1
- package/lib/public/assets/{Table-ucR4bnmc.js → Table-B_Va3V05.js} +1 -1
- package/lib/public/assets/{activity-K-hdylwq.js → activity-DxRFWvkN.js} +1 -1
- package/lib/public/assets/{ai-settings-BLhdwI3c.js → ai-settings-CQf-9_V6.js} +1 -1
- package/lib/public/assets/{api-keys-BbiN7rBI.js → api-keys-SoE1Kd7N.js} +1 -1
- package/lib/public/assets/{apps-Dez1petz.js → apps-CgGXTlgn.js} +1 -1
- package/lib/public/assets/{arrow-left-UjmFHLAJ.js → arrow-left-uQ6WIwiC.js} +1 -1
- package/lib/public/assets/{arrow-right-BVyEigbu.js → arrow-right-C9RyfzXS.js} +1 -1
- package/lib/public/assets/{arrow-up-right-B-ZlBof3.js → arrow-up-right-DFHi_8yG.js} +1 -1
- package/lib/public/assets/{auth-shell-DAQjAgUv.js → auth-shell-Bii1t4qc.js} +2 -2
- package/lib/public/assets/{builds-page-CmIPa5JK.js → builds-page-Bnv8Z9hi.js} +1 -1
- package/lib/public/assets/{button-Dd-IhMC1.js → button-_Y78wXiP.js} +1 -1
- package/lib/public/assets/{calendar-CAIeJwa5.js → calendar-D4MqSOc2.js} +1 -1
- package/lib/public/assets/{check-C4Jrjg8f.js → check-uGHCXe-L.js} +1 -1
- package/lib/public/assets/{chevron-left-BGzlf-4i.js → chevron-left-Dy1OqpzB.js} +1 -1
- package/lib/public/assets/{chevron-right-_wYqDjDC.js → chevron-right-BUsldEli.js} +1 -1
- package/lib/public/assets/{circle-check-Ckn00yMB.js → circle-check-D3Qq0tKp.js} +1 -1
- package/lib/public/assets/{circle-x-pdVkomLa.js → circle-x-B4IOD1DE.js} +1 -1
- package/lib/public/assets/{clock-B8vmwkHj.js → clock-BfYfzvSg.js} +1 -1
- package/lib/public/assets/{copy-BAqnFI-F.js → copy-DZwYE5Va.js} +1 -1
- package/lib/public/assets/{device-explorer-CYq_4F_N.js → device-explorer-CRG4Utt5.js} +1 -1
- package/lib/public/assets/{download-B6UEAp_1.js → download-CCDYo_TN.js} +1 -1
- package/lib/public/assets/{forgot-password-CLqNvmrN.js → forgot-password-DuLCmVFb.js} +1 -1
- package/lib/public/assets/{funnel-Da90BBbT.js → funnel-CtKNPqzb.js} +1 -1
- package/lib/public/assets/{index-BuIIcmGp.js → index-3uxl3o7K.js} +43 -43
- package/lib/public/assets/{index-MX7IzqyW.js → index-Zy1Op5C1.js} +1 -1
- package/lib/public/assets/{input-Bq9aY-We.js → input-CzTWvUXZ.js} +1 -1
- package/lib/public/assets/{keycodes-CIhXIJyP.js → keycodes-CI-nYDFM.js} +1 -1
- package/lib/public/assets/{layers-bXjpswQI.js → layers-YZZGFyTX.js} +1 -1
- package/lib/public/assets/{lock-pJ72y5Fy.js → lock-L8tzWgDo.js} +1 -1
- package/lib/public/assets/{login-UOcHALV2.js → login-DBbX-fu8.js} +1 -1
- package/lib/public/assets/{maintenance-settings-XnJgp5yb.js → maintenance-settings-AJwkOIsE.js} +1 -1
- package/lib/public/assets/{monitor-QkAhI1Jx.js → monitor-Di_66F2m.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-B42Da0m4.js → mouse-pointer-2-Cnab2zdg.js} +1 -1
- package/lib/public/assets/{network-CKVqcyp6.js → network-DiSumY-1.js} +1 -1
- package/lib/public/assets/{overview-BORGkN3N.js → overview-CwE4gXl5.js} +1 -1
- package/lib/public/assets/{page-header-CKWFOjOI.js → page-header-CfR4Sm9V.js} +1 -1
- package/lib/public/assets/{plus-BpFKVdC8.js → plus-DOS8P9CQ.js} +1 -1
- package/lib/public/assets/{profile-page-CcbKBicY.js → profile-page-BYpHwQIH.js} +1 -1
- package/lib/public/assets/{reset-password-BA0W9bxl.js → reset-password-BRVJU_qV.js} +1 -1
- package/lib/public/assets/{runbook-page-Cp7ZBq3s.js → runbook-page-Bi3JvAFZ.js} +1 -1
- package/lib/public/assets/{select-DIZWIoVR.js → select-Cqx7pEou.js} +1 -1
- package/lib/public/assets/{selector-detail-page-C_4YuvAp.js → selector-detail-page-C0DsncKj.js} +1 -1
- package/lib/public/assets/{selector-health-CXjD_Xth.js → selector-health-CAhpdilF.js} +1 -1
- package/lib/public/assets/{selector-health-page-CTqIHeoG.js → selector-health-page-CrCVeW2m.js} +1 -1
- package/lib/public/assets/{session-detail-page-B0KYkk8z.js → session-detail-page-F_hwYqEt.js} +1 -1
- package/lib/public/assets/{settings-BcRBKUXd.js → settings-C-s5pypL.js} +1 -1
- package/lib/public/assets/{status-pill-outline-Dh2PXSf_.js → status-pill-outline-DY5uzv6A.js} +1 -1
- package/lib/public/assets/{tablet-_sXqZztt.js → tablet-Dli7gwyg.js} +1 -1
- package/lib/public/assets/{teams-DGHT8MKm.js → teams-ui4IhHNC.js} +1 -1
- package/lib/public/assets/{trash-2-BV88cV3Z.js → trash-2-BV4hSmXJ.js} +1 -1
- package/lib/public/assets/{upload-CIDH9wkj.js → upload-DkGy_J-L.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-DYGuSNwl.js → use-password-reset-mode-BFtMqQOh.js} +1 -1
- package/lib/public/assets/{users-BUE7MoTq.js → users-BU0nXIVe.js} +1 -1
- package/lib/public/assets/{users-7yviqflc.js → users-Nb_rzRmF.js} +1 -1
- package/lib/public/assets/{video-off-BTRxtlGo.js → video-off-CUNzD2kH.js} +1 -1
- package/lib/public/assets/{webhook-settings-DMKD9Pgm.js → webhook-settings-Ca3UFXZe.js} +1 -1
- package/lib/public/assets/{zap-CWE94qcK.js → zap-B2bH2vKX.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/app/routers/control.js +5 -142
- package/lib/src/app/routers/nodePhoneControl.js +301 -0
- package/lib/src/app/ws/h264StreamWs.js +30 -4
- package/lib/src/app/ws/logcatWs.js +36 -7
- package/lib/src/app/ws/nodeSocketRelay.js +167 -0
- package/lib/src/data-service/device-store.js +11 -0
- package/lib/src/data-service/deviceClaims.js +18 -0
- package/lib/src/data-service/prisma-store.js +8 -0
- package/lib/src/device-managers/AndroidDeviceManager.js +27 -27
- package/lib/src/device-managers/android/AndroidH264StreamService.js +10 -2
- package/lib/src/device-managers/android/AndroidStreamService.js +3 -1
- package/lib/src/device-managers/ios/IOSStreamService.js +4 -2
- package/lib/src/middleware/deviceAccessGuard.js +4 -1
- package/lib/src/services/ServerManager.js +13 -0
- package/lib/src/services/device-access/ticketActorAccess.js +3 -1
- package/lib/src/services/lease/LeaseOrphanSweeper.js +4 -2
- package/lib/test/helpers/own-node-id.js +29 -0
- package/lib/test/setup/scratch-default-database.js +32 -0
- package/lib/test/unit/AndroidDeviceManager.spec.js +48 -0
- package/lib/test/unit/android-h264-service.spec.js +18 -0
- package/lib/test/unit/clipboard-route.spec.js +4 -0
- package/lib/test/unit/control-forward-decision.spec.js +17 -17
- package/lib/test/unit/device-access-guard.spec.js +38 -0
- package/lib/test/unit/hub-node-control-forward.spec.js +318 -1
- package/lib/test/unit/hub-node-race.spec.js +25 -0
- package/lib/test/unit/install-repository-app-team.spec.js +4 -0
- package/lib/test/unit/lease/lease-sweeper-expiry.spec.js +15 -0
- package/lib/test/unit/logcat-ws.spec.js +8 -0
- package/lib/test/unit/mark-node-busy-loki.spec.js +53 -0
- package/lib/test/unit/node-phone-control.spec.js +131 -0
- package/lib/test/unit/node-socket-relay.spec.js +357 -0
- package/lib/test/unit/stream-leave-route.spec.js +4 -0
- package/lib/test/unit/stream-stop-ownership.spec.js +4 -0
- package/lib/test/unit/stream-stop-recording.spec.js +4 -0
- package/lib/test/unit/stream-ticket-identity.spec.js +11 -1
- package/lib/test/unit/stream-watchdog-timers.spec.js +46 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OWN_NODE_ID = void 0;
|
|
4
|
+
exports.useOwnNodeId = useOwnNodeId;
|
|
5
|
+
const typedi_1 = require("typedi");
|
|
6
|
+
const PluginContext_1 = require("../../src/PluginContext");
|
|
7
|
+
/**
|
|
8
|
+
* The node id of "this server" in a spec, for fake rows that are its own
|
|
9
|
+
* phones. /control sends another server's phone's actions to that server
|
|
10
|
+
* (src/app/routers/nodePhoneControl.ts) and decides whose a phone is by its
|
|
11
|
+
* row's nodeId, which discovery always writes. A fake row without one, whose
|
|
12
|
+
* host isn't one of this server's, is another server's phone.
|
|
13
|
+
*/
|
|
14
|
+
exports.OWN_NODE_ID = 'this-server';
|
|
15
|
+
/**
|
|
16
|
+
* Give the PluginContext OWN_NODE_ID for each test of the enclosing
|
|
17
|
+
* `describe`, and put the old one back after it. Call it inside a describe.
|
|
18
|
+
*/
|
|
19
|
+
function useOwnNodeId() {
|
|
20
|
+
let saved = '';
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
const context = typedi_1.Container.get(PluginContext_1.PluginContext);
|
|
23
|
+
saved = context.nodeId;
|
|
24
|
+
context.nodeId = exports.OWN_NODE_ID;
|
|
25
|
+
});
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
typedi_1.Container.get(PluginContext_1.PluginContext).nodeId = saved;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Loaded with --require by `npm run test:all`, before any spec.
|
|
3
|
+
//
|
|
4
|
+
// src/config.ts reads DATABASE_URL once, at import time, and falls back to
|
|
5
|
+
// ~/.cache/xenon/xenon.db. Specs that don't stub the store (EventLogService,
|
|
6
|
+
// MetricsService and others writing in passing) used to write their rows into
|
|
7
|
+
// that file, the database a developer's own server uses. This points the whole
|
|
8
|
+
// run at a freshly migrated scratch file instead, and removes it at exit.
|
|
9
|
+
// Specs that need a database of their own still use useScratchDatabase().
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-var-requires -- plain CommonJS, loaded before ts-node */
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const dbPath = path.join(os.tmpdir(), `xenon-test-run-${process.pid}-${Date.now()}.db`);
|
|
16
|
+
const url = `file:${dbPath}`;
|
|
17
|
+
execSync('npx prisma migrate deploy', {
|
|
18
|
+
cwd: path.resolve(__dirname, '../..'),
|
|
19
|
+
env: Object.assign(Object.assign({}, process.env), { DATABASE_URL: url }),
|
|
20
|
+
stdio: 'pipe',
|
|
21
|
+
});
|
|
22
|
+
process.env.DATABASE_URL = url;
|
|
23
|
+
process.on('exit', () => {
|
|
24
|
+
for (const f of [dbPath, `${dbPath}-journal`]) {
|
|
25
|
+
try {
|
|
26
|
+
fs.unlinkSync(f);
|
|
27
|
+
}
|
|
28
|
+
catch (_a) {
|
|
29
|
+
// already gone
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
require("reflect-metadata");
|
|
16
16
|
const sinon_1 = __importDefault(require("sinon"));
|
|
17
|
+
const AndroidDeviceManager_1 = require("../../src/device-managers/AndroidDeviceManager");
|
|
17
18
|
const adbkit_1 = __importDefault(require("@devicefarmer/adbkit"));
|
|
18
19
|
const appium_adb_1 = require("appium-adb");
|
|
19
20
|
const chai_1 = require("chai");
|
|
@@ -165,6 +166,31 @@ describe('Android Device Manager', () => {
|
|
|
165
166
|
(0, chai_1.expect)(devices.length).to.be.equal(1);
|
|
166
167
|
(0, chai_1.expect)(devices[0]).to.have.property('udid', 'emulator-7777');
|
|
167
168
|
}));
|
|
169
|
+
// A phone adb reports as plugged (and every phone connected when the server
|
|
170
|
+
// starts) went through onDeviceAdded with no androidDeviceType check: a
|
|
171
|
+
// hub set to emulators only listed a real phone until its next sync pruned
|
|
172
|
+
// it, and could hand it to a session meanwhile.
|
|
173
|
+
describe('a plugged phone', () => {
|
|
174
|
+
const plug = (androidDeviceType, real) => __awaiter(void 0, void 0, void 0, function* () {
|
|
175
|
+
const manager = (0, test_container_1.createTestAndroidManager)({ platform: 'android', androidDeviceType });
|
|
176
|
+
sandbox.stub(manager, 'waitBootComplete').resolves(true);
|
|
177
|
+
sandbox.stub(manager, 'isRealDevice').resolves(real);
|
|
178
|
+
// Stands for the rest of the add: the port lease, the row, the hub.
|
|
179
|
+
const deviceInfo = sandbox.stub(manager, 'deviceInfo').resolves(undefined);
|
|
180
|
+
yield manager.onDeviceAdded(adb, { id: 'plugged-1', type: 'device' });
|
|
181
|
+
return deviceInfo.called;
|
|
182
|
+
});
|
|
183
|
+
it('is ignored when the server doesn’t serve its kind', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
184
|
+
(0, chai_1.expect)(yield plug('simulated', true), 'a real phone, emulators only').to.equal(false);
|
|
185
|
+
(0, chai_1.expect)(yield plug('real', false), 'an emulator, real phones only').to.equal(false);
|
|
186
|
+
}));
|
|
187
|
+
it('is added when the server serves its kind', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
188
|
+
(0, chai_1.expect)(yield plug('simulated', false)).to.equal(true);
|
|
189
|
+
(0, chai_1.expect)(yield plug('real', true)).to.equal(true);
|
|
190
|
+
(0, chai_1.expect)(yield plug('both', true)).to.equal(true);
|
|
191
|
+
(0, chai_1.expect)(yield plug('both', false)).to.equal(true);
|
|
192
|
+
}));
|
|
193
|
+
});
|
|
168
194
|
it('should handle device never completing boot', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
169
195
|
const androidDevices = (0, test_container_1.createTestAndroidManager)({ platform: 'android' });
|
|
170
196
|
// @ts-expect-error - Accessing private member for testing
|
|
@@ -183,3 +209,25 @@ describe('Android Device Manager', () => {
|
|
|
183
209
|
}).to.not.throw();
|
|
184
210
|
}));
|
|
185
211
|
});
|
|
212
|
+
// The one rule discovery and a plugged phone both apply.
|
|
213
|
+
describe('includesAndroidDevice', () => {
|
|
214
|
+
const real = { deviceType: 'real', state: 'device' };
|
|
215
|
+
const emulator = { deviceType: 'emulator', state: 'device' };
|
|
216
|
+
const offlineEmulator = { deviceType: 'emulator', state: 'offline' };
|
|
217
|
+
it('real: real phones only', () => {
|
|
218
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(real, 'real')).to.equal(true);
|
|
219
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(emulator, 'real')).to.equal(false);
|
|
220
|
+
});
|
|
221
|
+
it('simulated: emulators only, booted ones when bootedEmulators is on', () => {
|
|
222
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(real, 'simulated')).to.equal(false);
|
|
223
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(emulator, 'simulated')).to.equal(true);
|
|
224
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(offlineEmulator, 'simulated')).to.equal(true);
|
|
225
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(offlineEmulator, 'simulated', true)).to.equal(false);
|
|
226
|
+
});
|
|
227
|
+
it('both: everything, emulators booted when bootedEmulators is on', () => {
|
|
228
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(real, 'both')).to.equal(true);
|
|
229
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(emulator, 'both', true)).to.equal(true);
|
|
230
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(offlineEmulator, 'both', true)).to.equal(false);
|
|
231
|
+
(0, chai_1.expect)((0, AndroidDeviceManager_1.includesAndroidDevice)(Object.assign(Object.assign({}, real), { state: 'offline' }), 'both', true)).to.equal(true);
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
require("reflect-metadata");
|
|
16
16
|
const chai_1 = require("chai");
|
|
17
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
17
18
|
const typedi_1 = require("typedi");
|
|
18
19
|
const AndroidH264StreamService_1 = __importDefault(require("../../src/device-managers/android/AndroidH264StreamService"));
|
|
19
20
|
const PluginContext_1 = require("../../src/PluginContext");
|
|
@@ -50,6 +51,23 @@ describe('AndroidH264StreamService', () => {
|
|
|
50
51
|
(0, chai_1.expect)(mux).to.equal(svc.getMultiplexer('udid-x'));
|
|
51
52
|
(0, chai_1.expect)(mux.clientCount).to.equal(0);
|
|
52
53
|
}));
|
|
54
|
+
it('start() leaves no timer behind once the config packet arrives', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
55
|
+
const clock = sinon_1.default.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] });
|
|
56
|
+
try {
|
|
57
|
+
const svc = make();
|
|
58
|
+
svc.openCapture = (_udid, onPacket) => __awaiter(void 0, void 0, void 0, function* () {
|
|
59
|
+
onPacket({ type: 'config', data: Buffer.from([0]), ptsMs: 0 });
|
|
60
|
+
return { kill: () => undefined };
|
|
61
|
+
});
|
|
62
|
+
yield svc.start('udid-t');
|
|
63
|
+
// The 3 s wait is only a cap: a start that got its config in time must
|
|
64
|
+
// not leave it pending for the process to wait out.
|
|
65
|
+
(0, chai_1.expect)(clock.countTimers()).to.equal(0);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
clock.restore();
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
53
71
|
it('start() is idempotent per udid (concurrent starts share one capture)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
54
72
|
const svc = make();
|
|
55
73
|
let opens = 0;
|
|
@@ -26,6 +26,7 @@ const clipboardErrors_1 = require("../../src/device-managers/clipboardErrors");
|
|
|
26
26
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
27
27
|
const container_registration_1 = require("../helpers/container-registration");
|
|
28
28
|
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
29
|
+
const own_node_id_1 = require("../helpers/own-node-id");
|
|
29
30
|
/**
|
|
30
31
|
* GET/POST /control/:udid/clipboard answered 200 whatever happened on the
|
|
31
32
|
* device: the Android manager swallowed its errors, and its "set" had no
|
|
@@ -58,6 +59,8 @@ function buildApp() {
|
|
|
58
59
|
return app;
|
|
59
60
|
}
|
|
60
61
|
describe('GET/POST /control/:udid/clipboard', () => {
|
|
62
|
+
// The fake rows are this server's own phones.
|
|
63
|
+
(0, own_node_id_1.useOwnNodeId)();
|
|
61
64
|
let manager;
|
|
62
65
|
let restoreContainer;
|
|
63
66
|
// Served on 127.0.0.1, not by request(app): that listens on every address
|
|
@@ -73,6 +76,7 @@ describe('GET/POST /control/:udid/clipboard', () => {
|
|
|
73
76
|
return ({
|
|
74
77
|
udid: UDID,
|
|
75
78
|
host: '127.0.0.1',
|
|
79
|
+
nodeId: own_node_id_1.OWN_NODE_ID,
|
|
76
80
|
platform: 'android',
|
|
77
81
|
busy: false,
|
|
78
82
|
session_id: null,
|
|
@@ -21,11 +21,11 @@ const typedi_1 = require("typedi");
|
|
|
21
21
|
const control_1 = __importDefault(require("../../src/app/routers/control"));
|
|
22
22
|
const device_store_1 = require("../../src/data-service/device-store");
|
|
23
23
|
const device_managers_1 = require("../../src/device-managers");
|
|
24
|
-
const InternalHttpClient_1 = require("../../src/InternalHttpClient");
|
|
25
24
|
const PluginContext_1 = require("../../src/PluginContext");
|
|
26
25
|
const IPluginArgs_1 = require("../../src/interfaces/IPluginArgs");
|
|
27
26
|
const authMiddleware_1 = require("../../src/middleware/authMiddleware");
|
|
28
27
|
const container_registration_1 = require("../helpers/container-registration");
|
|
28
|
+
const loopbackServer_1 = require("../helpers/loopbackServer");
|
|
29
29
|
/**
|
|
30
30
|
* Whether a /control action is forwarded to another server is decided by the
|
|
31
31
|
* phone's own row (its nodeId, else its exact host against this server's own
|
|
@@ -64,8 +64,8 @@ function buildApp() {
|
|
|
64
64
|
return app;
|
|
65
65
|
}
|
|
66
66
|
describe('/control forwards only another server’s phone', () => {
|
|
67
|
+
const loopback = (0, loopbackServer_1.loopbackServers)();
|
|
67
68
|
let manager;
|
|
68
|
-
let post;
|
|
69
69
|
let restore;
|
|
70
70
|
let context;
|
|
71
71
|
let savedContext;
|
|
@@ -80,13 +80,13 @@ describe('/control forwards only another server’s phone', () => {
|
|
|
80
80
|
sinon_1.default
|
|
81
81
|
.stub(device_store_1.DeviceStoreFactory, 'getStore')
|
|
82
82
|
.returns({ findDevice: () => __awaiter(void 0, void 0, void 0, function* () { return (Object.assign({}, device)); }) });
|
|
83
|
-
post = sinon_1.default.stub(InternalHttpClient_1.InternalHttpClient, 'post').resolves({ data: {} });
|
|
84
83
|
});
|
|
85
|
-
afterEach(() => {
|
|
84
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
86
85
|
Object.assign(context, savedContext);
|
|
87
86
|
sinon_1.default.restore();
|
|
88
87
|
restore();
|
|
89
|
-
|
|
88
|
+
yield loopback.closeAll();
|
|
89
|
+
}));
|
|
90
90
|
const tap = (udid, hostHeader) => (0, loopbackRequest_1.default)(buildApp())
|
|
91
91
|
.post(`/xenon/api/control/${udid}/tap`)
|
|
92
92
|
.set('Host', hostHeader)
|
|
@@ -96,20 +96,20 @@ describe('/control forwards only another server’s phone', () => {
|
|
|
96
96
|
const res = yield tap('own-1', 'localhost:4723');
|
|
97
97
|
(0, chai_1.expect)(res.status, JSON.stringify(res.body)).to.equal(200);
|
|
98
98
|
(0, chai_1.expect)(manager.taps).to.deep.equal(['own-1']);
|
|
99
|
-
(0, chai_1.expect)(post.called, 'never forwarded to itself').to.equal(false);
|
|
100
99
|
}));
|
|
101
|
-
it('forwards a node’s phone once, never retried', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
};
|
|
100
|
+
it('forwards a node’s phone once, never retried, and relays its answer', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
101
|
+
const node = (0, express_1.default)();
|
|
102
|
+
const hits = [];
|
|
103
|
+
node.use((req, res) => {
|
|
104
|
+
hits.push(req.originalUrl);
|
|
105
|
+
res.status(503).json({ error: 'node busy' });
|
|
106
|
+
});
|
|
107
|
+
const nodeOrigin = `http://127.0.0.1:${(yield loopback.serve(node)).address().port}`;
|
|
108
|
+
device = { udid: 'node-phone', host: nodeOrigin, nodeId: 'node-9', platform: 'android' };
|
|
108
109
|
const res = yield tap('node-phone', '192.168.0.104:4723');
|
|
109
|
-
(0, chai_1.expect)(res.status
|
|
110
|
+
(0, chai_1.expect)(res.status).to.equal(503);
|
|
111
|
+
(0, chai_1.expect)(res.body).to.deep.equal({ error: 'node busy' });
|
|
110
112
|
(0, chai_1.expect)(manager.taps).to.deep.equal([]);
|
|
111
|
-
(0, chai_1.expect)(
|
|
112
|
-
(0, chai_1.expect)(post.firstCall.args[0]).to.equal('http://10.0.0.9:4725/xenon/api/control/node-phone/tap');
|
|
113
|
-
(0, chai_1.expect)(post.firstCall.args[2]).to.include({ retry: false });
|
|
113
|
+
(0, chai_1.expect)(hits).to.deep.equal(['/xenon/api/control/node-phone/tap']);
|
|
114
114
|
}));
|
|
115
115
|
});
|
|
@@ -261,3 +261,41 @@ describe('deviceAccessGuard', () => {
|
|
|
261
261
|
(0, chai_1.expect)(res.body.reached).to.equal(true);
|
|
262
262
|
}));
|
|
263
263
|
});
|
|
264
|
+
/**
|
|
265
|
+
* On a hub, a node's phone is busy when its node reports it busy (nodeBusy):
|
|
266
|
+
* a preview the node holds, say. The hub has no holder on record for that:
|
|
267
|
+
* no session and no hold of its own. Failing closed on "busy, owner unknown"
|
|
268
|
+
* refused every non-admin, the preview's own holder included, so the hub
|
|
269
|
+
* leaves that decision to the node, whose own guard knows the holder and
|
|
270
|
+
* judges every call the hub forwards. What the hub holds itself (a claim, a
|
|
271
|
+
* session, a hold) it still judges.
|
|
272
|
+
*/
|
|
273
|
+
describe('deviceAccessGuard on a node’s phone', () => {
|
|
274
|
+
const nodePhone = (row) => appWith({ actorUserId: BOB, findDeviceImpl: () => __awaiter(void 0, void 0, void 0, function* () { return row; }), sessionOwner: ALICE });
|
|
275
|
+
it('leaves a phone busy only by its node’s report to the node', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
276
|
+
const app = nodePhone({ busy: true, nodeBusy: true, session_id: null });
|
|
277
|
+
const tap = yield (0, loopbackRequest_1.default)(app).post(`/control/${UDID}/tap`);
|
|
278
|
+
(0, chai_1.expect)(tap.status).to.equal(200);
|
|
279
|
+
(0, chai_1.expect)(tap.body.reached).to.equal(true);
|
|
280
|
+
const logs = yield (0, loopbackRequest_1.default)(app).get(`/control/${UDID}/logs`);
|
|
281
|
+
(0, chai_1.expect)(logs.status).to.equal(200);
|
|
282
|
+
}));
|
|
283
|
+
it('still judges a session this server routed to the node', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
284
|
+
const tap = yield (0, loopbackRequest_1.default)(nodePhone({
|
|
285
|
+
busy: true,
|
|
286
|
+
nodeBusy: true,
|
|
287
|
+
session_id: 'appium-1',
|
|
288
|
+
claimSessionId: 'appium-1',
|
|
289
|
+
})).post(`/control/${UDID}/tap`);
|
|
290
|
+
(0, chai_1.expect)(tap.status).to.equal(409);
|
|
291
|
+
(0, chai_1.expect)(tap.body.error).to.equal('device_in_use_by_session');
|
|
292
|
+
}));
|
|
293
|
+
it('still refuses while this server’s own create for the phone is pending', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
294
|
+
const tap = yield (0, loopbackRequest_1.default)(nodePhone({ busy: true, nodeBusy: true, session_id: null, claimedAt: 1790000000000 })).post(`/control/${UDID}/tap`);
|
|
295
|
+
(0, chai_1.expect)(tap.status).to.equal(409);
|
|
296
|
+
}));
|
|
297
|
+
it('still refuses this server’s own phone busy with no holder on record', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
298
|
+
const tap = yield (0, loopbackRequest_1.default)(nodePhone({ busy: true, session_id: null })).post(`/control/${UDID}/tap`);
|
|
299
|
+
(0, chai_1.expect)(tap.status).to.equal(409);
|
|
300
|
+
}));
|
|
301
|
+
});
|