package handlers import ( "encoding/json" "net/http" "net/http/httptest" "testing" c "github.com/juanatsap/cv-site/internal/constants" ) // TestCmdKData tests the CmdKData handler // NOTE: This test requires running from project root due to data file path resolution // Run with: go test ./internal/handlers/ -run TestCmdKData -v func TestCmdKData(t *testing.T) { // Skip if running in short mode (CI) - requires project root if testing.Short() { t.Skip("Skipping CmdKData test - requires running from project root") } handler := newTestCVHandler(t, "localhost:8080", nil) tests := []struct { name string lang string expectStatus int expectExperiences bool // should have experiences expectProjects bool // should have projects expectCourses bool // should have courses expectMinExp int // minimum expected experiences expectMinProj int // minimum expected projects expectMinCourses int // minimum expected courses }{ { name: "Default language (English)", lang: "", expectStatus: http.StatusOK, expectExperiences: true, expectProjects: true, expectCourses: true, expectMinExp: 5, expectMinProj: 3, expectMinCourses: 2, }, { name: "English language", lang: "en", expectStatus: http.StatusOK, expectExperiences: true, expectProjects: true, expectCourses: true, expectMinExp: 5, expectMinProj: 3, expectMinCourses: 2, }, { name: "Spanish language", lang: "es", expectStatus: http.StatusOK, expectExperiences: true, expectProjects: true, expectCourses: true, expectMinExp: 5, expectMinProj: 3, expectMinCourses: 2, }, { name: "Invalid language defaults to English", lang: "fr", expectStatus: http.StatusOK, expectExperiences: true, expectProjects: true, expectCourses: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Build query string query := "/api/cmd-k" if tt.lang != "" { query += "?lang=" + tt.lang } req := httptest.NewRequest(http.MethodGet, query, nil) rec := httptest.NewRecorder() handler.CmdKData(rec, req) // Check status code if rec.Code != tt.expectStatus { t.Errorf("Expected status %d, got %d", tt.expectStatus, rec.Code) } // If success, validate JSON response if rec.Code == http.StatusOK { // Check content type contentType := rec.Header().Get(c.HeaderContentType) if contentType != c.ContentTypeJSON { t.Errorf("Expected Content-Type %s, got %s", c.ContentTypeJSON, contentType) } // Parse JSON response var response CmdKResponse if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil { t.Fatalf("Failed to parse JSON response: %v", err) } // Validate experiences if tt.expectExperiences && len(response.Experiences) == 0 { t.Error("Expected experiences but got none") } if tt.expectMinExp > 0 && len(response.Experiences) < tt.expectMinExp { t.Errorf("Expected at least %d experiences, got %d", tt.expectMinExp, len(response.Experiences)) } // Validate projects if tt.expectProjects && len(response.Projects) == 0 { t.Error("Expected projects but got none") } if tt.expectMinProj > 0 && len(response.Projects) < tt.expectMinProj { t.Errorf("Expected at least %d projects, got %d", tt.expectMinProj, len(response.Projects)) } // Validate courses if tt.expectCourses && len(response.Courses) == 0 { t.Error("Expected courses but got none") } if tt.expectMinCourses > 0 && len(response.Courses) < tt.expectMinCourses { t.Errorf("Expected at least %d courses, got %d", tt.expectMinCourses, len(response.Courses)) } // Validate structure of first experience (if present) if len(response.Experiences) > 0 { exp := response.Experiences[0] if exp.ID == "" { t.Error("Experience ID should not be empty") } if exp.Title == "" { t.Error("Experience Title should not be empty") } if exp.Section != "Experience" { t.Errorf("Experience Section should be 'Experience', got '%s'", exp.Section) } } // Validate structure of first project (if present) if len(response.Projects) > 0 { proj := response.Projects[0] if proj.ID == "" { t.Error("Project ID should not be empty") } if proj.Title == "" { t.Error("Project Title should not be empty") } if proj.Section != "Projects" { t.Errorf("Project Section should be 'Projects', got '%s'", proj.Section) } } // Validate structure of first course (if present) if len(response.Courses) > 0 { course := response.Courses[0] if course.ID == "" { t.Error("Course ID should not be empty") } if course.Title == "" { t.Error("Course Title should not be empty") } if course.Section != "Courses" { t.Errorf("Course Section should be 'Courses', got '%s'", course.Section) } } // Log counts for debugging t.Logf("Response: %d experiences, %d projects, %d courses", len(response.Experiences), len(response.Projects), len(response.Courses)) } }) } } // TestCmdKDataCaching tests that the response has proper cache headers func TestCmdKDataCaching(t *testing.T) { if testing.Short() { t.Skip("Skipping CmdKDataCaching test - requires running from project root") } handler := newTestCVHandler(t, "localhost:8080", nil) req := httptest.NewRequest(http.MethodGet, "/api/cmd-k", nil) rec := httptest.NewRecorder() handler.CmdKData(rec, req) // Check cache header cacheControl := rec.Header().Get(c.HeaderCacheControl) if cacheControl == "" { t.Error("Expected Cache-Control header to be set") } if cacheControl != c.CachePublic1Hour { t.Errorf("Expected Cache-Control '%s', got '%s'", c.CachePublic1Hour, cacheControl) } }