rony-chat-bot/internal/portfolio/store_test.go
Victor Hugo Vargas f33708534a feat: bootstrap rony-chat-bot Go module
Initial implementation of the bot:

- cmd/chat-bot: CLI entrypoint (serve, reindex, ask, version)
- internal/agent: LLM provider client + agent runner with RAG injection
- internal/config: YAML config loader (providers, RAG, persona, server)
- internal/i18n: response-language detection (EN/ES)
- internal/persona: persona system prompt assembly from YAML
- internal/portfolio: heading-based chunker + SQLite FTS5 indexer
- internal/server: chi router with /api/chat (SSE), /api/health, /api/info,
  /api/reindex, middleware (RequestID, Logging, CORS, RateLimit)
- internal/streaming: SSE protocol helpers (start, chunk, sources, done, error)
- web/: drop-in vanilla-JS chat widget (no build, no deps) + demo + README
- bench/: reproducible driver benchmark (modernc vs mattn SQLite)
- configs/portfolio-bot.yaml: llama.cpp default provider, SQLite RAG, canine persona
- docs/architecture.md / .es.md: aligned with SQLite FTS5 + llama.cpp decisions
- data/projects/README*.md: project data documentation
- README.md / .es.md: updated for current implementation

All tests pass (go test ./...). Bot is functional end-to-end with the
configured LLM provider.
2026-07-17 00:56:06 -07:00

116 lines
No EOL
3 KiB
Go

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] + "..."
}