test(electrobun): settings-db unit tests (partial, agents still running)
This commit is contained in:
parent
54aad5f383
commit
e73aeb4aaf
1 changed files with 235 additions and 0 deletions
235
ui-electrobun/src/bun/__tests__/settings-db.test.ts
Normal file
235
ui-electrobun/src/bun/__tests__/settings-db.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue