Two related fixes that must land together: 1. config.Load() now starts from DefaultConfig() before unmarshaling YAML. This ensures missing top-level fields (Performance, Monitoring, Resolver) use their documented defaults instead of zero values. Most importantly, file_cache is no longer silently disabled when users omit the performance: section. 2. startSingleMode() now checks Monitoring.Status.Enabled instead of Path/Allow to decide whether to register the status endpoint. Without this change, fix #1 would have caused a regression where the status handler is registered even when monitoring is disabled, because DefaultConfig() sets Path and Allow defaults. Also replace remaining log.Printf in status.go and lua/api_timer.go with zerolog to follow project logging conventions. Added tests: - config/load_test.go: verifies defaults are applied, explicit values override defaults, and monitoring stays disabled by default. - server/monitoring_registration_test.go: verifies /_status is only registered when enabled and remains reachable with static handler on path: /.
130 lines
3.3 KiB
Go
130 lines
3.3 KiB
Go
package server
|
||
|
||
import (
|
||
"testing"
|
||
"time"
|
||
|
||
"github.com/stretchr/testify/assert"
|
||
"github.com/valyala/fasthttp"
|
||
"rua.plus/lolly/internal/config"
|
||
"rua.plus/lolly/internal/matcher"
|
||
)
|
||
|
||
func TestMonitoringEndpoints_OnlyRegisteredWhenEnabled(t *testing.T) {
|
||
// Case 1: monitoring 未启用时,/_status 不应注册
|
||
cfg := &config.Config{
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Static: []config.StaticConfig{{
|
||
Path: "/",
|
||
Root: t.TempDir(),
|
||
}},
|
||
}},
|
||
}
|
||
|
||
srv := New(cfg)
|
||
go srv.Start()
|
||
defer srv.StopWithTimeout(5 * time.Second)
|
||
|
||
var addr string
|
||
for start := time.Now(); time.Since(start) < 2*time.Second; time.Sleep(10 * time.Millisecond) {
|
||
listeners := srv.GetListeners()
|
||
if len(listeners) > 0 {
|
||
addr = listeners[0].Addr().String()
|
||
break
|
||
}
|
||
}
|
||
if addr == "" {
|
||
t.Fatal("server has no listeners")
|
||
}
|
||
|
||
client := &fasthttp.Client{}
|
||
req := fasthttp.AcquireRequest()
|
||
resp := fasthttp.AcquireResponse()
|
||
defer fasthttp.ReleaseRequest(req)
|
||
defer fasthttp.ReleaseResponse(resp)
|
||
|
||
req.SetRequestURI("http://" + addr + "/_status")
|
||
req.Header.SetMethod("GET")
|
||
|
||
if err := client.Do(req, resp); err != nil {
|
||
t.Fatalf("request failed: %v", err)
|
||
}
|
||
|
||
// 未启用时应返回 404(被 static handler 处理,找不到文件)
|
||
assert.Equal(t, fasthttp.StatusNotFound, resp.StatusCode(),
|
||
"status endpoint should NOT be registered when monitoring is disabled")
|
||
}
|
||
|
||
func TestMonitoringEndpoints_ReachableWhenEnabled(t *testing.T) {
|
||
cfg := &config.Config{
|
||
Monitoring: config.MonitoringConfig{
|
||
Status: config.StatusConfig{
|
||
Enabled: true,
|
||
Path: "/_status",
|
||
Allow: []string{"127.0.0.1"},
|
||
},
|
||
},
|
||
Servers: []config.ServerConfig{{
|
||
Listen: "127.0.0.1:0",
|
||
Static: []config.StaticConfig{{
|
||
Path: "/",
|
||
Root: t.TempDir(),
|
||
}},
|
||
}},
|
||
}
|
||
|
||
srv := New(cfg)
|
||
go srv.Start()
|
||
defer srv.StopWithTimeout(5 * time.Second)
|
||
|
||
var addr string
|
||
for start := time.Now(); time.Since(start) < 2*time.Second; time.Sleep(10 * time.Millisecond) {
|
||
listeners := srv.GetListeners()
|
||
if len(listeners) > 0 {
|
||
addr = listeners[0].Addr().String()
|
||
break
|
||
}
|
||
}
|
||
if addr == "" {
|
||
t.Fatal("server has no listeners")
|
||
}
|
||
|
||
client := &fasthttp.Client{}
|
||
req := fasthttp.AcquireRequest()
|
||
resp := fasthttp.AcquireResponse()
|
||
defer fasthttp.ReleaseRequest(req)
|
||
defer fasthttp.ReleaseResponse(resp)
|
||
|
||
req.SetRequestURI("http://" + addr + "/_status")
|
||
req.Header.SetMethod("GET")
|
||
|
||
if err := client.Do(req, resp); err != nil {
|
||
t.Fatalf("request failed: %v", err)
|
||
}
|
||
|
||
assert.Equal(t, fasthttp.StatusOK, resp.StatusCode(),
|
||
"status endpoint should be reachable when enabled, even with static handler on /")
|
||
}
|
||
|
||
func TestLocationEngine_StatusExactBeatsStaticPrefix(t *testing.T) {
|
||
// 独立验证 location engine 的优先级:exact match 应该 beat prefix /
|
||
engine := matcher.NewLocationEngine()
|
||
|
||
engine.AddExact("/_status", func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(fasthttp.StatusOK)
|
||
}, false)
|
||
engine.AddPrefix("/", func(ctx *fasthttp.RequestCtx) {
|
||
ctx.SetStatusCode(fasthttp.StatusNotFound)
|
||
}, false)
|
||
engine.MarkInitialized()
|
||
|
||
result := engine.Match([]byte("/_status"))
|
||
if result == nil {
|
||
t.Fatal("expected match")
|
||
}
|
||
if result.LocationType != matcher.LocationTypeExact {
|
||
t.Errorf("expected exact match, got %s", result.LocationType)
|
||
}
|
||
}
|