package helper import ( "encoding/json" "net/http" "net/http/httptest" "testing" ) 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() 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() 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() 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)} 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() 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() 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() 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() 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", ""}, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { w := httptest.NewRecorder() 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() 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:] } 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() 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), } 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() 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} 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 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{} 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() 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 } }