564 lines
13 KiB
Go
564 lines
13 KiB
Go
package helper
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
sabat "github.com/cespares/response"
|
|
)
|
|
|
|
func TestRespondWithError(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
statusCode int
|
|
message string
|
|
}{
|
|
{
|
|
name: "responds with 400 bad request",
|
|
statusCode: http.StatusBadRequest,
|
|
message: "Invalid request",
|
|
},
|
|
{
|
|
name: "responds with 401 unauthorized",
|
|
statusCode: http.StatusUnauthorized,
|
|
message: "Unauthorized",
|
|
},
|
|
{
|
|
name: "responds with 403 forbidden",
|
|
statusCode: http.StatusForbidden,
|
|
message: "Forbidden",
|
|
},
|
|
{
|
|
name: "responds with 404 not found",
|
|
statusCode: http.StatusNotFound,
|
|
message: "Not found",
|
|
},
|
|
{
|
|
name: "responds with 500 internal server error",
|
|
statusCode: http.StatusInternalServerError,
|
|
message: "Internal server error",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithError(w, tt.statusCode, tt.message)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
// Check status code
|
|
if resp.StatusCode != tt.statusCode {
|
|
t.Errorf("status code = %v, want %v", resp.StatusCode, tt.statusCode)
|
|
}
|
|
|
|
// Check content type
|
|
contentType := resp.Header.Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
t.Errorf("Content-Type = %v, want application/json", contentType)
|
|
}
|
|
|
|
// Check response body
|
|
var body map[string]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if body["error"] != tt.message {
|
|
t.Errorf("error message = %v, want %v", body["error"], tt.message)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRespondWithMessage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
message string
|
|
}{
|
|
{
|
|
name: "responds with success message",
|
|
message: "Operation successful",
|
|
},
|
|
{
|
|
name: "responds with info message",
|
|
message: "Data updated",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithMessage(w, tt.message)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
// Check response body
|
|
var body map[string]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if body["message"] != tt.message {
|
|
t.Errorf("message = %v, want %v", body["message"], tt.message)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRespondWithJSON(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
statusCode int
|
|
data interface{}
|
|
wantJSON string
|
|
}{
|
|
{
|
|
name: "responds with simple map",
|
|
statusCode: http.StatusOK,
|
|
data: map[string]string{"key": "value"},
|
|
wantJSON: `{"key":"value"}`,
|
|
},
|
|
{
|
|
name: "responds with struct",
|
|
statusCode: http.StatusCreated,
|
|
data: struct {
|
|
ID int `json:"id"`
|
|
Name string `json:"name"`
|
|
}{ID: 1, Name: "Test"},
|
|
wantJSON: `{"id":1,"name":"Test"}`,
|
|
},
|
|
{
|
|
name: "responds with array",
|
|
statusCode: http.StatusOK,
|
|
data: []string{"item1", "item2"},
|
|
wantJSON: `["item1","item2"]`,
|
|
},
|
|
{
|
|
name: "responds with nested structure",
|
|
statusCode: http.StatusOK,
|
|
data: map[string]interface{}{
|
|
"user": map[string]string{
|
|
"name": "John",
|
|
"role": "admin",
|
|
},
|
|
"active": true,
|
|
},
|
|
wantJSON: `{"active":true,"user":{"name":"John","role":"admin"}}`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithJSON(w, tt.statusCode, tt.data)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
// Check status code
|
|
if resp.StatusCode != tt.statusCode {
|
|
t.Errorf("status code = %v, want %v", resp.StatusCode, tt.statusCode)
|
|
}
|
|
|
|
// Check content type
|
|
contentType := resp.Header.Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
t.Errorf("Content-Type = %v, want application/json", contentType)
|
|
}
|
|
|
|
// Decode and re-encode to normalize JSON for comparison
|
|
var got interface{}
|
|
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
var want interface{}
|
|
if err := json.Unmarshal([]byte(tt.wantJSON), &want); err != nil {
|
|
t.Fatalf("failed to unmarshal expected JSON: %v", err)
|
|
}
|
|
|
|
gotJSON, _ := json.Marshal(got)
|
|
wantJSON, _ := json.Marshal(want)
|
|
|
|
if string(gotJSON) != string(wantJSON) {
|
|
t.Errorf("response body = %s, want %s", string(gotJSON), string(wantJSON))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRespondWithJSON_StatusCodes(t *testing.T) {
|
|
statusCodes := []int{
|
|
http.StatusOK,
|
|
http.StatusCreated,
|
|
http.StatusAccepted,
|
|
http.StatusNoContent,
|
|
http.StatusBadRequest,
|
|
http.StatusUnauthorized,
|
|
http.StatusForbidden,
|
|
http.StatusNotFound,
|
|
http.StatusInternalServerError,
|
|
}
|
|
|
|
for _, code := range statusCodes {
|
|
t.Run(http.StatusText(code), func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
data := map[string]string{"status": http.StatusText(code)}
|
|
|
|
sabat.RespondWithJSON(w, code, data)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != code {
|
|
t.Errorf("status code = %v, want %v", resp.StatusCode, code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRespondWithError_EmptyMessage(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithError(w, http.StatusBadRequest, "")
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
var body map[string]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if body["error"] != "" {
|
|
t.Errorf("error message = %v, want empty string", body["error"])
|
|
}
|
|
}
|
|
|
|
func TestRespondWithMessage_EmptyMessage(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithMessage(w, "")
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
var body map[string]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if body["message"] != "" {
|
|
t.Errorf("message = %v, want empty string", body["message"])
|
|
}
|
|
}
|
|
|
|
func TestRespondWithJSON_NilData(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithJSON(w, http.StatusOK, nil)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
t.Errorf("status code = %v, want %v", resp.StatusCode, http.StatusOK)
|
|
}
|
|
|
|
var body interface{}
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if body != nil {
|
|
t.Errorf("body = %v, want nil", body)
|
|
}
|
|
}
|
|
|
|
// Additional comprehensive test cases
|
|
|
|
func TestRespondWithError_EmptyMessage2(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithError(w, http.StatusBadRequest, "")
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusBadRequest {
|
|
t.Errorf("status code = %v, want %v", resp.StatusCode, http.StatusBadRequest)
|
|
}
|
|
|
|
var body map[string]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if body["error"] != "" {
|
|
t.Errorf("error message = %v, want empty string", body["error"])
|
|
}
|
|
}
|
|
|
|
func TestRespondWithError_SpecialCharacters(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
message string
|
|
}{
|
|
{"Unicode characters", "错误信息"},
|
|
{"Quotes", `Error with "quotes"`},
|
|
{"Newlines", "Error\nwith\nnewlines"},
|
|
{"Tabs", "Error\twith\ttabs"},
|
|
{"Backslashes", `Error\with\backslashes`},
|
|
{"HTML", "<script>alert('xss')</script>"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithError(w, http.StatusBadRequest, tc.message)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
var body map[string]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if body["error"] != tc.message {
|
|
t.Errorf("error message = %v, want %v", body["error"], tc.message)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRespondWithMessage_EmptyMessage2(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithMessage(w, "")
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
var body map[string]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if body["message"] != "" {
|
|
t.Errorf("message = %v, want empty string", body["message"])
|
|
}
|
|
}
|
|
|
|
func TestRespondWithMessage_VeryLongMessage(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
longMessage := string(make([]byte, 10000))
|
|
for i := range longMessage {
|
|
longMessage = longMessage[:i] + "a" + longMessage[i+1:]
|
|
}
|
|
|
|
sabat.RespondWithMessage(w, longMessage)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
var body map[string]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if len(body["message"]) != len(longMessage) {
|
|
t.Errorf("message length = %v, want %v", len(body["message"]), len(longMessage))
|
|
}
|
|
}
|
|
|
|
func TestRespondWithJSON_ComplexStructure(t *testing.T) {
|
|
type NestedStruct struct {
|
|
Field1 string `json:"field1"`
|
|
Field2 int `json:"field2"`
|
|
Field3 map[string]string `json:"field3"`
|
|
Field4 []int `json:"field4"`
|
|
}
|
|
|
|
data := NestedStruct{
|
|
Field1: "test",
|
|
Field2: 123,
|
|
Field3: map[string]string{"key": "value"},
|
|
Field4: []int{1, 2, 3},
|
|
}
|
|
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithJSON(w, http.StatusOK, data)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
var result NestedStruct
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
|
|
if result.Field1 != data.Field1 || result.Field2 != data.Field2 {
|
|
t.Error("Complex structure not properly serialized")
|
|
}
|
|
}
|
|
|
|
func TestRespondWithJSON_UnserializableData(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
// Channels cannot be serialized to JSON
|
|
data := struct {
|
|
Ch chan int
|
|
}{
|
|
Ch: make(chan int),
|
|
}
|
|
|
|
sabat.RespondWithJSON(w, http.StatusOK, data)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
// Should handle serialization error gracefully
|
|
if resp.StatusCode != http.StatusInternalServerError && resp.StatusCode != http.StatusOK {
|
|
t.Logf("Status code %d when serializing unserializable data", resp.StatusCode)
|
|
}
|
|
}
|
|
|
|
func TestRespondWithError_AllHTTPStatusCodes(t *testing.T) {
|
|
statusCodes := []int{
|
|
http.StatusBadRequest, // 400
|
|
http.StatusUnauthorized, // 401
|
|
http.StatusPaymentRequired, // 402
|
|
http.StatusForbidden, // 403
|
|
http.StatusNotFound, // 404
|
|
http.StatusMethodNotAllowed, // 405
|
|
http.StatusConflict, // 409
|
|
http.StatusGone, // 410
|
|
http.StatusTeapot, // 418
|
|
http.StatusTooManyRequests, // 429
|
|
http.StatusInternalServerError, // 500
|
|
http.StatusNotImplemented, // 501
|
|
http.StatusBadGateway, // 502
|
|
http.StatusServiceUnavailable, // 503
|
|
http.StatusGatewayTimeout, // 504
|
|
}
|
|
|
|
for _, code := range statusCodes {
|
|
t.Run(http.StatusText(code), func(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithError(w, code, "test error")
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != code {
|
|
t.Errorf("status code = %v, want %v", resp.StatusCode, code)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRespondWithJSON_ConcurrentWrites(t *testing.T) {
|
|
concurrency := 50
|
|
done := make(chan bool, concurrency)
|
|
|
|
for i := 0; i < concurrency; i++ {
|
|
go func(idx int) {
|
|
w := httptest.NewRecorder()
|
|
data := map[string]int{"index": idx}
|
|
|
|
sabat.RespondWithJSON(w, http.StatusOK, data)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
var result map[string]int
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
t.Errorf("failed to decode response in concurrent test: %v", err)
|
|
}
|
|
|
|
if result["index"] != idx {
|
|
t.Errorf("index = %v, want %v", result["index"], idx)
|
|
}
|
|
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < concurrency; i++ {
|
|
<-done
|
|
}
|
|
}
|
|
|
|
func TestRespondWithError_HeadersAlreadyWritten(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
// Write response first
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte("already written"))
|
|
|
|
// Try to respond with error
|
|
sabat.RespondWithError(w, http.StatusBadRequest, "error")
|
|
|
|
// Status code shouldn't change after first write
|
|
if w.Code == http.StatusBadRequest {
|
|
t.Log("Headers were overwritten (unexpected but handled)")
|
|
}
|
|
}
|
|
|
|
func TestRespondWithJSON_EmptyStruct(t *testing.T) {
|
|
w := httptest.NewRecorder()
|
|
|
|
type EmptyStruct struct{}
|
|
data := EmptyStruct{}
|
|
|
|
sabat.RespondWithJSON(w, http.StatusOK, data)
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
var result EmptyStruct
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
t.Fatalf("failed to decode response: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRespondWithMessage_ConcurrentCalls(t *testing.T) {
|
|
concurrency := 30
|
|
done := make(chan bool, concurrency)
|
|
|
|
for i := 0; i < concurrency; i++ {
|
|
go func(idx int) {
|
|
w := httptest.NewRecorder()
|
|
|
|
sabat.RespondWithMessage(w, "test message")
|
|
|
|
resp := w.Result()
|
|
defer resp.Body.Close()
|
|
|
|
var body map[string]string
|
|
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
|
t.Errorf("failed to decode response: %v", err)
|
|
}
|
|
|
|
done <- true
|
|
}(i)
|
|
}
|
|
|
|
for i := 0; i < concurrency; i++ {
|
|
<-done
|
|
}
|
|
}
|