Artifact 178147bf36b7f1a5df529cd785af08588aa287899f71f8485ec60f208d4309f0:
- File
internal/harness/landing_flow_test.go
— part of check-in
[9a84a5d962]
at
2026-08-23 21:50:21
on branch trunk
— landings: prove the lifecycle end to end
open, comment, reject, accept through the forge against a real store — accept merges to trunk via zit#131's bare-store path. Also checks the record still reads as an ordinary forum thread. (user: dmestas, size: 5317) [annotate] [blame] [check-ins using] [more...]
package harness import ( "encoding/json" "io" "net/http" "os" "os/exec" "path/filepath" "strings" "testing" "craftdesign.group/quarry/internal/api" "craftdesign.group/quarry/internal/ops" ) // The whole landing lifecycle through quarry's own surfaces, against a real // store and a real zit: open a branch for landing, discuss it, and settle it. // Every step mints ordinary fossil artifacts, so the proof that this works is // the same proof that a stock fossil clone would still read the repository. func TestLandingLifecycleThroughTheForge(t *testing.T) { if _, err := exec.LookPath("zit"); err != nil { t.Skip("zit not on PATH") } store := buildStore(t, "flowseed") // A branch worth landing: a real edit, on its own branch. if err := os.WriteFile(filepath.Join(store, "feature.txt"), []byte("the work\n"), 0o644); err != nil { t.Fatal(err) } zrun(t, store, "add", "feature.txt") zrun(t, store, "commit", "-m", "work on the branch", "--branch", "feat") srv := ingestAndServeAuth(t, map[string]string{"alpha": store}, t.TempDir(), func(repo, user, pass string, _ ops.Auth) api.Decision { if user == "dan" && pass == "hunter2" { return api.Allowed } return api.Denied }) post := func(path, body string) (int, string) { t.Helper() req, _ := http.NewRequest("POST", srv.URL+path, strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") req.SetBasicAuth("dan", "hunter2") resp, err := srv.Client().Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() b, _ := io.ReadAll(resp.Body) return resp.StatusCode, string(b) } code, body := post("/api/v1/r/alpha/landing", `{"branch":"feat","target":"trunk","message":"land the feat work"}`) if code != http.StatusOK { t.Fatalf("opening a landing: %d %s", code, body) } var opened struct { ID string `json:"id"` } json.Unmarshal([]byte(body), &opened) if opened.ID == "" { t.Fatalf("no record id came back: %s", body) } // It is a forum thread, so it reads as one — the compatibility // promise, checked rather than asserted. req, _ := http.NewRequest("GET", srv.URL+"/api/v1/r/alpha/forum/"+opened.ID, nil) req.SetBasicAuth("dan", "hunter2") resp, _ := srv.Client().Do(req) if resp != nil { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("the record is not readable as an ordinary forum thread: %d", resp.StatusCode) } } if code, body := post("/api/v1/r/alpha/landing/"+opened.ID[:12]+"/comment", `{"message":"looks right to me"}`); code != http.StatusOK { t.Fatalf("commenting: %d %s", code, body) } // Settling it. reject writes no check-in, so it needs nothing more // than the capability to speak. if code, body := post("/api/v1/r/alpha/landing/"+opened.ID[:12]+"/reject", `{"message":"not this week"}`); code != http.StatusOK { t.Fatalf("rejecting: %d %s", code, body) } // A settled record does not reopen, and quarry says so rather than // letting the store refuse at the boundary. if code, _ := post("/api/v1/r/alpha/landing/"+opened.ID[:12]+"/reject", `{"message":"again"}`); code != http.StatusBadRequest { t.Errorf("re-settling a closed record: %d, want 400", code) } // Accept: the one op that MERGES. It writes a check-in to the // target, which is why it takes `i` and why nothing else here does. // Against a bare store this is zit#131's path — the forge has no // checkout, and until that shipped this could not run at all. if err := os.WriteFile(filepath.Join(store, "second.txt"), []byte("more work\n"), 0o644); err != nil { t.Fatal(err) } zrun(t, store, "update", "trunk") zrun(t, store, "add", "second.txt") zrun(t, store, "commit", "-m", "second branch work", "--branch", "feat2") code, body = post("/api/v1/r/alpha/landing", `{"branch":"feat2","target":"trunk","message":"land the second lot"}`) if code != http.StatusOK { t.Fatalf("opening the second landing: %d %s", code, body) } var two struct { ID string `json:"id"` } json.Unmarshal([]byte(body), &two) if code, body := post("/api/v1/r/alpha/landing/"+two.ID[:12]+"/accept", `{"message":"landing it"}`); code != http.StatusOK { t.Fatalf("accepting: %d %s", code, body) } // The branch is on trunk now — the whole point of the ceremony. merged := zout(t, store, "timeline", "-n", "5") if !strings.Contains(merged, "second branch work") && !strings.Contains(merged, "feat2") { t.Errorf("the accepted branch does not appear on trunk:\n%s", merged) } // And the listing agrees about where it ended up. req, _ = http.NewRequest("GET", srv.URL+"/api/v1/r/alpha/landing?state=rejected", nil) req.SetBasicAuth("dan", "hunter2") resp, err := srv.Client().Do(req) if err != nil { t.Fatal(err) } defer resp.Body.Close() b, _ := io.ReadAll(resp.Body) if !strings.Contains(string(b), opened.ID) { t.Errorf("the rejected record is not listed as rejected: %s", b) } } // zout runs a zit verb and returns its output, for the assertions that have // to ask the STORE what happened rather than trusting the forge's own report. func zout(t *testing.T, dir string, args ...string) string { t.Helper() cmd := exec.Command("zit", args...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("zit %v: %v\n%s", args, err, out) } return string(out) }