test(06-05): close framework coverage gaps in bouncer, surf, wire, fetchguard

- Registry neither-interface, nil-registry, and authenticate default
- MemoryStore sweep actually drops expired entries
- RegisterHouseMiddlewareFactory duplicate-name failure
- pathScopedCORS unmatched path plus empty-raw-group introspection
- Time UnmarshalJSON +00:00/Z and PublicOnlyMode host/IP cases
This commit is contained in:
Jakub Zych
2026-09-19 21:08:04 +02:00
parent f61776d0a6
commit 98dd09847a
6 changed files with 520 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
package surf
import (
"net/http"
"net/http/httptest"
"testing"
)
// Gap (d): pathScopedCORS with a path matching NONE of the configured
// globs. TestCORSPathScopedHeaders already covers the two named fonoteka
// groups; this fixture is framework-only (/healthz vs api/*).
func TestCORSPathScopedNoMatchIndependentOfFonoteka(t *testing.T) {
cfg := CORSConfig{
Paths: []string{"api/*", "oauth/mcp/*"},
AllowedMethods: []string{"*"},
AllowedOrigins: []string{"*"},
AllowedHeaders: []string{"*"},
}
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
})
h := pathScopedCORS(cfg, inner)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/healthz", nil))
if rec.Code != http.StatusNoContent {
t.Fatalf("status = %d", rec.Code)
}
if got := rec.Header().Get("Access-Control-Allow-Origin"); got != "" {
t.Fatalf("unmatched path must not set ACAO, got %q", got)
}
}
func TestCORSAllowOriginExactAndPattern(t *testing.T) {
cfg := CORSConfig{
Paths: []string{"api/*"},
AllowedMethods: []string{"GET", "POST"},
AllowedOrigins: []string{"https://app.example.test"},
AllowedOriginsPatterns: []string{`^https://.*\.example\.test$`},
AllowedHeaders: []string{"Authorization", "Content-Type"},
ExposedHeaders: []string{"X-RateLimit-Limit"},
MaxAge: 600,
SupportsCredentials: true,
}
inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
h := pathScopedCORS(cfg, inner)
t.Run("exact origin", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/v1/items", nil)
req.Header.Set("Origin", "https://app.example.test")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Header().Get("Access-Control-Allow-Origin") != "https://app.example.test" {
t.Fatalf("ACAO = %q", rec.Header().Get("Access-Control-Allow-Origin"))
}
if rec.Header().Get("Vary") != "Origin" {
t.Fatalf("Vary = %q", rec.Header().Get("Vary"))
}
if rec.Header().Get("Access-Control-Allow-Credentials") != "true" {
t.Fatal("missing credentials header")
}
if rec.Header().Get("Access-Control-Max-Age") != "600" {
t.Fatalf("Max-Age = %q", rec.Header().Get("Access-Control-Max-Age"))
}
})
t.Run("pattern origin", func(t *testing.T) {
req := httptest.NewRequest(http.MethodOptions, "/api/v1/items", nil)
req.Header.Set("Origin", "https://admin.example.test")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if rec.Code != http.StatusNoContent {
t.Fatalf("preflight status = %d", rec.Code)
}
if rec.Header().Get("Access-Control-Allow-Origin") != "https://admin.example.test" {
t.Fatalf("ACAO = %q", rec.Header().Get("Access-Control-Allow-Origin"))
}
})
t.Run("disallowed origin", func(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/api/v1/items", nil)
req.Header.Set("Origin", "https://evil.test")
rec := httptest.NewRecorder()
h.ServeHTTP(rec, req)
if got := rec.Header().Get("Access-Control-Allow-Origin"); got != "" {
t.Fatalf("disallowed origin ACAO = %q", got)
}
})
}

View File

@@ -0,0 +1,90 @@
package surf
import (
"net/http"
"net/http/httptest"
"net/netip"
"os"
"path/filepath"
"testing"
"time"
"git.golem15.com/golem15/summercms/compass"
)
// Gap (b): MemoryStore's sweep goroutine (purge), not TooManyAttempts' lazy
// expiry. Construct with a short sweep and assert the internal map drops
// an expired entry without calling TooManyAttempts.
func TestMemoryStoreSweepRemovesExpiredEntry(t *testing.T) {
s := NewMemoryStore(15 * time.Millisecond)
t.Cleanup(func() { close(s.stop) })
s.Hit("k", 25*time.Millisecond)
s.mu.Lock()
n := len(s.entries)
s.mu.Unlock()
if n != 1 {
t.Fatalf("after Hit, entries = %d", n)
}
deadline := time.Now().Add(200 * time.Millisecond)
for time.Now().Before(deadline) {
s.mu.Lock()
n = len(s.entries)
s.mu.Unlock()
if n == 0 {
return
}
time.Sleep(10 * time.Millisecond)
}
t.Fatalf("sweep did not drop expired entry, count=%d", n)
}
func TestMemoryStoreAvailableInExpiredAndMissing(t *testing.T) {
s := NewMemoryStore(0)
if d := s.AvailableIn("missing"); d != 0 {
t.Fatalf("missing AvailableIn = %s", d)
}
s.Hit("k", 20*time.Millisecond)
if d := s.AvailableIn("k"); d <= 0 {
t.Fatalf("live AvailableIn = %s", d)
}
time.Sleep(30 * time.Millisecond)
if d := s.AvailableIn("k"); d != 0 {
t.Fatalf("expired AvailableIn = %s", d)
}
}
func TestTrustedProxiesParsesConfig(t *testing.T) {
if TrustedProxies(nil) != nil {
t.Fatal("nil cfg must return nil")
}
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("name: t\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "http.yaml"), []byte("trusted_proxies:\n - 10.0.0.0/8\n - not-a-cidr\n - 192.168.0.0/16\n"), 0o644); err != nil {
t.Fatal(err)
}
cfg, err := compass.Open(compass.Options{Dir: dir, Environ: []string{}})
if err != nil {
t.Fatal(err)
}
got := TrustedProxies(cfg)
if len(got) != 2 || got[0].String() != "10.0.0.0/8" || got[1].String() != "192.168.0.0/16" {
t.Fatalf("TrustedProxies = %v", got)
}
}
func TestClientIPNilRequestAndEmptyXFF(t *testing.T) {
if got := ClientIP(nil, nil); got != "" {
t.Fatalf("nil request = %q", got)
}
trusted := []netip.Prefix{mustPrefix("10.0.0.0/8")}
req := httptest.NewRequest(http.MethodGet, "/", nil)
req.RemoteAddr = "10.0.0.1:443"
if got := ClientIP(req, trusted); got != "10.0.0.1" {
t.Fatalf("trusted RemoteAddr, empty XFF = %q", got)
}
}

View File

@@ -0,0 +1,83 @@
package surf
import (
"net/http"
"strings"
"testing"
"git.golem15.com/golem15/summercms/pact"
)
// Gap (c): RegisterHouseMiddlewareFactory duplicate-name failure. The
// non-house RegisterMiddlewareFactory duplicate path is already covered
// by TestDuplicateMiddlewareFactoryNamesPluginAndName.
func TestDuplicateHouseMiddlewareFactoryNamesPluginAndName(t *testing.T) {
r := New(nil)
fn := func(string) pact.Middleware {
return func(next http.Handler) http.Handler { return next }
}
if err := r.RegisterHouseMiddlewareFactory("golem15.demo", "house.param", fn); err != nil {
t.Fatal(err)
}
err := r.RegisterHouseMiddlewareFactory("golem15.other", "house.param", fn)
if err == nil || !strings.Contains(err.Error(), "golem15.demo") || !strings.Contains(err.Error(), "house.param") {
t.Fatalf("want plugin and factory name in error, got %v", err)
}
}
func TestRegisterHouseMiddlewareFactoryEmptyName(t *testing.T) {
r := New(nil)
err := r.RegisterHouseMiddlewareFactory("golem15.demo", "", func(string) pact.Middleware {
return func(next http.Handler) http.Handler { return next }
})
if err == nil || !strings.Contains(err.Error(), "golem15.demo") {
t.Fatalf("empty factory name: %v", err)
}
}
// Gap (g): Router.Routes() on an empty router, and an empty raw group
// (zero routes). Raw:true is inspectable on the Group itself; Routes()
// only lists registered handlers, so an empty raw group produces no
// RouteInfo — that is structural, not a missing assertion.
func TestRoutesEmptyRouter(t *testing.T) {
r := New(nil)
got := r.Routes()
if got == nil {
t.Fatal("empty router Routes() must return a non-nil empty slice")
}
if len(got) != 0 {
t.Fatalf("empty router Routes() = %#v", got)
}
if (*Router)(nil).Routes() != nil {
t.Fatal("nil router Routes() must return nil")
}
}
func TestEmptyRawGroupRawFlagOnGroupNotRouteInfo(t *testing.T) {
r := New(nil)
var inner *Group
r.GroupRaw("/oauth", nil, func(g pact.Router) {
inner = g.(*Group)
})
if inner == nil || !inner.raw {
t.Fatal("empty GroupRaw must keep raw=true on Group")
}
if len(r.Routes()) != 0 {
t.Fatalf("empty raw group leaked RouteInfo: %v", r.Routes())
}
// Group.GroupRaw (nested) is a distinct method from Router.GroupRaw.
r.Group("/wrap", nil, func(g pact.Router) {
g.GroupRaw("/inner", nil, func(c pact.Router) {
inner = c.(*Group)
})
})
if inner == nil || !inner.raw {
t.Fatal("nested GroupRaw must be raw")
}
if len(r.Routes()) != 0 {
t.Fatalf("nested empty raw group leaked RouteInfo: %v", r.Routes())
}
}