Files
MeshTender/internal/core/endpoints_shared_test.go

201 lines
6.5 KiB
Go

package core
import (
"io"
"net/http"
"net/url"
"strings"
"testing"
)
// These black-box tests cover the non-visual "plumbing" endpoints from the
// endpoint inventory (docs/endpoint-inventory.md): health, static assets, and the
// pure host/redirect behaviors. They use the splitServer harness (real three-host
// server + real Postgres) and assert status/redirect Location rather than any
// rendered UI.
// #1 /healthz — returns "ok" on every surface.
func TestHealthzEndpoint(t *testing.T) {
t.Parallel()
_, _, ts, h := splitServer(t)
for _, host := range []string{h.app, h.auth, h.root} {
resp := do(t, ts, host, "/healthz")
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusOK || strings.TrimSpace(string(body)) != "ok" {
t.Fatalf("%s/healthz = %d %q, want 200 \"ok\"", host, resp.StatusCode, body)
}
}
}
// TestHealthzReportsDBDown: /healthz is a readiness probe — when the database is
// unreachable it must fail (503) rather than report healthy. (A cookieless
// request touches no session DB rows, so the ping is what fails.)
func TestHealthzReportsDBDown(t *testing.T) {
t.Parallel()
st, _, ts, h := splitServer(t)
// Healthy: 200.
resp := do(t, ts, h.app, "/healthz")
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("healthy /healthz = %d, want 200", resp.StatusCode)
}
// Take the database down; the readiness ping must now fail closed.
st.Close()
down := do(t, ts, h.app, "/healthz")
down.Body.Close()
if down.StatusCode != http.StatusServiceUnavailable {
t.Fatalf("db-down /healthz = %d, want 503", down.StatusCode)
}
}
// TestStaticSkipsSessionMiddleware: static assets and /healthz are mounted ahead
// of the session middleware, so they don't pay its per-request DB cost. scs's
// LoadAndSave adds "Vary: Cookie" to everything it handles, so its absence on
// these responses — and presence on a session route — is the observable proof.
func TestStaticSkipsSessionMiddleware(t *testing.T) {
t.Parallel()
_, _, ts, h := splitServer(t)
varyHasCookie := func(resp *http.Response) bool {
for _, v := range resp.Header.Values("Vary") {
for _, part := range strings.Split(v, ",") {
if strings.EqualFold(strings.TrimSpace(part), "Cookie") {
return true
}
}
}
return false
}
for _, path := range []string{"/static/ui.js", "/healthz"} {
resp := do(t, ts, h.app, path)
resp.Body.Close()
if varyHasCookie(resp) {
t.Errorf("%s carries Vary: Cookie — it ran the session middleware", path)
}
}
// A session-scoped route still runs the middleware (so the check above isn't
// vacuously passing).
resp := do(t, ts, h.app, "/")
resp.Body.Close()
if !varyHasCookie(resp) {
t.Error("/ is missing Vary: Cookie — session middleware not applied to app routes")
}
}
// TestBrandedNotFound: unrouted paths and missing resources return a branded 404
// page (not Go's plain "404 page not found"), on every host.
func TestBrandedNotFound(t *testing.T) {
t.Parallel()
_, _, ts, h := splitServer(t)
check := func(host, path string) {
t.Helper()
resp := do(t, ts, host, path)
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusNotFound {
t.Fatalf("%s%s = %d, want 404", host, path, resp.StatusCode)
}
s := string(body)
if !strings.Contains(s, "Page not found") || !strings.Contains(s, "MeshTender") {
t.Fatalf("%s%s not the branded 404 page: %q", host, path, s)
}
if strings.Contains(s, "404 page not found") {
t.Fatalf("%s%s is Go's default 404, not branded", host, path)
}
}
// Unrouted path on each host → router NotFound handler.
check(h.app, "/no-such-path")
check(h.root, "/no-such-path")
check(h.auth, "/no-such-path")
// Missing resource on a real route → in-handler s.NotFound.
check(h.root, "/orgs/does-not-exist")
}
// #2 /static/* — serves an embedded asset.
func TestStaticAssetEndpoint(t *testing.T) {
t.Parallel()
_, _, ts, h := splitServer(t)
resp := do(t, ts, h.app, "/static/ui.js")
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("/static/ui.js = %d, want 200", resp.StatusCode)
}
if ct := resp.Header.Get("Content-Type"); !strings.Contains(ct, "javascript") {
t.Fatalf("/static/ui.js Content-Type = %q, want a javascript type", ct)
}
if len(body) == 0 {
t.Fatal("/static/ui.js served an empty body")
}
}
// #15 auth host `/` — bare visits 303 to the sign-in page.
func TestAuthRootRedirectsToLogin(t *testing.T) {
t.Parallel()
_, _, ts, h := splitServer(t)
resp := do(t, ts, h.auth, "/")
resp.Body.Close()
if resp.StatusCode != http.StatusSeeOther {
t.Fatalf("auth / = %d, want 303", resp.StatusCode)
}
if loc, _ := url.Parse(resp.Header.Get("Location")); loc.Path != "/login" {
t.Fatalf("auth / → %q, want /login", resp.Header.Get("Location"))
}
}
// #35 app host `/signup` — starts the signup handoff, bouncing to the auth host.
func TestAppSignupRedirectsToAuth(t *testing.T) {
t.Parallel()
_, _, ts, h := splitServer(t)
resp := do(t, ts, h.app, "/signup")
resp.Body.Close()
if resp.StatusCode != http.StatusSeeOther {
t.Fatalf("app /signup = %d, want 303", resp.StatusCode)
}
if loc, _ := url.Parse(resp.Header.Get("Location")); loc.Host != h.auth || loc.Path != "/signup" {
t.Fatalf("app /signup → %q, want auth host /signup", resp.Header.Get("Location"))
}
}
// #97 custom org domain — a verified domain serves the org's public page at `/`
// and 302-redirects every other path to the app host.
func TestCustomDomainRedirect(t *testing.T) {
t.Parallel()
st, ctx, ts, _ := splitServer(t)
owner, err := st.CreateUser(ctx, "domainowner", "")
if err != nil {
t.Fatal(err)
}
org, err := st.CreateOrg(ctx, "Domain Org", owner.ID)
if err != nil {
t.Fatal(err)
}
const customHost = "mesh.example.org"
dom, err := st.CreateOrgDomain(ctx, org.ID, customHost)
if err != nil {
t.Fatal(err)
}
if err := st.MarkOrgDomainVerified(ctx, org.ID, dom.ID); err != nil {
t.Fatal(err)
}
// Non-root path on the custom host → 302 to the same path on the app host.
resp := do(t, ts, customHost, "/repeaters/abc")
resp.Body.Close()
if resp.StatusCode != http.StatusFound {
t.Fatalf("custom-domain /repeaters/abc = %d, want 302", resp.StatusCode)
}
if loc, _ := url.Parse(resp.Header.Get("Location")); loc.Hostname() != testAppHost || loc.Path != "/repeaters/abc" {
t.Fatalf("custom-domain redirect = %q, want app host /repeaters/abc", resp.Header.Get("Location"))
}
// (The custom-domain `/` org page — inventory #96 — is a rendered page left to
// manual/browser verification.)
}