use crate::memory::db::Store;
use rusqlite::Connection;
fn create_legacy_db(path: &std::path::Path) {
let conn = Connection::open(path).expect("open legacy db");
conn.execute_batch(
r"
CREATE TABLE content (
hash TEXT PRIMARY KEY,
doc TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
collection TEXT NOT NULL,
path TEXT NOT NULL,
title TEXT NOT NULL,
hash TEXT NOT NULL,
created_at TEXT NOT NULL,
modified_at TEXT NOT NULL,
active INTEGER NOT NULL DEFAULT 1,
FOREIGN KEY (hash) REFERENCES content(hash) ON DELETE CASCADE,
UNIQUE(collection, path)
);
CREATE TABLE content_vectors (
hash TEXT NOT NULL,
seq INTEGER NOT NULL DEFAULT 0,
pos INTEGER NOT NULL DEFAULT 0,
model TEXT NOT NULL,
embedded_at TEXT NOT NULL,
PRIMARY KEY (hash, seq)
);
INSERT INTO content (hash, doc, created_at)
VALUES ('dochash', 'doc body', '2026-08-01T00:00:00Z');
INSERT INTO content_vectors (hash, seq, pos, model, embedded_at)
VALUES ('dochash', 0, 0, 'test-model', '2026-08-01T00:00:00Z');
",
)
.expect("create legacy schema");
}
fn has_chunk_hash_column(db_path: &std::path::Path) -> bool {
let conn = Connection::open(db_path).expect("reopen db for schema probe");
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM pragma_table_info('content_vectors')
WHERE name = 'chunk_hash'",
[],
|row| row.get(0),
)
.expect("probe content_vectors schema");
count > 0
}
#[test]
fn heals_legacy_database_and_preserves_rows() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("legacy.db");
create_legacy_db(&db_path);
let store = Store::open(&db_path).expect("open heals the legacy store");
assert!(
has_chunk_hash_column(&db_path),
"chunk_hash column must exist after open"
);
let conn = Connection::open(&db_path).unwrap();
let rows: i64 = conn
.query_row("SELECT COUNT(*) FROM content_vectors", [], |r| r.get(0))
.unwrap();
assert_eq!(rows, 1, "legacy content_vectors rows must be preserved");
drop(conn);
assert!(
store
.chunk_needs_embedding("dochash", 0, "any")
.expect("cache check on healed store"),
"legacy row with NULL chunk_hash must be re-embedded"
);
store
.ensure_vector_table(384)
.expect("vector table on healed store");
store
.insert_embedding(
"dochash",
1,
0,
&[0.0, 1.0],
"test-model",
"2026-08-01T00:00:00Z",
Some("c1"),
)
.expect("insert_embedding on healed store");
assert!(
!store
.chunk_needs_embedding("dochash", 1, "c1")
.expect("cache check after insert"),
"unchanged chunk must be skipped"
);
assert!(
store
.chunk_needs_embedding("dochash", 1, "c2")
.expect("cache check after content change"),
"changed chunk must be re-embedded"
);
}
#[test]
fn heal_is_idempotent_across_reopens() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("legacy.db");
create_legacy_db(&db_path);
Store::open(&db_path).expect("first open heals");
assert!(has_chunk_hash_column(&db_path));
let reopened = Store::open(&db_path).expect("second open must stay clean");
assert!(has_chunk_hash_column(&db_path));
reopened
.chunk_needs_embedding("dochash", 0, "any")
.expect("cache check on reopened store");
}
#[test]
fn fresh_database_has_chunk_hash_from_ddl() {
let dir = tempfile::tempdir().unwrap();
let db_path = dir.path().join("fresh.db");
let store = Store::open(&db_path).expect("open fresh store");
assert!(has_chunk_hash_column(&db_path));
assert!(
store
.chunk_needs_embedding("h", 0, "c")
.expect("cache check on fresh store"),
"no embedding exists yet on a fresh store"
);
}