218 lines
5.0 KiB
Go
218 lines
5.0 KiB
Go
|
|
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))
|
||
|
|
}
|
||
|
|
}
|