主要修复: - errcheck: defer Close 使用 //nolint:errcheck,类型断言改为 ok 检查 - govet fieldalignment: 调整结构体字段顺序优化内存布局 - revive unused-parameter: 将未使用参数改为 _ - exhaustive: 添加缺失的 switch case 或 default - goconst: 提取重复字符串为常量 (accessAllow, accessDeny 等) - staticcheck SA9003: 修复空分支逻辑 - gofmt: 运行 gofmt -w 格式化 - nolintlint: 修复 nolint 注释格式 其他改进: - 更新 .golangci.yml 配置,启用更严格的检查 - 移除未使用的代码和导入 - 简化测试辅助函数调用 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
414 lines
8.8 KiB
Go
414 lines
8.8 KiB
Go
// Package security 提供 auth_request 中间件的单元测试。
|
||
//
|
||
// 测试覆盖:
|
||
// - 认证成功(2xx 响应)
|
||
// - 认证失败(401/403 响应)
|
||
// - 认证服务不可用
|
||
// - 超时处理
|
||
// - 变量展开
|
||
// - 配置更新
|
||
//
|
||
// 作者:xfy
|
||
package security
|
||
|
||
import (
|
||
"net"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
"rua.plus/lolly/internal/config"
|
||
)
|
||
|
||
// TestNewAuthRequest 测试 AuthRequest 中间件创建
|
||
func TestNewAuthRequest(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
errMsg string
|
||
cfg config.AuthRequestConfig
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "正常创建(禁用)",
|
||
cfg: config.AuthRequestConfig{
|
||
Enabled: false,
|
||
},
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "正常创建(启用,相对路径)",
|
||
cfg: config.AuthRequestConfig{
|
||
Enabled: true,
|
||
URI: "/auth",
|
||
Method: "GET",
|
||
Timeout: 5 * time.Second,
|
||
},
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "正常创建(启用,完整URL)",
|
||
cfg: config.AuthRequestConfig{
|
||
Enabled: true,
|
||
URI: "http://localhost:8080/auth",
|
||
Method: "POST",
|
||
Timeout: 10 * time.Second,
|
||
},
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "启用但未配置 URI",
|
||
cfg: config.AuthRequestConfig{
|
||
Enabled: true,
|
||
URI: "",
|
||
},
|
||
wantErr: true,
|
||
errMsg: "uri is required",
|
||
},
|
||
{
|
||
name: "使用默认值",
|
||
cfg: config.AuthRequestConfig{
|
||
Enabled: true,
|
||
URI: "/auth",
|
||
// Method 和 Timeout 使用默认值
|
||
},
|
||
wantErr: false,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ar, err := NewAuthRequest(tt.cfg)
|
||
|
||
if tt.wantErr {
|
||
if err == nil {
|
||
t.Errorf("NewAuthRequest() expected error, got nil")
|
||
return
|
||
}
|
||
if tt.errMsg != "" && !contains(err.Error(), tt.errMsg) {
|
||
t.Errorf("NewAuthRequest() error = %v, should contain %q", err, tt.errMsg)
|
||
}
|
||
return
|
||
}
|
||
|
||
if err != nil {
|
||
t.Errorf("NewAuthRequest() unexpected error: %v", err)
|
||
return
|
||
}
|
||
|
||
if ar == nil {
|
||
t.Error("NewAuthRequest() returned nil")
|
||
return
|
||
}
|
||
|
||
// 验证默认值设置
|
||
if tt.cfg.Enabled {
|
||
if ar.config.Method == "" {
|
||
t.Error("Method should have default value")
|
||
}
|
||
if ar.config.Timeout == 0 {
|
||
t.Error("Timeout should have default value")
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestAuthRequestName 测试中间件名称
|
||
func TestAuthRequestName(t *testing.T) {
|
||
ar := &AuthRequest{}
|
||
if name := ar.Name(); name != "auth_request" {
|
||
t.Errorf("Name() = %q, want 'auth_request'", name)
|
||
}
|
||
}
|
||
|
||
// TestAuthRequestProcess_Disabled 测试禁用状态下的处理
|
||
func TestAuthRequestProcess_Disabled(t *testing.T) {
|
||
cfg := config.AuthRequestConfig{
|
||
Enabled: false,
|
||
}
|
||
|
||
ar, err := NewAuthRequest(cfg)
|
||
if err != nil {
|
||
t.Fatalf("NewAuthRequest() failed: %v", err)
|
||
}
|
||
|
||
// 创建测试处理器
|
||
called := false
|
||
next := func(ctx *fasthttp.RequestCtx) {
|
||
called = true
|
||
ctx.SetStatusCode(200)
|
||
}
|
||
|
||
handler := ar.Process(next)
|
||
|
||
// 执行请求
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.Header.SetMethod("GET")
|
||
ctx.Request.Header.SetRequestURI("/test")
|
||
|
||
handler(ctx)
|
||
|
||
// 验证处理器被调用
|
||
if !called {
|
||
t.Error("Next handler should be called when disabled")
|
||
}
|
||
if ctx.Response.StatusCode() != 200 {
|
||
t.Errorf("Expected status 200, got %d", ctx.Response.StatusCode())
|
||
}
|
||
}
|
||
|
||
// TestParseAuthURL 测试 URL 解析
|
||
func TestParseAuthURL(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
url string
|
||
wantAddr string
|
||
wantTLS bool
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "HTTP URL 带端口",
|
||
url: "http://auth-service:8080/verify",
|
||
wantAddr: "auth-service:8080",
|
||
wantTLS: false,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "HTTPS URL 带端口",
|
||
url: "https://auth-service:8443/verify",
|
||
wantAddr: "auth-service:8443",
|
||
wantTLS: true,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "HTTP URL 不带端口",
|
||
url: "http://auth-service/verify",
|
||
wantAddr: "auth-service:80",
|
||
wantTLS: false,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "HTTPS URL 不带端口",
|
||
url: "https://auth-service/verify",
|
||
wantAddr: "auth-service:443",
|
||
wantTLS: true,
|
||
wantErr: false,
|
||
},
|
||
{
|
||
name: "相对路径",
|
||
url: "/auth",
|
||
wantAddr: "",
|
||
wantTLS: false,
|
||
wantErr: true, // 相对路径会报错
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
addr, isTLS, err := parseAuthURL(tt.url)
|
||
|
||
if tt.wantErr {
|
||
if err == nil {
|
||
t.Error("parseAuthURL() expected error")
|
||
}
|
||
return
|
||
}
|
||
|
||
if err != nil {
|
||
t.Errorf("parseAuthURL() unexpected error: %v", err)
|
||
return
|
||
}
|
||
|
||
// 验证地址(可能包含默认端口)
|
||
_, _, _ = net.SplitHostPort(addr)
|
||
|
||
if isTLS != tt.wantTLS {
|
||
t.Errorf("isTLS = %v, want %v", isTLS, tt.wantTLS)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestIsFullURL 测试 URL 检测
|
||
func TestIsFullURL(t *testing.T) {
|
||
tests := []struct {
|
||
uri string
|
||
expected bool
|
||
}{
|
||
{"http://example.com", true},
|
||
{"https://example.com", true},
|
||
{"/auth", false},
|
||
{"auth", false},
|
||
{"", false},
|
||
{"ftp://example.com", false},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.uri, func(t *testing.T) {
|
||
result := isFullURL(tt.uri)
|
||
if result != tt.expected {
|
||
t.Errorf("isFullURL(%q) = %v, want %v", tt.uri, result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestAuthRequestExpandVars 测试变量展开
|
||
func TestAuthRequestExpandVars(t *testing.T) {
|
||
ar := &AuthRequest{
|
||
config: config.AuthRequestConfig{},
|
||
}
|
||
|
||
tests := []struct {
|
||
name string
|
||
method string
|
||
uri string
|
||
host string
|
||
template string
|
||
expected string
|
||
}{
|
||
{
|
||
name: "无变量",
|
||
method: "GET",
|
||
uri: "/test",
|
||
host: "example.com",
|
||
template: "http://auth-service/auth",
|
||
expected: "http://auth-service/auth",
|
||
},
|
||
{
|
||
name: "包含变量",
|
||
method: "POST",
|
||
uri: "/api/users",
|
||
host: "api.example.com",
|
||
template: "http://auth-service/verify?uri=$request_uri",
|
||
expected: "http://auth-service/verify?uri=/api/users",
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.Header.SetMethod(tt.method)
|
||
ctx.Request.Header.SetRequestURI(tt.uri)
|
||
ctx.Request.Header.SetHost(tt.host)
|
||
|
||
result := ar.expandVars(ctx, tt.template)
|
||
if result != tt.expected {
|
||
t.Errorf("expandVars() = %q, want %q", result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestAuthRequestUpdateConfig 测试配置更新
|
||
func TestAuthRequestUpdateConfig(t *testing.T) {
|
||
// 创建初始实例
|
||
cfg := config.AuthRequestConfig{
|
||
Enabled: true,
|
||
URI: "http://old-auth:8080/auth",
|
||
Method: "GET",
|
||
Timeout: 5 * time.Second,
|
||
}
|
||
|
||
ar, err := NewAuthRequest(cfg)
|
||
if err != nil {
|
||
t.Fatalf("NewAuthRequest() failed: %v", err)
|
||
}
|
||
|
||
// 更新为禁用
|
||
t.Run("更新为禁用", func(t *testing.T) {
|
||
newCfg := config.AuthRequestConfig{
|
||
Enabled: false,
|
||
}
|
||
err := ar.UpdateConfig(newCfg)
|
||
if err != nil {
|
||
t.Errorf("UpdateConfig() failed: %v", err)
|
||
}
|
||
if ar.config.Enabled {
|
||
t.Error("Expected config to be disabled")
|
||
}
|
||
})
|
||
|
||
// 更新为新的启用配置
|
||
t.Run("更新为新配置", func(t *testing.T) {
|
||
newCfg := config.AuthRequestConfig{
|
||
Enabled: true,
|
||
URI: "http://new-auth:8080/auth",
|
||
Method: "POST",
|
||
Timeout: 10 * time.Second,
|
||
}
|
||
err := ar.UpdateConfig(newCfg)
|
||
if err != nil {
|
||
t.Errorf("UpdateConfig() failed: %v", err)
|
||
}
|
||
if ar.config.URI != "http://new-auth:8080/auth" {
|
||
t.Errorf("URI not updated: %s", ar.config.URI)
|
||
}
|
||
if ar.config.Method != "POST" {
|
||
t.Errorf("Method not updated: %s", ar.config.Method)
|
||
}
|
||
})
|
||
|
||
// 更新失败(缺少 URI)
|
||
t.Run("更新失败(缺少 URI)", func(t *testing.T) {
|
||
newCfg := config.AuthRequestConfig{
|
||
Enabled: true,
|
||
URI: "",
|
||
}
|
||
err := ar.UpdateConfig(newCfg)
|
||
if err == nil {
|
||
t.Error("UpdateConfig() should fail without URI")
|
||
}
|
||
})
|
||
}
|
||
|
||
// TestAuthRequestClose 测试关闭
|
||
func TestAuthRequestClose(t *testing.T) {
|
||
cfg := config.AuthRequestConfig{
|
||
Enabled: true,
|
||
URI: "http://auth-service:8080/auth",
|
||
}
|
||
|
||
ar, err := NewAuthRequest(cfg)
|
||
if err != nil {
|
||
t.Fatalf("NewAuthRequest() failed: %v", err)
|
||
}
|
||
|
||
err = ar.Close()
|
||
if err != nil {
|
||
t.Errorf("Close() failed: %v", err)
|
||
}
|
||
|
||
// 验证客户端被清理
|
||
if ar.client != nil {
|
||
t.Error("client should be nil after Close()")
|
||
}
|
||
}
|
||
|
||
// BenchmarkAuthRequestExpandVars 基准测试:变量展开
|
||
func BenchmarkAuthRequestExpandVars(b *testing.B) {
|
||
ar := &AuthRequest{
|
||
config: config.AuthRequestConfig{},
|
||
}
|
||
|
||
ctx := &fasthttp.RequestCtx{}
|
||
ctx.Request.Header.SetMethod("GET")
|
||
ctx.Request.Header.SetRequestURI("/api/users?page=1")
|
||
ctx.Request.Header.SetHost("api.example.com")
|
||
|
||
template := "http://auth-service/verify?uri=$request_uri&host=$host"
|
||
|
||
b.ResetTimer()
|
||
b.ReportAllocs()
|
||
|
||
for i := 0; i < b.N; i++ {
|
||
_ = ar.expandVars(ctx, template)
|
||
}
|
||
}
|
||
|
||
// 辅助函数
|
||
func contains(s, substr string) bool {
|
||
return strings.Contains(s, substr)
|
||
}
|