mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-10-19 05:20:05 +00:00
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.
257 lines
8.8 KiB
TypeScript
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/);
|
|
});
|
|
});
|