@celilo/e2e 0.6.0 → 0.6.2
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 +2 -1
- package/config/dns/celilo.computer.zone +16 -0
- package/config/dns/iamtheinternet.org.zone +6 -4
- package/config/dns/knot-namecheap.conf +8 -0
- package/config/dns/knot-tld.conf +4 -0
- package/config/dns/lunacycle.net.zone +16 -0
- package/config/dns/net.zone +11 -0
- package/config/dns/org.zone +5 -0
- package/config/dns/root.zone +2 -0
- package/config/dns/templates/iamtheinternet.org.zone +16 -0
- package/config/dns/templates/lunacycle.net.zone +16 -0
- package/config/resolver/unbound.conf +10 -0
- package/config/routing/management-routes.sh +19 -0
- package/config/routing/resolver-internal-routes.sh +59 -0
- package/docker/Dockerfile.internal-resolver +16 -0
- package/docker/Dockerfile.isitup +37 -0
- package/docker/Dockerfile.registry +34 -0
- package/netapps/authentik.netapp +0 -0
- package/netapps/caddy.netapp +0 -0
- package/netapps/greenwave.netapp +0 -0
- package/netapps/homebridge.netapp +0 -0
- package/netapps/iptables.netapp +0 -0
- package/netapps/knot-unbound-internal.netapp +0 -0
- package/netapps/namecheap.netapp +0 -0
- package/netapps/technitium.netapp +0 -0
- package/netapps/test-ansible-output.netapp +0 -0
- package/package.json +2 -1
- package/simulators/isitup/Caddyfile +11 -0
- package/simulators/isitup/entrypoint.sh +47 -0
- package/simulators/isitup/server.ts +112 -0
- package/simulators/namecheap-ddns/server.ts +31 -2
- package/src/cli/build.ts +17 -3
- package/src/cli/command-registry.ts +3 -2
- package/src/cli/completion.ts +29 -5
- package/src/cli/index.ts +85 -15
- package/src/cli/module-discovery.ts +120 -0
- package/src/cli/scaffold.ts +4 -5
- package/src/container-manager.ts +358 -71
- package/src/docker-compose-generator.ts +119 -17
- package/src/parse-line.ts +14 -3
- package/src/runner.ts +122 -165
- package/src/simulator-ips.ts +42 -0
- package/src/types.ts +24 -0
package/src/runner.ts
CHANGED
|
@@ -20,13 +20,37 @@
|
|
|
20
20
|
import { spawn, spawnSync, execSync } from 'node:child_process';
|
|
21
21
|
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
22
22
|
import { basename, join, resolve } from 'node:path';
|
|
23
|
+
import { parse as parseYaml } from 'yaml';
|
|
24
|
+
import { ProgressDisplay } from '@celilo/cli-display';
|
|
23
25
|
import { parseLine } from './parse-line';
|
|
24
26
|
|
|
25
27
|
// ─── Config ──────────────────────────────────────────────────────────
|
|
26
28
|
|
|
27
29
|
const PKG_DIR = resolve(import.meta.dir, '..');
|
|
28
30
|
const testDir = process.env.E2E_TEST_DIR || process.cwd();
|
|
29
|
-
|
|
31
|
+
/** Set by `cele2e run <module-path>` to run tests from that module's e2e/ dir */
|
|
32
|
+
const moduleDir = process.env.E2E_MODULE_DIR;
|
|
33
|
+
/** Set by `cele2e run` (no args) when invoked outside a module — runs every discovered module's tests. */
|
|
34
|
+
const moduleDirs = process.env.E2E_MODULE_DIRS
|
|
35
|
+
? process.env.E2E_MODULE_DIRS.split('\n').filter(Boolean)
|
|
36
|
+
: moduleDir
|
|
37
|
+
? [moduleDir]
|
|
38
|
+
: [];
|
|
39
|
+
|
|
40
|
+
function resolveModuleTestsDir(dir: string): string {
|
|
41
|
+
const manifestPath = join(dir, 'manifest.yml');
|
|
42
|
+
if (existsSync(manifestPath)) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = parseYaml(readFileSync(manifestPath, 'utf-8')) as { e2e?: { tests_dir?: string } };
|
|
45
|
+
if (parsed?.e2e?.tests_dir) return resolve(dir, parsed.e2e.tests_dir);
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
return join(dir, 'e2e');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const testsPath = moduleDir
|
|
52
|
+
? resolveModuleTestsDir(moduleDir)
|
|
53
|
+
: (process.env.E2E_TESTS_PATH ?? join(testDir, 'tests'));
|
|
30
54
|
const timingFile = join(testDir, '.e2e-timing.json');
|
|
31
55
|
const persistentFile = join(testDir, '.e2e-persistent.json');
|
|
32
56
|
|
|
@@ -48,10 +72,6 @@ const red = '\x1b[31m';
|
|
|
48
72
|
const cyan = '\x1b[36m';
|
|
49
73
|
const reset = '\x1b[0m';
|
|
50
74
|
|
|
51
|
-
// Terminal control for interactive mode
|
|
52
|
-
const CLEAR_LINE = '\x1b[2K\r';
|
|
53
|
-
const MOVE_UP = '\x1b[A';
|
|
54
|
-
|
|
55
75
|
const interactive = process.stdout.isTTY && !process.argv.includes('--no-interactive') && !flagVerbose;
|
|
56
76
|
|
|
57
77
|
// ─── Timing ──────────────────────────────────────────────────────────
|
|
@@ -75,142 +95,6 @@ function formatDuration(secs: number): string {
|
|
|
75
95
|
return `${secs}s`;
|
|
76
96
|
}
|
|
77
97
|
|
|
78
|
-
/** Format countdown: positive = remaining, negative = over estimate */
|
|
79
|
-
function formatCountdown(elapsedSecs: number, expectedSecs: number | undefined): string {
|
|
80
|
-
if (!expectedSecs) return `${dim}${String(elapsedSecs).padStart(4)}s${reset}`;
|
|
81
|
-
const remaining = expectedSecs - elapsedSecs;
|
|
82
|
-
if (remaining >= 0) {
|
|
83
|
-
return `${dim}${String(remaining).padStart(4)}s${reset}`;
|
|
84
|
-
}
|
|
85
|
-
return `${red}${String(remaining).padStart(4)}s${reset}`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ─── Progress Display ────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
interface PendingStep {
|
|
91
|
-
doingMsg: string;
|
|
92
|
-
doneMsg: string;
|
|
93
|
-
startTime: number;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
class ProgressDisplay {
|
|
97
|
-
private pending: PendingStep | null = null;
|
|
98
|
-
private testStart = 0;
|
|
99
|
-
private expectedDuration: number | undefined;
|
|
100
|
-
/** Number of sub-event lines printed below the current "… doing" line */
|
|
101
|
-
private subEventCount = 0;
|
|
102
|
-
|
|
103
|
-
reset(testStart: number, expectedDuration: number | undefined) {
|
|
104
|
-
this.pending = null;
|
|
105
|
-
this.testStart = testStart;
|
|
106
|
-
this.expectedDuration = expectedDuration;
|
|
107
|
-
this.subEventCount = 0;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private time(): string {
|
|
111
|
-
const elapsed = Math.floor((Date.now() - this.testStart) / 1000);
|
|
112
|
-
return formatCountdown(elapsed, this.expectedDuration);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* In interactive mode: erase the "… doing" line and all sub-event
|
|
117
|
-
* lines below it, then print the "✔ done" line in their place.
|
|
118
|
-
* In non-interactive mode: just append the done line.
|
|
119
|
-
*/
|
|
120
|
-
private finalizePending() {
|
|
121
|
-
if (!this.pending) return;
|
|
122
|
-
if (interactive) {
|
|
123
|
-
// Erase sub-event lines + the pending "…" line
|
|
124
|
-
const linesToErase = this.subEventCount + 1;
|
|
125
|
-
for (let i = 0; i < linesToErase; i++) {
|
|
126
|
-
process.stdout.write(`${MOVE_UP}${CLEAR_LINE}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
process.stdout.write(` ${this.time()} ${green}✔${reset} ${this.pending.doneMsg}\n`);
|
|
130
|
-
this.pending = null;
|
|
131
|
-
this.subEventCount = 0;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private finalizeFailed(message: string) {
|
|
135
|
-
if (interactive && this.pending) {
|
|
136
|
-
const linesToErase = this.subEventCount + 1;
|
|
137
|
-
for (let i = 0; i < linesToErase; i++) {
|
|
138
|
-
process.stdout.write(`${MOVE_UP}${CLEAR_LINE}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
process.stdout.write(` ${this.time()} ${red}✗${reset} ${message}\n`);
|
|
142
|
-
this.pending = null;
|
|
143
|
-
this.subEventCount = 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
startStep(doingMsg: string, doneMsg: string) {
|
|
147
|
-
this.finalizePending();
|
|
148
|
-
this.pending = { doingMsg, doneMsg, startTime: Date.now() };
|
|
149
|
-
this.subEventCount = 0;
|
|
150
|
-
process.stdout.write(` ${this.time()} ${dim}…${reset} ${doingMsg}\n`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
doneStep(overrideMessage?: string) {
|
|
154
|
-
if (this.pending && overrideMessage) {
|
|
155
|
-
this.pending.doneMsg = overrideMessage;
|
|
156
|
-
}
|
|
157
|
-
this.finalizePending();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
failStep(message: string) {
|
|
161
|
-
this.finalizeFailed(message);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
instantEvent(message: string) {
|
|
165
|
-
this.finalizePending();
|
|
166
|
-
process.stdout.write(` ${this.time()} ${message}\n`);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Show a sub-event under the current "… doing" step.
|
|
171
|
-
* Interactive: replaces the "… doing" line with the sub-event,
|
|
172
|
-
* then reprints "… doing" below it — so the pending line is always last.
|
|
173
|
-
* Non-interactive: just appends indented.
|
|
174
|
-
*/
|
|
175
|
-
subEvent(message: string) {
|
|
176
|
-
if (interactive && this.pending) {
|
|
177
|
-
// Replace the "… doing" line with this sub-event (permanent)
|
|
178
|
-
process.stdout.write(`${MOVE_UP}${CLEAR_LINE}`);
|
|
179
|
-
process.stdout.write(` ${this.time()} ${message}\n`);
|
|
180
|
-
// Reprint the "… doing" line as the new bottom line
|
|
181
|
-
process.stdout.write(` ${this.time()} ${dim}…${reset} ${this.pending.doingMsg}\n`);
|
|
182
|
-
this.subEventCount++;
|
|
183
|
-
} else {
|
|
184
|
-
process.stdout.write(` ${this.time()} ${message}\n`);
|
|
185
|
-
if (this.pending) this.subEventCount++;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Finalize pending on success */
|
|
190
|
-
flush() {
|
|
191
|
-
this.finalizePending();
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/** Abandon pending step on failure (erase the … line in interactive mode) */
|
|
195
|
-
abandon() {
|
|
196
|
-
if (!this.pending) return;
|
|
197
|
-
if (interactive) {
|
|
198
|
-
const linesToErase = this.subEventCount + 1;
|
|
199
|
-
for (let i = 0; i < linesToErase; i++) {
|
|
200
|
-
process.stdout.write(`${MOVE_UP}${CLEAR_LINE}`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
this.pending = null;
|
|
204
|
-
this.subEventCount = 0;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/** Like abandon() but marks the step as failed so the user can see where it broke */
|
|
208
|
-
abandonFailed() {
|
|
209
|
-
if (!this.pending) return;
|
|
210
|
-
this.finalizeFailed(this.pending.doingMsg);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
98
|
// ─── Error Extraction ────────────────────────────────────────────────
|
|
215
99
|
|
|
216
100
|
// Strips ANSI escape sequences so pattern matching works on bun's coloured output
|
|
@@ -334,9 +218,9 @@ async function runTest(
|
|
|
334
218
|
|
|
335
219
|
const signalFile = join(PKG_DIR, `.debug-resume-${proc.pid}`);
|
|
336
220
|
const border = '═'.repeat(56);
|
|
337
|
-
process.stdout.write(`\n${bold}\x1b[33m╔${border}
|
|
338
|
-
process.stdout.write(`${bold}\x1b[33m║ DEBUG: test paused
|
|
339
|
-
process.stdout.write(`${bold}\x1b[33m╠${border}
|
|
221
|
+
process.stdout.write(`\n${bold}\x1b[33m╔${border}${reset}\n`);
|
|
222
|
+
process.stdout.write(`${bold}\x1b[33m║ DEBUG: test paused${reset}\n`);
|
|
223
|
+
process.stdout.write(`${bold}\x1b[33m╠${border}${reset}\n`);
|
|
340
224
|
process.stdout.write(`${bold}\x1b[33m║${reset} Project: ${bold}${project}${reset}\n`);
|
|
341
225
|
process.stdout.write(`${bold}\x1b[33m║${reset} Container: ${bold}${container}${reset}\n`);
|
|
342
226
|
process.stdout.write(`${bold}\x1b[33m║${reset}\n`);
|
|
@@ -347,7 +231,7 @@ async function runTest(
|
|
|
347
231
|
|
|
348
232
|
if (interactive) {
|
|
349
233
|
process.stdout.write(`${bold}\x1b[33m║${reset} ${dim}Or get a shell: type 'exit' to resume the test.${reset}\n`);
|
|
350
|
-
process.stdout.write(`${bold}\x1b[33m╚${border}
|
|
234
|
+
process.stdout.write(`${bold}\x1b[33m╚${border}${reset}\n\n`);
|
|
351
235
|
|
|
352
236
|
spawnSync('docker', [
|
|
353
237
|
'compose', '-f', 'docker-compose.test.yml', '-p', project,
|
|
@@ -358,7 +242,7 @@ async function runTest(
|
|
|
358
242
|
});
|
|
359
243
|
} else {
|
|
360
244
|
process.stdout.write(`${bold}\x1b[33m║${reset} ${dim}Resume: touch ${signalFile}${reset}\n`);
|
|
361
|
-
process.stdout.write(`${bold}\x1b[33m╚${border}
|
|
245
|
+
process.stdout.write(`${bold}\x1b[33m╚${border}${reset}\n\n`);
|
|
362
246
|
|
|
363
247
|
// Non-interactive: just wait for the signal file (no shell)
|
|
364
248
|
// The caller creates the file when they're done investigating.
|
|
@@ -393,6 +277,11 @@ async function runTest(
|
|
|
393
277
|
continue;
|
|
394
278
|
}
|
|
395
279
|
|
|
280
|
+
// Detect bun test failure so pending progress steps show ✗ instead of ✔
|
|
281
|
+
if (/^\s*\(fail\)\s/.test(trimmed) || /^\s*✗\s+.+\[/.test(trimmed)) {
|
|
282
|
+
display.markTestFailing();
|
|
283
|
+
}
|
|
284
|
+
|
|
396
285
|
parseLine(trimmed, display);
|
|
397
286
|
}
|
|
398
287
|
}
|
|
@@ -484,18 +373,63 @@ function stopSharedInfra(): void {
|
|
|
484
373
|
try { execSync('docker network prune -f', { stdio: 'pipe', timeout: 10_000 }); } catch {}
|
|
485
374
|
}
|
|
486
375
|
|
|
487
|
-
// ───
|
|
376
|
+
// ─── Docker Check ────────────────────────────────────────────────────
|
|
488
377
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
378
|
+
function assertDockerAvailable(): void {
|
|
379
|
+
try {
|
|
380
|
+
execSync('docker info', { stdio: 'pipe', timeout: 5_000 });
|
|
381
|
+
} catch {
|
|
382
|
+
console.error(`\n${red}✗ Docker is not running.${reset}`);
|
|
383
|
+
console.error(` Start it with: ${bold}colima start${reset}`);
|
|
384
|
+
console.error(` Then retry: ${bold}cele2e run${reset}\n`);
|
|
492
385
|
process.exit(1);
|
|
493
386
|
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ─── Main ────────────────────────────────────────────────────────────
|
|
494
390
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
391
|
+
async function main() {
|
|
392
|
+
assertDockerAvailable();
|
|
393
|
+
|
|
394
|
+
let testFiles: string[] = [];
|
|
395
|
+
|
|
396
|
+
if (moduleDirs.length > 0) {
|
|
397
|
+
// One or more modules — collect tests from each module's e2e dir.
|
|
398
|
+
for (const dir of moduleDirs) {
|
|
399
|
+
if (!existsSync(join(dir, 'manifest.yml'))) {
|
|
400
|
+
console.error(`${red}Not a celilo module: no manifest.yml found at ${dir}${reset}`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
const tp = resolveModuleTestsDir(dir);
|
|
404
|
+
if (!existsSync(tp)) {
|
|
405
|
+
console.error(`${red}No tests directory found at ${tp}${reset}`);
|
|
406
|
+
console.error(`${red}Set e2e.tests_dir in ${join(dir, 'manifest.yml')} to point to your test files.${reset}`);
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
// Auto-install e2e/ dependencies if package.json exists but node_modules is missing
|
|
410
|
+
const e2ePkg = join(tp, 'package.json');
|
|
411
|
+
const e2eNodeModules = join(tp, 'node_modules');
|
|
412
|
+
if (existsSync(e2ePkg) && !existsSync(e2eNodeModules)) {
|
|
413
|
+
process.stdout.write(`${dim}Installing e2e dependencies for ${basename(dir)}...${reset}\n`);
|
|
414
|
+
execSync('bun install', { cwd: tp, stdio: 'inherit' });
|
|
415
|
+
process.stdout.write('\n');
|
|
416
|
+
}
|
|
417
|
+
const files = readdirSync(tp)
|
|
418
|
+
.filter((f) => f.endsWith('.test.ts'))
|
|
419
|
+
.sort()
|
|
420
|
+
.map((f) => resolve(tp, f));
|
|
421
|
+
testFiles.push(...files);
|
|
422
|
+
}
|
|
423
|
+
} else {
|
|
424
|
+
if (!existsSync(testsPath)) {
|
|
425
|
+
console.error(`${red}No tests/ directory found at ${testsPath}${reset}`);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
testFiles = readdirSync(testsPath)
|
|
429
|
+
.filter((f) => f.endsWith('.test.ts'))
|
|
430
|
+
.sort()
|
|
431
|
+
.map((f) => resolve(testsPath, f));
|
|
432
|
+
}
|
|
499
433
|
|
|
500
434
|
if (patterns.length > 0) {
|
|
501
435
|
testFiles = testFiles.filter((f) => patterns.some((p) => basename(f, '.test.ts').includes(p)));
|
|
@@ -506,20 +440,40 @@ async function main() {
|
|
|
506
440
|
process.exit(1);
|
|
507
441
|
}
|
|
508
442
|
|
|
443
|
+
// --keep keeps a single test network alive after the run; only one network
|
|
444
|
+
// can live in `.e2e-persistent.json` at a time, so multiple tests would
|
|
445
|
+
// either fight for that slot or run with no functional --keep at all. Bail
|
|
446
|
+
// early with a clear message instead of silently doing the wrong thing.
|
|
447
|
+
if (flagKeep && testFiles.length > 1) {
|
|
448
|
+
console.error(
|
|
449
|
+
`${red}--keep matched ${testFiles.length} tests; --keep only works with a single test.${reset}`,
|
|
450
|
+
);
|
|
451
|
+
console.error(
|
|
452
|
+
`${dim}Narrow the pattern (e.g. \`cele2e run ${basename(testFiles[0], '.test.ts')} --keep\`) or drop --keep.${reset}`,
|
|
453
|
+
);
|
|
454
|
+
process.exit(1);
|
|
455
|
+
}
|
|
456
|
+
|
|
509
457
|
const history = loadTiming();
|
|
510
458
|
const testNames = testFiles.map((f) => basename(f, '.test.ts'));
|
|
511
459
|
const hasHistory = testNames.some((n) => history[n]);
|
|
512
460
|
const suiteExpected = testNames.reduce((sum, n) => sum + (history[n] || 0), 0);
|
|
513
461
|
|
|
462
|
+
const moduleLabel =
|
|
463
|
+
moduleDirs.length === 1
|
|
464
|
+
? ` — ${basename(moduleDirs[0])}`
|
|
465
|
+
: moduleDirs.length > 1
|
|
466
|
+
? ` — ${moduleDirs.length} modules`
|
|
467
|
+
: '';
|
|
514
468
|
console.log();
|
|
515
|
-
console.log(`${bold}
|
|
516
|
-
console.log(`${bold}║ Celilo E2E Tests
|
|
469
|
+
console.log(`${bold}╔══════════════════════════════════════════════════════════════${reset}`);
|
|
470
|
+
console.log(`${bold}║ Celilo E2E Tests${moduleLabel}${reset}`);
|
|
517
471
|
if (hasHistory) {
|
|
518
|
-
console.log(`${bold}║ ${testFiles.length} test(s) — expected ~${formatDuration(suiteExpected)
|
|
472
|
+
console.log(`${bold}║ ${testFiles.length} test(s) — expected ~${formatDuration(suiteExpected)}${reset}`);
|
|
519
473
|
} else {
|
|
520
|
-
console.log(`${bold}║ ${testFiles.length} test(s) — first run, no timing history
|
|
474
|
+
console.log(`${bold}║ ${testFiles.length} test(s) — first run, no timing history${reset}`);
|
|
521
475
|
}
|
|
522
|
-
console.log(`${bold}
|
|
476
|
+
console.log(`${bold}╚══════════════════════════════════════════════════════════════${reset}`);
|
|
523
477
|
console.log();
|
|
524
478
|
|
|
525
479
|
const suiteStart = Date.now();
|
|
@@ -527,7 +481,10 @@ async function main() {
|
|
|
527
481
|
const resultsDir = join(testDir, 'results', timestamp);
|
|
528
482
|
mkdirSync(resultsDir, { recursive: true });
|
|
529
483
|
|
|
530
|
-
const display = new ProgressDisplay(
|
|
484
|
+
const display = new ProgressDisplay({
|
|
485
|
+
mode: 'render',
|
|
486
|
+
out: { write: process.stdout.write.bind(process.stdout), isTTY: interactive },
|
|
487
|
+
});
|
|
531
488
|
let passed = 0;
|
|
532
489
|
let failed = 0;
|
|
533
490
|
const results: TestResult[] = [];
|
|
@@ -585,7 +542,7 @@ async function main() {
|
|
|
585
542
|
);
|
|
586
543
|
console.log(`${dim} Persistent network: ${result.projectName}${reset}`);
|
|
587
544
|
console.log(`${dim} Reuse: ${basename(process.argv[1])} --reuse${reset}`);
|
|
588
|
-
console.log(`${dim} Tear down:
|
|
545
|
+
console.log(`${dim} Tear down: cele2e down${reset}`);
|
|
589
546
|
}
|
|
590
547
|
|
|
591
548
|
if (result.status === 'pass') {
|
|
@@ -628,11 +585,11 @@ async function main() {
|
|
|
628
585
|
const suiteDuration = Math.floor((Date.now() - suiteStart) / 1000);
|
|
629
586
|
|
|
630
587
|
console.log();
|
|
631
|
-
console.log(`${bold}
|
|
588
|
+
console.log(`${bold}╔══════════════════════════════════════════════════════════════${reset}`);
|
|
632
589
|
console.log(
|
|
633
|
-
`${bold}║ Results: ${green}${passed} passed${reset}${bold}, ${red}${failed} failed${reset}${bold} — ${formatDuration(suiteDuration)} total${
|
|
590
|
+
`${bold}║ Results: ${green}${passed} passed${reset}${bold}, ${red}${failed} failed${reset}${bold} — ${formatDuration(suiteDuration)} total${reset}`,
|
|
634
591
|
);
|
|
635
|
-
console.log(`${bold}
|
|
592
|
+
console.log(`${bold}╠══════════════════════════════════════════════════════════════${reset}`);
|
|
636
593
|
|
|
637
594
|
for (const r of results) {
|
|
638
595
|
const icon = r.status === 'pass' ? '✓' : '✗';
|
|
@@ -643,7 +600,7 @@ async function main() {
|
|
|
643
600
|
}
|
|
644
601
|
}
|
|
645
602
|
|
|
646
|
-
console.log(`${bold}
|
|
603
|
+
console.log(`${bold}╚══════════════════════════════════════════════════════════════${reset}`);
|
|
647
604
|
console.log();
|
|
648
605
|
console.log(`${dim}Full logs: ${resultsDir}${reset}`);
|
|
649
606
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the IPs of e2e simulator containers on
|
|
3
|
+
* the `internet-external` Docker network.
|
|
4
|
+
*
|
|
5
|
+
* Renumbering one of these used to mean tracking down the value in
|
|
6
|
+
* eight-ish places (compose files, generator, zone files, resolver
|
|
7
|
+
* configs, routing scripts). This module is the canonical record for
|
|
8
|
+
* code; non-code files (Knot zones, shell scripts) still hard-code the
|
|
9
|
+
* values, but `scripts/check-simulator-ips.sh` flags any drift in CI.
|
|
10
|
+
*
|
|
11
|
+
* To renumber a simulator:
|
|
12
|
+
* 1. Update the constant here.
|
|
13
|
+
* 2. Run `bash packages/e2e/scripts/check-simulator-ips.sh` — it
|
|
14
|
+
* lists every non-code file that still has the old value.
|
|
15
|
+
* 3. Edit those files. Re-run the check until it passes.
|
|
16
|
+
* 4. `cele2e build-infra` to rebake the images.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export const SIMULATOR_IPS = {
|
|
20
|
+
/** Authoritative root DNS (`.` zone). */
|
|
21
|
+
ROOT_DNS: '100.64.0.53',
|
|
22
|
+
/** Authoritative TLD DNS for `com.`, `org.`, `net.`. */
|
|
23
|
+
TLD_DNS: '100.64.0.54',
|
|
24
|
+
/** Authoritative DNS for the customer domains; also serves the DDNS update endpoint at port 8080. */
|
|
25
|
+
NAMECHEAP_DNS: '100.64.0.55',
|
|
26
|
+
/** Internal e2e module registry (replaces production celilo.computer in tests). */
|
|
27
|
+
REGISTRY: '100.64.0.56',
|
|
28
|
+
/** External-website prober simulator (mimics isitup.org). */
|
|
29
|
+
ISITUP: '100.64.0.57',
|
|
30
|
+
/** Pebble ACME server (replaces production Let's Encrypt). */
|
|
31
|
+
PEBBLE: '100.64.0.100',
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
export type SimulatorIpName = keyof typeof SIMULATOR_IPS;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Iterable list of `[name, ip]` pairs — useful for the sanity check
|
|
38
|
+
* script and any future consumer that wants to enumerate the set.
|
|
39
|
+
*/
|
|
40
|
+
export const SIMULATOR_IP_ENTRIES: ReadonlyArray<readonly [SimulatorIpName, string]> = (
|
|
41
|
+
Object.entries(SIMULATOR_IPS) as Array<[SimulatorIpName, string]>
|
|
42
|
+
).map(([k, v]) => [k, v] as const);
|
package/src/types.ts
CHANGED
|
@@ -31,6 +31,12 @@ export interface NetworkConfig {
|
|
|
31
31
|
* If not set, auto-detected by walking up from the package directory.
|
|
32
32
|
*/
|
|
33
33
|
celiloRoot?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Skip DNS zone scrubbing before the test starts. Useful for --keep
|
|
36
|
+
* debugging where you want to inspect the exact DNS state left by a
|
|
37
|
+
* previous test. Default: false (scrub runs).
|
|
38
|
+
*/
|
|
39
|
+
skipDnsScrub?: boolean;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
export interface ExecResult {
|
|
@@ -81,6 +87,24 @@ export interface NetworkHandle {
|
|
|
81
87
|
label: string,
|
|
82
88
|
): Promise<void>;
|
|
83
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Set the Caddy ACME CA to the Pebble endpoint so TLS certificates are
|
|
92
|
+
* issued by the simulated Let's Encrypt rather than the real one.
|
|
93
|
+
*
|
|
94
|
+
* Call this after importing the caddy module and before deploying it.
|
|
95
|
+
* Every e2e test that involves TLS must call this — it is not automatic
|
|
96
|
+
* so the call site is self-documenting.
|
|
97
|
+
*/
|
|
98
|
+
configureAcme(): Promise<void>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Package a local module and upload it to the e2e registry so it can be
|
|
102
|
+
* imported via `celilo module import public-registry <name>`.
|
|
103
|
+
*
|
|
104
|
+
* @param localPath - Absolute or relative path to the module source directory
|
|
105
|
+
*/
|
|
106
|
+
publishModule(localPath: string): Promise<void>;
|
|
107
|
+
|
|
84
108
|
/** Tear down the entire network */
|
|
85
109
|
stop(): Promise<void>;
|
|
86
110
|
|