mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 02:42:24 +03:00
* fix(release): the packaged-app smoke verifies the database opened, not a driver line the primary path never prints (release/v3.8.51 twin of #12032) Same change as #12032 on main: the packaged app opens SQLite during the smoke but its primary open path prints no "[DB] Driver: …" line (only the recovery path and the sql.js fallback do), so the #7592 assertion failed every Linux release leg. The guard rejects the sql.js fallback line, accepts a native driver line, and otherwise accepts demonstrable database activity; after readiness the smoke requests /api/monitoring/health and waits for that activity outside the readiness loop. electron-smoke-script suite 10/10. * fix(release): reapply the smoke rework on top of release/v3.8.51's own copy of the script The previous commit copied main's file wholesale and dropped this branch's ensureSmokeEnvDirs(currentPlatform) fix and its tests; this reapplies only the DB-open evidence change as a patch. electron-smoke-script suite green.
195 lines
7.4 KiB
TypeScript
195 lines
7.4 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
assertNativeDriverSelected,
|
|
buildSmokeEnv,
|
|
ensureSmokeEnvDirs,
|
|
FATAL_LOG_PATTERNS,
|
|
LINUX_EXECUTABLE_NAMES,
|
|
stopApp,
|
|
waitForDatabaseOpen,
|
|
DB_TOUCH_PATH,
|
|
} from "../../scripts/dev/smoke-electron-packaged.mjs";
|
|
import { tarPack } from "../../scripts/build/optionalPackStaging.mjs";
|
|
|
|
test("electron smoke discovers the default Linux executable name", () => {
|
|
assert.ok(LINUX_EXECUTABLE_NAMES.includes("omniroute-desktop"));
|
|
});
|
|
|
|
test("electron smoke env allowlists runtime variables and drops secrets", () => {
|
|
const dataDir = path.join("/tmp", "omniroute-electron-smoke-test");
|
|
const env = buildSmokeEnv({
|
|
currentPlatform: "linux",
|
|
dataDir,
|
|
parentEnv: {
|
|
DISPLAY: ":99",
|
|
GITHUB_TOKEN: "should-not-leak",
|
|
PATH: "/usr/bin",
|
|
SNYK_TOKEN: "should-not-leak",
|
|
},
|
|
});
|
|
|
|
// Expected values are built with path.join so the assertions hold on every
|
|
// host platform: buildSmokeEnv() composes its redirected paths with join(),
|
|
// which yields backslashes on Windows even when currentPlatform is "linux".
|
|
assert.equal(env.DATA_DIR, dataDir);
|
|
assert.equal(env.DISPLAY, ":99");
|
|
assert.equal(env.PATH, "/usr/bin");
|
|
assert.equal(env.HOME, path.join(dataDir, "home"));
|
|
assert.equal(env.XDG_CONFIG_HOME, path.join(dataDir, "config"));
|
|
assert.equal(env.ELECTRON_ENABLE_LOGGING, "1");
|
|
assert.equal(env.ELECTRON_ENABLE_STACK_DUMPING, "1");
|
|
assert.equal(env.GITHUB_TOKEN, undefined);
|
|
assert.equal(env.SNYK_TOKEN, undefined);
|
|
});
|
|
|
|
test("electron smoke pre-creates the USERPROFILE-derived Roaming userData tree on Windows", async () => {
|
|
// #7592: Electron resolves userData from %USERPROFILE%\AppData\Roaming\<name>
|
|
// (USERPROFILE takes precedence over the APPDATA env var) and the path
|
|
// service throws — rather than creates — when the directory is missing, so
|
|
// requestSingleInstanceLock() returns false and the app exits(0) silently.
|
|
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-smoke-env-test-"));
|
|
try {
|
|
const smokeEnv = buildSmokeEnv({ currentPlatform: "win32", dataDir });
|
|
// Inject the smoke TARGET platform: ensureSmokeEnvDirs must key its win32
|
|
// userData-tree branches off the target, not the host OS — otherwise this
|
|
// guard can never pass on a Linux CI host (the 8/9 red the reviewer hit).
|
|
await ensureSmokeEnvDirs(smokeEnv, dataDir, { currentPlatform: "win32" });
|
|
|
|
for (const appName of ["omniroute-desktop", "OmniRoute", "omniroute"]) {
|
|
const derived = path.join(smokeEnv.USERPROFILE, "AppData", "Roaming", appName);
|
|
assert.ok(fs.existsSync(derived), `expected pre-created derived userData dir: ${derived}`);
|
|
const viaAppData = path.join(smokeEnv.APPDATA, appName);
|
|
assert.ok(fs.existsSync(viaAppData), `expected pre-created APPDATA dir: ${viaAppData}`);
|
|
}
|
|
} finally {
|
|
fs.rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
}
|
|
});
|
|
|
|
test("electron smoke tarPack handles absolute Windows-style tarball paths", () => {
|
|
// GNU tar treats `C:\...` in `-f` as a remote rsh target ("Cannot connect to
|
|
// C:"), which broke optional-pack staging on Windows. tarPack() must pass a
|
|
// bare filename with cwd at the tarball directory instead.
|
|
const staging = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-tar-pack-test-"));
|
|
try {
|
|
const nodeModules = path.join(staging, "pack", "node_modules");
|
|
fs.mkdirSync(path.join(nodeModules, "fixture-pkg"), { recursive: true });
|
|
fs.writeFileSync(path.join(nodeModules, "fixture-pkg", "index.js"), "module.exports = 1;");
|
|
|
|
const tarballPath = path.join(staging, "optional-pack-fixture.tar.gz");
|
|
tarPack(path.join(staging, "pack"), tarballPath);
|
|
|
|
assert.ok(fs.existsSync(tarballPath), "tarball should exist after tarPack");
|
|
assert.ok(fs.statSync(tarballPath).size > 0, "tarball should not be empty");
|
|
} finally {
|
|
fs.rmSync(staging, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
|
|
}
|
|
});
|
|
|
|
test("electron smoke treats Electron process errors as fatal startup logs", () => {
|
|
const logs = [
|
|
"[Electron] Unhandled Rejection: Error: startup failed",
|
|
"[Electron] Uncaught Exception: Error: startup failed",
|
|
];
|
|
|
|
for (const log of logs) {
|
|
assert.ok(
|
|
FATAL_LOG_PATTERNS.some((pattern) => pattern.test(log)),
|
|
`${log} should match a fatal log pattern`
|
|
);
|
|
}
|
|
});
|
|
|
|
test("electron smoke force-terminates the Windows process tree before the parent can exit", async () => {
|
|
const signals: string[] = [];
|
|
const waits: number[] = [];
|
|
const child = {
|
|
pid: 4242,
|
|
exitCode: 0,
|
|
signalCode: null,
|
|
};
|
|
|
|
await stopApp(child, {
|
|
currentPlatform: "win32",
|
|
signalProcessTreeFn: async (_child, signal) => {
|
|
signals.push(signal);
|
|
},
|
|
waitForProcessTreeExitFn: async (_child, timeoutMs) => {
|
|
waits.push(timeoutMs);
|
|
},
|
|
});
|
|
|
|
assert.deepEqual(signals, ["SIGKILL"]);
|
|
assert.deepEqual(waits, [2_000]);
|
|
});
|
|
|
|
// #7592: on a cold restart against an already-persisted DATA_DIR, a stale-ABI
|
|
// better-sqlite3 binary used to fail to load and silently fall through to the
|
|
// sql.js (WASM) driver. These are the regression guards for that assertion.
|
|
test("electron smoke accepts every native SQLite driver on the startup log", () => {
|
|
for (const driver of ["bun:sqlite", "better-sqlite3", "node:sqlite"]) {
|
|
assert.doesNotThrow(() =>
|
|
assertNativeDriverSelected(`[electron] [DB] Driver: ${driver} | file: /data/storage.sqlite`)
|
|
);
|
|
}
|
|
});
|
|
|
|
test("electron smoke flags a cold-restart fallback to the sql.js WASM driver", () => {
|
|
assert.throws(
|
|
() => assertNativeDriverSelected("[electron] [DB] Driver: sql.js | file: /data/storage.sqlite"),
|
|
/fell back to the sql\.js \(WASM\) driver/
|
|
);
|
|
});
|
|
|
|
test("electron smoke flags startup logs missing any driver selection line", () => {
|
|
assert.throws(
|
|
() => assertNativeDriverSelected("[electron] [server] listening on 20128"),
|
|
/no database activity/
|
|
);
|
|
});
|
|
|
|
test("electron smoke waits for database-open evidence after touching a DB-backed endpoint", async () => {
|
|
assert.equal(DB_TOUCH_PATH, "/api/monitoring/health");
|
|
let logs = "[electron] [Server] [STARTUP] ready\n";
|
|
setTimeout(() => {
|
|
logs += "[electron] [Server] [DB] Added usage_history.combo_strategy column\n";
|
|
}, 60);
|
|
const seen = await waitForDatabaseOpen(() => logs, { timeoutMs: 2_000, pollMs: 20 });
|
|
assert.match(seen, /\[DB\] Added/);
|
|
});
|
|
|
|
test("electron smoke fails clearly when the database never opens", async () => {
|
|
await assert.rejects(
|
|
() =>
|
|
waitForDatabaseOpen(() => "[electron] [Server] [STARTUP] ready\n", {
|
|
timeoutMs: 120,
|
|
pollMs: 20,
|
|
}),
|
|
/logged no \[DB\]\/\[Migration\] startup line within 120ms/
|
|
);
|
|
});
|
|
|
|
test("electron smoke driver guard: native line, DB evidence and sql.js fallback", () => {
|
|
assert.doesNotThrow(() =>
|
|
assertNativeDriverSelected("[DB] Driver: better-sqlite3 | file: /tmp/x/storage.sqlite\n")
|
|
);
|
|
assert.doesNotThrow(() =>
|
|
assertNativeDriverSelected(
|
|
"[electron] [Server] [DB] Added call_logs.session_tag column\n[electron] [Server] [Migration] Applied: 046_database_settings\n"
|
|
)
|
|
);
|
|
assert.throws(
|
|
() => assertNativeDriverSelected("[DB] Driver: sql.js | file: /tmp/x/storage.sqlite\n"),
|
|
/sql\.js \(WASM\) driver/
|
|
);
|
|
assert.throws(
|
|
() => assertNativeDriverSelected("[STARTUP] nothing here\n"),
|
|
/no database activity/
|
|
);
|
|
});
|