f5276431ea
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
100 lines
2.7 KiB
Go
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)
|
|
}
|