Files
golang/golang-learning/10-projects/03-web-server/server_test.go
2025-08-24 13:01:09 +08:00

359 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
server_test.go - Web服务器测试文件
测试HTTP服务器和API的各种功能
*/
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"webserver/models"
"webserver/server"
)
// TestHealthHandler 测试健康检查处理器
func TestHealthHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.HealthHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusOK {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK)
}
// 检查响应内容类型
expected := "application/json"
if ct := rr.Header().Get("Content-Type"); ct != expected {
t.Errorf("处理器返回错误内容类型: got %v want %v", ct, expected)
}
// 检查响应体
var response server.HealthResponse
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "healthy" {
t.Errorf("期望状态为 'healthy', 实际为 '%s'", response.Status)
}
}
// TestGetUsersHandler 测试获取用户列表处理器
func TestGetUsersHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/api/users", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.GetUsersHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusOK {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK)
}
// 检查响应内容类型
expected := "application/json"
if ct := rr.Header().Get("Content-Type"); ct != expected {
t.Errorf("处理器返回错误内容类型: got %v want %v", ct, expected)
}
// 检查响应体
var response server.Response
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "success" {
t.Errorf("期望状态为 'success', 实际为 '%s'", response.Status)
}
}
// TestCreateUserHandler 测试创建用户处理器
func TestCreateUserHandler(t *testing.T) {
user := models.User{
Name: "测试用户",
Email: "test@example.com",
Age: 25,
}
jsonData, err := json.Marshal(user)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/api/users", bytes.NewBuffer(jsonData))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.CreateUserHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusCreated {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusCreated)
}
// 检查响应体
var response server.Response
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "success" {
t.Errorf("期望状态为 'success', 实际为 '%s'", response.Status)
}
}
// TestCreateUserHandlerInvalidData 测试创建用户处理器(无效数据)
func TestCreateUserHandlerInvalidData(t *testing.T) {
// 测试空用户名
user := models.User{
Name: "",
Email: "test@example.com",
Age: 25,
}
jsonData, err := json.Marshal(user)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/api/users", bytes.NewBuffer(jsonData))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.CreateUserHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusBadRequest)
}
// 检查响应体
var response server.Response
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "error" {
t.Errorf("期望状态为 'error', 实际为 '%s'", response.Status)
}
}
// TestCreateUserHandlerInvalidJSON 测试创建用户处理器无效JSON
func TestCreateUserHandlerInvalidJSON(t *testing.T) {
invalidJSON := []byte(`{"name": "测试用户", "email": "test@example.com", "age":}`)
req, err := http.NewRequest("POST", "/api/users", bytes.NewBuffer(invalidJSON))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.CreateUserHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusBadRequest)
}
}
// TestStatsHandler 测试统计信息处理器
func TestStatsHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/api/stats", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.StatsHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusOK {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK)
}
// 检查响应体
var response server.StatsResponse
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "success" {
t.Errorf("期望状态为 'success', 实际为 '%s'", response.Status)
}
if response.NumCPU <= 0 {
t.Error("CPU数量应该大于0")
}
if response.NumGoroutine <= 0 {
t.Error("Goroutine数量应该大于0")
}
}
// TestAPIDocHandler 测试API文档处理器
func TestAPIDocHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/api", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.APIDocHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusOK {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK)
}
// 检查响应内容类型
expected := "application/json"
if ct := rr.Header().Get("Content-Type"); ct != expected {
t.Errorf("处理器返回错误内容类型: got %v want %v", ct, expected)
}
// 检查响应体包含API文档信息
var doc map[string]interface{}
if err := json.Unmarshal(rr.Body.Bytes(), &doc); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if _, exists := doc["title"]; !exists {
t.Error("API文档应该包含title字段")
}
if _, exists := doc["endpoints"]; !exists {
t.Error("API文档应该包含endpoints字段")
}
}
// TestServer 测试完整的服务器
func TestServer(t *testing.T) {
// 创建服务器
srv := server.NewServer(":0") // 使用随机端口
srv.SetupRoutes()
// 创建测试服务器
testServer := httptest.NewServer(srv.GetRouter())
defer testServer.Close()
// 测试健康检查
resp, err := http.Get(testServer.URL + "/health")
if err != nil {
t.Fatalf("健康检查请求失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("健康检查返回错误状态码: got %v want %v", resp.StatusCode, http.StatusOK)
}
// 测试获取用户列表
resp, err = http.Get(testServer.URL + "/api/users")
if err != nil {
t.Fatalf("获取用户列表请求失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("获取用户列表返回错误状态码: got %v want %v", resp.StatusCode, http.StatusOK)
}
}
// TestMiddleware 测试中间件
func TestMiddleware(t *testing.T) {
// 测试CORS中间件
req, err := http.NewRequest("OPTIONS", "/api/users", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
// 创建带中间件的处理器
handler := server.CORSMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
handler.ServeHTTP(rr, req)
// 检查CORS头
if origin := rr.Header().Get("Access-Control-Allow-Origin"); origin != "*" {
t.Errorf("期望CORS Origin为 '*', 实际为 '%s'", origin)
}
if methods := rr.Header().Get("Access-Control-Allow-Methods"); methods == "" {
t.Error("应该设置Access-Control-Allow-Methods头")
}
}
// BenchmarkHealthHandler 健康检查处理器基准测试
func BenchmarkHealthHandler(b *testing.B) {
req, _ := http.NewRequest("GET", "/health", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.HealthHandler)
handler.ServeHTTP(rr, req)
}
}
// BenchmarkGetUsersHandler 获取用户列表处理器基准测试
func BenchmarkGetUsersHandler(b *testing.B) {
req, _ := http.NewRequest("GET", "/api/users", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.GetUsersHandler)
handler.ServeHTTP(rr, req)
}
}
// ExampleHealthHandler 健康检查处理器示例
func ExampleHealthHandler() {
req, _ := http.NewRequest("GET", "/health", nil)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.HealthHandler)
handler.ServeHTTP(rr, req)
fmt.Printf("Status: %d", rr.Code)
// Output: Status: 200
}