diff --git a/pkg/github/actions.go b/pkg/github/actions.go index 1629b818f7..85dd99e1aa 100644 --- a/pkg/github/actions.go +++ b/pkg/github/actions.go @@ -802,7 +802,7 @@ func getWorkflowRun(ctx context.Context, client *github.Client, owner, repo stri return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get workflow run", resp, err), nil, nil } defer func() { _ = resp.Body.Close() }() - r, err := json.Marshal(workflowRun) + r, err := json.Marshal(convertToMinimalWorkflowRun(workflowRun)) if err != nil { return nil, nil, fmt.Errorf("failed to marshal workflow run: %w", err) } diff --git a/pkg/github/actions_test.go b/pkg/github/actions_test.go index e5c0662cc4..a25a35f704 100644 --- a/pkg/github/actions_test.go +++ b/pkg/github/actions_test.go @@ -307,14 +307,9 @@ func Test_ActionsGet_GetWorkflowRun(t *testing.T) { toolDef := ActionsGet(translations.NullTranslationHelper) t.Run("successful workflow run get", func(t *testing.T) { + run := actionsTestWorkflowRun() mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposActionsRunsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - run := &github.WorkflowRun{ - ID: github.Ptr(int64(12345)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - } w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(run) }), @@ -338,11 +333,21 @@ func Test_ActionsGet_GetWorkflowRun(t *testing.T) { require.False(t, result.IsError) textContent := getTextResult(t, result) - var response github.WorkflowRun + var response MinimalWorkflowRun err = json.Unmarshal([]byte(textContent.Text), &response) require.NoError(t, err) - assert.NotNil(t, response.ID) - assert.Equal(t, int64(12345), *response.ID) + + expected := convertToMinimalWorkflowRun(run) + assert.Equal(t, expected, response) + + var payload map[string]any + require.NoError(t, json.Unmarshal([]byte(textContent.Text), &payload)) + assert.Equal(t, marshalActionsObject(t, expected), payload) + assert.NotContains(t, payload, "node_id") + assert.NotContains(t, payload, "repository") + assert.NotContains(t, payload, "head_repository") + assert.NotContains(t, payload, "url") + assert.NotContains(t, payload, "jobs_url") }) } diff --git a/pkg/github/minimal_types.go b/pkg/github/minimal_types.go index c05c38e7bd..15993f35a0 100644 --- a/pkg/github/minimal_types.go +++ b/pkg/github/minimal_types.go @@ -699,6 +699,24 @@ type MinimalPRBranchRepo struct { Description string `json:"description,omitempty"` } +// MinimalRepoStatus is the trimmed output type for an individual commit status. +type MinimalRepoStatus struct { + State string `json:"state"` + Context string `json:"context"` + Description string `json:"description,omitempty"` + TargetURL string `json:"target_url,omitempty"` + CreatedAt string `json:"created_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` +} + +// MinimalCombinedStatus is the trimmed output type for a combined commit status. +type MinimalCombinedStatus struct { + State string `json:"state"` + SHA string `json:"sha"` + TotalCount int `json:"total_count"` + Statuses []MinimalRepoStatus `json:"statuses"` +} + type MinimalProjectStatusUpdate struct { ID string `json:"id"` Body string `json:"body,omitempty"` @@ -1057,6 +1075,42 @@ func convertToMinimalPRBranch(branch *github.PullRequestBranch) *MinimalPRBranch return b } +func convertToMinimalCombinedStatus(status *github.CombinedStatus) MinimalCombinedStatus { + minimalStatus := MinimalCombinedStatus{ + Statuses: make([]MinimalRepoStatus, 0), + } + if status == nil { + return minimalStatus + } + + minimalStatus.State = status.GetState() + minimalStatus.SHA = status.GetSHA() + minimalStatus.TotalCount = status.GetTotalCount() + minimalStatus.Statuses = make([]MinimalRepoStatus, 0, len(status.GetStatuses())) + for _, repoStatus := range status.GetStatuses() { + if repoStatus != nil { + minimalStatus.Statuses = append(minimalStatus.Statuses, convertToMinimalRepoStatus(repoStatus)) + } + } + + return minimalStatus +} + +func convertToMinimalRepoStatus(status *github.RepoStatus) MinimalRepoStatus { + if status == nil { + return MinimalRepoStatus{} + } + + return MinimalRepoStatus{ + State: status.GetState(), + Context: status.GetContext(), + Description: status.GetDescription(), + TargetURL: status.GetTargetURL(), + CreatedAt: formatMinimalTimestamp(status.CreatedAt), + UpdatedAt: formatMinimalTimestamp(status.UpdatedAt), + } +} + func convertToMinimalProject(fullProject *github.ProjectV2) *MinimalProject { if fullProject == nil { return nil diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index 9825ba8845..a86b699f7f 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -307,7 +307,7 @@ func GetPullRequestStatus(ctx context.Context, client *github.Client, owner, rep return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get combined status", resp, body), nil } - r, err := json.Marshal(status) + r, err := json.Marshal(convertToMinimalCombinedStatus(status)) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } @@ -1281,10 +1281,9 @@ func AddReplyToPullRequestComment(t translations.TranslationHelperFunc) inventor } } - var comment *github.PullRequestComment + var commentResponse *MinimalResponse if hasBody { - var resp *github.Response - comment, resp, err = client.PullRequests.CreateCommentInReplyTo(ctx, owner, repo, pullNumber, body, commentID) + comment, resp, err := client.PullRequests.CreateCommentInReplyTo(ctx, owner, repo, pullNumber, body, commentID) if err != nil { return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to add reply to pull request comment", resp, err), nil, nil } @@ -1297,19 +1296,24 @@ func AddReplyToPullRequestComment(t translations.TranslationHelperFunc) inventor } return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to add reply to pull request comment", resp, bodyBytes), nil, nil } + + commentResponse = &MinimalResponse{ + ID: fmt.Sprintf("%d", comment.GetID()), + URL: comment.GetHTMLURL(), + } } var result any switch { case hasBody && hasReaction: - result = map[string]any{ - "comment": comment, - "reaction": reactionResponse, + result = map[string]MinimalResponse{ + "comment": *commentResponse, + "reaction": *reactionResponse, } case hasReaction: result = reactionResponse default: - result = comment + result = commentResponse } r, err := json.Marshal(result) diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index 5fe1229dba..1edf16e7b7 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -1575,16 +1575,32 @@ func Test_GetPullRequestStatus(t *testing.T) { }, } - // Setup mock status for success case + statusCreatedAt := &github.Timestamp{Time: time.Date(2026, time.August, 11, 9, 30, 0, 0, time.UTC)} + statusUpdatedAt := &github.Timestamp{Time: time.Date(2026, time.August, 11, 9, 35, 0, 0, time.UTC)} mockStatus := &github.CombinedStatus{ + Name: github.Ptr("abcd1234"), State: github.Ptr("success"), - TotalCount: github.Ptr(3), + SHA: github.Ptr("abcd1234"), + TotalCount: github.Ptr(2), + CommitURL: github.Ptr("https://api.github.com/repos/owner/repo/commits/abcd1234"), + RepositoryURL: github.Ptr( + "https://api.github.com/repos/owner/repo", + ), Statuses: []*github.RepoStatus{ { + ID: github.Ptr(int64(101)), + NodeID: github.Ptr("SC_kwDOStatus101"), + URL: github.Ptr("https://api.github.com/repos/owner/repo/statuses/abcd1234"), State: github.Ptr("success"), Context: github.Ptr("continuous-integration/travis-ci"), Description: github.Ptr("Build succeeded"), TargetURL: github.Ptr("https://travis-ci.org/owner/repo/builds/123"), + AvatarURL: github.Ptr("https://avatars.githubusercontent.com/in/123"), + Creator: &github.User{ + Login: github.Ptr("ci-bot"), + }, + CreatedAt: statusCreatedAt, + UpdatedAt: statusUpdatedAt, }, { State: github.Ptr("success"), @@ -1592,25 +1608,25 @@ func Test_GetPullRequestStatus(t *testing.T) { Description: github.Ptr("Coverage increased"), TargetURL: github.Ptr("https://codecov.io/gh/owner/repo/pull/42"), }, - { - State: github.Ptr("success"), - Context: github.Ptr("lint/golangci-lint"), - Description: github.Ptr("No issues found"), - TargetURL: github.Ptr("https://golangci.com/r/owner/repo/pull/42"), - }, }, } + emptyStatus := &github.CombinedStatus{ + State: github.Ptr("pending"), + SHA: github.Ptr("abcd1234"), + TotalCount: github.Ptr(0), + Statuses: []*github.RepoStatus{nil}, + } tests := []struct { name string mockedClient *http.Client requestArgs map[string]any expectError bool - expectedStatus *github.CombinedStatus + expectedStatus *MinimalCombinedStatus expectedErrMsg string }{ { - name: "successful status fetch", + name: "successful status fetch with multiple statuses", mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR), GetReposCommitsStatusByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockStatus), @@ -1621,8 +1637,46 @@ func Test_GetPullRequestStatus(t *testing.T) { "repo": "repo", "pullNumber": float64(42), }, - expectError: false, - expectedStatus: mockStatus, + expectedStatus: &MinimalCombinedStatus{ + State: "success", + SHA: "abcd1234", + TotalCount: 2, + Statuses: []MinimalRepoStatus{ + { + State: "success", + Context: "continuous-integration/travis-ci", + Description: "Build succeeded", + TargetURL: "https://travis-ci.org/owner/repo/builds/123", + CreatedAt: "2026-08-11T09:30:00Z", + UpdatedAt: "2026-08-11T09:35:00Z", + }, + { + State: "success", + Context: "codecov/patch", + Description: "Coverage increased", + TargetURL: "https://codecov.io/gh/owner/repo/pull/42", + }, + }, + }, + }, + { + name: "successful status fetch with no statuses", + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR), + GetReposCommitsStatusByOwnerByRepoByRef: mockResponse(t, http.StatusOK, emptyStatus), + }), + requestArgs: map[string]any{ + "method": "get_status", + "owner": "owner", + "repo": "repo", + "pullNumber": float64(42), + }, + expectedStatus: &MinimalCombinedStatus{ + State: "pending", + SHA: "abcd1234", + TotalCount: 0, + Statuses: []MinimalRepoStatus{}, + }, }, { name: "PR fetch fails", @@ -1691,20 +1745,33 @@ func Test_GetPullRequestStatus(t *testing.T) { require.NoError(t, err) require.False(t, result.IsError) - // Parse the result and get the text content if no error textContent := getTextResult(t, result) - // Unmarshal and verify the result - var returnedStatus github.CombinedStatus + var returnedStatus MinimalCombinedStatus err = json.Unmarshal([]byte(textContent.Text), &returnedStatus) require.NoError(t, err) - assert.Equal(t, *tc.expectedStatus.State, *returnedStatus.State) - assert.Equal(t, *tc.expectedStatus.TotalCount, *returnedStatus.TotalCount) - assert.Len(t, returnedStatus.Statuses, len(tc.expectedStatus.Statuses)) - for i, status := range returnedStatus.Statuses { - assert.Equal(t, *tc.expectedStatus.Statuses[i].State, *status.State) - assert.Equal(t, *tc.expectedStatus.Statuses[i].Context, *status.Context) - assert.Equal(t, *tc.expectedStatus.Statuses[i].Description, *status.Description) + assert.Equal(t, *tc.expectedStatus, returnedStatus) + + expectedJSON, err := json.Marshal(tc.expectedStatus) + require.NoError(t, err) + assert.JSONEq(t, string(expectedJSON), textContent.Text) + + var payload map[string]any + require.NoError(t, json.Unmarshal([]byte(textContent.Text), &payload)) + assert.NotContains(t, payload, "name") + assert.NotContains(t, payload, "commit_url") + assert.NotContains(t, payload, "repository_url") + + statuses, ok := payload["statuses"].([]any) + require.True(t, ok) + for _, status := range statuses { + statusPayload, ok := status.(map[string]any) + require.True(t, ok) + assert.NotContains(t, statusPayload, "id") + assert.NotContains(t, statusPayload, "node_id") + assert.NotContains(t, statusPayload, "url") + assert.NotContains(t, statusPayload, "avatar_url") + assert.NotContains(t, statusPayload, "creator") } }) } @@ -4145,6 +4212,13 @@ func TestAddReplyToPullRequestComment(t *testing.T) { } replyCreatedAfterReactionFailure := &atomic.Bool{} + assertMinimalResponse := func(t *testing.T, response map[string]any, expectedID, expectedURL string) { + t.Helper() + assert.Len(t, response, 2) + assert.Equal(t, expectedID, response["id"]) + assert.Equal(t, expectedURL, response["url"]) + } + tests := []struct { name string mockedClient *http.Client @@ -4354,14 +4428,29 @@ func TestAddReplyToPullRequestComment(t *testing.T) { return } - // Parse the result and verify it's not an error require.False(t, result.IsError) textContent := getTextResult(t, result) - if _, ok := tc.requestArgs["body"]; ok { - assert.Contains(t, textContent.Text, "This is a reply to the comment") - } - if _, ok := tc.requestArgs["reaction"]; ok { - assert.Contains(t, textContent.Text, "789") + + var response map[string]any + require.NoError(t, json.Unmarshal([]byte(textContent.Text), &response)) + + _, hasBody := tc.requestArgs["body"] + _, hasReaction := tc.requestArgs["reaction"] + reactionURL := client.BaseURL() + "repos/owner/repo/pulls/comments/123/reactions/789" + + switch { + case hasBody && hasReaction: + assert.Len(t, response, 2) + commentResponse, ok := response["comment"].(map[string]any) + require.True(t, ok) + assertMinimalResponse(t, commentResponse, "456", "https://github.com/owner/repo/pull/42#discussion_r456") + reactionResponse, ok := response["reaction"].(map[string]any) + require.True(t, ok) + assertMinimalResponse(t, reactionResponse, "789", reactionURL) + case hasBody: + assertMinimalResponse(t, response, "456", "https://github.com/owner/repo/pull/42#discussion_r456") + default: + assertMinimalResponse(t, response, "789", reactionURL) } }) }