miravo 0.2.3 → 0.4.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/README.md +14 -150
- package/console-dist/assets/fira-code-cyrillic-ext-wght-normal-DhYMMuQd.woff2 +0 -0
- package/console-dist/assets/fira-code-cyrillic-wght-normal-Y3u8pIsh.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-ext-wght-normal-wWus70Ix.woff2 +0 -0
- package/console-dist/assets/fira-code-greek-wght-normal-B2SviObF.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-ext-wght-normal-Dvfvaomy.woff2 +0 -0
- package/console-dist/assets/fira-code-latin-wght-normal-CHoedHDv.woff2 +0 -0
- package/console-dist/assets/fira-code-symbols2-wght-normal-CE6EOz_n.woff2 +0 -0
- package/console-dist/assets/index-C3XMYq26.css +2 -0
- package/console-dist/assets/index-hUJSpKtB.js +119 -0
- package/console-dist/assets/karla-latin-ext-wght-normal-Dq0rrjgs.woff2 +0 -0
- package/console-dist/assets/karla-latin-wght-normal-C3-ma4ov.woff2 +0 -0
- package/console-dist/assets/schibsted-grotesk-latin-ext-wght-normal-hsMS0n0O.woff2 +0 -0
- package/console-dist/assets/schibsted-grotesk-latin-wght-normal-Bb8VGrTG.woff2 +0 -0
- package/console-dist/favicon.svg +37 -0
- package/console-dist/index.html +14 -0
- package/console-dist/miravo-logo.png +0 -0
- package/package.json +12 -9
- package/src/__tests__/integration/executable-methods-runner.ts +53 -45
- package/src/__tests__/integration/executable-methods.integration.ts +648 -80
- package/src/commands/adapter.ts +76 -0
- package/src/commands/cli-test-setup.ts +1 -1
- package/src/commands/fork.ts +14 -0
- package/src/commands/inject.test.ts +0 -24
- package/src/commands/inject.ts +16 -8
- package/src/commands/load.ts +167 -0
- package/src/commands/logs.ts +175 -0
- package/src/commands/models.test.ts +23 -2
- package/src/commands/models.ts +20 -0
- package/src/commands/pause.ts +67 -0
- package/src/commands/ps.ts +116 -0
- package/src/commands/remove.ts +38 -0
- package/src/commands/reset.ts +44 -0
- package/src/commands/rm.ts +97 -0
- package/src/commands/spawn.ts +45 -0
- package/src/commands/speed.ts +40 -0
- package/src/commands/start.test.ts +93 -0
- package/src/commands/start.ts +1043 -193
- package/src/commands/status.test.ts +0 -24
- package/src/commands/status.ts +12 -6
- package/src/commands/stop.test.ts +84 -0
- package/src/commands/stop.ts +158 -0
- package/src/commands/templates.test.ts +1 -14
- package/src/commands/templates.ts +13 -0
- package/src/index.ts +49 -7
- package/src/lib/admin-client.test.ts +35 -0
- package/src/lib/admin-client.ts +13 -1
- package/src/lib/admin-server.test.ts +339 -53
- package/src/lib/admin-server.ts +413 -62
- package/src/lib/daemon-registry.test.ts +429 -0
- package/src/lib/daemon-registry.ts +345 -0
- package/src/lib/mqtt-client.ts +1 -1
- package/src/lib/parse.ts +18 -0
- package/src/lib/resolve-simulation.ts +1 -1
- package/src/lib/shutdown.test.ts +16 -4
- package/src/lib/shutdown.ts +3 -3
- package/src/lib/template-category.test.ts +1 -0
- package/src/lib/template-utils.ts +33 -2
- package/src/lib/ws-hub.ts +255 -0
- package/src/logger.test.ts +19 -19
- package/src/logger.ts +34 -6
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import type { DaemonRegistryEntry } from "./daemon-registry.ts";
|
|
6
|
+
import {
|
|
7
|
+
acquireNameLock,
|
|
8
|
+
checkNameConflict,
|
|
9
|
+
checkPortConflicts,
|
|
10
|
+
displayHost,
|
|
11
|
+
isPidAlive,
|
|
12
|
+
listEntries,
|
|
13
|
+
probeEntryIdentity,
|
|
14
|
+
readEntry,
|
|
15
|
+
removeEntry,
|
|
16
|
+
sweepStaleEntries,
|
|
17
|
+
writeEntry,
|
|
18
|
+
} from "./daemon-registry.ts";
|
|
19
|
+
|
|
20
|
+
/** Override MIRAVO_HOME to isolate tests from real daemon state. */
|
|
21
|
+
let testHome: string;
|
|
22
|
+
const serverHandles: Array<{ stop: () => void }> = [];
|
|
23
|
+
|
|
24
|
+
async function setupTestHome(): Promise<void> {
|
|
25
|
+
testHome = await mkdtemp(join(tmpdir(), "miravo-daemon-test-"));
|
|
26
|
+
Bun.env["MIRAVO_HOME"] = testHome;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
afterEach(async () => {
|
|
30
|
+
for (const handle of serverHandles) {
|
|
31
|
+
handle.stop();
|
|
32
|
+
}
|
|
33
|
+
serverHandles.length = 0;
|
|
34
|
+
delete Bun.env["MIRAVO_HOME"];
|
|
35
|
+
if (testHome) {
|
|
36
|
+
await rm(testHome, { recursive: true, force: true });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function makeEntry(overrides: Partial<DaemonRegistryEntry> = {}): DaemonRegistryEntry {
|
|
41
|
+
return {
|
|
42
|
+
name: "test-sim",
|
|
43
|
+
pid: 99999,
|
|
44
|
+
adminPort: 8080,
|
|
45
|
+
adminHost: "127.0.0.1",
|
|
46
|
+
adapters: {
|
|
47
|
+
mqtt: { port: 1883, host: "0.0.0.0" },
|
|
48
|
+
},
|
|
49
|
+
templatePath: "/tmp/test.miravo.yaml",
|
|
50
|
+
startedAt: "2026-03-17T10:00:00Z",
|
|
51
|
+
seed: 42,
|
|
52
|
+
runId: "test-run-id",
|
|
53
|
+
...overrides,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe("daemon-registry", () => {
|
|
58
|
+
describe("writeEntry + readEntry", () => {
|
|
59
|
+
test("round-trip writes and reads an entry", async () => {
|
|
60
|
+
await setupTestHome();
|
|
61
|
+
const entry = makeEntry();
|
|
62
|
+
await writeEntry(entry);
|
|
63
|
+
const read = await readEntry("test-sim");
|
|
64
|
+
expect(read).toEqual(entry);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("overwrites existing entry", async () => {
|
|
68
|
+
await setupTestHome();
|
|
69
|
+
const entry1 = makeEntry({ pid: 111 });
|
|
70
|
+
const entry2 = makeEntry({ pid: 222 });
|
|
71
|
+
await writeEntry(entry1);
|
|
72
|
+
await writeEntry(entry2);
|
|
73
|
+
const read = await readEntry("test-sim");
|
|
74
|
+
expect(read?.pid).toBe(222);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("readEntry", () => {
|
|
79
|
+
test("returns null for missing file", async () => {
|
|
80
|
+
await setupTestHome();
|
|
81
|
+
const result = await readEntry("nonexistent");
|
|
82
|
+
expect(result).toBeNull();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("returns null for invalid JSON", async () => {
|
|
86
|
+
await setupTestHome();
|
|
87
|
+
const dir = join(testHome, "daemon");
|
|
88
|
+
const { mkdir } = await import("node:fs/promises");
|
|
89
|
+
await mkdir(dir, { recursive: true });
|
|
90
|
+
await Bun.write(join(dir, "bad.json"), "not json");
|
|
91
|
+
const result = await readEntry("bad");
|
|
92
|
+
expect(result).toBeNull();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("returns null for valid JSON that fails schema", async () => {
|
|
96
|
+
await setupTestHome();
|
|
97
|
+
const dir = join(testHome, "daemon");
|
|
98
|
+
const { mkdir } = await import("node:fs/promises");
|
|
99
|
+
await mkdir(dir, { recursive: true });
|
|
100
|
+
await Bun.write(join(dir, "partial.json"), JSON.stringify({ name: "partial" }));
|
|
101
|
+
const result = await readEntry("partial");
|
|
102
|
+
expect(result).toBeNull();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("removeEntry", () => {
|
|
107
|
+
test("removes an existing entry", async () => {
|
|
108
|
+
await setupTestHome();
|
|
109
|
+
await writeEntry(makeEntry());
|
|
110
|
+
await removeEntry("test-sim");
|
|
111
|
+
const result = await readEntry("test-sim");
|
|
112
|
+
expect(result).toBeNull();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("does not throw for missing entry", async () => {
|
|
116
|
+
await setupTestHome();
|
|
117
|
+
await expect(removeEntry("nonexistent")).resolves.toBeUndefined();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("listEntries", () => {
|
|
122
|
+
test("returns empty array when daemon dir does not exist", async () => {
|
|
123
|
+
await setupTestHome();
|
|
124
|
+
const entries = await listEntries();
|
|
125
|
+
expect(entries).toEqual([]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("returns all valid entries", async () => {
|
|
129
|
+
await setupTestHome();
|
|
130
|
+
await writeEntry(makeEntry({ name: "sim-a" }));
|
|
131
|
+
await writeEntry(makeEntry({ name: "sim-b" }));
|
|
132
|
+
const entries = await listEntries();
|
|
133
|
+
expect(entries).toHaveLength(2);
|
|
134
|
+
const names = entries.map((e) => e.name).sort();
|
|
135
|
+
expect(names).toEqual(["sim-a", "sim-b"]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("skips invalid files", async () => {
|
|
139
|
+
await setupTestHome();
|
|
140
|
+
await writeEntry(makeEntry({ name: "valid" }));
|
|
141
|
+
const dir = join(testHome, "daemon");
|
|
142
|
+
await Bun.write(join(dir, "invalid.json"), "broken");
|
|
143
|
+
const entries = await listEntries();
|
|
144
|
+
expect(entries).toHaveLength(1);
|
|
145
|
+
expect(entries[0]?.name).toBe("valid");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("skips .tmp files", async () => {
|
|
149
|
+
await setupTestHome();
|
|
150
|
+
await writeEntry(makeEntry({ name: "valid" }));
|
|
151
|
+
const dir = join(testHome, "daemon");
|
|
152
|
+
await Bun.write(join(dir, "leftover.json.tmp"), JSON.stringify(makeEntry({ name: "tmp" })));
|
|
153
|
+
const entries = await listEntries();
|
|
154
|
+
expect(entries).toHaveLength(1);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("isPidAlive", () => {
|
|
159
|
+
test("returns true for own PID", () => {
|
|
160
|
+
expect(isPidAlive(process.pid)).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("returns false for impossible PID", () => {
|
|
164
|
+
// PID 2^30 is extremely unlikely to exist
|
|
165
|
+
expect(isPidAlive(1_073_741_824)).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("sweepStaleEntries", () => {
|
|
170
|
+
test("removes entries with dead PIDs", async () => {
|
|
171
|
+
await setupTestHome();
|
|
172
|
+
// Use impossible PIDs so they're guaranteed dead
|
|
173
|
+
await writeEntry(makeEntry({ name: "dead-sim-a", pid: 1_073_741_824 }));
|
|
174
|
+
await writeEntry(makeEntry({ name: "dead-sim-b", pid: 1_073_741_825 }));
|
|
175
|
+
|
|
176
|
+
await sweepStaleEntries();
|
|
177
|
+
|
|
178
|
+
const entries = await listEntries();
|
|
179
|
+
expect(entries).toHaveLength(0);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("keeps entries whose /readyz reports the same runId with 503", async () => {
|
|
183
|
+
await setupTestHome();
|
|
184
|
+
const server = Bun.serve({
|
|
185
|
+
hostname: "127.0.0.1",
|
|
186
|
+
port: 0,
|
|
187
|
+
fetch() {
|
|
188
|
+
return Response.json(
|
|
189
|
+
{ status: "degraded", reason: "mqtt disconnected", runId: "test-run-id" },
|
|
190
|
+
{ status: 503 },
|
|
191
|
+
);
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
serverHandles.push(server);
|
|
195
|
+
const port = server.port;
|
|
196
|
+
if (port === undefined) {
|
|
197
|
+
throw new Error("Failed to allocate test port.");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await writeEntry(
|
|
201
|
+
makeEntry({
|
|
202
|
+
name: "paused-sim",
|
|
203
|
+
pid: process.pid,
|
|
204
|
+
adminPort: port,
|
|
205
|
+
runId: "test-run-id",
|
|
206
|
+
}),
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
await sweepStaleEntries();
|
|
210
|
+
|
|
211
|
+
expect(await readEntry("paused-sim")).not.toBeNull();
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe("probeEntryIdentity", () => {
|
|
216
|
+
test("treats non-ready responses with a matching runId as verified", async () => {
|
|
217
|
+
const server = Bun.serve({
|
|
218
|
+
hostname: "127.0.0.1",
|
|
219
|
+
port: 0,
|
|
220
|
+
fetch() {
|
|
221
|
+
return Response.json({ status: "paused", runId: "test-run-id" }, { status: 503 });
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
serverHandles.push(server);
|
|
225
|
+
const port = server.port;
|
|
226
|
+
if (port === undefined) {
|
|
227
|
+
throw new Error("Failed to allocate test port.");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const result = await probeEntryIdentity(
|
|
231
|
+
makeEntry({
|
|
232
|
+
pid: process.pid,
|
|
233
|
+
adminPort: port,
|
|
234
|
+
runId: "test-run-id",
|
|
235
|
+
}),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
expect(result).toEqual({ status: "verified", ready: false });
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("fails closed on malformed /readyz bodies", async () => {
|
|
242
|
+
const server = Bun.serve({
|
|
243
|
+
hostname: "127.0.0.1",
|
|
244
|
+
port: 0,
|
|
245
|
+
fetch() {
|
|
246
|
+
return new Response("<html>not json</html>", {
|
|
247
|
+
status: 200,
|
|
248
|
+
headers: { "content-type": "text/html" },
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
serverHandles.push(server);
|
|
253
|
+
const port = server.port;
|
|
254
|
+
if (port === undefined) {
|
|
255
|
+
throw new Error("Failed to allocate test port.");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const result = await probeEntryIdentity(
|
|
259
|
+
makeEntry({
|
|
260
|
+
pid: process.pid,
|
|
261
|
+
adminPort: port,
|
|
262
|
+
runId: "test-run-id",
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
expect(result).toEqual({ status: "mismatch", ready: false });
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe("checkPortConflicts", () => {
|
|
271
|
+
test("detects admin port conflict", async () => {
|
|
272
|
+
await setupTestHome();
|
|
273
|
+
await writeEntry(makeEntry({ name: "existing", pid: process.pid, adminPort: 8080 }));
|
|
274
|
+
|
|
275
|
+
const ports = new Map([["admin", 8080]]);
|
|
276
|
+
const conflicts = await checkPortConflicts(ports);
|
|
277
|
+
expect(conflicts).toHaveLength(1);
|
|
278
|
+
expect(conflicts[0]).toContain("8080");
|
|
279
|
+
expect(conflicts[0]).toContain("existing");
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test("detects adapter port conflict", async () => {
|
|
283
|
+
await setupTestHome();
|
|
284
|
+
await writeEntry(
|
|
285
|
+
makeEntry({
|
|
286
|
+
name: "existing",
|
|
287
|
+
pid: process.pid,
|
|
288
|
+
adapters: { mqtt: { port: 1883, host: "0.0.0.0" } },
|
|
289
|
+
}),
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const ports = new Map([["mqtt", 1883]]);
|
|
293
|
+
const conflicts = await checkPortConflicts(ports);
|
|
294
|
+
expect(conflicts.length).toBeGreaterThan(0);
|
|
295
|
+
expect(conflicts[0]).toContain("1883");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("excludes named entry from conflict check", async () => {
|
|
299
|
+
await setupTestHome();
|
|
300
|
+
await writeEntry(makeEntry({ name: "self", pid: process.pid, adminPort: 8080 }));
|
|
301
|
+
|
|
302
|
+
const ports = new Map([["admin", 8080]]);
|
|
303
|
+
const conflicts = await checkPortConflicts(ports, "self");
|
|
304
|
+
expect(conflicts).toHaveLength(0);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test("ignores entries with dead PIDs", async () => {
|
|
308
|
+
await setupTestHome();
|
|
309
|
+
await writeEntry(makeEntry({ name: "dead", pid: 1_073_741_824, adminPort: 8080 }));
|
|
310
|
+
|
|
311
|
+
const ports = new Map([["admin", 8080]]);
|
|
312
|
+
const conflicts = await checkPortConflicts(ports);
|
|
313
|
+
expect(conflicts).toHaveLength(0);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("returns empty array when no conflicts", async () => {
|
|
317
|
+
await setupTestHome();
|
|
318
|
+
await writeEntry(makeEntry({ name: "existing", pid: process.pid, adminPort: 9090 }));
|
|
319
|
+
|
|
320
|
+
const ports = new Map([["admin", 8080]]);
|
|
321
|
+
const conflicts = await checkPortConflicts(ports);
|
|
322
|
+
expect(conflicts).toHaveLength(0);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe("checkNameConflict", () => {
|
|
327
|
+
test("returns null when no entry exists", async () => {
|
|
328
|
+
await setupTestHome();
|
|
329
|
+
const result = await checkNameConflict("nonexistent");
|
|
330
|
+
expect(result).toBeNull();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test("removes entry when PID is dead", async () => {
|
|
334
|
+
await setupTestHome();
|
|
335
|
+
await writeEntry(makeEntry({ name: "stale", pid: 1_073_741_824 }));
|
|
336
|
+
const result = await checkNameConflict("stale");
|
|
337
|
+
expect(result).toBeNull();
|
|
338
|
+
// Verify file was cleaned up
|
|
339
|
+
const entry = await readEntry("stale");
|
|
340
|
+
expect(entry).toBeNull();
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
test("keeps the entry when the daemon is alive but not ready", async () => {
|
|
344
|
+
await setupTestHome();
|
|
345
|
+
const server = Bun.serve({
|
|
346
|
+
hostname: "127.0.0.1",
|
|
347
|
+
port: 0,
|
|
348
|
+
fetch() {
|
|
349
|
+
return Response.json({ status: "paused", runId: "test-run-id" }, { status: 503 });
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
serverHandles.push(server);
|
|
353
|
+
const port = server.port;
|
|
354
|
+
if (port === undefined) {
|
|
355
|
+
throw new Error("Failed to allocate test port.");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
await writeEntry(
|
|
359
|
+
makeEntry({
|
|
360
|
+
name: "paused-sim",
|
|
361
|
+
pid: process.pid,
|
|
362
|
+
adminPort: port,
|
|
363
|
+
runId: "test-run-id",
|
|
364
|
+
}),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const result = await checkNameConflict("paused-sim");
|
|
368
|
+
|
|
369
|
+
expect(result?.name).toBe("paused-sim");
|
|
370
|
+
expect(await readEntry("paused-sim")).not.toBeNull();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
describe("acquireNameLock", () => {
|
|
375
|
+
test("acquires lock when none exists", async () => {
|
|
376
|
+
await setupTestHome();
|
|
377
|
+
const release = await acquireNameLock("test-sim");
|
|
378
|
+
await release();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("throws when lock is held by a live process", async () => {
|
|
382
|
+
await setupTestHome();
|
|
383
|
+
const dir = join(testHome, "daemon");
|
|
384
|
+
await mkdir(dir, { recursive: true });
|
|
385
|
+
await Bun.write(join(dir, "test-sim.lock"), String(process.pid));
|
|
386
|
+
|
|
387
|
+
await expect(acquireNameLock("test-sim")).rejects.toThrow("already starting");
|
|
388
|
+
|
|
389
|
+
// Clean up the manually created lock so afterEach can remove the dir
|
|
390
|
+
await Bun.file(join(dir, "test-sim.lock"))
|
|
391
|
+
.exists()
|
|
392
|
+
.then(async (exists) => {
|
|
393
|
+
if (exists) {
|
|
394
|
+
const { unlink } = await import("node:fs/promises");
|
|
395
|
+
await unlink(join(dir, "test-sim.lock")).catch(() => {});
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
test("clears stale lock from a dead process and succeeds", async () => {
|
|
401
|
+
await setupTestHome();
|
|
402
|
+
const dir = join(testHome, "daemon");
|
|
403
|
+
await mkdir(dir, { recursive: true });
|
|
404
|
+
// Write a lock with an impossible PID — guaranteed to be dead
|
|
405
|
+
await Bun.write(join(dir, "test-sim.lock"), "1073741824");
|
|
406
|
+
|
|
407
|
+
const release = await acquireNameLock("test-sim");
|
|
408
|
+
await release();
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
describe("displayHost", () => {
|
|
413
|
+
test("maps 0.0.0.0 to 127.0.0.1", () => {
|
|
414
|
+
expect(displayHost("0.0.0.0")).toBe("127.0.0.1");
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
test("maps :: to 127.0.0.1", () => {
|
|
418
|
+
expect(displayHost("::")).toBe("127.0.0.1");
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("passes through 127.0.0.1", () => {
|
|
422
|
+
expect(displayHost("127.0.0.1")).toBe("127.0.0.1");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
test("passes through custom host", () => {
|
|
426
|
+
expect(displayHost("192.168.1.100")).toBe("192.168.1.100");
|
|
427
|
+
});
|
|
428
|
+
});
|
|
429
|
+
});
|