package portfolio import ( "context" "os" "path/filepath" "strings" "testing" ) func TestReindexAndSearch(t *testing.T) { // Use a temp dir so we don't pollute data/projects dir := t.TempDir() srcDir := filepath.Join(dir, "src") if err := os.MkdirAll(srcDir, 0o755); err != nil { t.Fatal(err) } // Copy the real example-project.md data, err := os.ReadFile("../../data/projects/example-project.md") if err != nil { t.Skip("example-project.md not found, skipping integration test:", err) } if err := os.WriteFile(filepath.Join(srcDir, "example-project.md"), data, 0o644); err != nil { t.Fatal(err) } dbPath := filepath.Join(dir, "test.db") store, err := OpenStore(dbPath) if err != nil { t.Fatal(err) } defer store.Close() files, chunks, err := store.Reindex(context.Background(), srcDir, DefaultChunkerConfig()) if err != nil { t.Fatal(err) } if files != 1 { t.Errorf("files = %d, want 1", files) } if chunks < 4 { t.Errorf("chunks = %d, want >= 4 (frontmatter + 4 H2 sections)", chunks) } // Search for something specific to the file hits, err := store.Search(context.Background(), "Go SQLite framework", 5) if err != nil { t.Fatal(err) } if len(hits) == 0 { t.Error("expected at least one hit for 'Go SQLite framework'") } t.Logf("Search hits: %d", len(hits)) for i, h := range hits { t.Logf(" [%d] project=%s section=%s score=%.3f body=%q", i, h.ProjectID, h.Section, h.Score, truncate(h.Content, 80)) } // No results for nonsense hits, err = store.Search(context.Background(), "asdfqwerty12345", 5) if err != nil { t.Fatal(err) } if len(hits) != 0 { t.Errorf("expected 0 hits for nonsense query, got %d", len(hits)) } } func TestReindexFromRealData(t *testing.T) { // Index the real data/projects/ to sanity-check on real data srcDir := "../../data/projects" if _, err := os.Stat(srcDir); err != nil { t.Skip("data/projects not found:", err) } dir := t.TempDir() dbPath := filepath.Join(dir, "real.db") store, err := OpenStore(dbPath) if err != nil { t.Fatal(err) } defer store.Close() files, chunks, err := store.Reindex(context.Background(), srcDir, DefaultChunkerConfig()) if err != nil { t.Fatal(err) } t.Logf("Indexed %d files, %d chunks", files, chunks) if files == 0 { t.Fatal("no files indexed") } // Print what we got sections := map[string]int{} _, _ = store.Search(context.Background(), "rony", 100) // noop // Walk DB to see sections — easier: just re-read with a fresh load _ = sections // Verify queries work for _, q := range []string{"rony-llm-agent", "Qwen", "agent harness", "tech stack", "deploy"} { hits, err := store.Search(context.Background(), q, 3) if err != nil { t.Fatal(err) } t.Logf("query %q → %d hits", q, len(hits)) for _, h := range hits { if !strings.Contains(strings.ToLower(h.Content), strings.ToLower(strings.SplitN(q, "-", 2)[0])) { // FTS5 might do prefix matches; this is a soft assertion } } } } func truncate(s string, n int) string { s = strings.ReplaceAll(s, "\n", " ") if len(s) <= n { return s } return s[:n] + "..." }