package main
import (
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"sync/atomic"
"testing"
"craftdesign.group/quarry/internal/web"
)
// No repository name is reserved: a repository named pickup, at one
// segment or under a namespace, still clones, pulls and pushes with real
// zit through the pickup front, none of it reaches the collector, and a
// request presenting the Pickup scheme does, at the syntactic route.
func TestARepositoryNamedPickupStillSyncs(t *testing.T) {
r := newFirstPushRig(t, "pickup")
var collected atomic.Int64
var gotRepo atomic.Value
collector := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
collected.Add(1)
gotRepo.Store(req.PathValue("repo"))
w.WriteHeader(http.StatusNoContent)
})
mux := http.NewServeMux()
mux.Handle("POST /r/{repo}", r.front)
// The namespace rewrite, as the page layer does it: /r/alice/x is
// the flat /r/x.
mux.HandleFunc("POST /r/alice/{repo}", func(w http.ResponseWriter, req *http.Request) {
inner := req.Clone(req.Context())
inner.URL.Path = "/r/" + req.PathValue("repo")
inner.SetPathValue("repo", req.PathValue("repo"))
r.front.ServeHTTP(w, inner)
})
srv := httptest.NewServer(web.PickupFront(mux, collector))
defer srv.Close()
flat, ns := srv.URL+"/r/pickup", srv.URL+"/r/alice/pickup"
user := []string{"--user", "jan", "--password", r.token}
proj := r.project(t, 1)
if out, err := r.zit(t, proj, append([]string{"push", flat}, user...)...); err != nil {
t.Fatalf("FAIL: push to the one-segment URL: %v\n%s", err, out)
}
cl := filepath.Join(t.TempDir(), "clone")
if out, err := r.zit(t, ".", append([]string{"clone", ns, cl}, user...)...); err != nil {
t.Fatalf("FAIL: clone from the namespaced URL: %v\n%s", err, out)
}
r.commit(t, cl)
if out, err := r.zit(t, cl, append([]string{"push", ns}, user...)...); err != nil {
t.Fatalf("FAIL: push to the namespaced URL: %v\n%s", err, out)
}
if out, err := r.zit(t, proj, append([]string{"pull", flat}, user...)...); err != nil {
t.Fatalf("FAIL: pull from the one-segment URL: %v\n%s", err, out)
}
if got, want := r.checkins(t, proj), r.checkins(t, cl); got != want {
t.Errorf("FAIL: the pulled checkout has %d timeline lines, want %d", got, want)
}
if n := collected.Load(); n != 0 {
t.Errorf("FAIL: %d syncs reached the collector", n)
}
// A sync-shaped POST at the namespaced path, with Basic or nothing,
// never reaches it either.
for _, auth := range []string{"", "Basic amFuOnB3"} {
req, _ := http.NewRequest("POST", ns, strings.NewReader("x"))
req.Header.Set("Content-Type", "application/x-fossil")
if auth != "" {
req.Header.Set("Authorization", auth)
}
if resp, err := http.DefaultClient.Do(req); err == nil {
resp.Body.Close()
}
}
if n := collected.Load(); n != 0 {
t.Errorf("FAIL: a non-pickup POST reached the collector")
}
// A pickup is read syntactically: the last segment is the route.
for _, path := range []string{"/r/alice/pickup/pickup", "/r/pickup/pickup"} {
req, _ := http.NewRequest("POST", srv.URL+path, nil)
req.Header.Set("Authorization", "Pickup abc")
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != http.StatusNoContent {
t.Errorf("FAIL: %s did not reach the collector: %v", path, err)
}
if resp != nil {
resp.Body.Close()
}
}
if collected.Load() != 2 || gotRepo.Load() == "" {
t.Errorf("FAIL: collector calls %d, repo %v", collected.Load(), gotRepo.Load())
}
if !t.Failed() {
t.Log("ok")
}
}
// Through the real handler, a POST that does not present the Pickup
// scheme is never the collector's, whatever the path; one that does is,
// at /r/{ns}/pickup/pickup as at any other address.
func TestOnlyAPickupSchemeReachesTheCollector(t *testing.T) {
g := newSiteRig(t)
n := 0
do := func(path, auth string) *httptest.ResponseRecorder {
n++
req := httptest.NewRequest("POST", path, strings.NewReader("x"))
req.RemoteAddr = "192.0.2." + string(rune('0'+n)) + ":1"
if auth != "" {
req.Header.Set("Authorization", auth)
}
w := httptest.NewRecorder()
g.h.ServeHTTP(w, req)
return w
}
for _, path := range []string{"/r/alice/pickup", "/r/pickup", "/r/alice/pub/pickup"} {
for _, auth := range []string{"", "Basic amFuOnB3", "Bearer abc"} {
if w := do(path, auth); strings.Contains(w.Body.String(), "no such pickup") {
t.Errorf("FAIL: %s with %q reached the collector", path, auth)
}
}
}
for _, path := range []string{"/r/alice/pickup/pickup", "/r/pickup/pickup"} {
if w := do(path, "Pickup AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); w.Code != 404 || !strings.Contains(w.Body.String(), "no such pickup") {
t.Errorf("FAIL: %s with the Pickup scheme: %d %s", path, w.Code, w.Body)
}
}
if !t.Failed() {
t.Log("ok")
}
}