lolly/internal/server/monitoring_registration_test.go
xfy e8fbbf368c fix(config,server): merge defaults on Load and fix monitoring registration
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: /.
2026-06-11 15:08:57 +08:00

130 lines
3.3 KiB
Go
Raw 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
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)
}
}