lolly/internal/server/pprof_test.go
xfy 91e04222b3 refactor: 统一 IP 白名单解析和 excludeSet 构建
Batch 1 重构:
- 新增 utils.ParseIPAllowList 统一 IP/CIDR 解析(含 localhost 特殊处理)
- pprof.go/status.go/purge.go 改用统一函数,减少 ~66 行重复代码
- 新增 loadbalance.buildExcludeSet 统一排除集合构建
- 更新 pprof_test.go 适配统一字段 allowed []net.IPNet

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 16:18:52 +08:00

949 lines
23 KiB
Go
Raw Permalink 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.

// Package server 提供 pprof 性能分析端点功能的测试。
//
// 该文件测试 pprof 处理器模块的各项功能,包括:
// - pprof 处理器创建
// - 配置解析和默认值
// - IP/CIDR 白名单验证
// - 路径返回
// - ServeHTTP 路径分发
// - 访问控制逻辑
// - HTML 索引页面生成
//
// 作者xfy
package server
import (
"bytes"
"net"
"strings"
"testing"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
)
func TestNewPprofHandler_Disabled(t *testing.T) {
cfg := &config.PprofConfig{
Enabled: false,
}
h, err := NewPprofHandler(cfg)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if h != nil {
t.Error("expected nil handler when disabled")
}
}
func TestNewPprofHandler_DefaultPath(t *testing.T) {
cfg := &config.PprofConfig{
Enabled: true,
Path: "",
}
h, err := NewPprofHandler(cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil handler")
}
if h.Path() != "/debug/pprof" {
t.Errorf("expected default path /debug/pprof, got %s", h.Path())
}
}
func TestNewPprofHandler_CustomPath(t *testing.T) {
cfg := &config.PprofConfig{
Enabled: true,
Path: "/custom/pprof",
}
h, err := NewPprofHandler(cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil handler")
}
if h.Path() != "/custom/pprof" {
t.Errorf("expected custom path /custom/pprof, got %s", h.Path())
}
}
func TestNewPprofHandler_SingleIP(t *testing.T) {
tests := []struct {
name string
allow []string
wantErr bool
}{
{
name: "valid IPv4",
allow: []string{"192.168.1.100"},
wantErr: false,
},
{
name: "valid IPv6",
allow: []string{"::1"},
wantErr: false,
},
{
name: "multiple IPs",
allow: []string{"192.168.1.1", "127.0.0.1", "::1"},
wantErr: false,
},
{
name: "empty allow list - use default localhost",
allow: []string{},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &config.PprofConfig{
Enabled: true,
Path: "/debug/pprof",
Allow: tt.allow,
}
h, err := NewPprofHandler(cfg)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil handler")
}
// 空列表时应该默认允许 localhost
if len(tt.allow) == 0 {
if len(h.allowed) != 2 {
t.Errorf("expected 2 default allowed IPs (127.0.0.1 and ::1), got %d", len(h.allowed))
}
}
}
})
}
}
func TestNewPprofHandler_CIDR(t *testing.T) {
tests := []struct {
name string
allow []string
wantErr bool
}{
{
name: "valid CIDR IPv4",
allow: []string{"192.168.1.0/24"},
wantErr: false,
},
{
name: "valid CIDR IPv6",
allow: []string{"2001:db8::/32"},
wantErr: false,
},
{
name: "multiple CIDRs",
allow: []string{"10.0.0.0/8", "172.16.0.0/12"},
wantErr: false,
},
{
name: "mixed IP and CIDR",
allow: []string{"192.168.1.1", "10.0.0.0/8"},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &config.PprofConfig{
Enabled: true,
Path: "/debug/pprof",
Allow: tt.allow,
}
h, err := NewPprofHandler(cfg)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
} else {
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil handler")
}
}
})
}
}
func TestNewPprofHandler_InvalidIP(t *testing.T) {
tests := []struct {
name string
allow []string
}{
{
name: "invalid IP format",
allow: []string{"not-an-ip"},
},
{
name: "invalid CIDR format",
allow: []string{"invalid-cidr"},
},
{
name: "CIDR with invalid mask",
allow: []string{"192.168.1.0/33"},
},
{
name: "mixed valid and invalid",
allow: []string{"127.0.0.1", "invalid"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &config.PprofConfig{
Enabled: true,
Path: "/debug/pprof",
Allow: tt.allow,
}
_, err := NewPprofHandler(cfg)
if err == nil {
t.Error("expected error for invalid IP/CIDR, got nil")
}
})
}
}
func TestPprofHandler_Path(t *testing.T) {
tests := []struct {
name string
path string
wantPath string
}{
{
name: "default path",
path: "",
wantPath: "/debug/pprof",
},
{
name: "custom path",
path: "/admin/pprof",
wantPath: "/admin/pprof",
},
{
name: "nested path",
path: "/api/v1/debug/pprof",
wantPath: "/api/v1/debug/pprof",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cfg := &config.PprofConfig{
Enabled: true,
Path: tt.path,
}
h, err := NewPprofHandler(cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil handler")
}
if h.Path() != tt.wantPath {
t.Errorf("expected path %s, got %s", tt.wantPath, h.Path())
}
})
}
}
func TestPprofHandler_isAllowed(t *testing.T) {
tests := []struct {
name string
clientIP string
allowedNets []string
wantAllowed bool
}{
{
name: "empty allow list - allow all",
allowedNets: []string{},
clientIP: "192.168.1.100",
wantAllowed: true,
},
{
name: "IP exact match (as /32 CIDR)",
allowedNets: []string{"127.0.0.1/32"},
clientIP: "127.0.0.1",
wantAllowed: true,
},
{
name: "IP no match",
allowedNets: []string{"127.0.0.1/32"},
clientIP: "127.0.0.2",
wantAllowed: false,
},
{
name: "CIDR match",
allowedNets: []string{"192.168.0.0/16"},
clientIP: "192.168.1.100",
wantAllowed: true,
},
{
name: "CIDR no match",
allowedNets: []string{"10.0.0.0/8"},
clientIP: "192.168.1.100",
wantAllowed: false,
},
{
name: "IPv6 CIDR match",
allowedNets: []string{"2001:db8::/32"},
clientIP: "2001:db8::1",
wantAllowed: true,
},
{
name: "IPv6 exact match (as /128 CIDR)",
allowedNets: []string{"::1/128"},
clientIP: "::1",
wantAllowed: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &PprofHandler{
allowed: parseNets(tt.allowedNets),
}
// 创建请求上下文,模拟客户端 IP
// 通过设置请求头来模拟 IP 需要特殊处理
// fasthttp 的 RemoteIP() 从连接获取,这里我们直接测试 isAllowed 逻辑
// 手动测试 isAllowed 的内部逻辑
clientIP := net.ParseIP(tt.clientIP)
if clientIP == nil {
t.Fatalf("failed to parse client IP: %s", tt.clientIP)
}
// 复制 isAllowed 的逻辑进行测试
allowed := false
if len(h.allowed) == 0 {
allowed = true
} else {
for _, n := range h.allowed {
if n.Contains(clientIP) {
allowed = true
break
}
}
}
if allowed != tt.wantAllowed {
t.Errorf("isAllowed() = %v, want %v", allowed, tt.wantAllowed)
}
})
}
}
// parseNets 辅助函数,解析 CIDR 字符串列表
func parseNets(cidrs []string) []net.IPNet {
result := make([]net.IPNet, 0, len(cidrs))
for _, cidr := range cidrs {
_, net, err := net.ParseCIDR(cidr)
if err == nil && net != nil {
result = append(result, *net)
}
}
return result
}
func TestPprofHandler_ServeHTTP_WithAllowListEmpty(t *testing.T) {
// 测试空 allow 列表时允许所有访问
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof")
h.ServeHTTP(ctx)
// 空 allow 列表时应允许访问(返回 200
if ctx.Response.StatusCode() != 200 {
t.Errorf("expected status 200 for open access, got %d", ctx.Response.StatusCode())
}
}
func TestPprofHandler_ServeHTTP_ProfileEndpoints(t *testing.T) {
// 使用空 allow 列表允许所有访问
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
tests := []struct {
name string
path string
wantStatus int
}{
{
name: "heap endpoint",
path: "/debug/pprof/heap",
wantStatus: 200,
},
{
name: "goroutine endpoint",
path: "/debug/pprof/goroutine",
wantStatus: 200,
},
{
name: "block endpoint",
path: "/debug/pprof/block",
wantStatus: 200,
},
{
name: "mutex endpoint",
path: "/debug/pprof/mutex",
wantStatus: 200,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI(tt.path)
h.ServeHTTP(ctx)
if ctx.Response.StatusCode() != tt.wantStatus {
t.Errorf("expected status %d, got %d", tt.wantStatus, ctx.Response.StatusCode())
}
})
}
}
func TestPprofHandler_handleIndex(t *testing.T) {
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof")
h.handleIndex(ctx)
// 验证状态码
if ctx.Response.StatusCode() != 200 {
t.Errorf("expected status 200, got %d", ctx.Response.StatusCode())
}
// 验证 Content-Type
contentType := string(ctx.Response.Header.Peek("Content-Type"))
if !strings.Contains(contentType, "text/html") {
t.Errorf("expected Content-Type text/html, got %s", contentType)
}
// 验证响应体包含关键内容
body := ctx.Response.Body()
if !bytes.Contains(body, []byte("Pprof Profiles")) {
t.Error("expected body to contain 'Pprof Profiles'")
}
if !bytes.Contains(body, []byte("/debug/pprof/profile")) {
t.Error("expected body to contain profile link")
}
if !bytes.Contains(body, []byte("/debug/pprof/heap")) {
t.Error("expected body to contain heap link")
}
if !bytes.Contains(body, []byte("/debug/pprof/goroutine")) {
t.Error("expected body to contain goroutine link")
}
if !bytes.Contains(body, []byte("/debug/pprof/block")) {
t.Error("expected body to contain block link")
}
if !bytes.Contains(body, []byte("/debug/pprof/mutex")) {
t.Error("expected body to contain mutex link")
}
}
func TestPprofHandler_ServeHTTP_PathRouting(t *testing.T) {
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
tests := []struct {
name string
path string
wantBody string
wantStatus int
}{
{
name: "index path",
path: "/debug/pprof",
wantStatus: 200,
wantBody: "Pprof Profiles",
},
{
name: "index path with slash",
path: "/debug/pprof/",
wantStatus: 200,
wantBody: "Pprof Profiles",
},
{
name: "unknown path",
path: "/debug/pprof/unknown",
wantStatus: 404,
wantBody: "Unknown profile",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI(tt.path)
h.ServeHTTP(ctx)
if ctx.Response.StatusCode() != tt.wantStatus {
t.Errorf("expected status %d, got %d", tt.wantStatus, ctx.Response.StatusCode())
}
if tt.wantBody != "" {
body := string(ctx.Response.Body())
if !strings.Contains(body, tt.wantBody) {
t.Errorf("expected body to contain '%s', got '%s'", tt.wantBody, body)
}
}
})
}
}
func TestPprofHandler_ServeHTTP_Forbidden(t *testing.T) {
// 创建只允许特定 IP 的 handler
_, ipNet, _ := net.ParseCIDR("10.0.0.1/32")
h := &PprofHandler{
allowed: []net.IPNet{*ipNet},
}
// 由于无法轻松设置 RemoteIP我们直接测试 isAllowed 返回 false 的情况
// 通过构造一个 allowed 非空的情况来触发检查
// 验证 handler 配置正确
if len(h.allowed) != 1 {
t.Errorf("expected 1 allowed IPNet, got %d", len(h.allowed))
}
// 验证 allowed 包含配置的 IP
expectedIP := net.ParseIP("10.0.0.1")
if !h.allowed[0].Contains(expectedIP) {
t.Error("expected allowed to contain configured IP")
}
}
func TestPprofHandler_handleCPU_Params(t *testing.T) {
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
tests := []struct {
name string
seconds string
wantType string
}{
{
name: "default seconds",
seconds: "",
wantType: "application/octet-stream",
},
{
name: "custom seconds",
seconds: "1",
wantType: "application/octet-stream",
},
{
name: "invalid seconds",
seconds: "invalid",
wantType: "application/octet-stream",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
if tt.seconds != "" {
ctx.Request.SetRequestURI("/debug/pprof/profile?seconds=" + tt.seconds)
} else {
ctx.Request.SetRequestURI("/debug/pprof/profile")
}
// 注意handleCPU 会启动实际的 CPU profile需要特殊处理
// 这里主要验证 Content-Type 设置正确
// 实际 profile 测试需要更复杂的设置
// 验证 handler 配置
if h.path != "/debug/pprof" {
t.Error("unexpected handler path")
}
})
}
}
// TestPprofHandler_handleCPU 测试 handleCPU 函数的参数解析。
// 使用 1 秒的最小采集时间来确保测试快速完成。
func TestPprofHandler_handleCPU(t *testing.T) {
t.Run("default seconds (30s) - verify headers only", func(t *testing.T) {
// 注意:默认 30 秒太长,这里只验证请求解析逻辑
// 实际的 profile 采集在 TestPprofHandler_handleCPU_WithShortDuration 中测试
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/profile")
// 验证请求路径解析正确
path := string(ctx.Path())
if path != "/debug/pprof/profile" {
t.Errorf("unexpected path: %s", path)
}
// 验证没有 seconds 参数时 QueryArgs 为空
secArg := ctx.QueryArgs().Peek("seconds")
if secArg != nil {
t.Errorf("expected no seconds arg, got: %s", secArg)
}
})
t.Run("custom seconds parameter", func(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/profile?seconds=5")
// 验证 seconds 参数解析正确
secArg := ctx.QueryArgs().Peek("seconds")
if string(secArg) != "5" {
t.Errorf("expected seconds=5, got: %s", secArg)
}
})
t.Run("invalid seconds parameter", func(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/profile?seconds=invalid")
// 验证参数存在
secArg := ctx.QueryArgs().Peek("seconds")
if string(secArg) != "invalid" {
t.Errorf("expected seconds=invalid, got: %s", secArg)
}
// strconv.Atoi("invalid") 会返回错误,函数会使用默认值 30
})
}
// TestPprofHandler_handleCPU_Execute 执行 handleCPU 并验证响应。
// 使用 1 秒采集时间来快速完成测试。
func TestPprofHandler_handleCPU_Execute(t *testing.T) {
// 确保之前的 CPU profile 已停止
stopCPUProfile()
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/profile?seconds=1")
// 执行 handleCPU
h.handleCPU(ctx)
// 验证 Content-Type
contentType := string(ctx.Response.Header.Peek("Content-Type"))
if contentType != "application/octet-stream" {
t.Errorf("expected Content-Type application/octet-stream, got: %s", contentType)
}
// 验证响应体CPU profile 数据)
body := ctx.Response.Body()
if len(body) == 0 {
t.Error("expected CPU profile output, got empty body")
}
// 验证响应体包含 pprof header 标识
// pprof 文件以特定的 magic number 开头
if len(body) > 0 {
// pprof 格式的文件应该有内容
t.Logf("CPU profile size: %d bytes", len(body))
}
}
// TestPprofHandler_handleCPU_NegativeSeconds 测试负数秒数参数解析。
// 负数秒会被 sec > 0 检查过滤,使用默认值 30 秒。
func TestPprofHandler_handleCPU_NegativeSeconds(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/profile?seconds=-1")
// 验证参数解析
secArg := ctx.QueryArgs().Peek("seconds")
if string(secArg) != "-1" {
t.Errorf("expected seconds=-1, got: %s", secArg)
}
// 注意:负数秒在 handleCPU 中会被 sec > 0 检查过滤,使用默认值 30 秒
// 为了测试效率,这里只验证参数解析,不实际执行 handleCPU
}
// TestPprofHandler_handleCPU_ZeroSeconds 测试零秒参数解析。
// 零秒会被 sec > 0 检查过滤,使用默认值 30 秒。
func TestPprofHandler_handleCPU_ZeroSeconds(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/profile?seconds=0")
// 验证参数解析
secArg := ctx.QueryArgs().Peek("seconds")
if string(secArg) != "0" {
t.Errorf("expected seconds=0, got: %s", secArg)
}
// 注意:零秒在 handleCPU 中会被 sec > 0 检查过滤,使用默认值 30 秒
// 为了测试效率,这里只验证参数解析,不实际执行 handleCPU
}
// TestPprofHandler_handleCPU_LargeSeconds 测试大数值秒数参数解析。
func TestPprofHandler_handleCPU_LargeSeconds(t *testing.T) {
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/profile?seconds=999999")
// 验证参数解析
secArg := ctx.QueryArgs().Peek("seconds")
if string(secArg) != "999999" {
t.Errorf("expected seconds=999999, got: %s", secArg)
}
// 注意:这里只验证参数解析不会溢出
// 为了测试效率,不实际执行 handleCPU会等待 999999 秒)
}
func TestPprofHandler_ConfigWithCIDRAndIP(t *testing.T) {
// 测试混合配置
cfg := &config.PprofConfig{
Enabled: true,
Path: "/debug/pprof",
Allow: []string{"127.0.0.1", "192.168.0.0/24", "::1"},
}
h, err := NewPprofHandler(cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil handler")
}
// 验证 IP 和 CIDR 都被正确解析(现在统一存储在 allowed 中)
// 127.0.0.1 -> 127.0.0.1/32
// ::1 -> ::1/128
// 192.168.0.0/24 保持不变
if len(h.allowed) != 3 {
t.Errorf("expected 3 allowed entries (2 IPs converted to /32 and /128, 1 CIDR), got %d", len(h.allowed))
}
// 验证具体内容 - 使用 Contains 检查
foundV4 := false
foundV6 := false
foundCIDR := false
for _, ipNet := range h.allowed {
if ipNet.Contains(net.ParseIP("127.0.0.1")) && ipNet.String() == "127.0.0.1/32" {
foundV4 = true
}
if ipNet.Contains(net.ParseIP("::1")) && ipNet.String() == "::1/128" {
foundV6 = true
}
if ipNet.String() == "192.168.0.0/24" {
foundCIDR = true
}
}
if !foundV4 {
t.Error("expected to find 127.0.0.1/32 in allowed")
}
if !foundV6 {
t.Error("expected to find ::1/128 in allowed")
}
if !foundCIDR {
t.Error("expected to find 192.168.0.0/24 in allowed")
}
}
func TestPprofHandler_DefaultLocalhostBehavior(t *testing.T) {
// 测试空配置时默认只允许 localhost
cfg := &config.PprofConfig{
Enabled: true,
Path: "/debug/pprof",
Allow: []string{},
}
h, err := NewPprofHandler(cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if h == nil {
t.Fatal("expected non-nil handler")
}
// 验证默认允许 localhost (解析为 127.0.0.1/32 和 ::1/128)
if len(h.allowed) != 2 {
t.Errorf("expected 2 default allowed entries (127.0.0.1/32 and ::1/128), got %d", len(h.allowed))
}
// 验证包含 IPv4 和 IPv6 localhost
hasV4 := false
hasV6 := false
for _, n := range h.allowed {
if n.Contains(net.ParseIP("127.0.0.1")) && n.String() == "127.0.0.1/32" {
hasV4 = true
}
if n.Contains(net.ParseIP("::1")) && n.String() == "::1/128" {
hasV6 = true
}
}
if !hasV4 {
t.Error("expected default to include 127.0.0.1/32")
}
if !hasV6 {
t.Error("expected default to include ::1/128")
}
}
func TestPprofHandler_handleHeap(t *testing.T) {
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/heap")
h.handleHeap(ctx)
// 验证状态码
if ctx.Response.StatusCode() != 200 {
t.Errorf("expected status 200, got %d", ctx.Response.StatusCode())
}
// 验证 Content-Type
contentType := string(ctx.Response.Header.Peek("Content-Type"))
if contentType != "application/octet-stream" {
t.Errorf("expected Content-Type application/octet-stream, got %s", contentType)
}
}
func TestPprofHandler_handleGoroutine(t *testing.T) {
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/goroutine")
h.handleGoroutine(ctx)
// 验证状态码
if ctx.Response.StatusCode() != 200 {
t.Errorf("expected status 200, got %d", ctx.Response.StatusCode())
}
// 验证 Content-Type
contentType := string(ctx.Response.Header.Peek("Content-Type"))
if contentType != "application/octet-stream" {
t.Errorf("expected Content-Type application/octet-stream, got %s", contentType)
}
}
func TestPprofHandler_handleBlock(t *testing.T) {
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/block")
h.handleBlock(ctx)
// 验证状态码
if ctx.Response.StatusCode() != 200 {
t.Errorf("expected status 200, got %d", ctx.Response.StatusCode())
}
// 验证 Content-Type
contentType := string(ctx.Response.Header.Peek("Content-Type"))
if contentType != "application/octet-stream" {
t.Errorf("expected Content-Type application/octet-stream, got %s", contentType)
}
}
func TestPprofHandler_handleMutex(t *testing.T) {
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("/debug/pprof/mutex")
h.handleMutex(ctx)
// 验证状态码
if ctx.Response.StatusCode() != 200 {
t.Errorf("expected status 200, got %d", ctx.Response.StatusCode())
}
// 验证 Content-Type
contentType := string(ctx.Response.Header.Peek("Content-Type"))
if contentType != "application/octet-stream" {
t.Errorf("expected Content-Type application/octet-stream, got %s", contentType)
}
}
// TestPprofHandler_isAllowed_RemoteIP 测试 isAllowed 方法使用 RemoteIP。
func TestPprofHandler_isAllowed_RemoteIP(t *testing.T) {
t.Run("empty allow lists - allow all", func(t *testing.T) {
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{},
}
ctx := &fasthttp.RequestCtx{}
// isAllowed should return true when no restrictions
if !h.isAllowed(ctx) {
t.Error("expected isAllowed to return true with empty allow lists")
}
})
t.Run("with allow list but cannot parse IP", func(t *testing.T) {
_, ipNet, _ := net.ParseCIDR("192.168.1.1/32")
h := &PprofHandler{
path: "/debug/pprof",
allowed: []net.IPNet{*ipNet},
}
ctx := &fasthttp.RequestCtx{}
// RemoteIP returns 0.0.0.0 for nil connection, which may not parse
// The function should handle this gracefully
result := h.isAllowed(ctx)
// Result depends on whether RemoteIP can be parsed
_ = result
})
}