Files
2026-07-13 13:39:12 +08:00

877 lines
26 KiB
TypeScript

import test 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 { pathToFileURL } from "node:url";
import Database from "better-sqlite3";
const serial = { concurrency: false };
const originalEnv = {
DATA_DIR: process.env.DATA_DIR,
NEXT_PHASE: process.env.NEXT_PHASE,
HOME: process.env.HOME,
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
APPDATA: process.env.APPDATA,
};
function restoreEnv() {
for (const [key, value] of Object.entries(originalEnv)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
}
function cleanupGlobalDb() {
try {
if (globalThis.__omnirouteDb?.open) {
globalThis.__omnirouteDb.close();
}
} catch {}
delete globalThis.__omnirouteDb;
}
function makeTempDir(prefix) {
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
}
function removePath(targetPath) {
fs.rmSync(targetPath, { recursive: true, force: true });
}
async function importFresh(modulePath) {
const url = pathToFileURL(path.resolve(modulePath)).href;
return import(`${url}?test=${Date.now()}-${Math.random().toString(16).slice(2)}`);
}
async function withEnv(overrides, fn) {
const snapshot = {};
for (const key of Object.keys(overrides)) {
snapshot[key] = process.env[key];
const value = overrides[key];
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
try {
return await fn();
} finally {
for (const [key, value] of Object.entries(snapshot)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value as string;
}
}
}
}
function createLegacySchemaDb(sqliteFile, { withData = false } = {}) {
const seedDb = new Database(sqliteFile);
seedDb.exec(`
CREATE TABLE schema_migrations (version TEXT);
CREATE TABLE provider_connections (
id TEXT PRIMARY KEY,
provider TEXT NOT NULL,
auth_type TEXT,
name TEXT,
email TEXT,
priority INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
access_token TEXT,
refresh_token TEXT,
expires_at TEXT,
token_expires_at TEXT,
scope TEXT,
project_id TEXT,
test_status TEXT,
error_code TEXT,
last_error TEXT,
last_error_at TEXT,
last_error_type TEXT,
last_error_source TEXT,
backoff_level INTEGER DEFAULT 0,
rate_limited_until TEXT,
health_check_interval INTEGER,
last_health_check_at TEXT,
last_tested TEXT,
api_key TEXT,
id_token TEXT,
provider_specific_data TEXT,
expires_in INTEGER,
display_name TEXT,
global_priority INTEGER,
default_model TEXT,
token_type TEXT,
consecutive_use_count INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX idx_pc_provider ON provider_connections(provider);
CREATE INDEX idx_pc_active ON provider_connections(is_active);
CREATE INDEX idx_pc_priority ON provider_connections(provider, priority);
`);
if (withData) {
const now = new Date().toISOString();
seedDb
.prepare(
"INSERT INTO provider_connections (id, provider, auth_type, name, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
)
.run("legacy-openai", "openai", "apikey", "Legacy", 1, now, now);
}
seedDb.close();
}
function createLegacyCallLogsDb(sqliteFile) {
const seedDb = new Database(sqliteFile);
seedDb.exec(`
CREATE TABLE provider_connections (
id TEXT PRIMARY KEY,
provider TEXT NOT NULL,
auth_type TEXT,
name TEXT,
email TEXT,
priority INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
access_token TEXT,
refresh_token TEXT,
expires_at TEXT,
token_expires_at TEXT,
scope TEXT,
project_id TEXT,
test_status TEXT,
error_code TEXT,
last_error TEXT,
last_error_at TEXT,
last_error_type TEXT,
last_error_source TEXT,
backoff_level INTEGER DEFAULT 0,
rate_limited_until TEXT,
health_check_interval INTEGER,
last_health_check_at TEXT,
last_tested TEXT,
api_key TEXT,
id_token TEXT,
provider_specific_data TEXT,
expires_in INTEGER,
display_name TEXT,
global_priority INTEGER,
default_model TEXT,
token_type TEXT,
consecutive_use_count INTEGER DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX idx_pc_provider ON provider_connections(provider);
CREATE INDEX idx_pc_active ON provider_connections(is_active);
CREATE INDEX idx_pc_priority ON provider_connections(provider, priority);
CREATE TABLE call_logs (
id TEXT PRIMARY KEY,
timestamp TEXT NOT NULL,
method TEXT,
path TEXT,
status INTEGER,
model TEXT,
provider TEXT,
account TEXT,
connection_id TEXT,
duration INTEGER DEFAULT 0,
tokens_in INTEGER DEFAULT 0,
tokens_out INTEGER DEFAULT 0,
source_format TEXT,
target_format TEXT,
api_key_id TEXT,
api_key_name TEXT,
combo_name TEXT,
request_body TEXT,
response_body TEXT,
error TEXT
);
CREATE INDEX idx_cl_timestamp ON call_logs(timestamp);
CREATE INDEX idx_cl_status ON call_logs(status);
`);
seedDb.close();
}
function createRecoverableDb(sqliteFile) {
const seedDb = new Database(sqliteFile);
const now = new Date().toISOString();
seedDb.exec(`
CREATE TABLE provider_connections (
id TEXT PRIMARY KEY,
provider TEXT NOT NULL,
auth_type TEXT,
name TEXT,
is_active INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE provider_nodes (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
name TEXT NOT NULL,
prefix TEXT,
api_type TEXT,
base_url TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE key_value (
namespace TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (namespace, key)
);
CREATE TABLE combos (
id TEXT PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
data TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE api_keys (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
key TEXT NOT NULL UNIQUE,
machine_id TEXT,
allowed_models TEXT DEFAULT '[]',
no_log INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL
);
`);
seedDb
.prepare(
"INSERT INTO provider_connections (id, provider, auth_type, name, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
)
.run("recover-openai", "openai", "apikey", "Recover Me", 1, now, now);
seedDb
.prepare(
"INSERT INTO provider_nodes (id, type, name, prefix, api_type, base_url, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.run(
"recover-node",
"custom",
"Recover Node",
"recover",
"openai",
"https://example.com",
now,
now
);
seedDb
.prepare("INSERT INTO key_value (namespace, key, value) VALUES (?, ?, ?)")
.run("settings", "globalFallbackModel", JSON.stringify("openai/gpt-4o-mini"));
seedDb
.prepare("INSERT INTO key_value (namespace, key, value) VALUES (?, ?, ?)")
.run("modelAliases", "fast-default", JSON.stringify("openai/gpt-4o-mini"));
seedDb
.prepare(
"INSERT INTO combos (id, name, data, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)"
)
.run(
"recover-combo",
"Recover Combo",
JSON.stringify({
id: "recover-combo",
name: "Recover Combo",
models: ["openai/gpt-4o-mini"],
}),
1,
now,
now
);
seedDb
.prepare(
"INSERT INTO api_keys (id, name, key, machine_id, allowed_models, no_log, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
)
.run(
"recover-key",
"Recover Key",
"sk-recover-key",
"machine-recover",
JSON.stringify(["openai/gpt-4o-mini"]),
1,
now
);
seedDb.close();
}
function createLegacySchemaDbWithName(sqliteFile, name) {
createLegacySchemaDb(sqliteFile, { withData: true });
const db = new Database(sqliteFile);
db.prepare("UPDATE provider_connections SET name = ? WHERE id = ?").run(name, "legacy-openai");
db.close();
}
function listProbeFailedBackups(sqliteFile) {
const directory = path.dirname(sqliteFile);
const prefix = `${path.basename(sqliteFile)}.probe-failed-`;
return fs
.readdirSync(directory)
.filter((name) => name.startsWith(prefix))
.map((name) => path.join(directory, name))
.sort();
}
test.beforeEach(() => {
restoreEnv();
cleanupGlobalDb();
});
test.afterEach(() => {
cleanupGlobalDb();
restoreEnv();
});
test.after(() => {
cleanupGlobalDb();
restoreEnv();
});
test("getDbInstance creates sqlite schema, metadata and applies migrations", serial, async () => {
const dataDir = makeTempDir("omniroute-db-core-");
try {
await withEnv({ DATA_DIR: dataDir, NEXT_PHASE: undefined }, async () => {
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
assert.equal(fs.existsSync(core.SQLITE_FILE), true);
assert.ok(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("provider_connections")
);
assert.deepEqual(db.prepare("SELECT value FROM db_meta WHERE key = 'schema_version'").get(), {
value: "1",
});
const versions = db
.prepare("SELECT version FROM _omniroute_migrations ORDER BY version")
.all()
.map((row) => row.version);
assert.equal(versions[0], "001");
assert.ok(versions.includes("017"));
assert.ok(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("version_manager")
);
core.resetDbInstance();
});
} finally {
removePath(dataDir);
}
});
test("getDbInstance reuses the singleton and closeDbInstance resets it", serial, async () => {
const dataDir = makeTempDir("omniroute-db-core-");
try {
await withEnv({ DATA_DIR: dataDir, NEXT_PHASE: undefined }, async () => {
const core = await importFresh("src/lib/db/core.ts");
const firstDb = core.getDbInstance();
const secondDb = core.getDbInstance();
assert.strictEqual(secondDb, firstDb);
assert.equal(core.closeDbInstance(), true);
assert.equal(firstDb.open, false);
assert.equal(core.closeDbInstance(), false);
const reopenedDb = core.getDbInstance();
assert.notStrictEqual(reopenedDb, firstDb);
core.resetDbInstance();
});
} finally {
removePath(dataDir);
}
});
test("local sqlite configuration enables WAL and sane pragmas", serial, async () => {
const dataDir = makeTempDir("omniroute-db-core-");
try {
await withEnv({ DATA_DIR: dataDir, NEXT_PHASE: undefined }, async () => {
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
assert.equal(db.pragma("journal_mode", { simple: true }), "wal");
// v3.8.32 intentionally capped busy_timeout at 2s (was 5s) so a contended
// synchronous write cannot park the Node event loop past the host watchdog's
// 6s liveness probe — see src/lib/db/core.ts.
assert.equal(db.pragma("busy_timeout", { simple: true }), 2000);
assert.equal(db.pragma("synchronous", { simple: true }), 1);
assert.equal(core.closeDbInstance({ checkpointMode: null }), true);
});
} finally {
removePath(dataDir);
}
});
test("module exports honor DATA_DIR from the environment", serial, async () => {
const dataDir = makeTempDir("omniroute-db-core-env-");
try {
await withEnv({ DATA_DIR: dataDir }, async () => {
const core = await importFresh("src/lib/db/core.ts");
assert.equal(core.DATA_DIR, path.resolve(dataDir));
assert.equal(core.SQLITE_FILE, path.join(path.resolve(dataDir), "storage.sqlite"));
assert.equal(core.DB_BACKUPS_DIR, path.join(path.resolve(dataDir), "db_backups"));
});
} finally {
removePath(dataDir);
}
});
test(
"module falls back to the default home data directory when DATA_DIR is absent",
serial,
async () => {
const fakeHome = makeTempDir("omniroute-home-");
try {
await withEnv(
{
DATA_DIR: undefined,
XDG_CONFIG_HOME: undefined,
HOME: fakeHome,
USERPROFILE: fakeHome,
APPDATA: undefined,
},
async () => {
const core = await importFresh("src/lib/db/core.ts");
const expectedDir =
process.platform === "win32"
? path.join(fakeHome, "AppData", "Roaming", "omniroute")
: path.join(fakeHome, ".omniroute");
assert.equal(core.DATA_DIR, expectedDir);
assert.equal(core.SQLITE_FILE, path.join(expectedDir, "storage.sqlite"));
}
);
} finally {
removePath(fakeHome);
}
}
);
test("build phase uses an in-memory database without creating sqlite files", serial, async () => {
const dataDir = makeTempDir("omniroute-db-build-");
try {
await withEnv(
{
DATA_DIR: dataDir,
NEXT_PHASE: "phase-production-build",
},
async () => {
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
assert.ok(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("provider_connections")
);
assert.equal(fs.existsSync(path.join(dataDir, "storage.sqlite")), false);
assert.equal(db.pragma("journal_mode", { simple: true }), "memory");
core.resetDbInstance();
}
);
} finally {
removePath(dataDir);
}
});
test("invalid DATA_DIR (a file where a dir is expected) surfaces as a startup failure", serial, async () => {
const sandboxDir = makeTempDir("omniroute-db-bad-path-");
const fileAsDir = path.join(sandboxDir, "not-a-directory");
fs.writeFileSync(fileAsDir, "blocked");
try {
// Since #4767, db/core.ts resolves a writable data dir at module load via
// resolveWritableDataDir() → mkdirSync(recursive). Pointing DATA_DIR at a
// regular file is a non-permission misconfiguration (EEXIST/ENOTDIR), which
// resolveWritableDataDir rethrows by design (only EACCES/EPERM fall back), so
// the failure now surfaces at import time, not lazily from getDbInstance().
let caught: unknown;
await withEnv({ DATA_DIR: fileAsDir }, () => importFresh("src/lib/db/core.ts")).then(
() => {
throw new Error("expected importing db/core with an invalid DATA_DIR to reject");
},
(err) => {
caught = err;
}
);
assert.ok(caught instanceof Error, "an invalid DATA_DIR must surface as a thrown Error");
assert.match(
String((caught as Error).message),
/unable to open database file|ENOTDIR|EEXIST|not a directory|file already exists/i
);
} finally {
removePath(sandboxDir);
}
});
test(
"legacy empty schema databases are renamed before a fresh sqlite database is created",
serial,
async () => {
const dataDir = makeTempDir("omniroute-db-legacy-empty-");
const sqliteFile = path.join(dataDir, "storage.sqlite");
createLegacySchemaDb(sqliteFile);
try {
await withEnv({ DATA_DIR: dataDir }, async () => {
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
assert.equal(fs.existsSync(`${sqliteFile}.old-schema`), true);
assert.ok(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("_omniroute_migrations")
);
assert.equal(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("schema_migrations"),
undefined
);
core.resetDbInstance();
});
} finally {
removePath(dataDir);
}
}
);
test(
"legacy databases with data preserve rows while removing the old migration table",
serial,
async () => {
const dataDir = makeTempDir("omniroute-db-legacy-data-");
const sqliteFile = path.join(dataDir, "storage.sqlite");
createLegacySchemaDb(sqliteFile, { withData: true });
try {
await withEnv({ DATA_DIR: dataDir }, async () => {
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
assert.deepEqual(
db
.prepare("SELECT id, provider FROM provider_connections WHERE id = ?")
.get("legacy-openai"),
{ id: "legacy-openai", provider: "openai" }
);
assert.equal(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?")
.get("schema_migrations"),
undefined
);
assert.ok(
db
.prepare("SELECT name FROM pragma_table_info('provider_connections') WHERE name = ?")
.get("rate_limit_protection")
);
assert.ok(
db
.prepare("SELECT name FROM pragma_table_info('provider_connections') WHERE name = ?")
.get("last_used_at")
);
core.resetDbInstance();
});
} finally {
removePath(dataDir);
}
}
);
test(
"provider connection max_concurrent column is healed even if migration 029 was already recorded",
serial,
async () => {
const dataDir = makeTempDir("omniroute-db-missing-max-concurrent-");
const sqliteFile = path.join(dataDir, "storage.sqlite");
const seedDb = new Database(sqliteFile);
const now = new Date().toISOString();
seedDb.exec(`
CREATE TABLE provider_connections (
id TEXT PRIMARY KEY,
provider TEXT NOT NULL,
auth_type TEXT,
name TEXT,
priority INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE _omniroute_migrations (
version TEXT PRIMARY KEY,
name TEXT NOT NULL,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);
INSERT INTO _omniroute_migrations (version, name) VALUES ('001', 'initial_schema');
INSERT INTO _omniroute_migrations (version, name) VALUES ('029', 'webhooks_templates');
`);
seedDb
.prepare(
"INSERT INTO provider_connections (id, provider, auth_type, name, priority, is_active, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
)
.run("missing-max-openai", "openai", "apikey", "Missing max", 0, 1, now, now);
seedDb.close();
try {
await withEnv({ DATA_DIR: dataDir }, async () => {
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
assert.ok(
db
.prepare("SELECT name FROM pragma_table_info('provider_connections') WHERE name = ?")
.get("max_concurrent")
);
assert.ok(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?")
.get("idx_pc_max_concurrent")
);
db.prepare(
"INSERT INTO provider_connections (id, provider, auth_type, name, priority, is_active, max_concurrent, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
).run("healed-openai", "openai", "apikey", "Healed", 0, 1, 2, now, now);
assert.deepEqual(
db
.prepare(
"SELECT max_concurrent AS maxConcurrent FROM provider_connections WHERE id = ?"
)
.get("healed-openai"),
{ maxConcurrent: 2 }
);
core.resetDbInstance();
});
} finally {
removePath(dataDir);
}
}
);
test(
"legacy call_logs schemas are upgraded before combo target indexes are created",
serial,
async () => {
const dataDir = makeTempDir("omniroute-db-legacy-call-logs-");
const sqliteFile = path.join(dataDir, "storage.sqlite");
createLegacyCallLogsDb(sqliteFile);
try {
await withEnv({ DATA_DIR: dataDir }, async () => {
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
assert.ok(
db
.prepare("SELECT name FROM pragma_table_info('call_logs') WHERE name = ?")
.get("requested_model")
);
assert.ok(
db
.prepare("SELECT name FROM pragma_table_info('call_logs') WHERE name = ?")
.get("request_type")
);
assert.ok(
db
.prepare("SELECT name FROM pragma_table_info('call_logs') WHERE name = ?")
.get("combo_step_id")
);
assert.ok(
db
.prepare("SELECT name FROM pragma_table_info('call_logs') WHERE name = ?")
.get("combo_execution_key")
);
assert.ok(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?")
.get("idx_call_logs_requested_model")
);
assert.ok(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?")
.get("idx_call_logs_request_type")
);
assert.ok(
db
.prepare("SELECT name FROM sqlite_master WHERE type = 'index' AND name = ?")
.get("idx_cl_combo_target")
);
core.resetDbInstance();
});
} finally {
removePath(dataDir);
}
}
);
test(
"probe failures restore preserved critical state instead of booting with an empty database",
serial,
async () => {
const dataDir = makeTempDir("omniroute-db-probe-recover-");
const sqliteFile = path.join(dataDir, "storage.sqlite");
createRecoverableDb(sqliteFile);
const originalPrepare = Database.prototype.prepare;
try {
Database.prototype.prepare = function patchedPrepare(sql, ...args) {
if (String(sql).includes("schema_migrations")) {
throw new Error("forced probe failure");
}
return originalPrepare.call(this, sql, ...args);
};
await withEnv({ DATA_DIR: dataDir }, async () => {
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
assert.deepEqual(
db
.prepare("SELECT id, provider, name FROM provider_connections WHERE id = ?")
.get("recover-openai"),
{ id: "recover-openai", provider: "openai", name: "Recover Me" }
);
assert.deepEqual(
db.prepare("SELECT id, name FROM provider_nodes WHERE id = ?").get("recover-node"),
{ id: "recover-node", name: "Recover Node" }
);
assert.deepEqual(
db.prepare("SELECT id, name FROM combos WHERE id = ?").get("recover-combo"),
{ id: "recover-combo", name: "Recover Combo" }
);
assert.deepEqual(
db.prepare("SELECT id, name, no_log FROM api_keys WHERE id = ?").get("recover-key"),
{ id: "recover-key", name: "Recover Key", no_log: 1 }
);
assert.deepEqual(
db
.prepare("SELECT value FROM key_value WHERE namespace = 'settings' AND key = ?")
.get("globalFallbackModel"),
{ value: JSON.stringify("openai/gpt-4o-mini") }
);
assert.equal(listProbeFailedBackups(sqliteFile).length >= 1, true);
core.resetDbInstance();
});
} finally {
Database.prototype.prepare = originalPrepare;
removePath(dataDir);
}
}
);
test(
"auto-restore picks latest probe-failed timestamp instead of latest mtime",
serial,
async () => {
const dataDir = makeTempDir("omniroute-db-probe-latest-");
const sqliteFile = path.join(dataDir, "storage.sqlite");
const olderBackup = `${sqliteFile}.probe-failed-1000`;
const newerBackup = `${sqliteFile}.probe-failed-2000`;
createLegacySchemaDbWithName(olderBackup, "Older Backup");
createLegacySchemaDbWithName(newerBackup, "Newer Backup");
fs.utimesSync(
olderBackup,
new Date("2030-01-01T00:00:00.000Z"),
new Date("2030-01-01T00:00:00.000Z")
);
fs.utimesSync(
newerBackup,
new Date("2020-01-01T00:00:00.000Z"),
new Date("2020-01-01T00:00:00.000Z")
);
try {
await withEnv({ DATA_DIR: dataDir }, async () => {
const core = await importFresh("src/lib/db/core.ts");
const db = core.getDbInstance();
assert.deepEqual(
db
.prepare("SELECT id, provider, name FROM provider_connections WHERE id = ?")
.get("legacy-openai"),
{ id: "legacy-openai", provider: "openai", name: "Newer Backup" }
);
assert.equal(fs.existsSync(sqliteFile), true);
assert.equal(fs.existsSync(newerBackup), false);
assert.equal(fs.existsSync(olderBackup), true);
core.resetDbInstance();
});
} finally {
removePath(dataDir);
}
}
);
test(
"probe failures without a safe snapshot abort startup and keep manual recovery explicit",
serial,
async () => {
const dataDir = makeTempDir("omniroute-db-probe-abort-");
const sqliteFile = path.join(dataDir, "storage.sqlite");
fs.writeFileSync(sqliteFile, "not-a-valid-sqlite-database");
try {
await withEnv({ DATA_DIR: dataDir }, async () => {
const core = await importFresh("src/lib/db/core.ts");
assert.throws(() => core.getDbInstance(), /Manual recovery required after probe failure/i);
assert.equal(fs.existsSync(sqliteFile), false);
assert.equal(listProbeFailedBackups(sqliteFile).length >= 1, true);
const restartedCore = await importFresh("src/lib/db/core.ts");
assert.throws(
() => restartedCore.getDbInstance(),
/Manual recovery required after probe failure/i
);
assert.equal(fs.existsSync(sqliteFile), false);
core.resetDbInstance();
});
} finally {
removePath(dataDir);
}
}
);