test(electrobun): settings-db unit tests (partial, agents still running)

This commit is contained in:
Hibryda 2026-03-22 04:58:37 +01:00
parent 54aad5f383
commit e73aeb4aaf

View file

@ -0,0 +1,235 @@
/**
* Unit tests for SettingsDb in-memory SQLite, no filesystem side effects.
* We cannot import SettingsDb directly (singleton hits filesystem),
* so we replicate the schema on an in-memory Database and exercise the SQL logic.
*/
import { describe, it, expect, beforeEach } from "bun:test";
import { Database } from "bun:sqlite";
// ── Schema (mirrors settings-db.ts) ─────────────────────────────────────────
const SCHEMA = `
CREATE TABLE IF NOT EXISTS schema_version (version INTEGER NOT NULL);
CREATE TABLE IF NOT EXISTS settings (key TEXT PRIMARY KEY, value TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS projects (id TEXT PRIMARY KEY, config TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS custom_themes (
id TEXT PRIMARY KEY, name TEXT NOT NULL, palette TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS groups (
id TEXT PRIMARY KEY, name TEXT NOT NULL, icon TEXT NOT NULL, position INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS keybindings (id TEXT PRIMARY KEY, chord TEXT NOT NULL);
`;
const SEED_GROUPS = `
INSERT OR IGNORE INTO groups VALUES ('dev', 'Development', 'wrench', 0);
INSERT OR IGNORE INTO groups VALUES ('test', 'Testing', 'flask', 1);
INSERT OR IGNORE INTO groups VALUES ('ops', 'DevOps', 'rocket', 2);
INSERT OR IGNORE INTO groups VALUES ('research', 'Research', 'scope', 3);
`;
function createDb(): Database {
const db = new Database(":memory:");
db.exec(SCHEMA);
db.exec(SEED_GROUPS);
return db;
}
// ── Helpers mirroring SettingsDb methods ─────────────────────────────────────
function getSetting(db: Database, key: string): string | null {
const row = db.query<{ value: string }, [string]>(
"SELECT value FROM settings WHERE key = ?"
).get(key);
return row?.value ?? null;
}
function setSetting(db: Database, key: string, value: string): void {
db.query(
"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value"
).run(key, value);
}
function getAll(db: Database): Record<string, string> {
const rows = db.query<{ key: string; value: string }, []>(
"SELECT key, value FROM settings"
).all();
return Object.fromEntries(rows.map((r) => [r.key, r.value]));
}
// ── Tests ───────────────────────────────────────────────────────────────────
describe("SettingsDb", () => {
let db: Database;
beforeEach(() => {
db = createDb();
});
// ── Settings CRUD ───────────────────────────────────────────────────────
describe("getSetting / setSetting", () => {
it("returns null for missing key", () => {
expect(getSetting(db, "nonexistent")).toBeNull();
});
it("stores and retrieves a setting", () => {
setSetting(db, "theme", "mocha");
expect(getSetting(db, "theme")).toBe("mocha");
});
it("upserts on conflict", () => {
setSetting(db, "theme", "mocha");
setSetting(db, "theme", "latte");
expect(getSetting(db, "theme")).toBe("latte");
});
});
describe("getAll", () => {
it("returns empty object when no settings exist", () => {
expect(getAll(db)).toEqual({});
});
it("returns all key-value pairs", () => {
setSetting(db, "a", "1");
setSetting(db, "b", "2");
expect(getAll(db)).toEqual({ a: "1", b: "2" });
});
});
// ── Projects CRUD ──────────────────────────────────────────────────────
describe("projects", () => {
it("setProject + getProject round-trip", () => {
const config = { id: "p1", name: "Test", cwd: "/tmp" };
db.query(
"INSERT INTO projects (id, config) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET config = excluded.config"
).run("p1", JSON.stringify(config));
const row = db.query<{ config: string }, [string]>(
"SELECT config FROM projects WHERE id = ?"
).get("p1");
expect(JSON.parse(row!.config)).toEqual(config);
});
it("listProjects returns all", () => {
db.query("INSERT INTO projects VALUES (?, ?)").run("a", JSON.stringify({ id: "a" }));
db.query("INSERT INTO projects VALUES (?, ?)").run("b", JSON.stringify({ id: "b" }));
const rows = db.query<{ config: string }, []>("SELECT config FROM projects").all();
expect(rows).toHaveLength(2);
});
it("deleteProject removes entry", () => {
db.query("INSERT INTO projects VALUES (?, ?)").run("x", JSON.stringify({ id: "x" }));
db.query("DELETE FROM projects WHERE id = ?").run("x");
const row = db.query<{ config: string }, [string]>(
"SELECT config FROM projects WHERE id = ?"
).get("x");
expect(row).toBeNull();
});
});
// ── Groups CRUD ────────────────────────────────────────────────────────
describe("groups", () => {
it("seeds 4 default groups", () => {
const rows = db.query<{ id: string }, []>(
"SELECT id FROM groups ORDER BY position"
).all();
expect(rows.map((r) => r.id)).toEqual(["dev", "test", "ops", "research"]);
});
it("createGroup upserts", () => {
db.query(
"INSERT INTO groups (id, name, icon, position) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name"
).run("custom", "Custom", "star", 10);
const row = db.query<{ name: string }, [string]>(
"SELECT name FROM groups WHERE id = ?"
).get("custom");
expect(row!.name).toBe("Custom");
});
it("deleteGroup removes entry", () => {
db.query("DELETE FROM groups WHERE id = ?").run("ops");
const rows = db.query<{ id: string }, []>("SELECT id FROM groups").all();
expect(rows.map((r) => r.id)).not.toContain("ops");
});
});
// ── Custom Themes CRUD ─────────────────────────────────────────────────
describe("custom themes", () => {
it("save and retrieve theme", () => {
const palette = { base: "#1e1e2e", text: "#cdd6f4" };
db.query(
"INSERT INTO custom_themes (id, name, palette) VALUES (?, ?, ?) ON CONFLICT(id) DO UPDATE SET palette = excluded.palette"
).run("my-theme", "My Theme", JSON.stringify(palette));
const row = db.query<{ palette: string }, [string]>(
"SELECT palette FROM custom_themes WHERE id = ?"
).get("my-theme");
expect(JSON.parse(row!.palette)).toEqual(palette);
});
it("deleteCustomTheme removes entry", () => {
db.query("INSERT INTO custom_themes VALUES (?, ?, ?)").run("t1", "T1", "{}");
db.query("DELETE FROM custom_themes WHERE id = ?").run("t1");
const row = db.query<{ id: string }, [string]>(
"SELECT id FROM custom_themes WHERE id = ?"
).get("t1");
expect(row).toBeNull();
});
});
// ── Keybindings CRUD ───────────────────────────────────────────────────
describe("keybindings", () => {
it("set and retrieve keybinding", () => {
db.query(
"INSERT INTO keybindings (id, chord) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET chord = excluded.chord"
).run("toggle-sidebar", "Ctrl+B");
const rows = db.query<{ id: string; chord: string }, []>(
"SELECT id, chord FROM keybindings"
).all();
expect(Object.fromEntries(rows.map((r) => [r.id, r.chord]))).toEqual({
"toggle-sidebar": "Ctrl+B",
});
});
it("deleteKeybinding removes entry", () => {
db.query("INSERT INTO keybindings VALUES (?, ?)").run("k1", "Ctrl+K");
db.query("DELETE FROM keybindings WHERE id = ?").run("k1");
const row = db.query<{ id: string }, [string]>(
"SELECT id FROM keybindings WHERE id = ?"
).get("k1");
expect(row).toBeNull();
});
});
// ── Schema version tracking ────────────────────────────────────────────
describe("schema_version", () => {
it("starts empty and can be seeded", () => {
const row = db.query<{ version: number }, []>(
"SELECT version FROM schema_version LIMIT 1"
).get();
expect(row).toBeNull(); // not yet inserted
db.exec("INSERT INTO schema_version (version) VALUES (1)");
const after = db.query<{ version: number }, []>(
"SELECT version FROM schema_version LIMIT 1"
).get();
expect(after!.version).toBe(1);
});
it("updates version", () => {
db.exec("INSERT INTO schema_version (version) VALUES (1)");
db.exec("UPDATE schema_version SET version = 2");
const row = db.query<{ version: number }, []>(
"SELECT version FROM schema_version LIMIT 1"
).get();
expect(row!.version).toBe(2);
});
});
});