fix(github): validate conflicting commit IDs and extract test helper

Address review findings from sonnet-review-bot (review 3086):

- PostReview now returns ErrConflictingCommitIDs when comments specify
  different non-empty CommitIDs, since the GitHub API accepts only a
  single commit_id per review. Previously the discrepancy was silently
  ignored, using only the first commit's ID.

- Extract newTestClient into helpers_test.go to make cross-file sharing
  between review_test.go and identity_test.go explicit.

Refs: #81
This commit is contained in:
claw
2026-05-13 01:44:11 -07:00
committed by Aaron Weiker
parent 8b256360bf
commit 9dd5e8dbac
3 changed files with 63 additions and 18 deletions
+19 -4
View File
@@ -17,6 +17,11 @@ import (
// a submitted review should use DismissReview instead.
var ErrCannotDeleteSubmittedReview = errors.New("cannot delete submitted review: use DismissReview instead")
// ErrConflictingCommitIDs is returned when PostReview receives comments with
// differing non-empty CommitIDs. The GitHub API accepts only a single commit_id
// per review submission; callers must ensure all comments target the same commit.
var ErrConflictingCommitIDs = errors.New("comments contain conflicting commit IDs: all must target the same commit")
// postReviewRequest is the GitHub API request body for creating a review.
type postReviewRequest struct {
CommitID string `json:"commit_id,omitempty"`
@@ -75,6 +80,11 @@ func translateGitHubReviewState(state string) string {
// ReviewComment.Position maps directly to the GitHub API position field.
// When req.Comments is empty, the payload omits the comments field entirely
// (via the omitempty tag on postReviewRequest.Comments).
//
// The GitHub API accepts a single commit_id per review submission. PostReview
// extracts it from the first comment with a non-empty CommitID. If any subsequent
// comment specifies a different CommitID, PostReview returns ErrConflictingCommitIDs.
// Comments with an empty CommitID are allowed and inherit the review-level value.
func (c *Client) PostReview(ctx context.Context, owner, repo string, number int, req vcs.ReviewRequest) (*vcs.Review, error) {
reqURL := fmt.Sprintf("%s/repos/%s/%s/pulls/%d/reviews",
c.baseURL, url.PathEscape(owner), url.PathEscape(repo), number)
@@ -84,11 +94,16 @@ func (c *Client) PostReview(ctx context.Context, owner, repo string, number int,
Event: string(req.Event),
}
// Populate CommitID from the first comment and build the payload in one pass.
// All comments in a single review share the same commit_id.
// Build the payload in one pass. The GitHub API accepts a single commit_id
// per review; we extract it from the first comment that supplies one and
// reject the request if any other comment disagrees.
for _, comment := range req.Comments {
if payload.CommitID == "" && comment.CommitID != "" {
payload.CommitID = comment.CommitID
if comment.CommitID != "" {
if payload.CommitID == "" {
payload.CommitID = comment.CommitID
} else if payload.CommitID != comment.CommitID {
return nil, ErrConflictingCommitIDs
}
}
payload.Comments = append(payload.Comments, reviewCommentEntry{
Path: comment.Path,