Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions github/code_quality.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,51 @@ import (
"fmt"
)

// CodeQualityFindingRule represents the rule associated with a code quality finding.
type CodeQualityFindingRule struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Help *string `json:"help,omitempty"`
Severity string `json:"severity"`
Category string `json:"category"`
}

// CodeQualityFindingLocation represents the location of a code quality finding.
type CodeQualityFindingLocation struct {
Path string `json:"path"`
StartLine *int `json:"start_line,omitempty"`
EndLine *int `json:"end_line,omitempty"`
StartColumn *int `json:"start_column,omitempty"`
EndColumn *int `json:"end_column,omitempty"`
}

// CodeQualityFindingMessage represents the message of a code quality finding.
type CodeQualityFindingMessage struct {
Text string `json:"text"`
Markdown string `json:"markdown"`
}

// CodeQualityFinding represents a single code quality finding.
type CodeQualityFinding struct {
Number int `json:"number"`
State string `json:"state"`
URL string `json:"url"`
Rule CodeQualityFindingRule `json:"rule"`
Location CodeQualityFindingLocation `json:"location"`
Message CodeQualityFindingMessage `json:"message"`
CreatedAt *Timestamp `json:"created_at,omitempty"`
}
Comment thread
Andrei-hub11 marked this conversation as resolved.

// ListCodeQualityFindingsOptions specifies the optional parameters to
// CodeQualityService.ListFindings.
type ListCodeQualityFindingsOptions struct {
State string `url:"state,omitempty"`
Direction string `url:"direction,omitempty"`

ListCursorOptions
}

// CodeQualityService handles communication with the code quality related
// methods of the GitHub API.
//
Expand Down Expand Up @@ -87,3 +132,52 @@ func (s *CodeQualityService) UpdateSetup(ctx context.Context, owner, repo string

return result, resp, nil
}

// ListFindings lists code quality findings for a repository.
//
// GitHub API docs: https://docs.github.com/rest/code-quality/code-quality?apiVersion=2022-11-28#list-code-quality-findings-for-a-repository
//
//meta:operation GET /repos/{owner}/{repo}/code-quality/findings
func (s *CodeQualityService) ListFindings(ctx context.Context, owner, repo string, opts *ListCodeQualityFindingsOptions) ([]*CodeQualityFinding, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/code-quality/findings", owner, repo)

u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}

req, err := s.client.NewRequest(ctx, "GET", u, nil)
if err != nil {
return nil, nil, err
}

var findings []*CodeQualityFinding
resp, err := s.client.Do(req, &findings)
if err != nil {
return nil, resp, err
}

return findings, resp, nil
}

// GetFinding gets a single code quality finding for a repository.
//
// GitHub API docs: https://docs.github.com/rest/code-quality/code-quality?apiVersion=2022-11-28#get-a-code-quality-finding
//
//meta:operation GET /repos/{owner}/{repo}/code-quality/findings/{finding_number}
func (s *CodeQualityService) GetFinding(ctx context.Context, owner, repo string, findingNumber int) (*CodeQualityFinding, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/code-quality/findings/%v", owner, repo, findingNumber)

req, err := s.client.NewRequest(ctx, "GET", u, nil)
if err != nil {
return nil, nil, err
}

var finding *CodeQualityFinding
resp, err := s.client.Do(req, &finding)
if err != nil {
return nil, resp, err
}

return finding, resp, nil
}
210 changes: 210 additions & 0 deletions github/code_quality_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,213 @@ func TestCodeQualityService_UpdateSetup_invalidOwner(t *testing.T) {
_, _, err := client.CodeQuality.UpdateSetup(ctx, "%", "r", CodeQualityUpdateSetupRequest{})
testURLParseError(t, err)
}

func TestCodeQualityService_ListFindings(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

mux.HandleFunc("/repos/o/r/code-quality/findings", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
testFormValues(t, r, values{
"state": "open",
"direction": "desc",
})
fmt.Fprint(w, `[
{
"number": 1,
"state": "open",
"url": "https://api.github.com/repos/o/r/code-quality/findings/1",
"rule": {
"id": "rule-1",
"title": "Example Rule",
"description": "An example rule description",
"help": "How to fix it",
"severity": "warning",
"category": "maintainability"
},
"location": {
"path": "src/main.go",
"start_line": 10,
"end_line": 10,
"start_column": 1,
"end_column": 20
},
"message": {
"text": "Issue found",
"markdown": "**Issue found**"
},
"created_at": `+referenceTimeStr+`
}
]`)
})

ctx := t.Context()
opts := &ListCodeQualityFindingsOptions{
State: "open",
Direction: "desc",
}
findings, _, err := client.CodeQuality.ListFindings(ctx, "o", "r", opts)
if err != nil {
t.Fatalf("CodeQuality.ListFindings returned error: %v", err)
}

want := []*CodeQualityFinding{
{
Number: 1,
State: "open",
URL: "https://api.github.com/repos/o/r/code-quality/findings/1",
Rule: CodeQualityFindingRule{
ID: "rule-1",
Title: "Example Rule",
Description: "An example rule description",
Help: Ptr("How to fix it"),
Severity: "warning",
Category: "maintainability",
},
Location: CodeQualityFindingLocation{
Path: "src/main.go",
StartLine: Ptr(10),
EndLine: Ptr(10),
StartColumn: Ptr(1),
EndColumn: Ptr(20),
},
Message: CodeQualityFindingMessage{
Text: "Issue found",
Markdown: "**Issue found**",
},
CreatedAt: &referenceTimestamp,
},
}
if diff := cmp.Diff(want, findings); diff != "" {
t.Errorf("CodeQuality.ListFindings mismatch (-want +got):\n%v", diff)
}

const methodName = "ListFindings"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.CodeQuality.ListFindings(ctx, "\n", "\n", opts)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.CodeQuality.ListFindings(ctx, "o", "r", opts)
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})
}

func TestCodeQualityService_ListFindings_noOpts(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

mux.HandleFunc("/repos/o/r/code-quality/findings", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `[]`)
})

ctx := t.Context()
findings, _, err := client.CodeQuality.ListFindings(ctx, "o", "r", nil)
if err != nil {
t.Fatalf("CodeQuality.ListFindings returned error: %v", err)
}

if len(findings) != 0 {
t.Errorf("CodeQuality.ListFindings returned %v findings, want 0", len(findings))
}
}

func TestCodeQualityService_GetFinding(t *testing.T) {
t.Parallel()
client, mux, _ := setup(t)

mux.HandleFunc("/repos/o/r/code-quality/findings/1", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "GET")
fmt.Fprint(w, `{
"number": 1,
"state": "open",
"url": "https://api.github.com/repos/o/r/code-quality/findings/1",
"rule": {
"id": "rule-1",
"title": "Example Rule",
"description": "An example rule description",
"severity": "error",
"category": "reliability"
},
"location": {
"path": "src/main.go",
"start_line": 5,
"end_line": 5
},
"message": {
"text": "Critical issue",
"markdown": "**Critical issue**"
},
"created_at": `+referenceTimeStr+`
}`)
})

ctx := t.Context()
finding, _, err := client.CodeQuality.GetFinding(ctx, "o", "r", 1)
if err != nil {
t.Fatalf("CodeQuality.GetFinding returned error: %v", err)
}

want := &CodeQualityFinding{
Number: 1,
State: "open",
URL: "https://api.github.com/repos/o/r/code-quality/findings/1",
Rule: CodeQualityFindingRule{
ID: "rule-1",
Title: "Example Rule",
Description: "An example rule description",
Severity: "error",
Category: "reliability",
},
Location: CodeQualityFindingLocation{
Path: "src/main.go",
StartLine: Ptr(5),
EndLine: Ptr(5),
},
Message: CodeQualityFindingMessage{
Text: "Critical issue",
Markdown: "**Critical issue**",
},
CreatedAt: &referenceTimestamp,
}
if diff := cmp.Diff(want, finding); diff != "" {
t.Errorf("CodeQuality.GetFinding mismatch (-want +got):\n%v", diff)
}

const methodName = "GetFinding"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.CodeQuality.GetFinding(ctx, "\n", "\n", 1)
return err
})

testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
got, resp, err := client.CodeQuality.GetFinding(ctx, "o", "r", 1)
if got != nil {
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
}
return resp, err
})
}

func TestCodeQualityService_ListFindings_invalidOwner(t *testing.T) {
t.Parallel()
client, _, _ := setup(t)

ctx := t.Context()
_, _, err := client.CodeQuality.ListFindings(ctx, "%", "r", nil)
testURLParseError(t, err)
}

func TestCodeQualityService_GetFinding_invalidOwner(t *testing.T) {
t.Parallel()
client, _, _ := setup(t)

ctx := t.Context()
_, _, err := client.CodeQuality.GetFinding(ctx, "%", "r", 1)
testURLParseError(t, err)
}
Loading
Loading