Move all CREATE TABLE/INDEX DDL from database.ts to schema.sql. database.ts loads and executes schema.sql via db.exec(). Add integration test verifying idempotent schema init. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
97 lines
2.9 KiB
TypeScript
97 lines
2.9 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
import * as fs from "fs";
|
|
import * as os from "os";
|
|
import * as path from "path";
|
|
import { DisclawDatabase } from "../../src/db/database";
|
|
|
|
/**
|
|
* Integration tests for DisclawDatabase schema initialization.
|
|
*
|
|
* Each test uses a fresh SQLite file in os.tmpdir() to ensure full isolation.
|
|
* The DB file is cleaned up after each test.
|
|
*/
|
|
|
|
function makeTmpDbPath(): string {
|
|
return path.join(os.tmpdir(), `disclaw-test-${Date.now()}-${Math.random().toString(36).slice(2)}.db`);
|
|
}
|
|
|
|
describe("db-init: schema initialization", () => {
|
|
let dbPath: string;
|
|
let db: DisclawDatabase;
|
|
|
|
beforeEach(() => {
|
|
dbPath = makeTmpDbPath();
|
|
});
|
|
|
|
afterEach(() => {
|
|
try {
|
|
db.close();
|
|
} catch {
|
|
// ignore if already closed
|
|
}
|
|
try {
|
|
fs.unlinkSync(dbPath);
|
|
} catch {
|
|
// best-effort cleanup
|
|
}
|
|
});
|
|
|
|
// (a) All expected tables exist after initialization
|
|
it("creates all expected tables on first init", () => {
|
|
db = new DisclawDatabase(dbPath);
|
|
|
|
// Query sqlite_master for user-created tables
|
|
const rawDb = (db as unknown as { db: import("better-sqlite3").Database }).db;
|
|
const tables = rawDb
|
|
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name")
|
|
.all() as { name: string }[];
|
|
|
|
const tableNames = tables.map((t) => t.name);
|
|
expect(tableNames).toContain("workspaces");
|
|
expect(tableNames).toContain("conversations");
|
|
});
|
|
|
|
// (b) Initializing the same DB path twice is idempotent (IF NOT EXISTS)
|
|
it("is idempotent when initialized twice (IF NOT EXISTS)", () => {
|
|
db = new DisclawDatabase(dbPath);
|
|
db.close();
|
|
|
|
// Second initialization must not throw
|
|
expect(() => {
|
|
db = new DisclawDatabase(dbPath);
|
|
}).not.toThrow();
|
|
|
|
const rawDb = (db as unknown as { db: import("better-sqlite3").Database }).db;
|
|
const tables = rawDb
|
|
.prepare("SELECT name FROM sqlite_master WHERE type = 'table' ORDER BY name")
|
|
.all() as { name: string }[];
|
|
|
|
const tableNames = tables.map((t) => t.name);
|
|
expect(tableNames).toContain("workspaces");
|
|
expect(tableNames).toContain("conversations");
|
|
});
|
|
|
|
// (c) Basic CRUD: create a workspace and retrieve it by channel_id
|
|
it("supports basic workspace CRUD", () => {
|
|
db = new DisclawDatabase(dbPath);
|
|
|
|
const created = db.createWorkspace(
|
|
"channel-001",
|
|
"guild-001",
|
|
"test-agent",
|
|
"/tmp/test-agent"
|
|
);
|
|
|
|
expect(created.id).toBeGreaterThan(0);
|
|
expect(created.channel_id).toBe("channel-001");
|
|
expect(created.guild_id).toBe("guild-001");
|
|
expect(created.agent_name).toBe("test-agent");
|
|
expect(created.workspace_path).toBe("/tmp/test-agent");
|
|
expect(created.created_at).toBeTruthy();
|
|
|
|
const fetched = db.getWorkspaceByChannelId("channel-001");
|
|
expect(fetched).toBeDefined();
|
|
expect(fetched!.id).toBe(created.id);
|
|
expect(fetched!.agent_name).toBe("test-agent");
|
|
});
|
|
});
|