package infra import ( "testing" "time" ) // makeTestVaultFile creates a minimal VaultFile for testing. func makeTestVaultFile(relPath, mime, bucket, subBucket string) VaultFile { return VaultFile{ VaultID: "test_vault", VaultName: "test", RelPath: relPath, Size: 100, Mtime: time.Now().Unix(), Sha256: "abc123def456abc123def456abc123def456abc123def456abc123def456abc1", Mime: mime, Ext: ".csv", Bucket: bucket, SubBucket: subBucket, } } func openInMemoryVaultIndex(t *testing.T) interface{ Close() error } { t.Helper() dir := t.TempDir() db, err := VaultIndexOpen(dir) if err != nil { t.Fatalf("VaultIndexOpen: %v", err) } return db } func TestVaultIndexWrite_FreshInsert(t *testing.T) { t.Run("N archivos nuevos — Inserted=N", func(t *testing.T) { dir := t.TempDir() db, err := VaultIndexOpen(dir) if err != nil { t.Fatal(err) } defer db.Close() files := []VaultFile{ makeTestVaultFile("data/raw/a.csv", "text/csv", "data", "raw"), makeTestVaultFile("data/raw/b.csv", "text/csv", "data", "raw"), makeTestVaultFile("knowledge/decisions/x.md", "text/markdown", "knowledge", "decisions"), } report, err := VaultIndexWrite(db, files, false) if err != nil { t.Fatalf("VaultIndexWrite: %v", err) } if report.Inserted != 3 { t.Errorf("Inserted = %d, want 3", report.Inserted) } if report.Updated != 0 { t.Errorf("Updated = %d, want 0", report.Updated) } if report.Pruned != 0 { t.Errorf("Pruned = %d, want 0", report.Pruned) } if report.FTS != 3 { t.Errorf("FTS = %d, want 3", report.FTS) } }) } func TestVaultIndexWrite_Upsert(t *testing.T) { t.Run("re-escritura con mtime distinto — Updated=N", func(t *testing.T) { dir := t.TempDir() db, err := VaultIndexOpen(dir) if err != nil { t.Fatal(err) } defer db.Close() files := []VaultFile{ makeTestVaultFile("data/raw/a.csv", "text/csv", "data", "raw"), makeTestVaultFile("data/raw/b.csv", "text/csv", "data", "raw"), } if _, err := VaultIndexWrite(db, files, false); err != nil { t.Fatalf("first write: %v", err) } // Modify mtime to simulate file change. files[0].Mtime = time.Now().Unix() + 100 files[1].Mtime = time.Now().Unix() + 200 report, err := VaultIndexWrite(db, files, false) if err != nil { t.Fatalf("second write: %v", err) } if report.Inserted != 0 { t.Errorf("Inserted = %d, want 0", report.Inserted) } if report.Updated != 2 { t.Errorf("Updated = %d, want 2", report.Updated) } }) } func TestVaultIndexWrite_Prune(t *testing.T) { t.Run("prune elimina filas ausentes", func(t *testing.T) { dir := t.TempDir() db, err := VaultIndexOpen(dir) if err != nil { t.Fatal(err) } defer db.Close() // Write A and B. ab := []VaultFile{ makeTestVaultFile("data/raw/a.csv", "text/csv", "data", "raw"), makeTestVaultFile("data/raw/b.csv", "text/csv", "data", "raw"), } if _, err := VaultIndexWrite(db, ab, false); err != nil { t.Fatalf("first write: %v", err) } // Write only A with prune=true — B should be deleted. onlyA := []VaultFile{ab[0]} report, err := VaultIndexWrite(db, onlyA, true) if err != nil { t.Fatalf("prune write: %v", err) } if report.Pruned != 1 { t.Errorf("Pruned = %d, want 1", report.Pruned) } // Verify B is gone. var count int err = db.QueryRow(`SELECT count(*) FROM files WHERE rel_path = 'data/raw/b.csv'`).Scan(&count) if err != nil { t.Fatalf("query: %v", err) } if count != 0 { t.Errorf("b.csv still present after prune") } }) } func TestVaultIndexWrite_NoPrune(t *testing.T) { t.Run("sin prune, filas previas persisten", func(t *testing.T) { dir := t.TempDir() db, err := VaultIndexOpen(dir) if err != nil { t.Fatal(err) } defer db.Close() ab := []VaultFile{ makeTestVaultFile("data/raw/a.csv", "text/csv", "data", "raw"), makeTestVaultFile("data/raw/b.csv", "text/csv", "data", "raw"), } if _, err := VaultIndexWrite(db, ab, false); err != nil { t.Fatalf("first write: %v", err) } // Write only A without prune — B must remain. onlyA := []VaultFile{ab[0]} report, err := VaultIndexWrite(db, onlyA, false) if err != nil { t.Fatalf("second write: %v", err) } if report.Pruned != 0 { t.Errorf("Pruned = %d, want 0", report.Pruned) } var count int err = db.QueryRow(`SELECT count(*) FROM files`).Scan(&count) if err != nil { t.Fatalf("query: %v", err) } if count != 2 { t.Errorf("files count = %d, want 2", count) } }) } func TestVaultIndexWrite_FTSMatch(t *testing.T) { t.Run("FTS5 MATCH funciona tras escritura", func(t *testing.T) { dir := t.TempDir() db, err := VaultIndexOpen(dir) if err != nil { t.Fatal(err) } defer db.Close() files := []VaultFile{ makeTestVaultFile("data/raw/foo_report.csv", "text/csv", "data", "raw"), makeTestVaultFile("data/raw/bar_data.csv", "text/csv", "data", "raw"), } if _, err := VaultIndexWrite(db, files, false); err != nil { t.Fatalf("write: %v", err) } // FTS5 on rel_path column: MATCH 'foo*' var count int err = db.QueryRow( `SELECT count(*) FROM files_fts WHERE files_fts MATCH 'rel_path:foo*'`, ).Scan(&count) if err != nil { t.Fatalf("FTS MATCH query: %v", err) } if count != 1 { t.Errorf("FTS MATCH rel_path:foo* = %d rows, want 1", count) } }) }