Files

189 lines
4.8 KiB
Go

package middleware
import (
"fmt"
"strings"
"sync"
"testing"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/test/gtest"
)
// TestRequestId_DualIDMechanism 测试双 ID 机制
func TestRequestId_DualIDMechanism(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
s := g.Server("mw-request-id-dual")
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(RequestId)
group.ALL("/test", func(r *ghttp.Request) {
serverID := r.GetCtxVar("RequestId").String()
clientID := r.GetCtxVar("ClientTraceId").String()
r.Response.WriteJson(g.Map{
"server_id": serverID,
"client_id": clientID,
})
})
})
s.SetAddr("127.0.0.1:0")
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
ctx := gctx.New()
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
tests := []struct {
name string
clientRequestId string
expectServerIDGenerated bool
expectClientIDInContext bool
}{
{
name: "无客户端 ID - 服务端生成双头",
clientRequestId: "",
expectServerIDGenerated: true,
expectClientIDInContext: false,
},
{
name: "有效客户端 ID - 回显 + 独立服务端 ID",
clientRequestId: "client-trace-123",
expectServerIDGenerated: true,
expectClientIDInContext: true,
},
{
name: "非法客户端 ID(含空格)- 拒绝",
clientRequestId: "invalid id with spaces",
expectServerIDGenerated: true,
expectClientIDInContext: false,
},
{
name: "非法客户端 ID(超长)- 拒绝",
clientRequestId: strings.Repeat("a", 65),
expectServerIDGenerated: true,
expectClientIDInContext: false,
},
}
for _, tt := range tests {
resp := client.Header(g.MapStrStr{
"X-Request-Id": tt.clientRequestId,
}).GetContent(ctx, "/test")
var body map[string]string
t.AssertNil(gjson.DecodeTo(resp, &body))
serverID := body["server_id"]
clientID := body["client_id"]
// 验证服务端 ID 必须存在
t.Assert(serverID != "", true)
// 验证客户端 ID
if tt.expectClientIDInContext {
t.Assert(clientID, tt.clientRequestId)
} else {
t.Assert(clientID, "")
}
}
})
}
// TestRequestId_SecurityIsolation 测试安全隔离:服务端 ID 不受客户端控制
func TestRequestId_SecurityIsolation(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
serverIDs := make(map[string]bool)
var mu sync.Mutex
s := g.Server("mw-request-id-isolation")
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(RequestId)
group.ALL("/test", func(r *ghttp.Request) {
serverID := r.GetCtxVar("RequestId").String()
mu.Lock()
serverIDs[serverID] = true
mu.Unlock()
r.Response.WriteJson(g.Map{
"server_id": serverID,
})
})
})
s.SetAddr("127.0.0.1:0")
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
ctx := gctx.New()
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
// 模拟恶意客户端:连续 10 次请求,传递相同的客户端 ID
maliciousClientID := "malicious-repeated-id"
for i := 0; i < 10; i++ {
client.Header(g.MapStrStr{
"X-Request-Id": maliciousClientID,
}).GetContent(ctx, "/test")
}
// 验证:每次请求的服务端 ID 必须不同
t.Assert(len(serverIDs), 10)
})
}
// TestRequestId_IdempotencyKeyUniqueness 测试幂等性键的唯一性保证
func TestRequestId_IdempotencyKeyUniqueness(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
const concurrency = 100
serverIDs := make(chan string, concurrency)
s := g.Server("mw-request-id-idempotency")
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(RequestId)
group.ALL("/test", func(r *ghttp.Request) {
serverID := r.GetCtxVar("RequestId").String()
r.Response.WriteJson(g.Map{
"server_id": serverID,
})
})
})
s.SetAddr("127.0.0.1:0")
s.SetDumpRouterMap(false)
s.Start()
defer s.Shutdown()
ctx := gctx.New()
client := g.Client()
client.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
// 并发发起请求
var wg sync.WaitGroup
for i := 0; i < concurrency; i++ {
wg.Add(1)
go func() {
defer wg.Done()
resp := client.GetContent(ctx, "/test")
var body map[string]string
if err := gjson.DecodeTo(resp, &body); err == nil {
serverIDs <- body["server_id"]
}
}()
}
wg.Wait()
close(serverIDs)
// 统计唯一 ID 数量
uniqueIDs := make(map[string]bool)
for id := range serverIDs {
uniqueIDs[id] = true
}
// 验证:所有服务端 ID 必须唯一
t.Assert(len(uniqueIDs), concurrency)
})
}