Skip to content
quarry Artifact [02fbba4c1a]
Login

Artifact [02fbba4c1a]

Artifact 02fbba4c1a65a590829db2c7634f8c5af06aa940ef1de718f4f1261c2a878cfe:


package web

import (
	"bytes"
	"context"
	"log"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/obs"
	"craftdesign.group/quarry/internal/ops"
	"craftdesign.group/quarry/internal/timing"
)

type slowPageIn struct{}
type slowPageOut struct{}

// Op timing moved from the page helper to the registry. A page that
// calls an op must still be filed once, with the op's time inside it,
// and the op must be timed and logged once — not by both layers.
func TestAWebRenderIsRecordedOnceWithItsSurface(t *testing.T) {
	reg := ops.NewRegistry()
	if err := ops.Register(reg, ops.Op[slowPageIn, slowPageOut]{
		Name: "test.slowpage", Doc: "sleeps past the slow-op threshold",
		Kind: ops.Query, Auth: ops.Anonymous, Route: "GET /api/v1/test/slowpage",
		Run: func(context.Context, slowPageIn) (slowPageOut, error) {
			time.Sleep(300 * time.Millisecond)
			return slowPageOut{}, nil
		},
	}); err != nil {
		t.Fatal(err)
	}
	var buf bytes.Buffer
	prev := log.Writer()
	log.SetOutput(&buf)
	t.Cleanup(func() { log.SetOutput(prev) })

	calls := -1
	mux := http.NewServeMux()
	mux.HandleFunc("GET /slowpage", func(w http.ResponseWriter, req *http.Request) {
		if _, err := invoke(req.Context(), reg, "test.slowpage", nil); err != nil {
			t.Error(err)
		}
		calls = timing.From(req.Context()).Calls(timing.Ops)
	})
	rec := obs.NewRecorder(16, nil)
	withTimerFor(rec, mux, nil, nil, mux).ServeHTTP(httptest.NewRecorder(),
		httptest.NewRequest("GET", "/slowpage", nil))

	if calls != 1 {
		t.Errorf("the op was timed %d times, want 1", calls)
	}
	rs := rec.Snapshot().Renders
	if len(rs) != 1 {
		t.Fatalf("%d renders filed, want 1", len(rs))
	}
	if rs[0].Surface != obs.SurfaceWeb {
		t.Errorf("surface = %q, want %q", rs[0].Surface, obs.SurfaceWeb)
	}
	if rs[0].Ops < 250*time.Millisecond {
		t.Errorf("render ops time %s, want the op's own time inside it", rs[0].Ops)
	}
	var slow []string
	for _, l := range strings.Split(buf.String(), "\n") {
		if strings.Contains(l, "slow op: test.slowpage ") {
			slow = append(slow, l)
		}
	}
	if len(slow) != 1 {
		t.Fatalf("%d slow-op lines, want 1:\n%s", len(slow), buf.String())
	}
	if !strings.Contains(slow[0], "surface=web") {
		t.Errorf("slow-op line does not name the web surface: %q", slow[0])
	}
}

// Both render tables on the operations surface carry the surface of
// each row, so an API call reads as one beside the pages.
func TestOpsTablesShowEachRowsSurface(t *testing.T) {
	var live opsView
	remarshal(map[string]any{"renders": []map[string]any{
		{"at": "2026-09-26T10:00:00Z", "path": "GET /api/v1/r/{repo}/ticket", "surface": "api", "status": 200},
		{"at": "2026-09-26T10:00:01Z", "path": "GET /r/{repo}/timeline", "surface": "web", "status": 200},
	}}, &live)
	var b bytes.Buffer
	if err := noHooks.template(opsPage).ExecuteTemplate(&b, "ops-body", pageData{Ops: live}); err != nil {
		t.Fatal(err)
	}
	checkSurfaceCells(t, "live renders", b.String())

	var hist opsHistoryView
	remarshal(map[string]any{
		"store": map[string]any{"configured": true},
		"rows": []map[string]any{
			{"at": "2026-09-26 10:00:00", "path": "GET /api/v1/r/{repo}/ticket", "surface": "api", "status": 200},
			{"at": "2026-09-26 10:00:01", "path": "GET /r/{repo}/timeline", "surface": "web", "status": 200},
		}}, &hist)
	b.Reset()
	if err := noHooks.template(opsHistoryPage).Execute(&b, pageData{OpsHistory: hist, FleetTab: "ops"}); err != nil {
		t.Fatal(err)
	}
	checkSurfaceCells(t, "history", b.String())
}

func checkSurfaceCells(t *testing.T, table, html string) {
	t.Helper()
	if n := strings.Count(html, `data-hook="ops-surface"`); n != 2 {
		t.Errorf("%s: %d surface cells, want one per row (2)", table, n)
	}
	for _, s := range []string{"api", "web"} {
		if !strings.Contains(html, `data-surface="`+s+`"`) {
			t.Errorf("%s: no row tagged surface %s", table, s)
		}
	}
}

// The recent renders can be narrowed to one surface. The control rides
// the page's own form, and the live refresh asks with the page's query
// so a tick does not reset it.
func TestOpsRecentRendersHaveASurfaceFilter(t *testing.T) {
	var b bytes.Buffer
	if err := noHooks.template(opsPage).Execute(&b, pageData{Ops: opsView{Surface: "api"}, FleetTab: "ops"}); err != nil {
		t.Fatal(err)
	}
	html := b.String()
	i := strings.Index(html, `data-hook="ops-surface-filter"`)
	if i < 0 {
		t.Fatal("no surface filter control on the ops page")
	}
	ctl := html[strings.LastIndex(html[:i], "<select"):]
	ctl = ctl[:strings.Index(ctl, "</select>")]
	for _, want := range []string{`name="surface"`, `form="ops-controls"`, `value=""`,
		`value="web"`, `value="api" selected`} {
		if !strings.Contains(ctl, want) {
			t.Errorf("surface control lacks %s:\n%s", want, ctl)
		}
	}
	if !strings.Contains(html, `id="ops-controls"`) {
		t.Error("the control names a form the page does not have")
	}
	if !strings.Contains(html, `@get('/fleet/ops.sse' + location.search)`) {
		t.Error("the live refresh does not carry the page's query")
	}
}