423 lines
10 KiB
Go
423 lines
10 KiB
Go
//go:build legacytests
|
||
// +build legacytests
|
||
|
||
package e2e
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"encoding/json"
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"sync"
|
||
"testing"
|
||
"time"
|
||
|
||
. "github.com/smartystreets/goconvey/convey"
|
||
"quyun/v2/app"
|
||
"quyun/v2/app/config"
|
||
)
|
||
|
||
// TestAPIHealth 测试 API 健康检查
|
||
func TestAPIHealth(t *testing.T) {
|
||
Convey("API 健康检查测试", t, func() {
|
||
var server *httptest.Server
|
||
var testConfig *config.Config
|
||
|
||
Convey("当启动测试服务器时", func() {
|
||
testConfig = &config.Config{
|
||
App: config.AppConfig{
|
||
Mode: "test",
|
||
BaseURI: "http://localhost:8080",
|
||
},
|
||
Http: config.HttpConfig{
|
||
Port: 8080,
|
||
},
|
||
Log: config.LogConfig{
|
||
Level: "debug",
|
||
Format: "text",
|
||
EnableCaller: true,
|
||
},
|
||
}
|
||
|
||
app := app.New(testConfig)
|
||
server = httptest.NewServer(app)
|
||
|
||
Convey("服务器应该成功启动", func() {
|
||
So(server, ShouldNotBeNil)
|
||
So(server.URL, ShouldNotBeEmpty)
|
||
})
|
||
})
|
||
|
||
Convey("当访问健康检查端点时", func() {
|
||
resp, err := http.Get(server.URL + "/health")
|
||
So(err, ShouldBeNil)
|
||
So(resp.StatusCode, ShouldEqual, http.StatusOK)
|
||
|
||
defer resp.Body.Close()
|
||
|
||
var result map[string]interface{}
|
||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||
So(err, ShouldBeNil)
|
||
|
||
Convey("响应应该包含正确的状态", func() {
|
||
So(result["status"], ShouldEqual, "ok")
|
||
})
|
||
|
||
Convey("响应应该包含时间戳", func() {
|
||
So(result, ShouldContainKey, "timestamp")
|
||
})
|
||
|
||
Convey("响应应该是 JSON 格式", func() {
|
||
So(resp.Header.Get("Content-Type"), ShouldEqual, "application/json; charset=utf-8")
|
||
})
|
||
})
|
||
|
||
Convey("当访问不存在的端点时", func() {
|
||
resp, err := http.Get(server.URL + "/api/nonexistent")
|
||
So(err, ShouldBeNil)
|
||
So(resp.StatusCode, ShouldEqual, http.StatusNotFound)
|
||
|
||
defer resp.Body.Close()
|
||
|
||
var result map[string]interface{}
|
||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||
So(err, ShouldBeNil)
|
||
|
||
Convey("响应应该包含错误信息", func() {
|
||
So(result, ShouldContainKey, "error")
|
||
})
|
||
})
|
||
|
||
Convey("当测试 CORS 支持", func() {
|
||
req, err := http.NewRequest("OPTIONS", server.URL+"/api/test", nil)
|
||
So(err, ShouldBeNil)
|
||
|
||
req.Header.Set("Origin", "http://localhost:3000")
|
||
req.Header.Set("Access-Control-Request-Method", "POST")
|
||
req.Header.Set("Access-Control-Request-Headers", "Content-Type,Authorization")
|
||
|
||
resp, err := http.DefaultClient.Do(req)
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
|
||
Convey("应该返回正确的 CORS 头", func() {
|
||
So(resp.StatusCode, ShouldEqual, http.StatusOK)
|
||
So(resp.Header.Get("Access-Control-Allow-Origin"), ShouldContainSubstring, "localhost")
|
||
So(resp.Header.Get("Access-Control-Allow-Methods"), ShouldContainSubstring, "POST")
|
||
})
|
||
})
|
||
|
||
Reset(func() {
|
||
if server != nil {
|
||
server.Close()
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
// TestAPIPerformance 测试 API 性能
|
||
func TestAPIPerformance(t *testing.T) {
|
||
Convey("API 性能测试", t, func() {
|
||
var server *httptest.Server
|
||
var testConfig *config.Config
|
||
|
||
Convey("当准备性能测试时", func() {
|
||
testConfig = &config.Config{
|
||
App: config.AppConfig{
|
||
Mode: "test",
|
||
BaseURI: "http://localhost:8080",
|
||
},
|
||
Http: config.HttpConfig{
|
||
Port: 8080,
|
||
},
|
||
Log: config.LogConfig{
|
||
Level: "error", // 减少日志输出以提升性能
|
||
Format: "text",
|
||
},
|
||
}
|
||
|
||
app := app.New(testConfig)
|
||
server = httptest.NewServer(app)
|
||
})
|
||
|
||
Convey("当测试响应时间时", func() {
|
||
start := time.Now()
|
||
resp, err := http.Get(server.URL + "/health")
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
|
||
duration := time.Since(start)
|
||
So(resp.StatusCode, ShouldEqual, http.StatusOK)
|
||
|
||
Convey("响应时间应该在合理范围内", func() {
|
||
So(duration, ShouldBeLessThan, 100*time.Millisecond)
|
||
})
|
||
})
|
||
|
||
Convey("当测试并发请求时", func() {
|
||
const numRequests = 50
|
||
const maxConcurrency = 10
|
||
const timeout = 5 * time.Second
|
||
|
||
var wg sync.WaitGroup
|
||
successCount := 0
|
||
errorCount := 0
|
||
var mu sync.Mutex
|
||
|
||
// 使用信号量控制并发数
|
||
sem := make(chan struct{}, maxConcurrency)
|
||
|
||
start := time.Now()
|
||
|
||
for i := 0; i < numRequests; i++ {
|
||
wg.Add(1)
|
||
go func(requestID int) {
|
||
defer wg.Done()
|
||
|
||
// 获取信号量
|
||
sem <- struct{}{}
|
||
defer func() { <-sem }()
|
||
|
||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||
defer cancel()
|
||
|
||
req, err := http.NewRequestWithContext(ctx, "GET", server.URL+"/health", nil)
|
||
if err != nil {
|
||
mu.Lock()
|
||
errorCount++
|
||
mu.Unlock()
|
||
return
|
||
}
|
||
|
||
client := &http.Client{
|
||
Timeout: timeout,
|
||
}
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
mu.Lock()
|
||
errorCount++
|
||
mu.Unlock()
|
||
return
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
mu.Lock()
|
||
if resp.StatusCode == http.StatusOK {
|
||
successCount++
|
||
} else {
|
||
errorCount++
|
||
}
|
||
mu.Unlock()
|
||
}(i)
|
||
}
|
||
|
||
wg.Wait()
|
||
duration := time.Since(start)
|
||
|
||
Convey("所有请求都应该完成", func() {
|
||
So(successCount+errorCount, ShouldEqual, numRequests)
|
||
})
|
||
|
||
Convey("所有请求都应该成功", func() {
|
||
So(errorCount, ShouldEqual, 0)
|
||
})
|
||
|
||
Convey("总耗时应该在合理范围内", func() {
|
||
So(duration, ShouldBeLessThan, 10*time.Second)
|
||
})
|
||
|
||
Convey("并发性能应该良好", func() {
|
||
avgTime := duration / numRequests
|
||
So(avgTime, ShouldBeLessThan, 200*time.Millisecond)
|
||
})
|
||
})
|
||
|
||
Reset(func() {
|
||
if server != nil {
|
||
server.Close()
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
// TestAPIBehavior 测试 API 行为
|
||
func TestAPIBehavior(t *testing.T) {
|
||
Convey("API 行为测试", t, func() {
|
||
var server *httptest.Server
|
||
var testConfig *config.Config
|
||
|
||
Convey("当准备行为测试时", func() {
|
||
testConfig = &config.Config{
|
||
App: config.AppConfig{
|
||
Mode: "test",
|
||
BaseURI: "http://localhost:8080",
|
||
},
|
||
Http: config.HttpConfig{
|
||
Port: 8080,
|
||
},
|
||
Log: config.LogConfig{
|
||
Level: "debug",
|
||
Format: "text",
|
||
EnableCaller: true,
|
||
},
|
||
}
|
||
|
||
app := app.New(testConfig)
|
||
server = httptest.NewServer(app)
|
||
})
|
||
|
||
Convey("当测试不同 HTTP 方法时", func() {
|
||
testURL := server.URL + "/health"
|
||
|
||
Convey("GET 请求应该成功", func() {
|
||
resp, err := http.Get(testURL)
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
So(resp.StatusCode, ShouldEqual, http.StatusOK)
|
||
})
|
||
|
||
Convey("POST 请求应该被处理", func() {
|
||
resp, err := http.Post(testURL, "application/json", bytes.NewBuffer([]byte{}))
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
// 健康检查端点通常支持所有方法
|
||
So(resp.StatusCode, ShouldBeIn, []int{http.StatusOK, http.StatusMethodNotAllowed})
|
||
})
|
||
|
||
Convey("PUT 请求应该被处理", func() {
|
||
req, err := http.NewRequest("PUT", testURL, bytes.NewBuffer([]byte{}))
|
||
So(err, ShouldBeNil)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
So(resp.StatusCode, ShouldBeIn, []int{http.StatusOK, http.StatusMethodNotAllowed})
|
||
})
|
||
|
||
Convey("DELETE 请求应该被处理", func() {
|
||
req, err := http.NewRequest("DELETE", testURL, nil)
|
||
So(err, ShouldBeNil)
|
||
resp, err := http.DefaultClient.Do(req)
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
So(resp.StatusCode, ShouldBeIn, []int{http.StatusOK, http.StatusMethodNotAllowed})
|
||
})
|
||
})
|
||
|
||
Convey("当测试自定义请求头时", func() {
|
||
req, err := http.NewRequest("GET", server.URL+"/health", nil)
|
||
So(err, ShouldBeNil)
|
||
|
||
// 设置各种请求头
|
||
req.Header.Set("User-Agent", "E2E-Test-Agent/1.0")
|
||
req.Header.Set("Accept", "application/json")
|
||
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
|
||
req.Header.Set("X-Custom-Header", "test-value")
|
||
req.Header.Set("X-Request-ID", "test-request-123")
|
||
req.Header.Set("Authorization", "Bearer test-token")
|
||
|
||
resp, err := http.DefaultClient.Do(req)
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
|
||
Convey("请求应该成功", func() {
|
||
So(resp.StatusCode, ShouldEqual, http.StatusOK)
|
||
})
|
||
|
||
Convey("响应应该是 JSON 格式", func() {
|
||
So(resp.Header.Get("Content-Type"), ShouldEqual, "application/json; charset=utf-8")
|
||
})
|
||
})
|
||
|
||
Convey("当测试错误处理时", func() {
|
||
Convey("访问不存在的路径应该返回 404", func() {
|
||
resp, err := http.Get(server.URL + "/api/v1/nonexistent")
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
So(resp.StatusCode, ShouldEqual, http.StatusNotFound)
|
||
})
|
||
|
||
Convey("访问非法路径应该返回 404", func() {
|
||
resp, err := http.Get(server.URL + "/../etc/passwd")
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
So(resp.StatusCode, ShouldEqual, http.StatusNotFound)
|
||
})
|
||
})
|
||
|
||
Reset(func() {
|
||
if server != nil {
|
||
server.Close()
|
||
}
|
||
})
|
||
})
|
||
}
|
||
|
||
// TestAPIDocumentation 测试 API 文档
|
||
func TestAPIDocumentation(t *testing.T) {
|
||
Convey("API 文档测试", t, func() {
|
||
var server *httptest.Server
|
||
var testConfig *config.Config
|
||
|
||
Convey("当准备文档测试时", func() {
|
||
testConfig = &config.Config{
|
||
App: config.AppConfig{
|
||
Mode: "test",
|
||
BaseURI: "http://localhost:8080",
|
||
},
|
||
Http: config.HttpConfig{
|
||
Port: 8080,
|
||
},
|
||
Log: config.LogConfig{
|
||
Level: "debug",
|
||
Format: "text",
|
||
EnableCaller: true,
|
||
},
|
||
}
|
||
|
||
app := app.New(testConfig)
|
||
server = httptest.NewServer(app)
|
||
})
|
||
|
||
Convey("当访问 Swagger UI 时", func() {
|
||
resp, err := http.Get(server.URL + "/swagger/index.html")
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
|
||
Convey("应该能够访问 Swagger UI", func() {
|
||
So(resp.StatusCode, ShouldEqual, http.StatusOK)
|
||
})
|
||
|
||
Convey("响应应该是 HTML 格式", func() {
|
||
contentType := resp.Header.Get("Content-Type")
|
||
So(contentType, ShouldContainSubstring, "text/html")
|
||
})
|
||
})
|
||
|
||
Convey("当访问 OpenAPI 规范时", func() {
|
||
resp, err := http.Get(server.URL + "/swagger/doc.json")
|
||
So(err, ShouldBeNil)
|
||
defer resp.Body.Close()
|
||
|
||
Convey("应该能够访问 OpenAPI 规范", func() {
|
||
// 如果存在则返回 200,不存在则返回 404
|
||
So(resp.StatusCode, ShouldBeIn, []int{http.StatusOK, http.StatusNotFound})
|
||
})
|
||
|
||
Convey("如果存在,响应应该是 JSON 格式", func() {
|
||
if resp.StatusCode == http.StatusOK {
|
||
contentType := resp.Header.Get("Content-Type")
|
||
So(contentType, ShouldContainSubstring, "application/json")
|
||
}
|
||
})
|
||
})
|
||
|
||
Reset(func() {
|
||
if server != nil {
|
||
server.Close()
|
||
}
|
||
})
|
||
})
|
||
}
|