package httputil import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" c "github.com/juanatsap/cv-site/internal/constants" ) func TestJSON(t *testing.T) { tests := []struct { name string status int data interface{} wantStatus int }{ { name: "200 OK with map", status: http.StatusOK, data: map[string]string{"message": "success"}, wantStatus: http.StatusOK, }, { name: "201 Created with struct", status: http.StatusCreated, data: struct{ ID int }{ID: 123}, wantStatus: http.StatusCreated, }, { name: "400 Bad Request with error", status: http.StatusBadRequest, data: map[string]string{"error": "invalid request"}, wantStatus: http.StatusBadRequest, }, { name: "500 Internal Server Error", status: http.StatusInternalServerError, data: map[string]string{"error": "server error"}, wantStatus: http.StatusInternalServerError, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rec := httptest.NewRecorder() err := JSON(rec, tt.status, tt.data) if err != nil { t.Errorf("JSON() error = %v", err) } if rec.Code != tt.wantStatus { t.Errorf("Status = %d, want %d", rec.Code, tt.wantStatus) } contentType := rec.Header().Get(c.HeaderContentType) if contentType != c.ContentTypeJSON { t.Errorf("Content-Type = %q, want %q", contentType, c.ContentTypeJSON) } // Verify JSON is valid var result interface{} if err := json.Unmarshal(rec.Body.Bytes(), &result); err != nil { t.Errorf("Response is not valid JSON: %v", err) } }) } } func TestJSON_Array(t *testing.T) { rec := httptest.NewRecorder() data := []int{1, 2, 3, 4, 5} err := JSON(rec, http.StatusOK, data) if err != nil { t.Errorf("JSON() error = %v", err) } var result []int if err := json.Unmarshal(rec.Body.Bytes(), &result); err != nil { t.Errorf("Failed to parse JSON array: %v", err) } if len(result) != 5 { t.Errorf("Array length = %d, want 5", len(result)) } } func TestJSONOk(t *testing.T) { rec := httptest.NewRecorder() data := map[string]string{"status": "ok"} err := JSONOk(rec, data) if err != nil { t.Errorf("JSONOk() error = %v", err) } if rec.Code != http.StatusOK { t.Errorf("Status = %d, want %d", rec.Code, http.StatusOK) } contentType := rec.Header().Get(c.HeaderContentType) if contentType != c.ContentTypeJSON { t.Errorf("Content-Type = %q, want %q", contentType, c.ContentTypeJSON) } } func TestJSONCached(t *testing.T) { tests := []struct { name string maxAge int }{ {"30 seconds", 30}, {"1 minute", 60}, {"1 hour", 3600}, {"1 day", 86400}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rec := httptest.NewRecorder() data := map[string]string{"data": "cached"} err := JSONCached(rec, data, tt.maxAge) if err != nil { t.Errorf("JSONCached() error = %v", err) } if rec.Code != http.StatusOK { t.Errorf("Status = %d, want %d", rec.Code, http.StatusOK) } cacheControl := rec.Header().Get(c.HeaderCacheControl) expectedCache := "public, max-age=" if !strings.HasPrefix(cacheControl, expectedCache) { t.Errorf("Cache-Control = %q, want prefix %q", cacheControl, expectedCache) } // Verify it contains the correct max-age value expectedValue := "max-age=" + string(rune(tt.maxAge+'0')) if tt.maxAge > 9 { // For multi-digit numbers, just check it starts correctly if !strings.Contains(cacheControl, "max-age=") { t.Errorf("Cache-Control doesn't contain max-age") } } _ = expectedValue }) } } func TestHTML(t *testing.T) { rec := httptest.NewRecorder() HTML(rec) contentType := rec.Header().Get(c.HeaderContentType) if contentType != c.ContentTypeHTML { t.Errorf("Content-Type = %q, want %q", contentType, c.ContentTypeHTML) } } func TestNoContent(t *testing.T) { rec := httptest.NewRecorder() NoContent(rec) if rec.Code != http.StatusNoContent { t.Errorf("Status = %d, want %d", rec.Code, http.StatusNoContent) } // 204 No Content should have empty body if rec.Body.Len() != 0 { t.Errorf("Body should be empty for 204 No Content, got %q", rec.Body.String()) } } func TestJSON_NestedStruct(t *testing.T) { type Inner struct { Value string `json:"value"` } type Outer struct { Name string `json:"name"` Inner Inner `json:"inner"` Values []int `json:"values"` } rec := httptest.NewRecorder() data := Outer{ Name: "test", Inner: Inner{Value: "nested"}, Values: []int{1, 2, 3}, } err := JSON(rec, http.StatusOK, data) if err != nil { t.Errorf("JSON() error = %v", err) } var result Outer if err := json.Unmarshal(rec.Body.Bytes(), &result); err != nil { t.Errorf("Failed to parse nested JSON: %v", err) } if result.Name != "test" { t.Errorf("Name = %q, want %q", result.Name, "test") } if result.Inner.Value != "nested" { t.Errorf("Inner.Value = %q, want %q", result.Inner.Value, "nested") } if len(result.Values) != 3 { t.Errorf("Values length = %d, want 3", len(result.Values)) } }