diff --git a/CLAUDE.md b/CLAUDE.md index c3f8a3fb..d515a1fd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,6 +24,9 @@ marketplace. - **E2E tests**: `go test -tags e2e ./...` (requires Ollama/LM Studio running) - **All tests must pass before commit** - Coverage tracked but not enforced +- **TDD required**: All features and bug fixes must follow red/green TDD — write + a failing test that demonstrates the bug or specifies the feature first, then + make it pass with the implementation. ### Linting & Errors diff --git a/cmd/search.go b/cmd/search.go index 1a64f457..b73f807d 100644 --- a/cmd/search.go +++ b/cmd/search.go @@ -151,7 +151,11 @@ func runSearch(cmd *cobra.Command, args []string) error { tr.record("path resolution", indexRoot) // Span 2: indexer setup - idx, err := setupIndexer(&cfg, indexRoot, nil) + dbPath := config.DBPathForProject(indexRoot, cfg.Model) + if err := os.MkdirAll(filepath.Dir(dbPath), 0o755); err != nil { + return fmt.Errorf("create db directory: %w", err) + } + idx, err := setupIndexer(&cfg, dbPath, nil) if err != nil { return fmt.Errorf("setup indexer: %w", err) } diff --git a/cmd/search_test.go b/cmd/search_test.go index a79b6a49..4c319671 100644 --- a/cmd/search_test.go +++ b/cmd/search_test.go @@ -2,9 +2,13 @@ package cmd import ( "bytes" + "os" + "path/filepath" "strings" "testing" "time" + + "github.com/ory/lumen/internal/config" ) func TestTracer_DisabledIsNoop(t *testing.T) { @@ -108,6 +112,43 @@ func TestSearchCmd_FlagsRegistered(t *testing.T) { } } +// TestSetupIndexer_DBPathVsDirectory is a regression test for +// https://github.com/ory/lumen/issues/72. +// +// runSearch previously passed the raw indexRoot directory to setupIndexer +// instead of the computed DB file path. SQLite cannot open a directory and +// returns an error containing "PRAGMA journal_mode=WAL". +// +// Red: passing the directory directly must fail with a SQLite error. +// Green: passing config.DBPathForProject(dir, model) must succeed. +func TestSetupIndexer_DBPathVsDirectory(t *testing.T) { + dir := t.TempDir() + cfg, err := config.Load() + if err != nil { + t.Fatalf("load config: %v", err) + } + + // RED: directory path → SQLite PRAGMA error (the pre-fix behaviour). + _, dirErr := setupIndexer(&cfg, dir, nil) + if dirErr == nil { + t.Fatal("expected error when passing a directory as the db path, got nil") + } + if !strings.Contains(dirErr.Error(), "PRAGMA") && !strings.Contains(dirErr.Error(), "unable to open") { + t.Fatalf("expected SQLite open error, got: %v", dirErr) + } + + // GREEN: proper db file path → no error. + dbPath := config.DBPathForProject(dir, cfg.Model) + if mkErr := os.MkdirAll(filepath.Dir(dbPath), 0o755); mkErr != nil { + t.Fatalf("MkdirAll: %v", mkErr) + } + idx, err := setupIndexer(&cfg, dbPath, nil) + if err != nil { + t.Fatalf("setupIndexer with db path failed: %v", err) + } + _ = idx.Close() +} + func TestSearchCmd_TraceSpanLabels(t *testing.T) { // Verify the trace span labels that runSearch records match the spec. tr := &tracer{enabled: true}