diff --git a/cmd/review-bot/integration_test.go b/cmd/review-bot/integration_test.go index d0da818..a19aa1f 100644 --- a/cmd/review-bot/integration_test.go +++ b/cmd/review-bot/integration_test.go @@ -130,7 +130,7 @@ func TestIntegration_PostAndCleanup(t *testing.T) { // Post a test review sentinel := "" testBody := "# Integration Test Review\n\nThis is a test review.\n\n" + sentinel - posted, err := giteaClient.PostReview(ctx, owner, repoName, prNumber, "COMMENT", testBody, nil) + posted, err := giteaClient.PostReview(ctx, owner, repoName, prNumber, "COMMENT", testBody, "", nil) if err != nil { t.Fatalf("PostReview: %v", err) } diff --git a/cmd/review-bot/main.go b/cmd/review-bot/main.go index 1289a99..9a9da91 100644 --- a/cmd/review-bot/main.go +++ b/cmd/review-bot/main.go @@ -444,7 +444,7 @@ func main() { // POST new review slog.Info("posting review", "event", event, "pr", prNumber) - posted, err := giteaClient.PostReview(ctx, owner, repoName, prNumber, event, reviewBody, inlineComments) + posted, err := giteaClient.PostReview(ctx, owner, repoName, prNumber, event, reviewBody, evaluatedSHA, inlineComments) if err != nil { slog.Error("failed to post review", "pr", prNumber, "event", event, "error", err) os.Exit(1) diff --git a/gitea/client.go b/gitea/client.go index 572ee18..e4f8dd7 100644 --- a/gitea/client.go +++ b/gitea/client.go @@ -263,17 +263,21 @@ func (c *Client) GetFileContentRef(ctx context.Context, owner, repo, filepath, r // PostReview submits a review to a PR and returns the created review. // event should be "APPROVED" or "REQUEST_CHANGES". +// commitID anchors the review to a specific commit SHA. If empty, Gitea +// defaults to the current PR head. // comments are optional inline comments attached to specific lines. -func (c *Client) PostReview(ctx context.Context, owner, repo string, number int, event, body string, comments []ReviewComment) (*Review, error) { +func (c *Client) PostReview(ctx context.Context, owner, repo string, number int, event, body, commitID string, comments []ReviewComment) (*Review, error) { reqURL := fmt.Sprintf("%s/api/v1/repos/%s/%s/pulls/%d/reviews", c.baseURL, url.PathEscape(owner), url.PathEscape(repo), number) payload := struct { Body string `json:"body"` Event string `json:"event"` + CommitID string `json:"commit_id,omitempty"` Comments []ReviewComment `json:"comments,omitempty"` }{ Body: body, Event: event, + CommitID: commitID, Comments: comments, } diff --git a/gitea/client_test.go b/gitea/client_test.go index 66b1bf7..19ac55b 100644 --- a/gitea/client_test.go +++ b/gitea/client_test.go @@ -117,8 +117,9 @@ func TestPostReview(t *testing.T) { } var payload struct { - Body string `json:"body"` - Event string `json:"event"` + Body string `json:"body"` + Event string `json:"event"` + CommitID string `json:"commit_id"` } if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { t.Fatalf("failed to decode payload: %v", err) @@ -129,14 +130,16 @@ func TestPostReview(t *testing.T) { if payload.Event != "APPROVED" { t.Errorf("expected event %q, got %q", "APPROVED", payload.Event) } - + if payload.CommitID != "abc123def" { + t.Errorf("expected commit_id %q, got %q", "abc123def", payload.CommitID) + } w.WriteHeader(http.StatusOK) w.Write([]byte(`{"id":100,"user":{"login":"review-bot"},"state":"APPROVED","stale":false}`)) })) defer server.Close() client := NewClient(server.URL, "test-token") - review, err := client.PostReview(context.Background(), "owner", "repo", 3, "APPROVED", "LGTM", nil) + review, err := client.PostReview(context.Background(), "owner", "repo", 3, "APPROVED", "LGTM", "abc123def", nil) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -183,12 +186,35 @@ func TestPostReview_Non200(t *testing.T) { defer server.Close() client := NewClient(server.URL, "test-token") - _, err := client.PostReview(context.Background(), "owner", "repo", 1, "APPROVED", "test", nil) + _, err := client.PostReview(context.Background(), "owner", "repo", 1, "APPROVED", "test", "", nil) if err == nil { t.Fatal("expected error for 403, got nil") } } +func TestPostReview_EmptyCommitID_OmittedFromPayload(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + var raw map[string]interface{} + if err := json.Unmarshal(body, &raw); err != nil { + t.Fatalf("failed to decode payload: %v", err) + } + if _, exists := raw["commit_id"]; exists { + t.Errorf("expected commit_id to be omitted from payload when empty, but it was present") + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"id":200,"user":{"login":"bot"},"state":"APPROVED","stale":false}`)) + })) + defer server.Close() + + client := NewClient(server.URL, "test-token") + _, err := client.PostReview(context.Background(), "owner", "repo", 1, "APPROVED", "ok", "", nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + func TestGetFileContent(t *testing.T) { expected := "# Conventions\n- Be nice\n" @@ -945,8 +971,6 @@ func TestDoGet_RespectsContextCancellation(t *testing.T) { t.Errorf("attempts = %d, expected 1 before context cancel during backoff", attempts) } } - - // mockTransport is a test helper that returns errors for the first N calls, // then delegates to a real server. type mockTransport struct { diff --git a/gitea/post_review_comments_test.go b/gitea/post_review_comments_test.go index e58d05d..7a8bf57 100644 --- a/gitea/post_review_comments_test.go +++ b/gitea/post_review_comments_test.go @@ -37,7 +37,7 @@ func TestPostReview_WithComments(t *testing.T) { {Path: "util.go", NewPosition: 10, Body: "[MINOR] Style issue"}, } - _, err := client.PostReview(context.Background(), "owner", "repo", 1, "REQUEST_CHANGES", "summary", comments) + _, err := client.PostReview(context.Background(), "owner", "repo", 1, "REQUEST_CHANGES", "summary", "", comments) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -72,7 +72,7 @@ func TestPostReview_NilComments(t *testing.T) { defer server.Close() client := NewClient(server.URL, "test-token") - _, err := client.PostReview(context.Background(), "owner", "repo", 1, "APPROVED", "all good", nil) + _, err := client.PostReview(context.Background(), "owner", "repo", 1, "APPROVED", "all good", "", nil) if err != nil { t.Fatalf("unexpected error: %v", err) }