Files
OmniRoute/tests/unit/tailscaleTunnel.test.ts
Diego Rodrigues de Sa e Souza 3d4f3e4960 test(infra): retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966) (#11968)
* test(infra): retry recursive temp-dir removal instead of failing a shard on ENOTEMPTY (#11966)

Two shards on release/v3.8.51 went red in one day with the same signature —
"ENOTEMPTY, Directory not empty: /tmp/omniroute-<test>-XXXXXX" — from
combo-same-provider-cascade (Unit Tests fast-path 4/4, on a PR that touches only
.github/) and auth-policy-embeddings-webfetch-7785 (the 20k-test TIA step). Both pass
alone and on re-run: the cleanup races something still writing into the directory
(SQLite WAL/-shm checkpoint, a worker, the backup) and under a loaded hosted runner
the window opens. 1154 test files do their own cleanup with
fs.rmSync(dir, { recursive: true, force: true }); 57 already asked for retries.

One-shot codemod (scripts/ad-hoc/codemod-rm-maxretries.mjs, kept for the record):
every rm / rmSync / rmdirSync option object with `recursive: true` and no
`maxRetries` gains `maxRetries: 5, retryDelay: 100` — Node itself then retries
ENOTEMPTY/EBUSY/EPERM for up to ~0.5 s before giving up. 2243 call sites in 1292
files under tests/, the shared tests/_setup/isolateDataDir.ts exit hook included.
Only the option object changes: no call site, assertion or import is touched.

Validation: prettier and ESLint (with the frozen suppressions) clean on all 1292
files; a random 20-file sample runs green (quota-redis-store hangs identically on
the untouched tree — it needs a Redis on localhost, an environment matter). The
four unit shards on this PR are the full run.

* fix(quality): let check-forgotten-sibling-tests read a 1,000-file diff

The gate shells out to `git diff` through execFileSync with Node's default 1 MB
maxBuffer; the 1,292-file codemod in this PR is the first diff large enough to
overflow it, and the gate died with `spawnSync git ENOBUFS` before comparing
anything. 64 MB is far above any real PR and costs nothing when unused.
2026-08-29 01:17:40 -03:00

222 lines
8.4 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
const TEST_DATA_DIR = await fs.mkdtemp(path.join(os.tmpdir(), "omniroute-tailscale-"));
process.env.DATA_DIR = TEST_DATA_DIR;
const tailscaleTunnel = await import("../../src/lib/tailscaleTunnel.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const dbCore = await import("../../src/lib/db/core.ts");
const mitmManager = await import("../../src/mitm/manager.ts");
const originalEnv = {
tailscaleBin: process.env.TAILSCALE_BIN,
tailscaledBin: process.env.TAILSCALED_BIN,
statusJson: process.env.TAILSCALE_TEST_STATUS_JSON,
funnelStatusJson: process.env.TAILSCALE_TEST_FUNNEL_STATUS_JSON,
funnelOutput: process.env.TAILSCALE_TEST_FUNNEL_OUTPUT,
funnelExitCode: process.env.TAILSCALE_TEST_FUNNEL_EXIT_CODE,
loginOutput: process.env.TAILSCALE_TEST_LOGIN_OUTPUT,
loginExitCode: process.env.TAILSCALE_TEST_LOGIN_EXIT_CODE,
};
async function createFakeTailscaleBinary() {
const fakePath = path.join(TEST_DATA_DIR, "fake-tailscale.sh");
await fs.writeFile(
fakePath,
`#!/usr/bin/env bash
args="$*"
if [[ "$args" == *"funnel status --json"* ]]; then
if [[ -n "$TAILSCALE_TEST_FUNNEL_STATUS_JSON" ]]; then
printf '%s' "$TAILSCALE_TEST_FUNNEL_STATUS_JSON"
exit 0
fi
exit 1
fi
if [[ "$args" == *"status --json"* ]]; then
if [[ -n "$TAILSCALE_TEST_STATUS_JSON" ]]; then
printf '%s' "$TAILSCALE_TEST_STATUS_JSON"
exit 0
fi
exit 1
fi
if [[ "$args" == *"funnel --bg reset"* ]]; then
exit 0
fi
if [[ "$args" == *"funnel --bg "* ]]; then
if [[ -n "$TAILSCALE_TEST_FUNNEL_OUTPUT" ]]; then
printf '%s' "$TAILSCALE_TEST_FUNNEL_OUTPUT"
fi
exit "\${TAILSCALE_TEST_FUNNEL_EXIT_CODE:-0}"
fi
if [[ "$args" == *"up --accept-routes"* ]]; then
if [[ -n "$TAILSCALE_TEST_LOGIN_OUTPUT" ]]; then
printf '%s' "$TAILSCALE_TEST_LOGIN_OUTPUT"
fi
exit "\${TAILSCALE_TEST_LOGIN_EXIT_CODE:-0}"
fi
exit 1
`,
"utf8"
);
await fs.chmod(fakePath, 0o755);
return fakePath;
}
function resetTailscaleTestEnv(fakeBinaryPath: string) {
process.env.TAILSCALE_BIN = fakeBinaryPath;
process.env.TAILSCALED_BIN = fakeBinaryPath;
delete process.env.TAILSCALE_TEST_STATUS_JSON;
delete process.env.TAILSCALE_TEST_FUNNEL_STATUS_JSON;
delete process.env.TAILSCALE_TEST_FUNNEL_OUTPUT;
delete process.env.TAILSCALE_TEST_FUNNEL_EXIT_CODE;
delete process.env.TAILSCALE_TEST_LOGIN_OUTPUT;
delete process.env.TAILSCALE_TEST_LOGIN_EXIT_CODE;
}
test.beforeEach(async () => {
const fakeBinaryPath = await createFakeTailscaleBinary();
resetTailscaleTestEnv(fakeBinaryPath);
mitmManager.clearCachedPassword();
dbCore.resetDbInstance();
await fs.rm(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
await fs.mkdir(TEST_DATA_DIR, { recursive: true });
const recreatedBinaryPath = await createFakeTailscaleBinary();
resetTailscaleTestEnv(recreatedBinaryPath);
});
test.after(async () => {
dbCore.resetDbInstance();
mitmManager.clearCachedPassword();
if (originalEnv.tailscaleBin === undefined) delete process.env.TAILSCALE_BIN;
else process.env.TAILSCALE_BIN = originalEnv.tailscaleBin;
if (originalEnv.tailscaledBin === undefined) delete process.env.TAILSCALED_BIN;
else process.env.TAILSCALED_BIN = originalEnv.tailscaledBin;
if (originalEnv.statusJson === undefined) delete process.env.TAILSCALE_TEST_STATUS_JSON;
else process.env.TAILSCALE_TEST_STATUS_JSON = originalEnv.statusJson;
if (originalEnv.funnelStatusJson === undefined)
delete process.env.TAILSCALE_TEST_FUNNEL_STATUS_JSON;
else process.env.TAILSCALE_TEST_FUNNEL_STATUS_JSON = originalEnv.funnelStatusJson;
if (originalEnv.funnelOutput === undefined) delete process.env.TAILSCALE_TEST_FUNNEL_OUTPUT;
else process.env.TAILSCALE_TEST_FUNNEL_OUTPUT = originalEnv.funnelOutput;
if (originalEnv.funnelExitCode === undefined) delete process.env.TAILSCALE_TEST_FUNNEL_EXIT_CODE;
else process.env.TAILSCALE_TEST_FUNNEL_EXIT_CODE = originalEnv.funnelExitCode;
if (originalEnv.loginOutput === undefined) delete process.env.TAILSCALE_TEST_LOGIN_OUTPUT;
else process.env.TAILSCALE_TEST_LOGIN_OUTPUT = originalEnv.loginOutput;
if (originalEnv.loginExitCode === undefined) delete process.env.TAILSCALE_TEST_LOGIN_EXIT_CODE;
else process.env.TAILSCALE_TEST_LOGIN_EXIT_CODE = originalEnv.loginExitCode;
await fs.rm(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
});
test("extractTailscaleAuthUrl and extractTailscaleEnableUrl parse login URLs", () => {
assert.equal(
tailscaleTunnel.extractTailscaleAuthUrl(
"To authenticate, visit https://login.tailscale.com/a/demo-token in your browser."
),
"https://login.tailscale.com/a/demo-token"
);
assert.equal(
tailscaleTunnel.extractTailscaleEnableUrl(
"Funnel is not enabled yet. Open https://login.tailscale.com/f/funnel-demo to continue."
),
"https://login.tailscale.com/f/funnel-demo"
);
});
test("getTailscaleUrlFromStatusPayload normalizes DNS names into HTTPS URLs", () => {
assert.equal(
tailscaleTunnel.getTailscaleUrlFromStatusPayload({
Self: { DNSName: "omniroute-demo.tail123.ts.net." },
}),
"https://omniroute-demo.tail123.ts.net"
);
});
test("getTailscaleTunnelStatus reflects a logged-in running funnel", async () => {
process.env.TAILSCALE_TEST_STATUS_JSON = JSON.stringify({
BackendState: "Running",
Self: { DNSName: "omniroute-demo.tail123.ts.net." },
});
process.env.TAILSCALE_TEST_FUNNEL_STATUS_JSON = JSON.stringify({
AllowFunnel: {
"443": true,
},
});
const status = await tailscaleTunnel.getTailscaleTunnelStatus();
assert.equal(status.installed, true);
assert.equal(status.loggedIn, true);
assert.equal(status.running, true);
assert.equal(status.phase, "running");
assert.equal(status.tunnelUrl, "https://omniroute-demo.tail123.ts.net");
assert.equal(status.apiUrl, "https://omniroute-demo.tail123.ts.net/v1");
});
test("enableTailscaleTunnel returns an auth URL when login is still required", async () => {
process.env.TAILSCALE_TEST_STATUS_JSON = JSON.stringify({
BackendState: "NeedsLogin",
Self: { DNSName: "omniroute-demo.tail123.ts.net." },
});
process.env.TAILSCALE_TEST_LOGIN_OUTPUT =
"Authenticate at https://login.tailscale.com/a/login-token";
const result = await tailscaleTunnel.enableTailscaleTunnel();
assert.equal(result.success, false);
assert.equal("needsLogin" in result, true);
if ("needsLogin" in result) {
assert.equal(result.authUrl, "https://login.tailscale.com/a/login-token");
}
});
test("enableTailscaleTunnel returns the funnel enable URL when the tailnet has Funnel disabled", async () => {
process.env.TAILSCALE_TEST_STATUS_JSON = JSON.stringify({
BackendState: "Running",
Self: { DNSName: "omniroute-demo.tail123.ts.net." },
});
process.env.TAILSCALE_TEST_FUNNEL_OUTPUT =
"Funnel is not enabled. Continue at https://login.tailscale.com/f/funnel-token";
const result = await tailscaleTunnel.enableTailscaleTunnel();
assert.equal(result.success, false);
assert.equal("funnelNotEnabled" in result, true);
if ("funnelNotEnabled" in result) {
assert.equal(result.enableUrl, "https://login.tailscale.com/f/funnel-token");
}
});
test("enableTailscaleTunnel stores the funnel URL and disableTailscaleTunnel clears it", async () => {
process.env.TAILSCALE_TEST_STATUS_JSON = JSON.stringify({
BackendState: "Running",
Self: { DNSName: "omniroute-demo.tail123.ts.net." },
});
process.env.TAILSCALE_TEST_FUNNEL_STATUS_JSON = JSON.stringify({
AllowFunnel: {
"443": true,
},
});
process.env.TAILSCALE_TEST_FUNNEL_OUTPUT = "Available at https://omniroute-demo.tail123.ts.net";
const enabled = await tailscaleTunnel.enableTailscaleTunnel();
const settingsAfterEnable = await settingsDb.getSettings();
assert.equal(enabled.success, true);
if (enabled.success) {
assert.equal(enabled.tunnelUrl, "https://omniroute-demo.tail123.ts.net");
}
assert.equal(settingsAfterEnable.tailscaleEnabled, true);
assert.equal(settingsAfterEnable.tailscaleUrl, "https://omniroute-demo.tail123.ts.net");
const disabled = await tailscaleTunnel.disableTailscaleTunnel();
const settingsAfterDisable = await settingsDb.getSettings();
assert.equal(disabled.success, true);
assert.equal(settingsAfterDisable.tailscaleEnabled, false);
assert.equal(settingsAfterDisable.tailscaleUrl, "");
});