package web
import (
"net/http"
"net/http/httptest"
"testing"
)
// place stands in for the registry: where each repository appears, and
// whether a name is a repository at all.
func place(m map[string]string) func(string) (string, bool) {
return func(name string) (string, bool) {
ns, ok := m[name]
return ns, ok
}
}
func servedPath(t *testing.T, h http.Handler, method, path string) (status int, served string, location string) {
t.Helper()
inner := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
served = req.URL.Path
})
w := httptest.NewRecorder()
namespaced(inner).ServeHTTP(w, httptest.NewRequest(method, path, nil))
return w.Code, served, w.Header().Get("Location")
}
func TestNamespacedPaths(t *testing.T) {
old := repoPlace
t.Cleanup(func() { repoPlace = old })
// "quarry" is a repository AND, in the last case, the name of a
// team — the collision the ordering exists to survive.
repoPlace = place(map[string]string{
"quarry": "dmestas",
"zit": "quarry",
"fossil": "",
"orchard": "grove",
})
for _, tc := range []struct {
name, method, path string
wantStatus int
wantServed string
wantLocation string
}{
{"a namespaced path reaches the repository", "GET", "/r/grove/orchard/tree",
200, "/r/orchard/tree", ""},
{"a namespaced path with no tail", "GET", "/r/dmestas/quarry",
200, "/r/quarry", ""},
{"a flat path is sent to where the repository appears", "GET", "/r/orchard",
302, "", "/r/grove/orchard"},
{"a forge-held repository stays flat", "GET", "/r/fossil/tree",
200, "/r/fossil/tree", ""},
{"a repository name wins over a namespace of the same name", "GET", "/r/quarry/zit",
302, "", "/r/dmestas/quarry/zit"},
{"a post is served, never redirected", "POST", "/r/orchard/settings",
200, "/r/orchard/settings", ""},
{"an unknown first segment is left alone", "GET", "/r/nosuch/thing",
200, "/r/nosuch/thing", ""},
{"paths outside /r/ are untouched", "GET", "/t/grove",
200, "/t/grove", ""},
} {
status, served, loc := servedPath(t, nil, tc.method, tc.path)
if status != tc.wantStatus {
t.Errorf("FAIL: %s: status %d, want %d", tc.name, status, tc.wantStatus)
continue
}
if tc.wantServed != "" && served != tc.wantServed {
t.Errorf("FAIL: %s: served %q, want %q", tc.name, served, tc.wantServed)
continue
}
if tc.wantLocation != "" && loc != tc.wantLocation {
t.Errorf("FAIL: %s: Location %q, want %q", tc.name, loc, tc.wantLocation)
continue
}
t.Logf("ok: %s", tc.name)
}
}
// The redirect must not be cached: where a repository appears changes
// when a team takes it, when a team is dissolved, and when the deed is
// handed on.
func TestTheCanonicalRedirectIsNotPermanent(t *testing.T) {
old := repoPlace
t.Cleanup(func() { repoPlace = old })
repoPlace = place(map[string]string{"orchard": "grove"})
status, _, _ := servedPath(t, nil, "GET", "/r/orchard")
if status == http.StatusMovedPermanently {
t.Fatal("FAIL: a permanent redirect to a target that moves is cached against the truth")
}
if status != http.StatusFound {
t.Fatalf("FAIL: status %d, want 302", status)
}
t.Log("ok: the canonical redirect is temporary")
}
// Unwired, the forge serves flat paths exactly as it did.
func TestWithoutTheSeamPathsAreUntouched(t *testing.T) {
old := repoPlace
t.Cleanup(func() { repoPlace = old })
repoPlace = nil
status, served, _ := servedPath(t, nil, "GET", "/r/orchard/tree")
if status != 200 || served != "/r/orchard/tree" {
t.Fatalf("FAIL: status %d served %q, want 200 and the path unchanged", status, served)
}
t.Log("ok: no seam, no rewriting")
}