Skip to content
quarry Artifact [39a0d0f69c]
Login

Artifact [39a0d0f69c]

Artifact 39a0d0f69c4fe5e8a5d4d372a569560ad450c784a8a36823e09ed344873a2b4e:


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)
		}
	}
}