mirror of
https://github.com/MeshTender/MeshTender.git
synced 2026-09-02 01:38:17 +00:00
201 lines
6.5 KiB
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.)
|
|
}
|