Artifact 433f7f99b0b6a0d0c5846ad180c28e85f97ec672b1f0b5010f89dc7c28ba665a:
- File
internal/harness/harness_test.go
— part of check-in
[893885b3d5]
at
2026-08-21 19:49:17
on branch quarry-forum
— forum subscriptions: auto-sub on authoring, reply/mention fanout, unsub sticks
Fanout rides the write path only (a reindex can never re-notify); delivery/dedup stays in the notify companion via TxnID. (user: dmestas, size: 10200) [annotate] [blame] [check-ins using] [more...]
// Package harness runs the disposability loop (0001 §11): ingest real // stores, snapshot every rendered surface, rm -rf the derived data // directory, re-ingest from the stores, snapshot again, and semantically // diff. Any diff is hostage state and fails the build. The fleet version // runs two stores through one process — shared NATS, per-repo indexes — // and asserts they cannot bleed into each other. package harness import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "os" "os/exec" "path/filepath" "strings" "testing" "craftdesign.group/quarry/internal/api" "craftdesign.group/quarry/internal/events" "craftdesign.group/quarry/internal/index" "craftdesign.group/quarry/internal/ingest" "craftdesign.group/quarry/internal/oltp" "craftdesign.group/quarry/internal/ops" "craftdesign.group/quarry/internal/semdiff" "craftdesign.group/quarry/internal/web" "craftdesign.group/quarry/internal/zitcli" ) type surface struct { path string kind semdiff.Kind } // surfaces enumerates what gets snapshotted for one repo, plus the // fleet-level pages. func surfaces(repos []string) []surface { out := []surface{ {"/", semdiff.HTML}, {"/api/v1/repos", semdiff.JSON}, {"/api/v1/version", semdiff.JSON}, {"/api/v1/openapi.json", semdiff.JSON}, } for _, r := range repos { p := "/r/" + r a := "/api/v1/r/" + r out = append(out, surface{p + "/timeline", semdiff.HTML}, surface{p + "/wiki", semdiff.HTML}, surface{p + "/wiki/Guide", semdiff.HTML}, surface{p + "/wiki/Guide/history", semdiff.HTML}, surface{a + "/wiki/Guide", semdiff.JSON}, surface{a + "/wiki/Guide/history", semdiff.JSON}, surface{p + "/ticket", semdiff.HTML}, surface{p + "/search?q=README", semdiff.HTML}, surface{a + "/timeline", semdiff.JSON}, surface{a + "/wiki", semdiff.JSON}, surface{a + "/ticket", semdiff.JSON}, surface{a + "/search?q=README", semdiff.JSON}, ) } return out } func zrun(t *testing.T, dir string, args ...string) { t.Helper() cmd := exec.Command("zit", args...) cmd.Dir = dir if out, err := cmd.CombinedOutput(); err != nil { t.Fatalf("zit %v: %v\n%s", args, err, out) } } // buildStore creates one store under test: a zit repository with a few // commits carrying a distinguishing seed word. func buildStore(t *testing.T, seed string) string { t.Helper() if _, err := exec.LookPath("zit"); err != nil { t.Skip("zit not on PATH") } repo := t.TempDir() zrun(t, repo, "init") for _, f := range []struct{ name, body, msg string }{ {"README.md", "# store " + seed + "\n", "add README " + seed}, {"src/main.go", "package " + seed + "\n", "add main"}, {"README.md", "# store " + seed + "\nmore\n", "extend README"}, } { if err := os.MkdirAll(filepath.Dir(filepath.Join(repo, f.name)), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(repo, f.name), []byte(f.body), 0o644); err != nil { t.Fatal(err) } zrun(t, repo, "add", f.name) zrun(t, repo, "commit", "-m", f.msg) } // Non-checkin artifacts too (zit#71 verbs): the disposability rule // covers every rendered surface, wiki and tickets included. wiki := exec.Command("zit", "wiki", "commit", "Guide", "--user", seed, "--mimetype", "text/x-markdown") wiki.Dir = repo wiki.Stdin = strings.NewReader("# Guide\n\n" + seed + " wiki body\n") if out, err := wiki.CombinedOutput(); err != nil { t.Fatalf("zit wiki commit: %v\n%s", err, out) } zrun(t, repo, "ticket", "new", "--user", seed, "title="+seed+" ticket", "status=Open") return repo } // ingestAndServe is the from-nothing build: one embedded NATS and one // index per repo, all under dataDir (so the wipe destroys stream, // cursors, and every table), each store flowing watcher → events → fold // under its own project code, both registry consumers on top. func ingestAndServe(t *testing.T, repos map[string]string, dataDir string) *httptest.Server { t.Helper() return ingestAndServeAuth(t, repos, dataDir, nil) } func ingestAndServeAuth(t *testing.T, repos map[string]string, dataDir string, verify api.Verify) *httptest.Server { t.Helper() return ingestAndServeAuthMod(t, repos, dataDir, verify, nil, nil, nil) } // ingestAndServeAuthMod additionally wires the moderation trust seam // and the AI hook, both nil-safe. func ingestAndServeAuthMod(t *testing.T, repos map[string]string, dataDir string, verify api.Verify, trusted func(account, repo string) bool, hook func(context.Context, oltp.Staged) (api.HookVerdict, error), notify api.NotifyPlane) *httptest.Server { t.Helper() ev, err := events.Start(filepath.Join(dataDir, "nats")) if err != nil { t.Fatal(err) } t.Cleanup(ev.Close) // The vote plane is wired exactly as the server wires it (one OLTP // under dataDir), so vote state dies with the wipe like all forge // state — and its absence would silently unregister forum.vote. odb, err := oltp.Open(filepath.Join(dataDir, "oltp.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { odb.Close() }) deps := api.Deps{Version: "0.0.0-harness", Repos: api.NewRepos(nil), Votes: odb, Forum: odb, TrustedAuthor: trusted, ForumHook: hook, Notify: notify} for name, path := range repos { ix, err := index.Open(filepath.Join(dataDir, name+".db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { ix.Close() }) cli := zitcli.New(path) pipe := ingest.NewPipeline(cli, ev, ix, name, "harness") // Leaves must be wired here exactly as the server wires it, or // the harness proves things about a quarry that does not exist: // leaf status comes from zit, and an unwired provider silently // marks nothing. zc := cli deps.Repos.Put(name, &api.Repo{Index: ix, Zit: cli, Sync: pipe.Sync, Leaves: func() (map[string]bool, error) { return zc.Leaves(context.Background()) }}) if _, err := pipe.Sync(); err != nil { t.Fatal(err) } } r := ops.NewRegistry() if err := api.RegisterAll(r, deps); err != nil { t.Fatal(err) } apiRouter, err := api.Router(r, "0.0.0-harness", verify) if err != nil { t.Fatal(err) } mux := http.NewServeMux() mux.Handle("/api/", apiRouter) mux.Handle("/", web.Handler(r, nil, nil, web.Verify(verify))) srv := httptest.NewServer(mux) t.Cleanup(srv.Close) return srv } 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) { stores := map[string]string{ "alpha": buildStore(t, "alphaseed"), "beta": buildStore(t, "betaseed"), } sf := surfaces([]string{"alpha", "beta"}) dataA := filepath.Join(t.TempDir(), "data") if err := os.MkdirAll(dataA, 0o755); err != nil { t.Fatal(err) } first := ingestAndServe(t, stores, dataA) // Per-repo check-in and file surfaces are discovered live; their // paths are content-addressed hashes, identical across rebuilds. for _, repo := range []string{"alpha", "beta"} { var tl struct { Entries []struct { Kind string `json:"kind"` Hash string `json:"hash"` } `json:"entries"` } if err := json.Unmarshal(snapshot(t, first, "/api/v1/r/"+repo+"/timeline?y=ci"), &tl); err != nil { t.Fatal(err) } if len(tl.Entries) == 0 { t.Fatalf("%s: no check-ins", repo) } for _, e := range tl.Entries { if e.Kind != "ci" { continue } sf = append(sf, surface{"/r/" + repo + "/checkin/" + e.Hash, semdiff.HTML}, surface{"/r/" + repo + "/checkin/" + e.Hash + "/diff", semdiff.HTML}, surface{"/api/v1/r/" + repo + "/checkin/" + e.Hash, semdiff.JSON}) } var tip struct { Files []struct { Name string `json:"name"` } `json:"files"` } if err := json.Unmarshal(snapshot(t, first, "/api/v1/r/"+repo+"/checkin/"+tl.Entries[0].Hash), &tip); err != nil { t.Fatal(err) } for _, f := range tip.Files { sf = append(sf, surface{"/r/" + repo + "/file/" + tl.Entries[0].Hash + "/" + f.Name, semdiff.HTML}, surface{"/api/v1/r/" + repo + "/file/" + tl.Entries[0].Hash + "/" + f.Name, semdiff.JSON}) } } // Isolation: repo A's content is invisible from repo B's surfaces. var cross struct { Count int `json:"count"` } if err := json.Unmarshal(snapshot(t, first, "/api/v1/r/beta/search?q=alphaseed"), &cross); err != nil { t.Fatal(err) } if cross.Count != 0 { t.Fatalf("repo isolation broken: alpha content searchable from beta (%d hits)", cross.Count) } if err := json.Unmarshal(snapshot(t, first, "/api/v1/r/alpha/search?q=alphaseed"), &cross); err != nil { t.Fatal(err) } if cross.Count == 0 { t.Fatal("alpha content not searchable from alpha") } before := map[string][]byte{} for _, s := range sf { before[s.path] = snapshot(t, first, s.path) } first.Close() // The wipe (0001 §11 step 2): every derived byte goes — both // indexes, the event stream, the cursors. if err := os.RemoveAll(dataA); err != nil { t.Fatal(err) } dataB := filepath.Join(t.TempDir(), "data") if err := os.MkdirAll(dataB, 0o755); err != nil { t.Fatal(err) } second := ingestAndServe(t, stores, dataB) 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 wipe-and-reingest — 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) } }