- Add deployment-name branding to titles, mastheads, and OG tags - Share one grant delivery-state query with lineage across grants surfaces - Show pool status/usage, org owners, and config readiness - Make billing views projection-aware with recency and sync vocabulary - Guard FedWiki creation without domains and render route-aware 404s
133 lines
5.4 KiB
Go
133 lines
5.4 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
type fakeAuthorizer struct {
|
|
served map[string]bool
|
|
err error
|
|
lastQuery string
|
|
calls int
|
|
}
|
|
|
|
func (f *fakeAuthorizer) AuthorizeDomain(_ context.Context, fqdn string) (bool, error) {
|
|
f.calls++
|
|
f.lastQuery = fqdn
|
|
if f.err != nil {
|
|
return false, f.err
|
|
}
|
|
return f.served[fqdn], nil
|
|
}
|
|
|
|
// TestDomainAskHandler covers the domain-authorization spec scenarios that
|
|
// live at the HTTP layer: 200 iff the authorizer says servable, exact-match
|
|
// only (no implicit child authorization), normalization before lookup, and
|
|
// non-200 for missing parameters and authorizer failures. The
|
|
// servable-status semantics (readonly served, archived refused) and the
|
|
// legacy-answerer fallback live behind the seam, in the domains registry's
|
|
// RegistryAuthorizer (internal/domains/authorizer.go).
|
|
//
|
|
// wantQuery == "" doubles as "the authorizer must not be reached at all",
|
|
// which is what the malformed-name rows assert: the route is
|
|
// unauthenticated, so a name that cannot be an FQDN is refused on shape
|
|
// before it can cost a lookup.
|
|
func TestDomainAskHandler(t *testing.T) {
|
|
served := map[string]bool{"alice.example.test": true}
|
|
tests := []struct {
|
|
name string
|
|
target string
|
|
served map[string]bool
|
|
err error
|
|
wantStatus int
|
|
wantQuery string
|
|
}{
|
|
{"servable domain authorized", "/domains/ask?domain=alice.example.test", served, nil, 200, "alice.example.test"},
|
|
{"unknown domain refused", "/domains/ask?domain=nope.example.test", served, nil, 404, "nope.example.test"},
|
|
{"child of allocated name not implicitly authorized", "/domains/ask?domain=blog.alice.example.test", served, nil, 404, "blog.alice.example.test"},
|
|
{"query normalized before lookup", "/domains/ask?domain=Alice.Example.Test.", served, nil, 200, "alice.example.test"},
|
|
{"missing domain parameter", "/domains/ask", served, nil, 400, ""},
|
|
{"empty domain parameter", "/domains/ask?domain=", served, nil, 400, ""},
|
|
{"whitespace-only domain parameter", "/domains/ask?domain=%20%20", served, nil, 400, ""},
|
|
{"single label refused without a lookup", "/domains/ask?domain=localhost", served, nil, 400, ""},
|
|
{"illegal characters refused without a lookup", "/domains/ask?domain=not_a~domain.example.test", served, nil, 400, ""},
|
|
{"empty label refused without a lookup", "/domains/ask?domain=alice..example.test", served, nil, 400, ""},
|
|
{"leading-hyphen label refused without a lookup", "/domains/ask?domain=-alice.example.test", served, nil, 400, ""},
|
|
{"over-length name refused without a lookup", "/domains/ask?domain=" + strings.Repeat("a.", 130) + "test", served, nil, 400, ""},
|
|
{"authorizer failure is non-200", "/domains/ask?domain=alice.example.test", served, errors.New("db down"), 500, "alice.example.test"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
fake := &fakeAuthorizer{served: tt.served, err: tt.err}
|
|
rec := httptest.NewRecorder()
|
|
// No Accept header, matching a TLS proxy's on-demand-TLS ask: the
|
|
// machine-facing contract (status + plain-text body) applies.
|
|
DomainAskHandler(fake, slog.Default(), nil)(rec, httptest.NewRequest("GET", tt.target, nil))
|
|
if rec.Code != tt.wantStatus {
|
|
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
|
}
|
|
if tt.wantQuery == "" {
|
|
if fake.calls != 0 {
|
|
t.Errorf("authorizer called %d times for missing/empty param, want 0", fake.calls)
|
|
}
|
|
} else if fake.lastQuery != tt.wantQuery {
|
|
t.Errorf("authorizer queried %q, want %q", fake.lastQuery, tt.wantQuery)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDomainAskHandlerBrowserRendering covers the domain-authorization
|
|
// delta's added requirement: a malformed request from a browser (Accept:
|
|
// text/html) gets the styled, human-readable error page, never a bare
|
|
// internal parameter name on an unstyled page; the status and the
|
|
// machine-facing plain-text contract are unchanged for a caller that does
|
|
// not ask for HTML (spec: domain-authorization).
|
|
func TestDomainAskHandlerBrowserRendering(t *testing.T) {
|
|
fake := &fakeAuthorizer{served: map[string]bool{}}
|
|
tmpl := errorPageTemplates(t)
|
|
|
|
t.Run("browser sees the styled page", func(t *testing.T) {
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/domains/ask", nil)
|
|
req.Header.Set("Accept", "text/html,application/xhtml+xml")
|
|
DomainAskHandler(fake, slog.Default(), tmpl)(rec, req)
|
|
|
|
if rec.Code != 400 {
|
|
t.Fatalf("status = %d, want 400", rec.Code)
|
|
}
|
|
body := rec.Body.String()
|
|
if !strings.Contains(body, "<html") {
|
|
t.Errorf("browser request got unstyled body: %q", body)
|
|
}
|
|
if strings.Contains(body, "domain parameter required") {
|
|
t.Errorf("styled page still carries the bare internal parameter name: %q", body)
|
|
}
|
|
if !strings.Contains(body, "missing its domain") {
|
|
t.Errorf("styled page missing plain-language explanation: %q", body)
|
|
}
|
|
})
|
|
|
|
t.Run("non-browser caller keeps the plain-text contract", func(t *testing.T) {
|
|
rec := httptest.NewRecorder()
|
|
req := httptest.NewRequest("GET", "/domains/ask", nil) // no Accept header
|
|
DomainAskHandler(fake, slog.Default(), tmpl)(rec, req)
|
|
|
|
if rec.Code != 400 {
|
|
t.Fatalf("status = %d, want 400", rec.Code)
|
|
}
|
|
body := rec.Body.String()
|
|
if strings.Contains(body, "<html") {
|
|
t.Errorf("non-browser caller got a styled page: %q", body)
|
|
}
|
|
if strings.TrimSpace(body) != "domain parameter required" {
|
|
t.Errorf("body = %q, want the unchanged machine-facing message", body)
|
|
}
|
|
})
|
|
}
|