Skip to content
quarry Artifact [bcd7c284ea]
Login

Artifact [bcd7c284ea]

Artifact bcd7c284ea15424c72e5aaba6b05931a1f762690df99a00ca46c775bb35de584:


package api

import (
	"context"
	"errors"
	"os/exec"
	"path/filepath"
	"testing"

	"craftdesign.group/quarry/internal/artifact"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
	"craftdesign.group/quarry/internal/zitcli"
)

// strataRetargetIndex holds three open landings — base on trunk, mid on base,
// and solo on trunk by itself — and one rejected one.
func strataRetargetIndex(t *testing.T, extra ...index.Parsed) *index.Index {
	t.Helper()
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	open := func(hash, who, branch, target string) index.Parsed {
		return index.Parsed{Hash: hash, Art: &artifact.Artifact{
			Type: artifact.Forum, ThreadTitle: branch, Date: "2026-01-01T10:00:00", User: who,
			Body: []byte("zit-landing: 1\nbranch: " + branch + "\ntarget: " + target + "\n\nready\n")}}
	}
	batch := []index.Parsed{
		open("b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0", "ada", "base", "trunk"),
		open("c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1", "ada", "mid", "base"),
		open("d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2", "bob", "solo", "trunk"),
		open("e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3", "bob", "done", "trunk"),
		{Hash: "f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4f4", Art: &artifact.Artifact{Type: artifact.Forum,
			ThreadRoot: "e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3", Date: "2026-01-01T11:00:00", User: "mai",
			Body: []byte("zit-landing-state: rejected\n\ndone\n")}},
	}
	if err := ix.FoldNow(append(batch, extra...)); err != nil {
		t.Fatal(err)
	}
	return ix
}

func stackOps(t *testing.T, extra ...index.Parsed) *ops.Registry {
	t.Helper()
	if _, err := exec.LookPath("zit"); err != nil {
		t.Skip("zit not on PATH")
	}
	// An empty store, so a call that passes the gate fails in the
	// write rather than panicking on a nil client.
	dir := t.TempDir()
	if err := zitcli.Init(context.Background(), dir); err != nil {
		t.Fatal(err)
	}
	deps := Deps{
		Repos: NewRepos(map[string]*Repo{"test": {Index: strataRetargetIndex(t, extra...), Zit: zitcli.New(dir)}}),
		// trunk is owned by mai; the feature branches by nobody.
		Protect: stubProtect{rules: []oltp.Protection{{Branch: "trunk", Owners: []string{"mai"}}}},
	}
	r := ops.NewRegistry()
	if err := registerLandingRetarget(r, deps); err != nil {
		t.Fatal(err)
	}
	return r
}

func retarget(r *ops.Registry, ctx context.Context, id, to string) error {
	_, err := r.Invoke(ctx, "landing.retarget", landingRetargetIn{Repo: "test", ID: id, To: to})
	return err
}

// A stacked rung's own target is a feature branch nobody protects, so
// asking who may join THAT would let any developer reorder a strata.
// Who may move it is its author or an owner of where the strata lands.
func TestAStackedRungMovesOnlyForItsAuthorOrTheStrataTargetsOwner(t *testing.T) {
	r := stackOps(t)
	mid := "c1c1c1c1c1c1"
	if err := retarget(r, as("bob", "i"), mid, "trunk"); !errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: a developer who owns nothing moved somebody else's stacked rung: %v", err)
	}
	for _, who := range []string{"ada", "mai"} {
		// Past the gate, the write fails for want of a store; that is
		// not the refusal this test is about.
		if err := retarget(r, as(who, "i"), mid, "trunk"); errors.Is(err, ops.ErrForbidden) {
			t.Errorf("FAIL: %s may not unstack the rung: %v", who, err)
		}
	}
}

// The trunk owner may not carry work into a destination they do not
// own, nor anyone stack another's landing into a strata whose target
// they cannot accept onto.
func TestStackingAsksTheOwnerOfWhereTheStrataWouldLand(t *testing.T) {
	r := stackOps(t)
	// solo (bob's) onto mid: the strata lands on trunk either way, so
	// mai may and a stranger may not.
	if err := retarget(r, as("eve", "i"), "d2d2d2d2d2d2", "!c1c1c1c1c1c1"); !errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: a stranger stacked bob's landing: %v", err)
	}
	if err := retarget(r, as("mai", "i"), "d2d2d2d2d2d2", "!c1c1c1c1c1c1"); errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: trunk's owner may not order what lands on trunk: %v", err)
	}
}

// Stacking a rung onto one that already stands on it closes a loop no
// walk could land; a settled landing has no strata to join.
func TestStackingRefusesALoopAndASettledLanding(t *testing.T) {
	r := stackOps(t)
	if err := retarget(r, as("ada", "i"), "b0b0b0b0b0b0", "!c1c1c1c1c1c1"); !errors.Is(err, ops.ErrInvalid) {
		t.Errorf("FAIL: base stacked onto the rung above it: %v", err)
	}
	if err := retarget(r, as("bob", "i"), "d2d2d2d2d2d2", "!e3e3e3e3e3e3"); !errors.Is(err, ops.ErrInvalid) {
		t.Errorf("FAIL: stacked onto a rejected landing: %v", err)
	}
	if err := retarget(r, as("bob", "i"), "d2d2d2d2d2d2", "!0000000000"); !errors.Is(err, ops.ErrNotFound) {
		t.Errorf("FAIL: stacked onto no landing at all: %v", err)
	}
}

func TestAStrataIsGreenOnlyWhenEveryRungIs(t *testing.T) {
	seg := func(ci ...string) []index.StackSegment {
		var out []index.StackSegment
		for _, c := range ci {
			out = append(out, index.StackSegment{CI: c})
		}
		return out
	}
	for _, c := range []struct {
		in   []string
		want string
	}{
		{[]string{"ci-pass", "ci-pass"}, "ci-pass"},
		{[]string{"ci-pass", "ci-run", "ci-fail"}, "ci-fail"},
		{[]string{"", "ci-pass"}, ""},
		{[]string{"ci-waiting", "ci-pass"}, "ci-waiting"},
		{nil, ""},
	} {
		if got := strataVerdict(seg(c.in...)); got != c.want {
			t.Errorf("FAIL: strataVerdict(%v) = %q, want %q", c.in, got, c.want)
		}
	}
}

// The author may arrange their own proposal, but moving a base carries
// every rung stacked on it; when one of those is somebody else's, it is
// the destination owner's act, not the base author's.
func TestABaseAuthorMayNotCarryOthersRungs(t *testing.T) {
	r := stackOps(t, index.Parsed{Hash: "a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5", Art: &artifact.Artifact{
		Type: artifact.Forum, ThreadTitle: "top", Date: "2026-01-01T10:00:00", User: "cat",
		Body: []byte("zit-landing: 1\nbranch: top\ntarget: mid\n\nready\n")}})
	if err := retarget(r, as("ada", "i"), "b0b0b0b0b0b0", "release"); !errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: ada carried cat's rung off trunk without owning it: %v", err)
	}
	// mid is ada's and carries cat's top too; trunk's owner may move it.
	if err := retarget(r, as("mai", "i"), "c1c1c1c1c1c1", "trunk"); errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: trunk's owner may not unstack: %v", err)
	}
	// cat's own top rung carries nobody: cat may move it.
	if err := retarget(r, as("cat", "i"), "a5a5a5a5a5a5", "trunk"); errors.Is(err, ops.ErrForbidden) {
		t.Errorf("FAIL: cat may not move cat's own top rung: %v", err)
	}
}