package web
import (
"context"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"craftdesign.group/quarry/internal/ops"
)
type dashFeedIn struct {
Kind string `json:"kind"`
Find string `json:"find"`
Repo string `json:"repo"`
Offset int `json:"offset"`
}
type dashAcrossIn struct {
Awaiting bool `json:"awaiting"`
Limit int `json:"limit"`
}
// The home page arrives with its content (remembered, or built within a
// short budget) and never waits long on the trackers; and the band, which reaches into every
// tracker the reader can see, is asked for only under ticket changes,
// the one chip that draws it. Computed under every chip and before the
// page could arrive, it cost the home page four seconds whenever those
// trackers were cold.
func TestTheHomeAsksForTheWaitingBandOnlyWhereItIsDrawn(t *testing.T) {
r := ops.NewRegistry()
across := 0
type empty struct{}
must := func(err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
must(ops.Register(r, ops.Op[dashFeedIn, struct {
Total int `json:"total"`
}]{Name: "me.feed", Doc: "test double", Kind: ops.Query, Auth: ops.Token, Route: "GET /api/v1/me/feed",
Run: func(context.Context, dashFeedIn) (struct {
Total int `json:"total"`
}, error) {
return struct {
Total int `json:"total"`
}{}, nil
}}))
must(ops.Register(r, ops.Op[dashAcrossIn, struct {
Tickets []struct{} `json:"tickets"`
}]{Name: "ticket.across", Doc: "test double", Kind: ops.Query, Auth: ops.Token, Route: "GET /api/v1/me/tickets",
Run: func(context.Context, dashAcrossIn) (struct {
Tickets []struct{} `json:"tickets"`
}, error) {
across++
return struct {
Tickets []struct{} `json:"tickets"`
}{}, nil
}}))
must(ops.Register(r, ops.Op[empty, struct {
Version string `json:"version"`
}]{Name: "meta.version", Doc: "test double", Kind: ops.Query, Auth: ops.Anonymous, Route: "GET /api/v1/version",
Run: func(context.Context, empty) (struct {
Version string `json:"version"`
}, error) {
return struct {
Version string `json:"version"`
}{"t"}, nil
}}))
h := HandlerWith(r, nil, nil, []Option{WithSessions(fakeSessions{user: "jan", roles: map[string]ops.Auth{"": ops.Token}})})
get := func(path string) (int, string) {
req := httptest.NewRequest("GET", path, nil)
req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
w := httptest.NewRecorder()
h.ServeHTTP(w, req)
if w.Code != http.StatusOK {
t.Fatalf("%s: %d", path, w.Code)
}
return across, w.Body.String()
}
clear(dashCache)
// Quick content arrives with the page, and only under ticket
// changes does it ask for the band.
for _, path := range []string{"/", "/?kind=c", "/?kind=w", "/?frag=all", "/?frag=all&kind=c"} {
if n, body := get(path); n != 0 || !strings.Contains(body, `id="dash-feed"`) {
t.Errorf("FAIL: %s asked for the waiting band (%d), or brought no feed", path, n)
}
}
if n, body := get("/?kind=t"); n != 1 || !strings.Contains(body, `id="dash-feed"`) {
t.Errorf("FAIL: ticket changes did not ask for its band (%d)", n)
}
// The next visit draws what it remembered at once and brings it up
// to date in place, rather than arriving blank.
if _, body := get("/"); !strings.Contains(body, `id="dash-feed"`) || !strings.Contains(body, `data-init="@get('/?frag=all')"`) {
t.Errorf("FAIL: a second visit did not draw the remembered dashboard and refresh it")
}
}
// homeContent is the home page's content URL for path: the page itself
// is a frame, and what fills it is ?frag=all with the same query.
func homeContent(path string) string {
if !strings.HasPrefix(path, "/") || strings.HasPrefix(path, "/?view=explore") {
return path
}
rest := strings.TrimPrefix(strings.TrimPrefix(path, "/"), "?")
if path != "/" && !strings.HasPrefix(path, "/?") {
return path
}
if rest == "" {
return "/?frag=all"
}
return "/?frag=all&" + rest
}
// unpatch is a Datastar element patch read back as the HTML it carries,
// so a test reads the streamed content as it would the page.
func unpatch(body string) string {
if !strings.HasPrefix(body, "event: datastar-patch-elements") {
return body
}
var out []string
for _, l := range strings.Split(body, "\n") {
if rest, ok := strings.CutPrefix(l, "data: elements "); ok {
out = append(out, rest)
}
}
return strings.Join(out, "\n")
}
// Content slower than the budget does not hold the page: the page
// arrives with its frame and streams the content in.
func TestASlowDashboardDoesNotHoldThePage(t *testing.T) {
clear(dashCache)
r := ops.NewRegistry()
type empty struct{}
if err := ops.Register(r, ops.Op[dashFeedIn, struct {
Total int `json:"total"`
}]{Name: "me.feed", Doc: "test double", Kind: ops.Query, Auth: ops.Token, Route: "GET /api/v1/me/feed",
Run: func(context.Context, dashFeedIn) (struct {
Total int `json:"total"`
}, error) {
time.Sleep(4 * dashBudget)
return struct {
Total int `json:"total"`
}{}, nil
}}); err != nil {
t.Fatal(err)
}
if err := ops.Register(r, ops.Op[empty, struct {
Version string `json:"version"`
}]{Name: "meta.version", Doc: "test double", Kind: ops.Query, Auth: ops.Anonymous, Route: "GET /api/v1/version",
Run: func(context.Context, empty) (struct {
Version string `json:"version"`
}, error) {
return struct {
Version string `json:"version"`
}{"t"}, nil
}}); err != nil {
t.Fatal(err)
}
h := HandlerWith(r, nil, nil, []Option{WithSessions(fakeSessions{user: "slow", roles: map[string]ops.Auth{"": ops.Token}})})
req := httptest.NewRequest("GET", "/", nil)
req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
w := httptest.NewRecorder()
start := time.Now()
h.ServeHTTP(w, req)
took := time.Since(start)
if !strings.Contains(w.Body.String(), `data-hook="dash-frame"`) || !strings.Contains(w.Body.String(), `data-init="@get('/?frag=all')"`) {
t.Fatalf("FAIL: a slow dashboard did not arrive as its frame")
}
if took > 3*dashBudget {
t.Errorf("FAIL: the page waited %v for a slow dashboard", took)
}
}