@xenon-device-management/xenon 2.6.2 → 2.8.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 +1 -1
- package/lib/public/assets/{AnnotationOverlay-U9aJANVi.js → AnnotationOverlay-XmDGA9CJ.js} +1 -1
- package/lib/public/assets/{ApiKeyGate-6265m84B.js → ApiKeyGate-BilRCzB-.js} +1 -1
- package/lib/public/assets/{BugReportButton-CGeVEuQ-.js → BugReportButton-_buEV98z.js} +1 -1
- package/lib/public/assets/{DeviceMosaicView-C_3HwhVZ.js → DeviceMosaicView-DTEiC4gk.js} +1 -1
- package/lib/public/assets/{EmptyState-D5BTZSIa.js → EmptyState-BhQfPUga.js} +1 -1
- package/lib/public/assets/{FieldGroup-CDBhSDsT.js → FieldGroup-VPsU3LwN.js} +1 -1
- package/lib/public/assets/{FilterMenu-7JEhwNEM.js → FilterMenu-Br9o7dBS.js} +1 -1
- package/lib/public/assets/{Menu-DWl5uJ9E.js → Menu-dF_HlxcB.js} +1 -1
- package/lib/public/assets/{Modal-BZEfRpq8.js → Modal-51305CEc.js} +1 -1
- package/lib/public/assets/{RecordingPage-B_EK9yKX.js → RecordingPage-BYQfiIEd.js} +1 -1
- package/lib/public/assets/{RecordingsPage-CptMr6IM.js → RecordingsPage-C7A2ZMU_.js} +1 -1
- package/lib/public/assets/{SegmentedControl-D7BBuUDP.js → SegmentedControl--_Zp_x-E.js} +1 -1
- package/lib/public/assets/{SettingCard-W3QrpCyh.js → SettingCard-DKlLEbu9.js} +1 -1
- package/lib/public/assets/{Table-BthtE6KF.js → Table-mknFnLyL.js} +1 -1
- package/lib/public/assets/{activity-CdoCkXe1.js → activity-D4657Amj.js} +1 -1
- package/lib/public/assets/{ai-settings-CkGIJyKN.js → ai-settings-_sXFJMDz.js} +1 -1
- package/lib/public/assets/{api-keys-2lHmq44B.js → api-keys-BV7AQW2N.js} +1 -1
- package/lib/public/assets/{apps-BCHpRvo_.js → apps-D-qPk58H.js} +1 -1
- package/lib/public/assets/{arrow-left-CpaUvzcV.js → arrow-left-B6xZI-lv.js} +1 -1
- package/lib/public/assets/{arrow-right-TCqk2-9N.js → arrow-right-KOusXQT_.js} +1 -1
- package/lib/public/assets/{arrow-up-right-B6JBqR-t.js → arrow-up-right--y5SjylA.js} +1 -1
- package/lib/public/assets/{auth-shell-BSyFjCa8.js → auth-shell-BOGfjiRi.js} +2 -2
- package/lib/public/assets/builds-page-CgobTBVN.js +11 -0
- package/lib/public/assets/{button-CB1ehl9z.js → button-Ci58y6Ou.js} +1 -1
- package/lib/public/assets/{calendar-DXZ0-sa6.js → calendar-BYLnN-JG.js} +1 -1
- package/lib/public/assets/{check-Bq0QWhtG.js → check-BZFRUKhB.js} +1 -1
- package/lib/public/assets/{chevron-left-BMI8J11V.js → chevron-left-C-olFL5E.js} +1 -1
- package/lib/public/assets/{chevron-right-tbezHlhW.js → chevron-right-CmxzLyEE.js} +1 -1
- package/lib/public/assets/{circle-check-BgkLodxU.js → circle-check-Duvlh9lf.js} +1 -1
- package/lib/public/assets/{circle-x-DuFD0enL.js → circle-x-DoQgpxKQ.js} +1 -1
- package/lib/public/assets/{clock-C7AhkXMm.js → clock-yvaEQMYo.js} +1 -1
- package/lib/public/assets/{copy-CiNhxJMO.js → copy-CIcmMH4s.js} +1 -1
- package/lib/public/assets/{device-explorer-BFWU04bT.js → device-explorer-Cf-d-udL.js} +1 -1
- package/lib/public/assets/{download-Di_q2qj5.js → download-Ck75G4Sr.js} +1 -1
- package/lib/public/assets/{forgot-password-gtciaIez.js → forgot-password-DcOi08XK.js} +1 -1
- package/lib/public/assets/{funnel-BY1R0ydM.js → funnel-CafvAmsz.js} +1 -1
- package/lib/public/assets/{index-B1iJx0w1.js → index-9Npot8Nw.js} +2 -2
- package/lib/public/assets/{index-Bgx65Mb_.js → index-QT33_ARg.js} +1 -1
- package/lib/public/assets/{input-B83lhHJ_.js → input-DUA3Tk33.js} +1 -1
- package/lib/public/assets/{keycodes-BjiWgQik.js → keycodes-Dj17elyp.js} +1 -1
- package/lib/public/assets/{layers-CxMkF86z.js → layers-5SyoCGaK.js} +1 -1
- package/lib/public/assets/{list-checks-2GnUUcTR.js → list-checks-Blsk6Sts.js} +1 -1
- package/lib/public/assets/{lock-BRKQ_6ix.js → lock-DUyg9vcf.js} +1 -1
- package/lib/public/assets/{login-Chmwsz_d.js → login-DWRTaaNU.js} +1 -1
- package/lib/public/assets/{maintenance-settings-DEb0LrBA.js → maintenance-settings-CwthiKtl.js} +1 -1
- package/lib/public/assets/{monitor-ClL-76V7.js → monitor-D7kOVM-I.js} +1 -1
- package/lib/public/assets/{mouse-pointer-2-C815rsZl.js → mouse-pointer-2-BzwITYs-.js} +1 -1
- package/lib/public/assets/{network-CBd_S-GU.js → network-3c26VFLV.js} +1 -1
- package/lib/public/assets/{overview-B0paJ7DP.js → overview-BamopQBx.js} +1 -1
- package/lib/public/assets/{page-header-D9FkEgQm.js → page-header-Cr568Bj2.js} +1 -1
- package/lib/public/assets/{plus-DF-cFokG.js → plus-egFVGGUW.js} +1 -1
- package/lib/public/assets/{profile-page-BrdnJBRA.js → profile-page-BetCnNKO.js} +1 -1
- package/lib/public/assets/{reset-password-DJFBWJnT.js → reset-password-BgjuXPN-.js} +1 -1
- package/lib/public/assets/{runbook-page-D5SIpqrf.js → runbook-page-BbAREoE4.js} +1 -1
- package/lib/public/assets/{select-Wc_B9XUe.js → select-BfZw_3u2.js} +1 -1
- package/lib/public/assets/{selector-detail-page-q7-lRo_l.js → selector-detail-page-BqswNrpr.js} +1 -1
- package/lib/public/assets/{selector-health-Qe-NusTk.js → selector-health-Cbdio_9h.js} +1 -1
- package/lib/public/assets/{selector-health-page-Dw2WUlDJ.js → selector-health-page-qdM7e6Lx.js} +1 -1
- package/lib/public/assets/{session-detail-page-bZpHxz1l.js → session-detail-page-DFnhbs82.js} +1 -1
- package/lib/public/assets/{settings-DA_CIMVB.js → settings-Bg81ksOI.js} +1 -1
- package/lib/public/assets/stat-tile-CCR1O7iE.js +8 -0
- package/lib/public/assets/{tablet-_Uwg1m0t.js → tablet-DAHOR-iq.js} +1 -1
- package/lib/public/assets/{teams-ZTGk8Wky.js → teams-Bz13DBCK.js} +1 -1
- package/lib/public/assets/{trash-2-BcWlaBK8.js → trash-2-BgqAVxUr.js} +1 -1
- package/lib/public/assets/{upload-47Lf5U7r.js → upload-Rvl2CCKn.js} +1 -1
- package/lib/public/assets/{use-password-reset-mode-B3cWCSu5.js → use-password-reset-mode--PRWElm2.js} +1 -1
- package/lib/public/assets/{users-DGHET-4G.js → users-BRkxjzNQ.js} +1 -1
- package/lib/public/assets/{users-IiOEsNnr.js → users-O123SAYx.js} +1 -1
- package/lib/public/assets/{video-off-D-jBA0at.js → video-off-DkNxxAWE.js} +1 -1
- package/lib/public/assets/{webhook-settings-HqV4V6_J.js → webhook-settings-D6UmShyI.js} +1 -1
- package/lib/public/assets/{zap-iAfVTWG0.js → zap-D6UAiMKL.js} +1 -1
- package/lib/public/index.html +1 -1
- package/lib/src/app/routers/dashboard.js +21 -11
- package/lib/src/app/routers/sessionInsights.js +46 -9
- package/lib/src/device-managers/ios/IOSLogStreamService.js +6 -3
- package/lib/src/device-managers/ios/IOSStreamService.js +38 -122
- package/lib/src/device-managers/ios/IOSTunnels.js +376 -0
- package/lib/src/device-managers/ios/WDAClient.js +5 -2
- package/lib/src/services/PortAllocator.js +65 -0
- package/lib/test/unit/ios-go-ios-tunnel-env.spec.js +154 -0
- package/lib/test/unit/ios-stream-appium-tunnel.spec.js +122 -32
- package/lib/test/unit/ios-stream-port-leases.spec.js +26 -5
- package/lib/test/unit/ios-tunnels.spec.js +387 -0
- package/lib/test/unit/port-allocator-pairs.spec.js +110 -0
- package/lib/test/unit/session-list-insights.spec.js +39 -0
- package/lib/test/unit/stream-own-device-row.spec.js +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/lib/public/assets/builds-page-D0RnbdGV.js +0 -11
- package/lib/public/assets/stat-tile-CxsAGcDU.js +0 -8
|
@@ -0,0 +1,387 @@
|
|
|
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 child_process_1 = __importDefault(require("child_process"));
|
|
18
|
+
const events_1 = require("events");
|
|
19
|
+
const http_1 = __importDefault(require("http"));
|
|
20
|
+
const sinon_1 = __importDefault(require("sinon"));
|
|
21
|
+
const typedi_1 = require("typedi");
|
|
22
|
+
const IOSTunnels_1 = require("../../src/device-managers/ios/IOSTunnels");
|
|
23
|
+
const ProcessRegistry_1 = require("../../src/services/ProcessRegistry");
|
|
24
|
+
/**
|
|
25
|
+
* Each iOS 17+ phone gets its own go-ios tunnel, an isolated per-device agent
|
|
26
|
+
* on its own pair of leased ports. Through 2.7 every tunnel took go-ios's
|
|
27
|
+
* default 60105/60106, so a second iPhone's stream killed the first's.
|
|
28
|
+
*
|
|
29
|
+
* go-ios keeps a per-device agent running when its phone is unplugged, and
|
|
30
|
+
* starts the replugged phone's tunnel on its next traffic port, P + 2: the
|
|
31
|
+
* next phone's pair. So a tunnel that loses its phone, or moves its traffic
|
|
32
|
+
* port, is stopped.
|
|
33
|
+
*
|
|
34
|
+
* go-ios, the allocator, HTTP and timers are fakes here, except in the last
|
|
35
|
+
* block, which runs the real seams against a local HTTP server and a stubbed
|
|
36
|
+
* spawn.
|
|
37
|
+
*/
|
|
38
|
+
const PHONE_A = 'test-iphone-a-00008150';
|
|
39
|
+
const PHONE_B = 'test-iphone-b-00008110';
|
|
40
|
+
class FakeProcess extends events_1.EventEmitter {
|
|
41
|
+
constructor(pid) {
|
|
42
|
+
super();
|
|
43
|
+
this.pid = pid;
|
|
44
|
+
this.exitCode = null;
|
|
45
|
+
this.stdout = new events_1.EventEmitter();
|
|
46
|
+
this.stderr = new events_1.EventEmitter();
|
|
47
|
+
}
|
|
48
|
+
exit(code = 1) {
|
|
49
|
+
this.exitCode = code;
|
|
50
|
+
this.emit('exit', code);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Two adjacent ports per phone from 12100, as PortAllocator.acquirePair leases them. */
|
|
54
|
+
function fakeAllocator() {
|
|
55
|
+
let next = 12100;
|
|
56
|
+
const leased = new Map();
|
|
57
|
+
return {
|
|
58
|
+
leased,
|
|
59
|
+
acquirePair: sinon_1.default.stub().callsFake((_purpose, udid) => __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
const port = next;
|
|
61
|
+
next += 2;
|
|
62
|
+
leased.set(port, udid);
|
|
63
|
+
leased.set(port + 1, udid);
|
|
64
|
+
return port;
|
|
65
|
+
})),
|
|
66
|
+
release: sinon_1.default.stub().callsFake((port, udid) => __awaiter(this, void 0, void 0, function* () {
|
|
67
|
+
if (leased.get(port) === udid)
|
|
68
|
+
leased.delete(port);
|
|
69
|
+
})),
|
|
70
|
+
touch: sinon_1.default.stub().resolves(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
class TestTunnels extends IOSTunnels_1.IOSTunnels {
|
|
74
|
+
constructor() {
|
|
75
|
+
super(...arguments);
|
|
76
|
+
this.versions = new Map();
|
|
77
|
+
this.spawned = [];
|
|
78
|
+
this.killed = [];
|
|
79
|
+
this.sleeps = [];
|
|
80
|
+
/** Each tunnel-info poll: true is the phone's tunnel on P + 1, false a 404. */
|
|
81
|
+
this.answers = () => true;
|
|
82
|
+
this.ports = fakeAllocator();
|
|
83
|
+
this.nextPid = 7000;
|
|
84
|
+
}
|
|
85
|
+
iosVersion(udid) {
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
const version = this.versions.get(udid);
|
|
88
|
+
if (version === undefined)
|
|
89
|
+
throw new Error(`no such phone: ${udid}`);
|
|
90
|
+
return version;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
spawnTunnel(udid, args) {
|
|
94
|
+
const proc = new FakeProcess(this.nextPid++);
|
|
95
|
+
this.spawned.push({ udid, args, proc });
|
|
96
|
+
return proc;
|
|
97
|
+
}
|
|
98
|
+
tunnelInfo(port, udid) {
|
|
99
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
const answer = this.answers(port, udid);
|
|
101
|
+
if (answer === true)
|
|
102
|
+
return { state: 'up', userspacePort: port + 1 };
|
|
103
|
+
if (answer === false)
|
|
104
|
+
return { state: 'gone' };
|
|
105
|
+
return answer;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
sleep(ms) {
|
|
109
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
110
|
+
this.sleeps.push(ms);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
killGroup(pid) {
|
|
114
|
+
this.killed.push(pid);
|
|
115
|
+
}
|
|
116
|
+
allocator() {
|
|
117
|
+
return this.ports;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const settle = () => new Promise((resolve) => setImmediate(resolve));
|
|
121
|
+
describe('IOSTunnels: a go-ios tunnel per iPhone', () => {
|
|
122
|
+
let t;
|
|
123
|
+
beforeEach(() => {
|
|
124
|
+
sinon_1.default.stub(process, 'kill');
|
|
125
|
+
t = new TestTunnels();
|
|
126
|
+
t.versions.set(PHONE_A, 17.4);
|
|
127
|
+
t.versions.set(PHONE_B, 18.1);
|
|
128
|
+
});
|
|
129
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
130
|
+
yield t.stop(PHONE_A);
|
|
131
|
+
yield t.stop(PHONE_B);
|
|
132
|
+
sinon_1.default.restore();
|
|
133
|
+
}));
|
|
134
|
+
it('starts no tunnel for a phone below iOS 17', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
135
|
+
t.versions.set('old-iphone', 16.7);
|
|
136
|
+
(0, chai_1.expect)(yield t.ensure('old-iphone')).to.equal(null);
|
|
137
|
+
(0, chai_1.expect)(t.spawned).to.deep.equal([]);
|
|
138
|
+
(0, chai_1.expect)(t.ports.acquirePair.called).to.equal(false);
|
|
139
|
+
}));
|
|
140
|
+
it("starts no tunnel when the phone's version can't be read", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
141
|
+
(0, chai_1.expect)(yield t.ensure('unknown-iphone')).to.equal(null);
|
|
142
|
+
(0, chai_1.expect)(t.spawned).to.deep.equal([]);
|
|
143
|
+
}));
|
|
144
|
+
it("leases a pair and starts the phone's own agent on it", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
145
|
+
const port = yield t.ensure(PHONE_A);
|
|
146
|
+
(0, chai_1.expect)(port).to.equal(12100);
|
|
147
|
+
(0, chai_1.expect)(t.ports.acquirePair.calledOnceWithExactly('tunnel', PHONE_A, { ttlMs: IOSTunnels_1.TUNNEL_LEASE_TTL_MS })).to.equal(true);
|
|
148
|
+
(0, chai_1.expect)(t.spawned.map((s) => s.args)).to.deep.equal([
|
|
149
|
+
['tunnel', 'start', '--udid', PHONE_A, '--userspace', '--tunnel-info-port', '12100'],
|
|
150
|
+
]);
|
|
151
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(12100);
|
|
152
|
+
}));
|
|
153
|
+
it('is ready only once the tunnel answers for the phone', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
154
|
+
const asked = [];
|
|
155
|
+
t.answers = (port, udid) => {
|
|
156
|
+
asked.push([port, udid]);
|
|
157
|
+
return asked.length === 3;
|
|
158
|
+
};
|
|
159
|
+
(0, chai_1.expect)(yield t.ensure(PHONE_A)).to.equal(12100);
|
|
160
|
+
(0, chai_1.expect)(asked).to.deep.equal([
|
|
161
|
+
[12100, PHONE_A],
|
|
162
|
+
[12100, PHONE_A],
|
|
163
|
+
[12100, PHONE_A],
|
|
164
|
+
]);
|
|
165
|
+
(0, chai_1.expect)(t.sleeps).to.deep.equal([500, 500]);
|
|
166
|
+
}));
|
|
167
|
+
it('waits 20 s in all for a tunnel that never answers, then goes on with it', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
168
|
+
t.answers = () => false;
|
|
169
|
+
(0, chai_1.expect)(yield t.ensure(PHONE_A)).to.equal(12100);
|
|
170
|
+
(0, chai_1.expect)(t.sleeps.reduce((a, b) => a + b, 0)).to.equal(IOSTunnels_1.TUNNEL_READY_TIMEOUT_MS);
|
|
171
|
+
(0, chai_1.expect)(t.portFor(PHONE_A), 'still tracked').to.equal(12100);
|
|
172
|
+
}));
|
|
173
|
+
it('fails the start when the tunnel exits before it is ready, and gives its ports back', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
174
|
+
t.answers = () => {
|
|
175
|
+
t.spawned[0].proc.exit(1);
|
|
176
|
+
return false;
|
|
177
|
+
};
|
|
178
|
+
const err = yield t.ensure(PHONE_A).then(() => null, (e) => e);
|
|
179
|
+
yield settle();
|
|
180
|
+
(0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message).to.match(/exited before it was ready/);
|
|
181
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(undefined);
|
|
182
|
+
(0, chai_1.expect)([...t.ports.leased.keys()]).to.deep.equal([]);
|
|
183
|
+
}));
|
|
184
|
+
it('fails the start when the tunnel comes up on another traffic port, and stops it', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
185
|
+
t.answers = () => ({ state: 'up', userspacePort: 12102 });
|
|
186
|
+
const err = yield t.ensure(PHONE_A).then(() => null, (e) => e);
|
|
187
|
+
yield settle();
|
|
188
|
+
(0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message).to.match(/12102/);
|
|
189
|
+
(0, chai_1.expect)(t.killed).to.deep.equal([t.spawned[0].proc.pid]);
|
|
190
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(undefined);
|
|
191
|
+
(0, chai_1.expect)([...t.ports.leased.keys()]).to.deep.equal([]);
|
|
192
|
+
}));
|
|
193
|
+
it('gives a second phone its own pair, and leaves the first alone', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
194
|
+
yield t.ensure(PHONE_A);
|
|
195
|
+
(0, chai_1.expect)(yield t.ensure(PHONE_B)).to.equal(12102);
|
|
196
|
+
(0, chai_1.expect)(t.spawned[1].args).to.include.members(['--udid', PHONE_B, '12102']);
|
|
197
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(12100);
|
|
198
|
+
(0, chai_1.expect)(t.killed).to.deep.equal([]);
|
|
199
|
+
}));
|
|
200
|
+
it("reuses a phone's running tunnel", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
201
|
+
yield t.ensure(PHONE_A);
|
|
202
|
+
(0, chai_1.expect)(yield t.ensure(PHONE_A)).to.equal(12100);
|
|
203
|
+
(0, chai_1.expect)(t.spawned.length).to.equal(1);
|
|
204
|
+
(0, chai_1.expect)(t.ports.acquirePair.callCount).to.equal(1);
|
|
205
|
+
}));
|
|
206
|
+
it("stops only that phone's tunnel and gives its pair back", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
207
|
+
yield t.ensure(PHONE_A);
|
|
208
|
+
yield t.ensure(PHONE_B);
|
|
209
|
+
yield t.stop(PHONE_A);
|
|
210
|
+
(0, chai_1.expect)(t.killed).to.deep.equal([t.spawned[0].proc.pid]);
|
|
211
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(undefined);
|
|
212
|
+
(0, chai_1.expect)(t.portFor(PHONE_B)).to.equal(12102);
|
|
213
|
+
(0, chai_1.expect)([...t.ports.leased.entries()]).to.deep.equal([
|
|
214
|
+
[12102, PHONE_B],
|
|
215
|
+
[12103, PHONE_B],
|
|
216
|
+
]);
|
|
217
|
+
}));
|
|
218
|
+
it('stops nothing for a phone with no tunnel', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
219
|
+
yield t.stop(PHONE_A);
|
|
220
|
+
(0, chai_1.expect)(t.killed).to.deep.equal([]);
|
|
221
|
+
(0, chai_1.expect)(t.ports.release.called).to.equal(false);
|
|
222
|
+
}));
|
|
223
|
+
it('lets a tunnel whose process exits go, and starts a new one next time', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
224
|
+
yield t.ensure(PHONE_A);
|
|
225
|
+
t.spawned[0].proc.exit(2); // go-ios crashed
|
|
226
|
+
yield settle();
|
|
227
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(undefined);
|
|
228
|
+
(0, chai_1.expect)(t.ports.leased.has(12100)).to.equal(false);
|
|
229
|
+
(0, chai_1.expect)(t.ports.leased.has(12101)).to.equal(false);
|
|
230
|
+
(0, chai_1.expect)(yield t.ensure(PHONE_A)).to.equal(12102);
|
|
231
|
+
(0, chai_1.expect)(t.spawned.length).to.equal(2);
|
|
232
|
+
}));
|
|
233
|
+
it('a failed lease starts nothing', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
234
|
+
t.ports.acquirePair.rejects(new Error("Port range for purpose 'tunnel' is exhausted"));
|
|
235
|
+
const err = yield t.ensure(PHONE_A).then(() => null, (e) => e);
|
|
236
|
+
(0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message).to.match(/exhausted/);
|
|
237
|
+
(0, chai_1.expect)(t.spawned).to.deep.equal([]);
|
|
238
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(undefined);
|
|
239
|
+
}));
|
|
240
|
+
it('envFor sets GO_IOS_AGENT_PORT only while the phone has a tunnel', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
241
|
+
const inherited = process.env.GO_IOS_AGENT_PORT;
|
|
242
|
+
(0, chai_1.expect)(t.envFor(PHONE_A).ENABLE_GO_IOS_AGENT).to.equal('yes');
|
|
243
|
+
(0, chai_1.expect)(t.envFor(PHONE_A).GO_IOS_AGENT_PORT).to.equal(inherited);
|
|
244
|
+
yield t.ensure(PHONE_A);
|
|
245
|
+
(0, chai_1.expect)(t.envFor(PHONE_A).GO_IOS_AGENT_PORT).to.equal('12100');
|
|
246
|
+
(0, chai_1.expect)(t.envFor(PHONE_B).GO_IOS_AGENT_PORT, 'another phone').to.equal(inherited);
|
|
247
|
+
yield t.stop(PHONE_A);
|
|
248
|
+
(0, chai_1.expect)(t.envFor(PHONE_A).GO_IOS_AGENT_PORT).to.equal(inherited);
|
|
249
|
+
}));
|
|
250
|
+
it("touch keeps both of the phone's ports leased", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
251
|
+
yield t.ensure(PHONE_A);
|
|
252
|
+
yield t.touch(PHONE_A);
|
|
253
|
+
yield t.touch(PHONE_B); // no tunnel: nothing to touch
|
|
254
|
+
(0, chai_1.expect)(t.ports.touch.args).to.deep.equal([
|
|
255
|
+
[12100, IOSTunnels_1.TUNNEL_LEASE_TTL_MS],
|
|
256
|
+
[12101, IOSTunnels_1.TUNNEL_LEASE_TTL_MS],
|
|
257
|
+
]);
|
|
258
|
+
}));
|
|
259
|
+
});
|
|
260
|
+
describe('IOSTunnels: a phone that goes away (go-ios keeps its agent running)', () => {
|
|
261
|
+
let t;
|
|
262
|
+
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
263
|
+
sinon_1.default.stub(process, 'kill');
|
|
264
|
+
t = new TestTunnels();
|
|
265
|
+
t.versions.set(PHONE_A, 17.4);
|
|
266
|
+
t.versions.set(PHONE_B, 18.1);
|
|
267
|
+
yield t.ensure(PHONE_A);
|
|
268
|
+
yield t.ensure(PHONE_B);
|
|
269
|
+
}));
|
|
270
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
271
|
+
yield t.stop(PHONE_A);
|
|
272
|
+
yield t.stop(PHONE_B);
|
|
273
|
+
sinon_1.default.restore();
|
|
274
|
+
}));
|
|
275
|
+
it("stops the tunnel of a phone that went away, gives its ports back, and leaves the other phone's", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
276
|
+
t.answers = (_port, udid) => udid !== PHONE_A; // A's agent: 404 for A
|
|
277
|
+
yield t.checkTunnels();
|
|
278
|
+
(0, chai_1.expect)(t.killed).to.deep.equal([t.spawned[0].proc.pid]);
|
|
279
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(undefined);
|
|
280
|
+
(0, chai_1.expect)(t.portFor(PHONE_B)).to.equal(12102);
|
|
281
|
+
(0, chai_1.expect)([...t.ports.leased.keys()]).to.deep.equal([12102, 12103]);
|
|
282
|
+
}));
|
|
283
|
+
it('stops a tunnel that came back on another traffic port (a replug)', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
284
|
+
t.answers = (port, udid) => udid === PHONE_A ? { state: 'up', userspacePort: port + 2 } : true;
|
|
285
|
+
yield t.checkTunnels();
|
|
286
|
+
(0, chai_1.expect)(t.killed).to.deep.equal([t.spawned[0].proc.pid]);
|
|
287
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(undefined);
|
|
288
|
+
(0, chai_1.expect)(t.portFor(PHONE_B)).to.equal(12102);
|
|
289
|
+
}));
|
|
290
|
+
it("leaves a tunnel alone when its agent doesn't answer (busy, not gone)", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
291
|
+
t.answers = () => ({ state: 'unknown' });
|
|
292
|
+
yield t.checkTunnels();
|
|
293
|
+
(0, chai_1.expect)(t.killed).to.deep.equal([]);
|
|
294
|
+
(0, chai_1.expect)(t.portFor(PHONE_A)).to.equal(12100);
|
|
295
|
+
}));
|
|
296
|
+
it('leaves a tunnel that never came up alone while it answers 404', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
297
|
+
t.versions.set('slow-iphone', 17.0);
|
|
298
|
+
t.answers = (_port, udid) => udid !== 'slow-iphone'; // never ready: the start goes on after 20 s
|
|
299
|
+
yield t.ensure('slow-iphone');
|
|
300
|
+
yield t.checkTunnels();
|
|
301
|
+
(0, chai_1.expect)(t.killed).to.deep.equal([]);
|
|
302
|
+
(0, chai_1.expect)(t.portFor('slow-iphone')).to.equal(12104);
|
|
303
|
+
yield t.stop('slow-iphone');
|
|
304
|
+
}));
|
|
305
|
+
it('checks every 5 s while a tunnel runs, and stops checking when none does', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
306
|
+
yield t.stop(PHONE_A);
|
|
307
|
+
yield t.stop(PHONE_B);
|
|
308
|
+
const clock = sinon_1.default.useFakeTimers({ toFake: ['setInterval', 'clearInterval'] });
|
|
309
|
+
try {
|
|
310
|
+
const check = sinon_1.default.stub(t, 'checkTunnels').resolves();
|
|
311
|
+
yield t.ensure(PHONE_A);
|
|
312
|
+
clock.tick(IOSTunnels_1.TUNNEL_CHECK_MS);
|
|
313
|
+
(0, chai_1.expect)(check.callCount).to.equal(1);
|
|
314
|
+
yield t.stop(PHONE_A);
|
|
315
|
+
clock.tick(IOSTunnels_1.TUNNEL_CHECK_MS * 3);
|
|
316
|
+
(0, chai_1.expect)(check.callCount, 'no tunnel, no checks').to.equal(1);
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
clock.restore();
|
|
320
|
+
}
|
|
321
|
+
}));
|
|
322
|
+
});
|
|
323
|
+
describe('iosVersionOf', () => {
|
|
324
|
+
it('reads the version ios info reports', () => {
|
|
325
|
+
(0, chai_1.expect)((0, IOSTunnels_1.iosVersionOf)({ ProductVersion: '17.2.1' })).to.equal(17.2);
|
|
326
|
+
(0, chai_1.expect)((0, IOSTunnels_1.iosVersionOf)({ HumanReadableProductVersionString: 'iOS 18.0' })).to.equal(18);
|
|
327
|
+
(0, chai_1.expect)((0, IOSTunnels_1.iosVersionOf)({})).to.equal(0);
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
describe('IOSTunnels: its real seams', () => {
|
|
331
|
+
afterEach(() => sinon_1.default.restore());
|
|
332
|
+
it("reads the phone's tunnel from its agent: up with its traffic port, gone on 404, unknown when unreachable", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
333
|
+
const server = http_1.default.createServer((req, res) => {
|
|
334
|
+
if (req.url === `/tunnel/${PHONE_A}`) {
|
|
335
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
336
|
+
// go-ios 1.2.1's answer, as the lab's agent gave it
|
|
337
|
+
res.end(JSON.stringify({
|
|
338
|
+
address: 'fd51:c966:7c74::1',
|
|
339
|
+
rsdPort: 53752,
|
|
340
|
+
udid: PHONE_A,
|
|
341
|
+
userspaceTun: true,
|
|
342
|
+
userspaceTunPort: 12101,
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
res.writeHead(404);
|
|
347
|
+
res.end();
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
yield new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
351
|
+
const { port } = server.address();
|
|
352
|
+
const t = new IOSTunnels_1.IOSTunnels();
|
|
353
|
+
try {
|
|
354
|
+
(0, chai_1.expect)(yield t.tunnelInfo(port, PHONE_A)).to.deep.equal({
|
|
355
|
+
state: 'up',
|
|
356
|
+
userspacePort: 12101,
|
|
357
|
+
});
|
|
358
|
+
(0, chai_1.expect)(yield t.tunnelInfo(port, PHONE_B), 'another phone: 404').to.deep.equal({
|
|
359
|
+
state: 'gone',
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
yield new Promise((resolve) => server.close(() => resolve(undefined)));
|
|
364
|
+
}
|
|
365
|
+
(0, chai_1.expect)(yield t.tunnelInfo(port, PHONE_A), 'nothing listening').to.deep.equal({
|
|
366
|
+
state: 'unknown',
|
|
367
|
+
});
|
|
368
|
+
}));
|
|
369
|
+
it('spawns the tunnel detached, with the agent setting, and tracks it', () => {
|
|
370
|
+
const fake = new FakeProcess(4321);
|
|
371
|
+
const spawn = sinon_1.default.stub(child_process_1.default, 'spawn').returns(fake);
|
|
372
|
+
const track = sinon_1.default.stub();
|
|
373
|
+
const real = typedi_1.Container.get.bind(typedi_1.Container);
|
|
374
|
+
sinon_1.default
|
|
375
|
+
.stub(typedi_1.Container, 'get')
|
|
376
|
+
.callsFake((token) => (token === ProcessRegistry_1.ProcessRegistry ? { track } : real(token)));
|
|
377
|
+
const t = new IOSTunnels_1.IOSTunnels();
|
|
378
|
+
const proc = t.spawnTunnel(PHONE_A, ['tunnel', 'start', '--udid', PHONE_A]);
|
|
379
|
+
(0, chai_1.expect)(proc).to.equal(fake);
|
|
380
|
+
const [command, args, opts] = spawn.firstCall.args;
|
|
381
|
+
(0, chai_1.expect)(command).to.equal(t.goIOSPath);
|
|
382
|
+
(0, chai_1.expect)(args).to.deep.equal(['tunnel', 'start', '--udid', PHONE_A]);
|
|
383
|
+
(0, chai_1.expect)(opts.detached).to.equal(true);
|
|
384
|
+
(0, chai_1.expect)(opts.env.ENABLE_GO_IOS_AGENT).to.equal('yes');
|
|
385
|
+
(0, chai_1.expect)(track.calledOnceWithExactly({ kind: 'other', udid: PHONE_A, process: fake })).to.equal(true);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
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 net_1 = __importDefault(require("net"));
|
|
17
|
+
const PortAllocator_1 = require("../../src/services/PortAllocator");
|
|
18
|
+
const scratch_port_leases_1 = require("../helpers/scratch-port-leases");
|
|
19
|
+
/**
|
|
20
|
+
* A go-ios tunnel needs two adjacent ports: P for its tunnel-info API and
|
|
21
|
+
* P + 1, which go-ios derives for the phone's userspace traffic. These run
|
|
22
|
+
* PortAllocator's real queries against a scratch lease table, and real binds.
|
|
23
|
+
*/
|
|
24
|
+
describe('PortAllocator.acquirePair (go-ios tunnel ports)', () => {
|
|
25
|
+
const scratch = (0, scratch_port_leases_1.useScratchPortLeases)();
|
|
26
|
+
const listeners = [];
|
|
27
|
+
function allocator(range) {
|
|
28
|
+
const a = new PortAllocator_1.PortAllocator();
|
|
29
|
+
a.configure({ tunnel: range });
|
|
30
|
+
return a;
|
|
31
|
+
}
|
|
32
|
+
/** Something else listening on `port`, as another program's socket would. */
|
|
33
|
+
function hold(port) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const server = net_1.default.createServer();
|
|
36
|
+
server.once('error', reject);
|
|
37
|
+
server.listen(port, '0.0.0.0', () => resolve());
|
|
38
|
+
listeners.push(server);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function leasesOf(udid) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
const rows = yield scratch.db.portLease.findMany({
|
|
44
|
+
where: { leasedToUdid: udid },
|
|
45
|
+
orderBy: { port: 'asc' },
|
|
46
|
+
});
|
|
47
|
+
return rows.map((r) => r.port);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function leaseTo(udid, port, purpose) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
yield scratch.db.portLease.create({
|
|
54
|
+
data: { port, purpose, leasedToUdid: udid, leasedAt: now, expiresAt: now + 60000 },
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
afterEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
59
|
+
yield Promise.all(listeners.splice(0).map((s) => new Promise((resolve) => s.close(() => resolve(undefined)))));
|
|
60
|
+
}));
|
|
61
|
+
it('leases an even port and the next one, and returns the even one', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
62
|
+
const b = scratch.base;
|
|
63
|
+
const port = yield allocator([b, b + 5]).acquirePair('tunnel', 'phone-a', { ttlMs: 60000 });
|
|
64
|
+
(0, chai_1.expect)(port).to.equal(b);
|
|
65
|
+
(0, chai_1.expect)(yield leasesOf('phone-a')).to.deep.equal([b, b + 1]);
|
|
66
|
+
const rows = yield scratch.db.portLease.findMany({ where: { leasedToUdid: 'phone-a' } });
|
|
67
|
+
(0, chai_1.expect)(rows.map((r) => r.purpose)).to.deep.equal(['tunnel', 'tunnel']);
|
|
68
|
+
}));
|
|
69
|
+
it('gives a second phone the next pair', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
70
|
+
const b = scratch.base;
|
|
71
|
+
const a = allocator([b, b + 5]);
|
|
72
|
+
yield a.acquirePair('tunnel', 'phone-a');
|
|
73
|
+
(0, chai_1.expect)(yield a.acquirePair('tunnel', 'phone-b')).to.equal(b + 2);
|
|
74
|
+
(0, chai_1.expect)(yield leasesOf('phone-b')).to.deep.equal([b + 2, b + 3]);
|
|
75
|
+
}));
|
|
76
|
+
it('skips a pair whose second port is leased, whatever for, and keeps none of it', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
77
|
+
const b = scratch.base;
|
|
78
|
+
yield leaseTo('another-device', b + 1, 'wda');
|
|
79
|
+
(0, chai_1.expect)(yield allocator([b, b + 5]).acquirePair('tunnel', 'phone-a')).to.equal(b + 2);
|
|
80
|
+
(0, chai_1.expect)(yield leasesOf('phone-a')).to.deep.equal([b + 2, b + 3]);
|
|
81
|
+
}));
|
|
82
|
+
it('skips a pair whose second port something else listens on', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
83
|
+
const b = scratch.base;
|
|
84
|
+
yield hold(b + 1);
|
|
85
|
+
(0, chai_1.expect)(yield allocator([b, b + 5]).acquirePair('tunnel', 'phone-a')).to.equal(b + 2);
|
|
86
|
+
(0, chai_1.expect)(yield leasesOf('phone-a')).to.deep.equal([b + 2, b + 3]);
|
|
87
|
+
}));
|
|
88
|
+
it('starts at the first even port of a range that starts odd', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
89
|
+
const b = scratch.base;
|
|
90
|
+
(0, chai_1.expect)(yield allocator([b + 1, b + 4]).acquirePair('tunnel', 'phone-a')).to.equal(b + 2);
|
|
91
|
+
}));
|
|
92
|
+
it('throws PortRangeExhaustedError when no pair is left, and keeps no half pair', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
93
|
+
const b = scratch.base;
|
|
94
|
+
yield leaseTo('another-phone', b + 1, 'tunnel');
|
|
95
|
+
const err = yield allocator([b, b + 1])
|
|
96
|
+
.acquirePair('tunnel', 'phone-a')
|
|
97
|
+
.then(() => null, (e) => e);
|
|
98
|
+
(0, chai_1.expect)(err).to.be.instanceOf(PortAllocator_1.PortRangeExhaustedError);
|
|
99
|
+
(0, chai_1.expect)(err === null || err === void 0 ? void 0 : err.message).to.match(/tunnel/);
|
|
100
|
+
(0, chai_1.expect)(yield leasesOf('phone-a')).to.deep.equal([]);
|
|
101
|
+
}));
|
|
102
|
+
it("releasePurpose deletes only that purpose's leases", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
103
|
+
const b = scratch.base;
|
|
104
|
+
const a = allocator([b, b + 5]);
|
|
105
|
+
yield a.acquirePair('tunnel', 'phone-a');
|
|
106
|
+
yield leaseTo('phone-a', b + 50, 'wda');
|
|
107
|
+
yield a.releasePurpose('tunnel');
|
|
108
|
+
(0, chai_1.expect)(yield leasesOf('phone-a')).to.deep.equal([b + 50]);
|
|
109
|
+
}));
|
|
110
|
+
});
|
|
@@ -72,6 +72,45 @@ describe('Sessions page data', () => {
|
|
|
72
72
|
data: { id, name, email, passwordHash: 'x', accessKey: `ak-${id}` },
|
|
73
73
|
});
|
|
74
74
|
describe('GET /session', () => {
|
|
75
|
+
// Pages, newest first: limit, and a cursor at the last row of the page
|
|
76
|
+
// before (before = its createdAt, beforeId = its id). The id breaks ties,
|
|
77
|
+
// so rows created in the same millisecond are neither skipped nor repeated.
|
|
78
|
+
describe('paging', () => {
|
|
79
|
+
const t = (min) => new Date(now - min * MIN);
|
|
80
|
+
const ids = (res) => res.body.map((r) => r.id);
|
|
81
|
+
beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
82
|
+
yield session({ id: 'a', createdAt: t(1) });
|
|
83
|
+
yield session({ id: 'b', createdAt: t(2) });
|
|
84
|
+
yield session({ id: 'c', createdAt: t(2) });
|
|
85
|
+
yield session({ id: 'd', createdAt: t(3) });
|
|
86
|
+
}));
|
|
87
|
+
it('keeps to the limit, newest first, the id breaking ties', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
88
|
+
(0, chai_1.expect)(ids(yield (0, loopbackRequest_1.default)(app()).get('/session?limit=2'))).to.deep.equal(['a', 'c']);
|
|
89
|
+
}));
|
|
90
|
+
it('continues after the cursor, neither skipping nor repeating a row', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
91
|
+
const res = yield (0, loopbackRequest_1.default)(app()).get(`/session?limit=2&before=${t(2).toISOString()}&beforeId=c`);
|
|
92
|
+
(0, chai_1.expect)(ids(res)).to.deep.equal(['b', 'd']);
|
|
93
|
+
}));
|
|
94
|
+
it('takes a cursor with no id as everything older than its time', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
95
|
+
const res = yield (0, loopbackRequest_1.default)(app()).get(`/session?before=${t(2).toISOString()}`);
|
|
96
|
+
(0, chai_1.expect)(ids(res)).to.deep.equal(['d']);
|
|
97
|
+
}));
|
|
98
|
+
it('refuses a limit or a cursor it cannot read', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
99
|
+
for (const q of ['limit=abc', 'limit=0', 'limit=-5', 'limit=2.5']) {
|
|
100
|
+
const res = yield (0, loopbackRequest_1.default)(app()).get(`/session?${q}`);
|
|
101
|
+
(0, chai_1.expect)(res.status, q).to.equal(400);
|
|
102
|
+
(0, chai_1.expect)(res.body.error, q).to.equal('invalid_limit');
|
|
103
|
+
}
|
|
104
|
+
const res = yield (0, loopbackRequest_1.default)(app()).get('/session?before=soon');
|
|
105
|
+
(0, chai_1.expect)(res.status).to.equal(400);
|
|
106
|
+
(0, chai_1.expect)(res.body.error).to.equal('invalid_before');
|
|
107
|
+
}));
|
|
108
|
+
it('answers a limit over the most with the most, not an error', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
109
|
+
const res = yield (0, loopbackRequest_1.default)(app()).get('/session?limit=999999');
|
|
110
|
+
(0, chai_1.expect)(res.status).to.equal(200);
|
|
111
|
+
(0, chai_1.expect)(ids(res)).to.deep.equal(['a', 'c', 'b', 'd']);
|
|
112
|
+
}));
|
|
113
|
+
});
|
|
75
114
|
it('keeps the sessions created since the given time, newest first', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
76
115
|
yield session({ id: 'old', createdAt: ago(3 * HOUR) });
|
|
77
116
|
yield session({ id: 'recent', createdAt: ago(30 * MIN) });
|
|
@@ -128,7 +128,7 @@ describe("stream services act on this server's own row for a udid", () => {
|
|
|
128
128
|
wdaProcess: null,
|
|
129
129
|
forwardWDAProcess: null,
|
|
130
130
|
forwardMJPEGProcess: null,
|
|
131
|
-
|
|
131
|
+
tunnelPort: null,
|
|
132
132
|
wdaPort: 28101,
|
|
133
133
|
mjpegPort: 29101,
|
|
134
134
|
status: 'running',
|