Skip to content
quarry Artifact [8e68b510ec]
Login

Artifact [8e68b510ec]

Artifact 8e68b510ec9980aba6e4c8329e92bcbd660a387f8ea1d6a39f86b5e3b7df97ca:


package api

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

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

type slowAPIIn struct{}
type slowAPIOut struct {
	OK bool `json:"ok"`
}

// An op called over /api/v1 was never timed: the slow-op log and the
// ops history lived in the web's page helper, so a slow ticket.set from
// an agent or the runner was invisible on /fleet/ops. It is filed now,
// beside the web renders, tagged with the surface it came in on.
func TestASlowAPICallReachesTheOpsHistoryAndTheSlowOpLog(t *testing.T) {
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{})}); err != nil {
		t.Fatal(err)
	}
	// A wildcard so every run has its own URL: the recorder is the
	// process-wide one, and a repeated run must not count the last.
	const route = "GET /api/v1/test/slow-op-262/{run}"
	url := fmt.Sprintf("/api/v1/test/slow-op-262/%d", time.Now().UnixNano())
	if err := ops.Register(r, ops.Op[slowAPIIn, slowAPIOut]{
		Name: "test.slow262", Doc: "sleeps past the slow-op threshold",
		Kind: ops.Query, Auth: ops.Anonymous, Route: route,
		Run: func(context.Context, slowAPIIn) (slowAPIOut, error) {
			time.Sleep(300 * time.Millisecond)
			return slowAPIOut{OK: true}, nil
		},
	}); err != nil {
		t.Fatal(err)
	}
	h, err := Router(r, "v")
	if err != nil {
		t.Fatal(err)
	}

	var buf bytes.Buffer
	prev := log.Writer()
	log.SetOutput(&buf)
	t.Cleanup(func() { log.SetOutput(prev) })

	w := httptest.NewRecorder()
	h.ServeHTTP(w, httptest.NewRequest("GET", url, nil))
	if w.Code != http.StatusOK {
		t.Fatalf("slow op answered %d: %s", w.Code, w.Body)
	}

	var slow []string
	for _, l := range strings.Split(buf.String(), "\n") {
		if strings.Contains(l, "slow op: test.slow262 ") {
			slow = append(slow, l)
		}
	}
	if len(slow) != 1 {
		t.Fatalf("%d slow-op lines for the API call, want 1:\n%s", len(slow), buf.String())
	}
	if !strings.Contains(slow[0], "surface=api") {
		t.Errorf("the slow-op line does not say it came over the API: %q", slow[0])
	}

	// The ops history is read the way the page reads it: through
	// ops.status, over the API.
	w = httptest.NewRecorder()
	h.ServeHTTP(w, httptest.NewRequest("GET", "/api/v1/ops?limit=200", nil))
	if w.Code != http.StatusOK {
		t.Fatalf("ops.status answered %d: %s", w.Code, w.Body)
	}
	var out struct {
		Renders []map[string]any `json:"renders"`
	}
	if err := json.Unmarshal(w.Body.Bytes(), &out); err != nil {
		t.Fatal(err)
	}
	var rows []map[string]any
	for _, row := range out.Renders {
		if row["path"] == route && row["url"] == url {
			rows = append(rows, row)
		}
	}
	if len(rows) != 1 {
		t.Fatalf("%d history rows for the API call, want 1: %v", len(rows), out.Renders)
	}
	row := rows[0]
	if row["surface"] != "api" {
		t.Errorf("surface = %v, want api", row["surface"])
	}
	if ms, _ := row["opsMs"].(float64); ms < 250 {
		t.Errorf("opsMs = %v, want the op's own time", row["opsMs"])
	}
	if st, _ := row["status"].(float64); st != http.StatusOK {
		t.Errorf("status = %v, want 200", row["status"])
	}
}

// The recent view can be narrowed to one surface; empty keeps both.
func TestOpsStatusNarrowsRecentRendersBySurface(t *testing.T) {
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{})}); err != nil {
		t.Fatal(err)
	}
	now := time.Now()
	obs.Default.RecordRender(obs.Render{At: now, Path: "GET /surface-filter-web", Surface: obs.SurfaceWeb})
	obs.Default.RecordRender(obs.Render{At: now, Path: "GET /api/v1/surface-filter-api", Surface: obs.SurfaceAPI})
	for _, tc := range []struct {
		surface  string
		web, api bool
	}{{"", true, true}, {"web", true, false}, {"api", false, true}} {
		got, err := r.Invoke(ops.WithUser(context.Background(), "dan"), "ops.status",
			opsStatusIn{Limit: 200, Surface: tc.surface})
		if err != nil {
			t.Fatal(err)
		}
		seen := map[string]bool{}
		for _, row := range got.(opsStatusOut).Renders {
			if tc.surface != "" && row.Surface != tc.surface {
				t.Errorf("surface=%q returned a %s row", tc.surface, row.Surface)
			}
			seen[row.Path] = true
		}
		if seen["GET /surface-filter-web"] != tc.web || seen["GET /api/v1/surface-filter-api"] != tc.api {
			t.Errorf("surface=%q: web row %v api row %v, want %v %v", tc.surface,
				seen["GET /surface-filter-web"], seen["GET /api/v1/surface-filter-api"], tc.web, tc.api)
		}
	}
}

// The ops panels show what was rendered, and a requested path names a
// private repository's wiki pages, check-ins and searches. For a
// repository the viewing admin cannot read, a row keeps its route and
// its timings and loses the path (#318).
func TestTheOpsPanelsHideAPrivateRepositorysPaths(t *testing.T) {
	r := ops.NewRegistry()
	if err := RegisterAll(r, Deps{Version: "v", Repos: NewRepos(map[string]*Repo{}),
		CanRead: func(ctx context.Context, repo string) bool { return repo != "vault318" },
	}); err != nil {
		t.Fatal(err)
	}
	const route = "GET /r/{repo}/wiki318/{name}"
	now := time.Now()
	obs.Default.RecordRender(obs.Render{At: now, Path: route, URL: "/r/vault318/wiki318/Secret-Plan", Repo: "vault318", Surface: obs.SurfaceWeb})
	obs.Default.RecordRender(obs.Render{At: now, Path: route, URL: "/r/alpha318/wiki318/Open-Plan", Repo: "alpha318", Surface: obs.SurfaceWeb})
	ctx := ops.WithUser(context.Background(), "root")
	urls := map[string]string{}
	got, err := r.Invoke(ctx, "ops.route", opsRouteIn{Path: route, Limit: 20})
	if err != nil {
		t.Fatal(err)
	}
	for _, row := range got.(opsRouteOut).Renders {
		urls["route:"+row.Repo] = row.URL
	}
	st, err := r.Invoke(ctx, "ops.status", opsStatusIn{Limit: 200})
	if err != nil {
		t.Fatal(err)
	}
	for _, row := range st.(opsStatusOut).Renders {
		if row.Path == route {
			urls["status:"+row.Repo] = row.URL
		}
	}
	for _, panel := range []string{"route", "status"} {
		if u := urls[panel+":vault318"]; strings.Contains(u, "Secret") || u == "" {
			t.Errorf("FAIL: ops.%s shows %q for a repository the admin cannot read", panel, u)
		}
		if u := urls[panel+":alpha318"]; !strings.Contains(u, "Open-Plan") {
			t.Errorf("FAIL: ops.%s lost a readable repository's path: %q", panel, u)
		}
	}
}