- Hybrid BM25 + HNSW vector search with context enrichment - Knowledge graph with entities, relations, and community detection - Host-following LLM route with fallback backends - 3-tier lint system (static + HNSW dup + contradiction detection) - Q&A Synthesis (Karpathy LLM Wiki pattern) - 16 MCP tools for agent-driven workflows - Incremental wiki generation with checkpointing - SQLite-backed item store with FTS5 + vector indexes
124 lines
3.8 KiB
TypeScript
124 lines
3.8 KiB
TypeScript
/**
|
|
* Database schema tests for HaBraid.
|
|
*
|
|
* Tests that the full schema migration runs correctly and produces
|
|
* all expected tables, indexes, and triggers.
|
|
*/
|
|
|
|
import Database from "better-sqlite3";
|
|
|
|
import { getSchemaVersion } from "../db/database.js";
|
|
import { createTestDb } from "./setup.js";
|
|
|
|
describe("Database Schema", () => {
|
|
let db: Database.Database;
|
|
|
|
beforeEach(() => {
|
|
db = createTestDb();
|
|
});
|
|
|
|
afterEach(() => {
|
|
db.close();
|
|
});
|
|
|
|
it("should have schema version 7 after migration", () => {
|
|
const version = getSchemaVersion(db);
|
|
expect(version).toBe(7);
|
|
});
|
|
|
|
it("should have all expected tables", () => {
|
|
const tables = db
|
|
.prepare(
|
|
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name",
|
|
)
|
|
.all() as Array<{ name: string }>;
|
|
|
|
const tableNames = tables.map((t) => t.name);
|
|
|
|
expect(tableNames).toContain("items");
|
|
expect(tableNames).toContain("schema_version");
|
|
expect(tableNames).toContain("item_vectors");
|
|
expect(tableNames).toContain("kg_entities");
|
|
expect(tableNames).toContain("kg_relations");
|
|
expect(tableNames).toContain("contradictions");
|
|
expect(tableNames).toContain("kg_communities");
|
|
expect(tableNames).toContain("kg_community_members");
|
|
});
|
|
|
|
it("should have FTS5 virtual table", () => {
|
|
const tables = db
|
|
.prepare(
|
|
"SELECT name FROM sqlite_master WHERE type='table' AND name='items_fts'",
|
|
)
|
|
.all() as Array<{ name: string }>;
|
|
|
|
expect(tables.length).toBe(1);
|
|
expect(tables[0].name).toBe("items_fts");
|
|
});
|
|
|
|
it("should have WAL mode or memory mode", () => {
|
|
// In-memory databases report "memory" instead of "wal"
|
|
const result = db.pragma("journal_mode") as Array<{ journal_mode: string }>;
|
|
expect(["wal", "memory"]).toContain(result[0].journal_mode);
|
|
});
|
|
|
|
it("should have foreign keys enabled", () => {
|
|
const result = db.pragma("foreign_keys") as Array<{ foreign_keys: number }>;
|
|
expect(result[0].foreign_keys).toBe(1);
|
|
});
|
|
|
|
it("should have all expected triggers", () => {
|
|
const triggers = db
|
|
.prepare(
|
|
"SELECT name FROM sqlite_master WHERE type='trigger' ORDER BY name",
|
|
)
|
|
.all() as Array<{ name: string }>;
|
|
|
|
const triggerNames = triggers.map((t) => t.name);
|
|
|
|
expect(triggerNames).toContain("items_ai");
|
|
expect(triggerNames).toContain("items_ad");
|
|
expect(triggerNames).toContain("items_au");
|
|
});
|
|
|
|
it("should have expected indexes", () => {
|
|
const indexes = db
|
|
.prepare(
|
|
"SELECT name FROM sqlite_master WHERE type='index' AND name NOT LIKE 'sqlite_%' ORDER BY name",
|
|
)
|
|
.all() as Array<{ name: string }>;
|
|
|
|
const indexNames = indexes.map((i) => i.name);
|
|
|
|
expect(indexNames).toContain("idx_item_vectors_model");
|
|
expect(indexNames).toContain("idx_kg_relations_subject");
|
|
expect(indexNames).toContain("idx_kg_relations_object");
|
|
expect(indexNames).toContain("idx_kg_entities_type");
|
|
expect(indexNames).toContain("idx_contradictions_status");
|
|
expect(indexNames).toContain("idx_contradictions_item_a");
|
|
expect(indexNames).toContain("idx_contradictions_item_b");
|
|
expect(indexNames).toContain("idx_contradictions_field");
|
|
expect(indexNames).toContain("idx_kg_community_members_entity");
|
|
});
|
|
|
|
it("items table should have content_hash column", () => {
|
|
const cols = db.pragma("table_info(items)") as Array<{
|
|
name: string;
|
|
type: string;
|
|
}>;
|
|
const colNames = cols.map((c) => c.name);
|
|
|
|
expect(colNames).toContain("content_hash");
|
|
});
|
|
|
|
it("contradictions table should have slug columns", () => {
|
|
const cols = db.pragma("table_info(contradictions)") as Array<{
|
|
name: string;
|
|
}>;
|
|
const colNames = cols.map((c) => c.name);
|
|
|
|
expect(colNames).toContain("item_a_slug");
|
|
expect(colNames).toContain("item_b_slug");
|
|
});
|
|
});
|