lolly/internal/middleware/security/headers_test.go
xfy 9d24263918 feat(stream,server,handler): 实现 Phase 6 性能优化和热升级
新增功能:
- stream 模块: 流式传输支持,优化大文件和实时数据传输
- Goroutine 池: 限制并发数量,减少调度开销
- 优雅升级: 零停机热升级,继承父进程监听器
- sendfile: 零拷贝文件传输,大文件直接从内核传输

重构改进:
- App 结构体封装,支持热升级和信号处理
- 配置结构字段对齐和代码清理
- 完善错误处理和日志记录

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-03 10:39:22 +08:00

248 lines
6.3 KiB
Go

package security
import (
"testing"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
)
func TestNewSecurityHeaders(t *testing.T) {
tests := []struct {
name string
cfg *config.SecurityHeaders
}{
{
name: "nil config uses defaults",
cfg: nil,
},
{
name: "custom config",
cfg: &config.SecurityHeaders{
XFrameOptions: "SAMEORIGIN",
XContentTypeOptions: "nosniff",
ContentSecurityPolicy: "default-src 'self'",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
sh := NewSecurityHeaders(tt.cfg)
if sh == nil {
t.Error("Expected non-nil SecurityHeadersMiddleware")
}
})
}
}
func TestSecurityHeadersName(t *testing.T) {
sh := NewSecurityHeaders(nil)
if sh.Name() != "security_headers" {
t.Errorf("Expected name 'security_headers', got %s", sh.Name())
}
}
func TestSecurityHeadersProcess(t *testing.T) {
cfg := &config.SecurityHeaders{
XFrameOptions: "DENY",
XContentTypeOptions: "nosniff",
ContentSecurityPolicy: "default-src 'self'",
ReferrerPolicy: "strict-origin-when-cross-origin",
PermissionsPolicy: "geolocation=()",
}
sh := NewSecurityHeaders(cfg)
handlerCalled := false
nextHandler := func(ctx *fasthttp.RequestCtx) {
handlerCalled = true
ctx.WriteString("OK")
}
handler := sh.Process(nextHandler)
if handler == nil {
t.Fatal("Process() returned nil handler")
}
// Create request context and call handler
ctx := &fasthttp.RequestCtx{}
handler(ctx)
// Check headers were set
headers := &ctx.Response.Header
if string(headers.Peek("X-Frame-Options")) != "DENY" {
t.Errorf("X-Frame-Options not set correctly, got %s", headers.Peek("X-Frame-Options"))
}
if string(headers.Peek("X-Content-Type-Options")) != "nosniff" {
t.Errorf("X-Content-Type-Options not set correctly, got %s", headers.Peek("X-Content-Type-Options"))
}
if string(headers.Peek("Content-Security-Policy")) != "default-src 'self'" {
t.Errorf("Content-Security-Policy not set correctly, got %s", headers.Peek("Content-Security-Policy"))
}
if string(headers.Peek("Referrer-Policy")) != "strict-origin-when-cross-origin" {
t.Errorf("Referrer-Policy not set correctly, got %s", headers.Peek("Referrer-Policy"))
}
if string(headers.Peek("Permissions-Policy")) != "geolocation=()" {
t.Errorf("Permissions-Policy not set correctly, got %s", headers.Peek("Permissions-Policy"))
}
if !handlerCalled {
t.Error("Next handler was not called")
}
}
func TestSecurityHeadersHSTS(t *testing.T) {
cfg := &config.SecurityHeaders{
XFrameOptions: "DENY",
}
sh := NewSecurityHeaders(cfg)
nextHandler := func(ctx *fasthttp.RequestCtx) {
ctx.WriteString("OK")
}
handler := sh.Process(nextHandler)
// Simulate TLS connection
ctx := &fasthttp.RequestCtx{}
ctx.Request.SetRequestURI("https://example.com/")
// Note: In actual testing, ctx.IsTLS() requires connection setup
handler(ctx)
// HSTS header would be set when TLS is active
// In this test we verify the handler doesn't panic
}
func TestSecurityHeadersUpdate(t *testing.T) {
sh := NewSecurityHeaders(nil)
// Update X-Frame-Options
sh.SetXFrameOptions("SAMEORIGIN")
cfg := sh.GetConfig()
if cfg.XFrameOptions != "SAMEORIGIN" {
t.Errorf("Expected X-Frame-Options 'SAMEORIGIN', got %s", cfg.XFrameOptions)
}
// Update CSP
sh.SetContentSecurityPolicy("default-src 'unsafe-inline'")
cfg = sh.GetConfig()
if cfg.ContentSecurityPolicy != "default-src 'unsafe-inline'" {
t.Errorf("Expected CSP update, got %s", cfg.ContentSecurityPolicy)
}
// Update Referrer-Policy
sh.SetReferrerPolicy("no-referrer")
cfg = sh.GetConfig()
if cfg.ReferrerPolicy != "no-referrer" {
t.Errorf("Expected Referrer-Policy 'no-referrer', got %s", cfg.ReferrerPolicy)
}
// Update Permissions-Policy
sh.SetPermissionsPolicy("camera=()")
cfg = sh.GetConfig()
if cfg.PermissionsPolicy != "camera=()" {
t.Errorf("Expected Permissions-Policy 'camera=()', got %s", cfg.PermissionsPolicy)
}
}
func TestUpdateConfig(t *testing.T) {
sh := NewSecurityHeaders(nil)
newCfg := &config.SecurityHeaders{
XFrameOptions: "DENY",
ReferrerPolicy: "no-referrer",
}
sh.UpdateConfig(newCfg)
cfg := sh.GetConfig()
if cfg.XFrameOptions != "DENY" {
t.Errorf("Expected X-Frame-Options 'DENY', got %s", cfg.XFrameOptions)
}
if cfg.ReferrerPolicy != "no-referrer" {
t.Errorf("Expected Referrer-Policy 'no-referrer', got %s", cfg.ReferrerPolicy)
}
}
func TestDefaultSecurityHeaders(t *testing.T) {
cfg := DefaultSecurityHeaders()
if cfg.XFrameOptions != "DENY" {
t.Errorf("Expected default X-Frame-Options 'DENY', got %s", cfg.XFrameOptions)
}
if cfg.XContentTypeOptions != "nosniff" {
t.Errorf("Expected default X-Content-Type-Options 'nosniff', got %s", cfg.XContentTypeOptions)
}
}
func TestStrictSecurityHeaders(t *testing.T) {
cfg := StrictSecurityHeaders()
if cfg.XFrameOptions != "DENY" {
t.Errorf("Expected X-Frame-Options 'DENY', got %s", cfg.XFrameOptions)
}
if cfg.ReferrerPolicy != "no-referrer" {
t.Errorf("Expected Referrer-Policy 'no-referrer', got %s", cfg.ReferrerPolicy)
}
if cfg.ContentSecurityPolicy == "" {
t.Error("Expected non-empty CSP for strict config")
}
}
func TestDevelopmentSecurityHeaders(t *testing.T) {
cfg := DevelopmentSecurityHeaders()
if cfg.XFrameOptions != "SAMEORIGIN" {
t.Errorf("Expected X-Frame-Options 'SAMEORIGIN' for dev, got %s", cfg.XFrameOptions)
}
}
func TestFormatHSTSValue(t *testing.T) {
tests := []struct {
name string
maxAge int
includeSubDomains bool
preload bool
expected string
}{
{
name: "basic HSTS",
maxAge: 31536000,
includeSubDomains: true,
preload: false,
expected: "max-age=31536000; includeSubDomains",
},
{
name: "HSTS with preload",
maxAge: 31536000,
includeSubDomains: true,
preload: true,
expected: "max-age=31536000; includeSubDomains; preload",
},
{
name: "HSTS without subdomains",
maxAge: 86400,
includeSubDomains: false,
preload: false,
expected: "max-age=86400",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := formatHSTSValue(tt.maxAge, tt.includeSubDomains, tt.preload)
if result != tt.expected {
t.Errorf("formatHSTSValue() = %s, expected %s", result, tt.expected)
}
})
}
}