test(10-05): cover every Phase 10 Go change with branch-level tests

- bouncer TestPhase10CookieGuard: cookie read without Bearer, Bearer wins,
  empty cookie, frontend audience and blacklisted jti rejected
- boardwalk TestPhase10BoardwalkServing: HEAD, query strings, encoded
  traversal, index by name, nested prefix, MIME fallback, constructor errors
- cabana TestPhase10Coverage: mounted unsafe routes vs the CSRF walk, option
  and filter edges, read-only labels, relation message defaults, bundle
  fallback locale, cookie refresh of an expired token in the refresh window
- phrasebook override precedence, new locale, Bundle merge order, Forms shapes
- surf prefix collision for deeper paths and the default /backend prefix
- swagger2openapi TestUnionRewrite and converter branch tests
- framework tests no longer name the application (acme fixtures instead)
This commit is contained in:
Jakub Zych
2026-09-27 18:05:28 +02:00
parent 1c2a66df45
commit ef448da1cc
14 changed files with 1055 additions and 35 deletions

View File

@@ -28,6 +28,8 @@ func TestPhase10AdminPrefixCollision(t *testing.T) {
{"exact prefix", http.MethodGet, "/acme-admin", false},
{"under prefix", http.MethodPost, "/acme-admin/hook", false},
{"raw under api", http.MethodGet, "/acme-admin/api/v1/extra", true},
{"deeper path", http.MethodGet, "/acme-admin/a/b/c", false},
{"exact prefix raw", http.MethodPost, "/acme-admin", true},
} {
t.Run(tc.name, func(t *testing.T) {
app := adminPrefixApp(t)
@@ -57,9 +59,52 @@ func TestPhase10AdminPrefixCollision(t *testing.T) {
t.Fatalf("sibling path rejected: %v", err)
}
})
t.Run("default /backend prefix", func(t *testing.T) {
for _, tc := range []struct {
path string
collide bool
}{
{"/backend", true},
{"/backend/deep/hook", true},
{"/backendx", false},
{"/back", false},
{"/api/backend", false},
} {
app := adminPrefixAppWith(t, "")
plugins := []party.Plugin{
adminPrefixPlugin{},
prefixRoutePlugin{id: "acme.intruder", method: http.MethodGet, path: tc.path},
}
_, err := BuildRouter(app, plugins)
if tc.collide && (err == nil || !strings.Contains(err.Error(), "/backend")) {
t.Fatalf("%s under the default prefix: err=%v", tc.path, err)
}
if !tc.collide && err != nil {
t.Fatalf("%s rejected next to the default prefix: %v", tc.path, err)
}
}
})
t.Run("no admin means no prefix check", func(t *testing.T) {
app := adminPrefixApp(t)
plugins := []party.Plugin{prefixRoutePlugin{id: "acme.site", method: http.MethodGet, path: "/acme-admin"}}
if _, err := BuildRouter(app, plugins); err != nil {
t.Fatalf("a route at the prefix without admin controllers was rejected: %v", err)
}
if err := (&Router{}).checkAdminPrefix(""); err != nil {
t.Fatalf("empty prefix: %v", err)
}
})
}
func adminPrefixApp(t *testing.T) *backpack.App {
t.Helper()
return adminPrefixAppWith(t, "/acme-admin")
}
// adminPrefixAppWith configures backend.uri; an empty uri keeps the default.
func adminPrefixAppWith(t *testing.T, uri string) *backpack.App {
t.Helper()
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("name: admin-prefix\n"), 0o644); err != nil {
@@ -68,8 +113,10 @@ func adminPrefixApp(t *testing.T) *backpack.App {
if err := os.WriteFile(filepath.Join(dir, "http.yaml"), []byte("body_limits:\n default_bytes: 1024\n upload_bytes: 1024\n"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "backend.yaml"), []byte("uri: /acme-admin\n"), 0o644); err != nil {
t.Fatal(err)
if uri != "" {
if err := os.WriteFile(filepath.Join(dir, "backend.yaml"), []byte("uri: "+uri+"\n"), 0o644); err != nil {
t.Fatal(err)
}
}
cfg, err := compass.Open(compass.Options{
Dir: dir,

View File

@@ -7,10 +7,10 @@ import (
)
// Gap (d): pathScopedCORS with a path matching NONE of the configured
// globs. TestCORSPathScopedHeaders already covers the two named fonoteka
// globs. TestCORSPathScopedHeaders already covers the two named acme
// groups; this fixture is framework-only (/healthz vs api/*).
func TestCORSPathScopedNoMatchIndependentOfFonoteka(t *testing.T) {
func TestCORSPathScopedNoMatchIndependentOfAcme(t *testing.T) {
cfg := CORSConfig{
Paths: []string{"api/*", "oauth/mcp/*"},
AllowedMethods: []string{"*"},

View File

@@ -14,11 +14,11 @@ import (
func TestCORSLaravelGlobMatchesNestedPaths(t *testing.T) {
re := compileLaravelGlob("api/*")
if re == nil || !re.MatchString("api/v1/fonoteka/genres") {
t.Fatal("api/* must match api/v1/fonoteka/genres (Laravel Str::is, not Go path.Match)")
if re == nil || !re.MatchString("api/v1/acme/genres") {
t.Fatal("api/* must match api/v1/acme/genres (Laravel Str::is, not Go path.Match)")
}
if re.MatchString("_fonoteka/api/v1/genres") {
t.Fatal("api/* must not match _fonoteka/api/v1/genres")
if re.MatchString("_acme/api/v1/genres") {
t.Fatal("api/* must not match _acme/api/v1/genres")
}
mcp := compileLaravelGlob("oauth/mcp/*")
if mcp == nil || !mcp.MatchString("oauth/mcp/token") {
@@ -34,22 +34,22 @@ func TestCORSPathScopedHeaders(t *testing.T) {
AllowedHeaders: []string{"*"},
}
mux := http.NewServeMux()
mux.HandleFunc("GET /api/v1/fonoteka/genres", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("GET /api/v1/acme/genres", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("GET /_fonoteka/api/v1/genres", func(w http.ResponseWriter, r *http.Request) {
mux.HandleFunc("GET /_acme/api/v1/genres", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
h := pathScopedCORS(cfg, mux)
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/v1/fonoteka/genres", nil))
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/v1/acme/genres", nil))
if rec.Header().Get("Access-Control-Allow-Origin") != "*" {
t.Fatalf("token group ACAO = %q", rec.Header().Get("Access-Control-Allow-Origin"))
}
rec = httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/_fonoteka/api/v1/genres", nil))
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/_acme/api/v1/genres", nil))
if got := rec.Header().Get("Access-Control-Allow-Origin"); got != "" {
t.Fatalf("JWT group ACAO = %q, want empty", got)
}

View File

@@ -101,7 +101,7 @@ func TestPipelineOrderRecoverCORSLocaleAuthPasswordOrgRateHandler(t *testing.T)
if err := r.RegisterMiddleware("golem15.user", "jwt.auth", record("jwt.auth")); err != nil {
t.Fatal(err)
}
if err := r.RegisterMiddleware("golem15.fonoteka", "inv.must-change-password", record("inv.must-change-password")); err != nil {
if err := r.RegisterMiddleware("golem15.acme", "inv.must-change-password", record("inv.must-change-password")); err != nil {
t.Fatal(err)
}
r.BindPlugin("golem15.demo")

View File

@@ -436,7 +436,7 @@ func TestMiddlewareFactoryReceivesParam(t *testing.T) {
r := New(nil)
var gotParam string
ran := false
if err := r.RegisterMiddlewareFactory("golem15.fonoteka", "inv.scope", func(param string) pact.Middleware {
if err := r.RegisterMiddlewareFactory("golem15.acme", "inv.scope", func(param string) pact.Middleware {
gotParam = param
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
@@ -472,11 +472,11 @@ func TestDuplicateMiddlewareFactoryNamesPluginAndName(t *testing.T) {
fn := func(string) pact.Middleware {
return func(next http.Handler) http.Handler { return next }
}
if err := r.RegisterMiddlewareFactory("golem15.fonoteka", "inv.scope", fn); err != nil {
if err := r.RegisterMiddlewareFactory("golem15.acme", "inv.scope", fn); err != nil {
t.Fatal(err)
}
err := r.RegisterMiddlewareFactory("golem15.other", "inv.scope", fn)
if err == nil || !strings.Contains(err.Error(), "golem15.fonoteka") || !strings.Contains(err.Error(), "inv.scope") {
if err == nil || !strings.Contains(err.Error(), "golem15.acme") || !strings.Contains(err.Error(), "inv.scope") {
t.Fatalf("want plugin and factory name in error, got %v", err)
}
}