Files
cv-site/internal/handlers/test_helpers_test.go
T
juanatsap f5276431ea feat: add AI chat widget powered by ADK Go 1.0
Visitors can ask questions about the CV via a floating chat panel.
The agent uses Gemini to answer questions about experience, projects,
skills, and education by querying the cached CV JSON data.

- internal/chat/agent.go: LLM agent with query_cv tool that searches
  CV data by section (experience, projects, skills, etc.) with keyword filtering
- internal/chat/handler.go: POST /api/chat endpoint with session management,
  graceful degradation when GOOGLE_API_KEY is not set
- chat-widget.html: HTMX-powered floating chat panel with Hyperscript toggle
- _chat.css: Responsive chat UI with dark theme support
- Wired into existing architecture via dependency injection (CVHandler,
  routes, main.go) — zero breaking changes, all existing tests pass
2026-04-08 00:20:48 +01:00

100 lines
2.7 KiB
Go

package handlers
import (
"os"
"path/filepath"
"runtime"
"testing"
"github.com/juanatsap/cv-site/internal/cache"
"github.com/juanatsap/cv-site/internal/config"
"github.com/juanatsap/cv-site/internal/email"
"github.com/juanatsap/cv-site/internal/templates"
)
// chDirToProjectRoot changes the working directory to the project root.
// This is needed for tests that load files using paths relative to project root.
// Returns a cleanup function that restores the original directory.
func chDirToProjectRoot(t testing.TB) func() {
t.Helper()
// Get the directory of this test file
_, filename, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("Failed to get current file path")
}
// Navigate from internal/handlers to project root (../../)
projectRoot := filepath.Join(filepath.Dir(filename), "..", "..")
projectRoot, err := filepath.Abs(projectRoot)
if err != nil {
t.Fatalf("Failed to get absolute path: %v", err)
}
// Save current directory
originalDir, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current directory: %v", err)
}
// Change to project root
if err := os.Chdir(projectRoot); err != nil {
t.Fatalf("Failed to change to project root: %v", err)
}
// Return cleanup function
return func() {
if err := os.Chdir(originalDir); err != nil {
t.Errorf("Failed to restore directory: %v", err)
}
}
}
// testCache is a shared cache instance for all tests
var testCache *cache.DataCache
// getTestCache returns a shared cache instance, initializing it once
func getTestCache(t testing.TB) *cache.DataCache {
t.Helper()
if testCache != nil {
return testCache
}
var err error
testCache, err = cache.New([]string{"en", "es"})
if err != nil {
t.Fatalf("Failed to create test cache: %v", err)
}
return testCache
}
// newTestCVHandler creates a CVHandler for testing with all required dependencies
// If fromProjectRoot is true, uses paths relative to project root; otherwise uses relative paths from handlers dir
func newTestCVHandler(t testing.TB, serverAddr string, emailService *email.Service) *CVHandler {
t.Helper()
// Determine if we're running from project root by checking if templates/ exists
templatesDir := "templates"
partialsDir := "templates/partials"
// If templates doesn't exist at current dir, try relative path from handlers
if _, err := os.Stat(templatesDir); os.IsNotExist(err) {
templatesDir = "../../templates"
partialsDir = "../../templates/partials"
}
cfg := &config.TemplateConfig{
Dir: templatesDir,
PartialsDir: partialsDir,
HotReload: false,
}
tmplManager, err := templates.NewManager(cfg)
if err != nil {
t.Fatalf("Failed to create template manager: %v", err)
}
dataCache := getTestCache(t)
return NewCVHandler(tmplManager, serverAddr, emailService, dataCache, false)
}