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) } }