-
Notifications
You must be signed in to change notification settings - Fork 304
Expand file tree
/
Copy pathglobal-setup.ts
More file actions
102 lines (96 loc) · 3.18 KB
/
Copy pathglobal-setup.ts
File metadata and controls
102 lines (96 loc) · 3.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import { execSync } from "node:child_process";
import { request } from "node:https";
import { Socket } from "node:net";
/**
* Brings up the device topology and waits until it is reachable.
*
* - docker mode (default): starts the two `meshtasticd` sim nodes via compose.
* - hardware mode (E2E_DEVICE_MODE=hardware): skips compose; expects the env
* endpoints to point at real devices.
*/
const MODE = process.env.E2E_DEVICE_MODE ?? "docker";
const COMPOSE_FILE = "e2e/device/docker-compose.yml";
const NODE_A_URL = process.env.E2E_NODE_A_URL ?? "https://127.0.0.1:9443";
const PEER_HOST = process.env.E2E_PEER_HOST ?? "127.0.0.1";
const PEER_PORT = Number(process.env.E2E_PEER_PORT ?? 14404);
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
/** Poll the device HTTPS phone API until it answers (cert is self-signed). */
async function waitForHttps(url: string, timeoutMs: number): Promise<void> {
const deadline = Date.now() + timeoutMs;
let lastErr = "connection refused";
while (Date.now() < deadline) {
const ok = await new Promise<boolean>((resolve) => {
const req = request(
url,
{ method: "GET", rejectUnauthorized: false, timeout: 4000 },
(res) => {
res.resume();
resolve((res.statusCode ?? 0) > 0);
},
);
req.on("error", (e) => {
lastErr = e.message;
resolve(false);
});
req.on("timeout", () => {
req.destroy();
resolve(false);
});
req.end();
});
if (ok) return;
await sleep(1000);
}
throw new Error(
`device webserver not ready at ${url} within ${timeoutMs}ms (last: ${lastErr})`,
);
}
/** Poll a TCP port until it accepts a connection. */
async function waitForTcp(
host: string,
port: number,
timeoutMs: number,
): Promise<void> {
const deadline = Date.now() + timeoutMs;
let lastErr = "connection refused";
while (Date.now() < deadline) {
const ok = await new Promise<boolean>((resolve) => {
const sock = new Socket();
sock.setTimeout(3000);
sock.once("connect", () => {
sock.destroy();
resolve(true);
});
sock.once("error", (e) => {
lastErr = e.message;
sock.destroy();
resolve(false);
});
sock.once("timeout", () => {
sock.destroy();
resolve(false);
});
sock.connect(port, host);
});
if (ok) return;
await sleep(1000);
}
throw new Error(
`peer node TCP not ready at ${host}:${port} within ${timeoutMs}ms (last: ${lastErr})`,
);
}
export default async function globalSetup(): Promise<void> {
if (MODE === "docker") {
console.log("[e2e] bringing up meshtasticd two-node mesh ...");
execSync(`docker compose -f ${COMPOSE_FILE} up -d`, { stdio: "inherit" });
} else {
console.log(
`[e2e] hardware mode: device=${NODE_A_URL} peer=${PEER_HOST}:${PEER_PORT}`,
);
}
console.log(`[e2e] waiting for device webserver ${NODE_A_URL} ...`);
await waitForHttps(`${NODE_A_URL}/api/v1/fromradio?all=true`, 120_000);
console.log(`[e2e] waiting for peer node ${PEER_HOST}:${PEER_PORT} ...`);
await waitForTcp(PEER_HOST, PEER_PORT, 60_000);
console.log("[e2e] topology ready.");
}