Artifact 705a1a7451e4c8ba252bfeb08616cfb1726ddc4bdb91943741df801ea38059c0:
- File
internal/harness/harness_test.go
— part of check-in
[bf969e9d1a]
at
2026-08-15 02:10:44
on branch trunk
— phase 0 exit: serve, HTML page via op, harness loop, provenance gate
API router + web page as registry consumers; disposability loop over all surfaces; third_party ledger enforced by arch test. (user: dmestas, size: 3593) [annotate] [blame] [check-ins using]
// Package harness runs the disposability loop (0001 §11): render every // snapshot surface, wipe derived state, rebuild from scratch, render // again, and semantically diff. Today "wipe" rebuilds the registry and // handlers (no persistent index exists yet); when phase-1 ingest lands it // becomes rm -rf of the data directory plus re-ingest of the fixture // corpus — this file's loop and comparison stay as they are. package harness import ( "io" "net/http" "net/http/httptest" "strings" "testing" "craftdesign.group/quarry/internal/api" "craftdesign.group/quarry/internal/ops" "craftdesign.group/quarry/internal/semdiff" "craftdesign.group/quarry/internal/web" ) type surface struct { path string kind semdiff.Kind } // buildServer stands the whole system up from nothing — the "ingest" of // the phase-0 loop. func buildServer(t *testing.T) *httptest.Server { t.Helper() r := ops.NewRegistry() if err := api.RegisterAll(r, "0.0.0-harness"); err != nil { t.Fatal(err) } apiRouter, err := api.Router(r, "0.0.0-harness") if err != nil { t.Fatal(err) } mux := http.NewServeMux() mux.Handle("/api/", apiRouter) mux.Handle("/", web.Handler(r)) srv := httptest.NewServer(mux) t.Cleanup(srv.Close) return srv } // surfaces enumerates what gets snapshotted: every HTML page, the OpenAPI // document, and — generated from the registry (0003 §4) — every GET op // that needs no input. New parameterless ops gain coverage automatically; // parameterized ops join when the fixture corpus drives them. func surfaces(t *testing.T) []surface { t.Helper() out := []surface{ {"/", semdiff.HTML}, {"/api/v1/openapi.json", semdiff.JSON}, } r := ops.NewRegistry() if err := api.RegisterAll(r, "0.0.0-harness"); err != nil { t.Fatal(err) } for _, d := range r.All() { if d.Method == http.MethodGet && d.In.NumField() == 0 { out = append(out, surface{d.Pattern, semdiff.JSON}) } } return out } func snapshot(t *testing.T, srv *httptest.Server, path string) []byte { t.Helper() resp, err := srv.Client().Get(srv.URL + path) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != 200 { t.Fatalf("%s: status %d", path, resp.StatusCode) } body, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } return body } func TestDisposability(t *testing.T) { sf := surfaces(t) if len(sf) < 3 { t.Fatalf("suspiciously few surfaces: %v", sf) } before := map[string][]byte{} first := buildServer(t) for _, s := range sf { before[s.path] = snapshot(t, first, s.path) } first.Close() // the wipe: nothing survives but the stores (none yet) second := buildServer(t) for _, s := range sf { after := snapshot(t, second, s.path) d, err := semdiff.Diff(s.kind, before[s.path], after) if err != nil { t.Fatalf("%s: %v", s.path, err) } if d != "" { t.Errorf("%s: semantic diff after rebuild — hostage state:\n%s", s.path, d) } } } // TestSemanticNotByteEquality pins why the harness diffs semantically: // a rendering-order change must pass, missing data must fail (0001 §11). func TestSemanticNotByteEquality(t *testing.T) { d, err := semdiff.Diff(semdiff.JSON, []byte(`{"b":1,"a":"2026-08-14T10:00:00+02:00"}`), []byte(`{"a":"2026-08-14 08:00:00Z","b":1}`)) if err != nil || d != "" { t.Fatalf("harmless drift flagged: %q %v", d, err) } d, err = semdiff.Diff(semdiff.JSON, []byte(`{"a":1,"b":2}`), []byte(`{"a":1}`)) if err != nil || d == "" { t.Fatal("dropped data not flagged") } if !strings.Contains(d, "b") { t.Fatalf("diff should name the loss: %s", d) } }