Files
MrAlders0n 94b4b40f9b planguard: plan and result regression checks, fixture tier in CI, read-only mode (#240)
* feat(planguard): read sqlc statement text and load a strict plan corpus

* fix(planguard): fail on unextracted statements, keep fragments mode-neutral

* feat(planguard): plan assertions, scoped sort exemptions and narrow retired-index verdicts

* fix(planguard): an errored run always fails

* fix(planguard): keep subplan conds off their parent scan; fail on unanalyzed budgets

* feat(planguard): read-only plan runner, reports and the run command

* fix(planguard): validate identifiers in emitted scripts; prove read-only runs

* feat(planguard): end-state corpus, planted regressions and the generated cached-plan policy

* fix(planguard): derive observer cases from data; mark legacy generic plans

* fix(planguard): bound the dense-region route resolvers; test policy refusals

* fix(planguard): re-measure the dense-region route cases; cover every policy refusal

* feat(planguard): synthetic fixture built through the migrations, checked in CI

* ci(planguard): build the fixture once; guard fixture data for identifiers

* fix(planguard): hash every fixture generator input; harden the fixture guard

* feat(planguard): snapshot diff over every column, named result classes and cursor walks

* fix(planguard): bound the relaxed diff classes; derive the cursor tie from data

* fix(planguard): refuse multi-statement text in the corpus runner

* feat: read-only server mode and an HTTP page diff between two builds

* fix(planguard): refuse escape strings and psql meta-commands in corpus text

* fix: storediff follows each build's own cursor; prove read-only gates via the log

* fix(planguard): match the PostgreSQL lexer on high-bit bytes; check every emitted line

* feat(admin): daily read-only plan check on the maintenance pool, its metric and report

* feat(planguard): daily prod check behind planguard.daily_check

* fix(planguard): cancel fresh runs at shutdown; expose a never-ran check

* ci(planguard): pin the fixture server and collation; refresh baselines

* fix(planguard): calibrate route budgets on one dataset; skip entries whose index is absent
2026-10-19 13:45:39 -04:00

235 lines
7.5 KiB
Go

// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
package handlers
import (
"context"
"encoding/json"
"errors"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
)
type stubStatements struct {
items []api.DBStatement
err error
}
func (s stubStatements) DBStatements(context.Context) ([]api.DBStatement, error) {
return s.items, s.err
}
func serveStatements(t *testing.T, reader DBStatementsReader) *httptest.ResponseRecorder {
t.Helper()
w := httptest.NewRecorder()
DBAdminRouter(reader, nil).ServeHTTP(w, httptest.NewRequest("GET", "/statements", nil))
return w
}
func TestDBAdminStatements(t *testing.T) {
items := []api.DBStatement{
{Name: "ListKnownRoutes", Calls: 7, ExecMs: 12.345, PlanMs: 0.5, SharedHit: 100, SharedRead: 3, Rows: 70, WALBytes: 0},
{Name: "(unnamed)", Calls: 2, ExecMs: 1.001, SharedHit: 4, Rows: 2, WALBytes: 512},
}
w := serveStatements(t, stubStatements{items: items})
if w.Code != 200 {
t.Fatalf("status = %d; body %s", w.Code, w.Body)
}
var raw map[string][]map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &raw); err != nil {
t.Fatal(err)
}
if len(raw["items"]) != 2 {
t.Fatalf("items = %v", raw)
}
for _, key := range []string{"name", "calls", "exec_ms", "plan_ms", "shared_hit", "shared_read", "rows", "wal_bytes"} {
if _, ok := raw["items"][0][key]; !ok {
t.Errorf("item lacks %q: %v", key, raw["items"][0])
}
}
var got api.DBStatementsResponse
if err := json.Unmarshal(w.Body.Bytes(), &got); err != nil {
t.Fatal(err)
}
if got.Items[0] != items[0] || got.Items[1] != items[1] {
t.Fatalf("items = %+v; want %+v", got.Items, items)
}
}
func TestDBAdminStatementsEmptyIsArray(t *testing.T) {
w := serveStatements(t, stubStatements{})
if w.Code != 200 || strings.TrimSpace(w.Body.String()) != `{"items":[]}` {
t.Fatalf("got %d %s", w.Code, w.Body)
}
}
func TestDBAdminStatementsErrors(t *testing.T) {
for _, tc := range []struct {
err error
status int
code string
message string
}{
{api.ErrStatementsUnavailable, 503, "service_unavailable", "pg_stat_statements is not available: create the extension and add it to shared_preload_libraries"},
{errors.New("PRIVATE_CANARY"), 500, "internal_server_error", "cannot read statement statistics"},
} {
w := serveStatements(t, stubStatements{err: tc.err})
var body map[string]APIError
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
if w.Code != tc.status || body["error"].Code != tc.code || body["error"].Message != tc.message {
t.Errorf("err %v: got %d %s", tc.err, w.Code, w.Body)
}
}
}
type stubPlanguard struct {
last api.PlanguardReport
lastErr error
fresh api.PlanguardReport
runErr error
runs int
}
func (s *stubPlanguard) Last() (api.PlanguardReport, error) { return s.last, s.lastErr }
func (s *stubPlanguard) RunNow(context.Context) (api.PlanguardReport, error) {
s.runs++
return s.fresh, s.runErr
}
func servePlanguard(t *testing.T, pg PlanguardSource, target string) *httptest.ResponseRecorder {
t.Helper()
w := httptest.NewRecorder()
DBAdminRouter(stubStatements{}, pg).ServeHTTP(w, httptest.NewRequest("GET", target, nil))
return w
}
func errorBody(t *testing.T, w *httptest.ResponseRecorder) APIError {
t.Helper()
var body map[string]APIError
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
t.Fatalf("%v: %s", err, w.Body)
}
return body["error"]
}
func TestDBAdminPlanguard(t *testing.T) {
report := api.PlanguardReport{
Tier: "prod",
StartedAt: time.Date(2026, 10, 19, 1, 2, 3, 0, time.UTC),
DurationMs: 1234.5,
Clean: false,
Counts: map[string]int{"ok": 1, "MISSED": 1},
Failures: []api.PlanguardRun{{Entry: "b", Stmt: "S", Mode: "generic", Verdict: "MISSED", Failures: []string{"driver: x"}}},
Runs: []api.PlanguardRun{
{Entry: "a", Stmt: "S", Mode: "custom", Verdict: "ok", Drivers: []string{"known_routes:Index Scan:ix"}},
{Entry: "b", Stmt: "S", Mode: "generic", Verdict: "MISSED", Failures: []string{"driver: x"}},
},
}
t.Run("not run yet", func(t *testing.T) {
pg := &stubPlanguard{lastErr: api.ErrPlanguardNotRun}
w := servePlanguard(t, pg, "/planguard")
if e := errorBody(t, w); w.Code != 404 || e.Code != "not_found" || e.Message != "planguard has not run yet" {
t.Fatalf("got %d %s", w.Code, w.Body)
}
if pg.runs != 0 {
t.Fatalf("RunNow called %d times", pg.runs)
}
})
t.Run("last report", func(t *testing.T) {
pg := &stubPlanguard{last: report}
w := servePlanguard(t, pg, "/planguard")
if w.Code != 200 || pg.runs != 0 {
t.Fatalf("got %d (runs %d) %s", w.Code, pg.runs, w.Body)
}
var raw map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &raw); err != nil {
t.Fatal(err)
}
for _, key := range []string{"tier", "started_at", "duration_ms", "clean", "counts", "failures", "runs"} {
if _, ok := raw[key]; !ok {
t.Errorf("report lacks %q: %s", key, w.Body)
}
}
if raw["tier"] != "prod" || raw["clean"] != false || raw["started_at"] != "2026-10-19T01:02:03Z" {
t.Errorf("report = %s", w.Body)
}
run := raw["runs"].([]any)[0].(map[string]any)
for _, key := range []string{"entry", "stmt", "mode", "verdict", "drivers"} {
if _, ok := run[key]; !ok {
t.Errorf("run lacks %q: %v", key, run)
}
}
if _, ok := run["failures"]; ok {
t.Errorf("a run without failures carries the key: %v", run)
}
})
t.Run("empty failures is an array", func(t *testing.T) {
clean := api.PlanguardReport{Tier: "prod", Clean: true, Counts: map[string]int{}, Failures: []api.PlanguardRun{}, Runs: []api.PlanguardRun{}}
w := servePlanguard(t, &stubPlanguard{last: clean}, "/planguard")
if w.Code != 200 || !strings.Contains(w.Body.String(), `"failures":[]`) {
t.Fatalf("got %d %s", w.Code, w.Body)
}
})
t.Run("fresh", func(t *testing.T) {
fresh := report
fresh.DurationMs = 99
pg := &stubPlanguard{lastErr: api.ErrPlanguardNotRun, fresh: fresh}
w := servePlanguard(t, pg, "/planguard?fresh=true")
if w.Code != 200 || pg.runs != 1 || !strings.Contains(w.Body.String(), `"duration_ms":99`) {
t.Fatalf("got %d (runs %d) %s", w.Code, pg.runs, w.Body)
}
})
t.Run("fresh fails", func(t *testing.T) {
pg := &stubPlanguard{last: report, runErr: errors.New("PRIVATE_CANARY")}
w := servePlanguard(t, pg, "/planguard?fresh=true")
if e := errorBody(t, w); w.Code != 500 || e.Message != "cannot run the plan check" || strings.Contains(w.Body.String(), "PRIVATE_CANARY") {
t.Fatalf("got %d %s", w.Code, w.Body)
}
})
t.Run("bad fresh", func(t *testing.T) {
pg := &stubPlanguard{last: report}
w := servePlanguard(t, pg, "/planguard?fresh=maybe")
if w.Code != 400 || pg.runs != 0 {
t.Fatalf("got %d (runs %d) %s", w.Code, pg.runs, w.Body)
}
})
t.Run("statements still served", func(t *testing.T) {
w := servePlanguard(t, &stubPlanguard{last: report}, "/statements")
if w.Code != 200 || strings.TrimSpace(w.Body.String()) != `{"items":[]}` {
t.Fatalf("got %d %s", w.Code, w.Body)
}
})
t.Run("disabled", func(t *testing.T) {
for _, target := range []string{"/planguard", "/planguard?fresh=true"} {
pg := &stubPlanguard{lastErr: api.ErrPlanguardDisabled, runErr: api.ErrPlanguardDisabled}
w := servePlanguard(t, pg, target)
if e := errorBody(t, w); w.Code != 404 || e.Code != "not_found" || e.Message != "planguard daily check is disabled" {
t.Fatalf("%s: got %d %s", target, w.Code, w.Body)
}
}
})
t.Run("no source", func(t *testing.T) {
w := servePlanguard(t, nil, "/planguard?fresh=true")
if w.Code != 404 {
t.Fatalf("got %d %s", w.Code, w.Body)
}
})
}