OmniRoute/tests/unit/bootstrap-env.test.ts
Lance Woodson 5fb51218a5
docs(i18n): carry the corrected ENVIRONMENT.md segments into all locale mirrors (#15618)
Onda 3 (woodsonl) validada num único board em `.claude/worktrees/batch-w3` sobre `origin/release/v3.8.52` (`4f4c983347b9`), 27 PRs, superfícies protegidas 0.

Evidência: `typecheck:core` limpo. i18n drift limpo no board. changelog-integrity ok. file-size ok (170 congelados). complexity 2975/3218 e cognitive-complexity 1357/1437 dentro do baseline. node:test focado: 1868 passaram, 0 falharam. Vitest da UI: 150 passaram em 12 arquivos. Os 38 do RTK caem no `useCallback` nulo de `node_modules/node_modules/react` via zustand, o mesmo React duplicado desta worktree. Onde dois PRs mexiam no mesmo save, o board ficou com os dois comportamentos: escrita parcial de engine por id e a fila/resync do PR que entrou depois.

O hash de `.i18n-state.json` do guia de quality-gates entra no follow-up, fora do squash.

⚠️ base-red inherited: #15306 (timeouts de suíte no tip, não neste diff).

Obrigado, @woodsonl.
2026-10-06 18:34:57 -03:00

257 lines
8.8 KiB
TypeScript

// ENVIRONMENT NOTE (sandbox better-sqlite3 / glibc limitation, not a code defect):
// This test constructs or exercises a real better-sqlite3-backed SQLite database.
// better-sqlite3 is a native addon; production and CI load it normally, but some
// sandboxes/dev boxes ship a system glibc older than the prebuilt binary requires
// ("GLIBC_2.29 not found"), so the native module fails to dlopen and any test that
// reaches better-sqlite3 directly (or asserts stdout that the load-failure warning
// would pollute) fails HERE while passing in CI. This is a known environment
// limitation, not a defect in the code under test: the OmniRoute runtime itself
// cascades to node:sqlite/sql.js when better-sqlite3 is unavailable. See
// tests/unit/_helpers/betterSqlite3Availability.ts for a guard helper.
import test, { mock } from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import Database from "better-sqlite3";
import { bootstrapEnv } from "../../scripts/build/bootstrap-env.mjs";
function withTempEnv(fn) {
const originalCwd = process.cwd();
const originalEnv = { ...process.env };
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-bootstrap-test-"));
const tempCwd = path.join(tempRoot, "cwd");
const tempHome = path.join(tempRoot, "home");
fs.mkdirSync(tempCwd, { recursive: true });
fs.mkdirSync(tempHome, { recursive: true });
delete process.env.DATA_DIR;
delete process.env.XDG_CONFIG_HOME;
delete process.env.APPDATA;
delete process.env.JWT_SECRET;
delete process.env.STORAGE_ENCRYPTION_KEY;
delete process.env.STORAGE_ENCRYPTION_KEY_VERSION;
delete process.env.API_KEY_SECRET;
delete process.env.INITIAL_PASSWORD;
process.env.HOME = tempHome;
process.chdir(tempCwd);
try {
fn({ tempRoot, tempCwd, tempHome, dataDir: path.join(tempHome, ".omniroute") });
} finally {
process.chdir(originalCwd);
for (const key of Object.keys(process.env)) {
if (!(key in originalEnv)) delete process.env[key];
}
for (const [key, value] of Object.entries(originalEnv)) {
process.env[key] = value;
}
fs.rmSync(tempRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
}
}
function captureStderr(fn) {
const write = mock.method(process.stderr, "write", () => true);
try {
fn();
} finally {
write.mock.restore();
}
return write.mock.calls.map((call) => String(call.arguments[0])).join("");
}
test("bootstrapEnv prefers ~/.omniroute/.env over server.env", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
fs.mkdirSync(dataDir, { recursive: true });
fs.writeFileSync(
path.join(dataDir, ".env"),
"STORAGE_ENCRYPTION_KEY=from-dot-env\nJWT_SECRET=jwt-from-dot-env\n",
"utf8"
);
fs.writeFileSync(
path.join(dataDir, "server.env"),
"STORAGE_ENCRYPTION_KEY=from-server-env\nJWT_SECRET=jwt-from-server-env\n",
"utf8"
);
const env = bootstrapEnv({ quiet: true });
assert.equal(env.STORAGE_ENCRYPTION_KEY, "from-dot-env");
assert.equal(env.JWT_SECRET, "jwt-from-dot-env");
});
});
test("bootstrapEnv strips matching quotes from env values", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
fs.mkdirSync(dataDir, { recursive: true });
fs.writeFileSync(
path.join(dataDir, "server.env"),
'JWT_SECRET="jwt-from-server-env"\nCLAUDE_USER_AGENT="claude-cli/2.1.219 (external, cli)"\n',
"utf8"
);
const env = bootstrapEnv({ quiet: true });
assert.equal(env.JWT_SECRET, "jwt-from-server-env");
assert.equal(env.CLAUDE_USER_AGENT, "claude-cli/2.1.219 (external, cli)");
});
});
test("bootstrapEnv refuses to generate a new key over encrypted data", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
fs.mkdirSync(dataDir, { recursive: true });
const db = new Database(path.join(dataDir, "storage.sqlite"));
try {
db.exec(`
CREATE TABLE provider_connections (
id TEXT PRIMARY KEY,
access_token TEXT,
refresh_token TEXT,
api_key TEXT,
id_token TEXT
);
`);
db.prepare("INSERT INTO provider_connections (id, access_token) VALUES (?, ?)").run(
"conn-1",
"enc:v1:deadbeef:feedface:cafebabe"
);
} finally {
db.close();
}
assert.throws(
() => bootstrapEnv({ quiet: true }),
/Refusing to auto-generate STORAGE_ENCRYPTION_KEY/
);
});
});
test("bootstrapEnv fails closed when existing database cannot be inspected", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
fs.mkdirSync(path.join(dataDir, "storage.sqlite"), { recursive: true });
assert.throws(() => bootstrapEnv({ quiet: true }), /Unable to inspect existing database/);
});
});
test("bootstrapEnv ignores blank process.env values that would override persisted secrets (#6824)", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
fs.mkdirSync(dataDir, { recursive: true });
// Persisted secrets in server.env
fs.writeFileSync(
path.join(dataDir, "server.env"),
"STORAGE_ENCRYPTION_KEY=persisted-key\nJWT_SECRET=persisted-jwt\n",
"utf8"
);
// Simulate Docker `-e STORAGE_ENCRYPTION_KEY=` — sets an empty string
process.env.STORAGE_ENCRYPTION_KEY = "";
process.env.JWT_SECRET = "";
const env = bootstrapEnv({ quiet: true });
// Empty process.env values must NOT override persisted secrets
assert.equal(env.STORAGE_ENCRYPTION_KEY, "persisted-key");
assert.equal(env.JWT_SECRET, "persisted-jwt");
});
});
test("bootstrapEnv ignores blank dataDirOverride values", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
fs.mkdirSync(dataDir, { recursive: true });
fs.writeFileSync(path.join(dataDir, ".env"), "JWT_SECRET=jwt-from-dot-env\n", "utf8");
const env = bootstrapEnv({ dataDirOverride: " ", quiet: true });
assert.equal(env.JWT_SECRET, "jwt-from-dot-env");
});
});
test("bootstrapEnv does not claim a CHANGEME default when INITIAL_PASSWORD is unset", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
const output = captureStderr(() => bootstrapEnv());
assert.match(output, /INITIAL_PASSWORD is unset here/);
assert.match(output, /onboarding wizard/);
assert.doesNotMatch(output, /CHANGEME/);
});
});
test("bootstrapEnv warns that the CHANGEME placeholder becomes the password", () => {
withTempEnv(({ tempCwd, dataDir }) => {
process.env.DATA_DIR = dataDir;
fs.writeFileSync(path.join(tempCwd, ".env"), "INITIAL_PASSWORD=CHANGEME\n", "utf8");
const output = captureStderr(() => bootstrapEnv());
assert.match(output, /placeholder 'CHANGEME', a publicly known/);
assert.match(output, /In Docker, do it before the/);
assert.match(output, /reset-password/);
assert.doesNotMatch(output, /is unset here/);
});
});
test("bootstrapEnv warns on placeholder spellings that differ by case or surrounding whitespace", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
process.env.INITIAL_PASSWORD = " changeme ";
const output = captureStderr(() => bootstrapEnv());
assert.match(output, /placeholder 'CHANGEME', a publicly known/);
assert.doesNotMatch(output, /is unset here/);
assert.doesNotMatch(output, /only whitespace/);
});
});
test("bootstrapEnv leaves ./.env to Next.js when another .env is preferred", () => {
// When another .env is preferred, bootstrap never reads ./.env, so a directory there
// cannot stop startup on this path.
withTempEnv(({ tempCwd, dataDir }) => {
process.env.DATA_DIR = dataDir;
fs.mkdirSync(dataDir, { recursive: true });
fs.writeFileSync(path.join(dataDir, ".env"), "JWT_SECRET=jwt-from-dot-env\n", "utf8");
fs.mkdirSync(path.join(tempCwd, ".env"));
const output = captureStderr(() => bootstrapEnv());
assert.match(output, /INITIAL_PASSWORD is unset here/);
assert.doesNotMatch(output, /CHANGEME/);
});
});
test("bootstrapEnv warns that a whitespace-only INITIAL_PASSWORD becomes the password", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
process.env.INITIAL_PASSWORD = " ";
const output = captureStderr(() => bootstrapEnv());
assert.match(output, /only whitespace/);
assert.match(output, /works from any address/);
assert.match(output, /Set a real/);
assert.doesNotMatch(output, /CHANGEME|is unset here/);
});
});
test("bootstrapEnv prints no password warning for a real INITIAL_PASSWORD", () => {
withTempEnv(({ dataDir }) => {
process.env.DATA_DIR = dataDir;
process.env.INITIAL_PASSWORD = "a-strong-unique-password";
const output = captureStderr(() => bootstrapEnv());
assert.doesNotMatch(output, /INITIAL_PASSWORD/);
});
});