package gitx
import (
"context"
"errors"
"strings"
"testing"
"sourcecraft.dev/bigbes/sr-ht-spec/core"
)
func TestCreateProposalBranch(t *testing.T) {
repo, _ := newSpace(t)
ctx := context.Background()
base := pushApproved(t, repo, ownerMeta("seed", 1),
Write{Path: "specs/0007.md", Content: doc("SPEC-0007", "Storage", "v1")})
head, err := repo.CreateProposalBranch(ctx, "proposals/1", base.String())
if err != nil {
t.Fatalf("CreateProposalBranch: %v", err)
}
if head != base {
t.Fatalf("branch cut at %s, want the base %s", head, base)
}
got, err := repo.BranchHead(ctx, "proposals/1")
if err != nil {
t.Fatalf("BranchHead: %v", err)
}
if got != base {
t.Fatalf("proposals/1 = %s, want %s", got, base)
}
// Cutting the same branch twice is a caller bug, not an idempotent retry:
// the second call would silently discard whatever the first accumulated.
if _, err := repo.CreateProposalBranch(ctx, "proposals/1", base.String()); !errors.Is(err, ErrExists) {
t.Fatalf("second CreateProposalBranch = %v, want ErrExists", err)
}
// Only proposals/* branches exist to be cut.
for _, b := range []string{"main", "scratch", "proposals", ""} {
if _, err := repo.CreateProposalBranch(ctx, b, base.String()); !errors.Is(err, ErrBadRev) {
t.Fatalf("CreateProposalBranch(%q) = %v, want ErrBadRev", b, err)
}
}
if _, err := repo.CreateProposalBranch(ctx, "proposals/2", "nosuchrev"); !errors.Is(err, ErrNotFound) {
t.Fatalf("CreateProposalBranch at an unknown base = %v, want ErrNotFound", err)
}
}
func TestCommitProposalSplicesWholeDocuments(t *testing.T) {
repo, _ := newSpace(t)
ctx := context.Background()
base := pushApproved(t, repo, ownerMeta("seed", 1),
Write{Path: "specs/0007.md", Content: doc("SPEC-0007", "Storage", "v1")},
Write{Path: "specs/deep/nested/0008.md", Content: doc("SPEC-0008", "Nested", "n1")},
Write{Path: "specs/diagram.png", Content: []byte{0x89, 'P', 'N', 'G'}},
)
if _, err := repo.CreateProposalBranch(ctx, "proposals/1", base.String()); err != nil {
t.Fatal(err)
}
res, err := repo.CommitProposal(ctx, "proposals/1", []Write{
{Path: "specs/0007.md", Content: doc("SPEC-0007", "Storage", "v2")},
{Path: "notes/new.md", Content: doc("NOTE-0001", "New", "fresh")},
}, meta("revise 0007 and add a note", 2,
Trailer{Key: "X-Agent-Session", Value: "8fb9c9a4"},
Trailer{Key: "X-Agent-Base", Value: base.String()},
))
if err != nil {
t.Fatalf("CommitProposal: %v", err)
}
if len(res.Parents) != 1 || res.Parents[0] != base {
t.Fatalf("parents = %v, want [%s]", res.Parents, base)
}
if len(res.Blobs) != 2 {
t.Fatalf("Blobs = %v, want one per write", res.Blobs)
}
c, err := repo.repo.CommitObject(res.Commit)
if err != nil {
t.Fatal(err)
}
// Author is the agent, committer the owner: the provenance the design
// requires, supplied whole by the caller.
if !strings.HasPrefix(c.Author.Name, "claude-code/") || c.Committer.Name != "bigbes" {
t.Fatalf("identities = author %q, committer %q", c.Author.Name, c.Committer.Name)
}
wantMsg := "revise 0007 and add a note\n\nX-Agent-Session: 8fb9c9a4\nX-Agent-Base: " + base.String() + "\n"
if c.Message != wantMsg {
t.Fatalf("message = %q, want %q", c.Message, wantMsg)
}
// The splice replaced one document, added another, and left everything else
// — including the nested subtree and the attachment — exactly as it was.
if got := mustRead(t, repo, "proposals/1", "specs/0007.md"); !strings.Contains(got, "v2") {
t.Fatalf("edited document = %q", got)
}
if got := mustRead(t, repo, "proposals/1", "notes/new.md"); !strings.Contains(got, "fresh") {
t.Fatalf("added document = %q", got)
}
if got := mustRead(t, repo, "proposals/1", "specs/deep/nested/0008.md"); !strings.Contains(got, "n1") {
t.Fatalf("untouched nested document = %q", got)
}
png, _, err := repo.ReadBlob(ctx, "proposals/1", "specs/diagram.png")
if err != nil || len(png) != 4 {
t.Fatalf("attachment survived as %v, %v", png, err)
}
// The approved branch did not move.
if head, _ := repo.ApprovedHead(ctx); head != base {
t.Fatalf("a proposal commit moved the approved branch to %s", head)
}
}
// TestCommitProposalRefusesTheApprovedBranch is the second half of the refs
// rule, enforced in-process: the approved branch moves by a human push or by
// Merge, and by nothing else.
func TestCommitProposalRefusesTheApprovedBranch(t *testing.T) {
repo, _ := newSpace(t)
ctx := context.Background()
w := []Write{{Path: "specs/0007.md", Content: doc("SPEC-0007", "Storage", "v1")}}
for _, branch := range []string{DefaultApprovedBranch, "scratch", "proposals", "refs/heads/proposals/1"} {
_, err := repo.CommitProposal(ctx, branch, w, meta("nope", 2))
if !errors.Is(err, ErrBadRev) {
t.Fatalf("CommitProposal(%q) = %v, want ErrBadRev", branch, err)
}
}
}
func TestCommitProposalValidatesItsWrites(t *testing.T) {
repo, _ := newSpace(t)
ctx := context.Background()
base, err := repo.ApprovedHead(ctx)
if err != nil {
t.Fatal(err)
}
if _, err := repo.CreateProposalBranch(ctx, "proposals/1", base.String()); err != nil {
t.Fatal(err)
}
good := doc("SPEC-0007", "Storage", "v1")
// Only document paths. .spec.yml is a real file in a space but it is not a
// document, and the write plane is a whole-document PUT.
for _, p := range []string{core.PolicyFile, "specs/0007.txt", "../escape.md", "/abs.md", ""} {
_, err := repo.CommitProposal(ctx, "proposals/1", []Write{{Path: p, Content: good}}, meta("bad path", 2))
if !errors.Is(err, core.ErrInvalidPath) {
t.Fatalf("CommitProposal to %q = %v, want core.ErrInvalidPath", p, err)
}
}
// A commit with nothing in it is a caller bug.
if _, err := repo.CommitProposal(ctx, "proposals/1", nil, meta("empty", 2)); err == nil {
t.Fatal("CommitProposal with no writes succeeded")
}
// The same path twice in one commit: the second would silently win.
_, err = repo.CommitProposal(ctx, "proposals/1", []Write{
{Path: "specs/0007.md", Content: good},
{Path: "specs/0007.md", Content: doc("SPEC-0007", "Storage", "v2")},
}, meta("twice", 2))
if err == nil {
t.Fatal("CommitProposal writing one path twice succeeded")
}
// Identities are required.
if _, err := repo.CommitProposal(ctx, "proposals/1", []Write{{Path: "specs/0007.md", Content: good}},
CommitMeta{Message: "no identity"}); err == nil {
t.Fatal("CommitProposal with no identities succeeded")
}
// The branch has to exist first.
if _, err := repo.CommitProposal(ctx, "proposals/9", []Write{{Path: "specs/0007.md", Content: good}},
meta("absent", 2)); !errors.Is(err, ErrNotFound) {
t.Fatalf("CommitProposal to an absent branch = %v, want ErrNotFound", err)
}
}
func TestCommitProposalAccumulatesEdits(t *testing.T) {
repo, _ := newSpace(t)
ctx := context.Background()
base, err := repo.ApprovedHead(ctx)
if err != nil {
t.Fatal(err)
}
if _, err := repo.CreateProposalBranch(ctx, "proposals/1", base.String()); err != nil {
t.Fatal(err)
}
first, err := repo.CommitProposal(ctx, "proposals/1",
[]Write{{Path: "notes/a.md", Content: doc("NOTE-0001", "A", "one")}}, meta("first", 2))
if err != nil {
t.Fatal(err)
}
second, err := repo.CommitProposal(ctx, "proposals/1",
[]Write{{Path: "notes/b.md", Content: doc("NOTE-0002", "B", "two")}}, meta("second", 3))
if err != nil {
t.Fatal(err)
}
if second.Parents[0] != first.Commit {
t.Fatalf("second commit parent = %s, want %s", second.Parents[0], first.Commit)
}
paths := docPaths(t, repo, "proposals/1")
if strings.Join(paths, ",") != "notes/a.md,notes/b.md" {
t.Fatalf("proposal branch holds %v", paths)
}
}
func TestCommitProposalRetriesALostRefCAS(t *testing.T) {
repo, _ := newSpace(t)
ctx := context.Background()
base, err := repo.ApprovedHead(ctx)
if err != nil {
t.Fatal(err)
}
if _, err := repo.CreateProposalBranch(ctx, "proposals/1", base.String()); err != nil {
t.Fatal(err)
}
var raced bool
repo.beforeCAS = func() {
if raced {
return
}
raced = true
pushBranch(t, repo, "proposals/1", meta("someone else", 3),
Write{Path: "notes/other.md", Content: doc("NOTE-0009", "Other", "landed first")})
}
res, err := repo.CommitProposal(ctx, "proposals/1",
[]Write{{Path: "notes/a.md", Content: doc("NOTE-0001", "A", "one")}}, meta("mine", 4))
if err != nil {
t.Fatalf("CommitProposal that lost a CAS = %v, want a retry", err)
}
if !raced {
t.Fatal("the race hook never fired")
}
if res.Parents[0] == base {
t.Fatal("the retry kept the stale parent instead of rebuilding")
}
paths := docPaths(t, repo, "proposals/1")
if strings.Join(paths, ",") != "notes/a.md,notes/other.md" {
t.Fatalf("proposal branch holds %v; the concurrent write was lost", paths)
}
}
func TestMutableTreeRefusesFileDirectoryCollisions(t *testing.T) {
repo, _ := newSpace(t)
h, err := repo.writeBlob("x", []byte("x"))
if err != nil {
t.Fatal(err)
}
n := newMutableTree()
if err := n.set("specs/0007.md", h); err != nil {
t.Fatal(err)
}
// "specs/0007.md" is a file, so it cannot also be a directory...
if err := n.set("specs/0007.md/inner.md", h); err == nil {
t.Fatal("set under an existing file succeeded")
}
// ...and "specs" is a directory, so it cannot also be a file.
if err := n.set("specs", h); err == nil {
t.Fatal("set over an existing directory succeeded")
}
}
func TestMutableTreeDropsEmptySubtrees(t *testing.T) {
repo, _ := newSpace(t)
h, err := repo.writeBlob("x", []byte("x"))
if err != nil {
t.Fatal(err)
}
n := newMutableTree()
if err := n.set("a/b/c.md", h); err != nil {
t.Fatal(err)
}
withChild, err := n.write(repo.repo.Storer)
if err != nil {
t.Fatal(err)
}
if !n.remove("a/b/c.md") {
t.Fatal("remove reported nothing removed")
}
if n.remove("a/b/c.md") {
t.Fatal("remove reported a second removal")
}
empty, err := n.write(repo.repo.Storer)
if err != nil {
t.Fatal(err)
}
if empty == withChild {
t.Fatal("emptying the tree did not change its hash")
}
tree, err := repo.repo.TreeObject(empty)
if err != nil {
t.Fatal(err)
}
if len(tree.Entries) != 0 {
t.Fatalf("empty subtrees survived: %+v", tree.Entries)
}
}