package web
import (
"net/http/httptest"
"sync"
"testing"
"craftdesign.group/quarry/internal/api"
"craftdesign.group/quarry/internal/ops"
)
// Building a handler must not write anything a handler already serving
// reads. The parallel tests build handlers while others serve, and a
// forge that ever builds a second one (a reload, a test harness) would
// otherwise hand a request half of one configuration and half of the
// other. Meaningful under -race, which is how the gate runs it.
func TestBuildingAHandlerDoesNotRaceOneServing(t *testing.T) {
r := ops.NewRegistry()
if err := api.RegisterAll(r, api.Deps{Version: "v", Repos: api.NewRepos(map[string]*api.Repo{})}); err != nil {
t.Fatal(err)
}
opts := func() []Option {
return []Option{
WithNamespaces(place(map[string]string{"orchard": "grove"})),
WithNamespaceHistory(func(string, string) bool { return false }),
WithMarks(func(string, string) (string, bool) { return "", false }),
WithMarksChanged(func() {}),
WithAgents(func(string) string { return "" }),
}
}
serving := HandlerWith(r, nil, nil, opts())
var wg sync.WaitGroup
for i := 0; i < 4; i++ {
wg.Add(2)
go func() {
defer wg.Done()
for _, path := range []string{"/r/orchard", "/r/grove/orchard", "/"} {
serving.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest("GET", path, nil))
}
}()
go func() {
defer wg.Done()
HandlerWith(r, nil, nil, opts())
}()
}
wg.Wait()
// The first handler still answers by its own configuration.
rec := httptest.NewRecorder()
serving.ServeHTTP(rec, httptest.NewRequest("GET", "/r/orchard", nil))
if rec.Code != 302 || rec.Header().Get("Location") != "/r/grove/orchard" {
t.Errorf("FAIL: a flat path answered %d %q, want 302 to /r/grove/orchard", rec.Code, rec.Header().Get("Location"))
}
}
// drawWith has the page templates draw with h until the test ends.
func drawWith(t *testing.T, h handlerHooks) {
t.Helper()
was := drawn.Load()
t.Cleanup(func() { drawn.Store(was) })
drawn.Store(&h)
}