Skip to content

Commit 8a83da7

Browse files
authored
feat: Add code quality findings support (google#4330)
1 parent 93679ac commit 8a83da7

6 files changed

Lines changed: 777 additions & 0 deletions

File tree

github/code_quality.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,51 @@ import (
1010
"fmt"
1111
)
1212

13+
// CodeQualityFindingRule represents the rule associated with a code quality finding.
14+
type CodeQualityFindingRule struct {
15+
ID string `json:"id"`
16+
Title string `json:"title"`
17+
Description string `json:"description"`
18+
Help *string `json:"help,omitempty"`
19+
Severity string `json:"severity"`
20+
Category string `json:"category"`
21+
}
22+
23+
// CodeQualityFindingLocation represents the location of a code quality finding.
24+
type CodeQualityFindingLocation struct {
25+
Path string `json:"path"`
26+
StartLine *int `json:"start_line,omitempty"`
27+
EndLine *int `json:"end_line,omitempty"`
28+
StartColumn *int `json:"start_column,omitempty"`
29+
EndColumn *int `json:"end_column,omitempty"`
30+
}
31+
32+
// CodeQualityFindingMessage represents the message of a code quality finding.
33+
type CodeQualityFindingMessage struct {
34+
Text string `json:"text"`
35+
Markdown string `json:"markdown"`
36+
}
37+
38+
// CodeQualityFinding represents a single code quality finding.
39+
type CodeQualityFinding struct {
40+
Number int `json:"number"`
41+
State string `json:"state"`
42+
URL string `json:"url"`
43+
Rule CodeQualityFindingRule `json:"rule"`
44+
Location CodeQualityFindingLocation `json:"location"`
45+
Message CodeQualityFindingMessage `json:"message"`
46+
CreatedAt *Timestamp `json:"created_at,omitempty"`
47+
}
48+
49+
// ListCodeQualityFindingsOptions specifies the optional parameters to
50+
// CodeQualityService.ListFindings.
51+
type ListCodeQualityFindingsOptions struct {
52+
State string `url:"state,omitempty"`
53+
Direction string `url:"direction,omitempty"`
54+
55+
ListCursorOptions
56+
}
57+
1358
// CodeQualityService handles communication with the code quality related
1459
// methods of the GitHub API.
1560
//
@@ -87,3 +132,52 @@ func (s *CodeQualityService) UpdateSetup(ctx context.Context, owner, repo string
87132

88133
return result, resp, nil
89134
}
135+
136+
// ListFindings lists code quality findings for a repository.
137+
//
138+
// GitHub API docs: https://docs.github.com/rest/code-quality/code-quality?apiVersion=2022-11-28#list-code-quality-findings-for-a-repository
139+
//
140+
//meta:operation GET /repos/{owner}/{repo}/code-quality/findings
141+
func (s *CodeQualityService) ListFindings(ctx context.Context, owner, repo string, opts *ListCodeQualityFindingsOptions) ([]*CodeQualityFinding, *Response, error) {
142+
u := fmt.Sprintf("repos/%v/%v/code-quality/findings", owner, repo)
143+
144+
u, err := addOptions(u, opts)
145+
if err != nil {
146+
return nil, nil, err
147+
}
148+
149+
req, err := s.client.NewRequest(ctx, "GET", u, nil)
150+
if err != nil {
151+
return nil, nil, err
152+
}
153+
154+
var findings []*CodeQualityFinding
155+
resp, err := s.client.Do(req, &findings)
156+
if err != nil {
157+
return nil, resp, err
158+
}
159+
160+
return findings, resp, nil
161+
}
162+
163+
// GetFinding gets a single code quality finding for a repository.
164+
//
165+
// GitHub API docs: https://docs.github.com/rest/code-quality/code-quality?apiVersion=2022-11-28#get-a-code-quality-finding
166+
//
167+
//meta:operation GET /repos/{owner}/{repo}/code-quality/findings/{finding_number}
168+
func (s *CodeQualityService) GetFinding(ctx context.Context, owner, repo string, findingNumber int) (*CodeQualityFinding, *Response, error) {
169+
u := fmt.Sprintf("repos/%v/%v/code-quality/findings/%v", owner, repo, findingNumber)
170+
171+
req, err := s.client.NewRequest(ctx, "GET", u, nil)
172+
if err != nil {
173+
return nil, nil, err
174+
}
175+
176+
var finding *CodeQualityFinding
177+
resp, err := s.client.Do(req, &finding)
178+
if err != nil {
179+
return nil, resp, err
180+
}
181+
182+
return finding, resp, nil
183+
}

github/code_quality_test.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,213 @@ func TestCodeQualityService_UpdateSetup_invalidOwner(t *testing.T) {
182182
_, _, err := client.CodeQuality.UpdateSetup(ctx, "%", "r", CodeQualityUpdateSetupRequest{})
183183
testURLParseError(t, err)
184184
}
185+
186+
func TestCodeQualityService_ListFindings(t *testing.T) {
187+
t.Parallel()
188+
client, mux, _ := setup(t)
189+
190+
mux.HandleFunc("/repos/o/r/code-quality/findings", func(w http.ResponseWriter, r *http.Request) {
191+
testMethod(t, r, "GET")
192+
testFormValues(t, r, values{
193+
"state": "open",
194+
"direction": "desc",
195+
})
196+
fmt.Fprint(w, `[
197+
{
198+
"number": 1,
199+
"state": "open",
200+
"url": "https://api.github.com/repos/o/r/code-quality/findings/1",
201+
"rule": {
202+
"id": "rule-1",
203+
"title": "Example Rule",
204+
"description": "An example rule description",
205+
"help": "How to fix it",
206+
"severity": "warning",
207+
"category": "maintainability"
208+
},
209+
"location": {
210+
"path": "src/main.go",
211+
"start_line": 10,
212+
"end_line": 10,
213+
"start_column": 1,
214+
"end_column": 20
215+
},
216+
"message": {
217+
"text": "Issue found",
218+
"markdown": "**Issue found**"
219+
},
220+
"created_at": `+referenceTimeStr+`
221+
}
222+
]`)
223+
})
224+
225+
ctx := t.Context()
226+
opts := &ListCodeQualityFindingsOptions{
227+
State: "open",
228+
Direction: "desc",
229+
}
230+
findings, _, err := client.CodeQuality.ListFindings(ctx, "o", "r", opts)
231+
if err != nil {
232+
t.Fatalf("CodeQuality.ListFindings returned error: %v", err)
233+
}
234+
235+
want := []*CodeQualityFinding{
236+
{
237+
Number: 1,
238+
State: "open",
239+
URL: "https://api.github.com/repos/o/r/code-quality/findings/1",
240+
Rule: CodeQualityFindingRule{
241+
ID: "rule-1",
242+
Title: "Example Rule",
243+
Description: "An example rule description",
244+
Help: Ptr("How to fix it"),
245+
Severity: "warning",
246+
Category: "maintainability",
247+
},
248+
Location: CodeQualityFindingLocation{
249+
Path: "src/main.go",
250+
StartLine: Ptr(10),
251+
EndLine: Ptr(10),
252+
StartColumn: Ptr(1),
253+
EndColumn: Ptr(20),
254+
},
255+
Message: CodeQualityFindingMessage{
256+
Text: "Issue found",
257+
Markdown: "**Issue found**",
258+
},
259+
CreatedAt: &referenceTimestamp,
260+
},
261+
}
262+
if diff := cmp.Diff(want, findings); diff != "" {
263+
t.Errorf("CodeQuality.ListFindings mismatch (-want +got):\n%v", diff)
264+
}
265+
266+
const methodName = "ListFindings"
267+
testBadOptions(t, methodName, func() (err error) {
268+
_, _, err = client.CodeQuality.ListFindings(ctx, "\n", "\n", opts)
269+
return err
270+
})
271+
272+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
273+
got, resp, err := client.CodeQuality.ListFindings(ctx, "o", "r", opts)
274+
if got != nil {
275+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
276+
}
277+
return resp, err
278+
})
279+
}
280+
281+
func TestCodeQualityService_ListFindings_noOpts(t *testing.T) {
282+
t.Parallel()
283+
client, mux, _ := setup(t)
284+
285+
mux.HandleFunc("/repos/o/r/code-quality/findings", func(w http.ResponseWriter, r *http.Request) {
286+
testMethod(t, r, "GET")
287+
fmt.Fprint(w, `[]`)
288+
})
289+
290+
ctx := t.Context()
291+
findings, _, err := client.CodeQuality.ListFindings(ctx, "o", "r", nil)
292+
if err != nil {
293+
t.Fatalf("CodeQuality.ListFindings returned error: %v", err)
294+
}
295+
296+
if len(findings) != 0 {
297+
t.Errorf("CodeQuality.ListFindings returned %v findings, want 0", len(findings))
298+
}
299+
}
300+
301+
func TestCodeQualityService_GetFinding(t *testing.T) {
302+
t.Parallel()
303+
client, mux, _ := setup(t)
304+
305+
mux.HandleFunc("/repos/o/r/code-quality/findings/1", func(w http.ResponseWriter, r *http.Request) {
306+
testMethod(t, r, "GET")
307+
fmt.Fprint(w, `{
308+
"number": 1,
309+
"state": "open",
310+
"url": "https://api.github.com/repos/o/r/code-quality/findings/1",
311+
"rule": {
312+
"id": "rule-1",
313+
"title": "Example Rule",
314+
"description": "An example rule description",
315+
"severity": "error",
316+
"category": "reliability"
317+
},
318+
"location": {
319+
"path": "src/main.go",
320+
"start_line": 5,
321+
"end_line": 5
322+
},
323+
"message": {
324+
"text": "Critical issue",
325+
"markdown": "**Critical issue**"
326+
},
327+
"created_at": `+referenceTimeStr+`
328+
}`)
329+
})
330+
331+
ctx := t.Context()
332+
finding, _, err := client.CodeQuality.GetFinding(ctx, "o", "r", 1)
333+
if err != nil {
334+
t.Fatalf("CodeQuality.GetFinding returned error: %v", err)
335+
}
336+
337+
want := &CodeQualityFinding{
338+
Number: 1,
339+
State: "open",
340+
URL: "https://api.github.com/repos/o/r/code-quality/findings/1",
341+
Rule: CodeQualityFindingRule{
342+
ID: "rule-1",
343+
Title: "Example Rule",
344+
Description: "An example rule description",
345+
Severity: "error",
346+
Category: "reliability",
347+
},
348+
Location: CodeQualityFindingLocation{
349+
Path: "src/main.go",
350+
StartLine: Ptr(5),
351+
EndLine: Ptr(5),
352+
},
353+
Message: CodeQualityFindingMessage{
354+
Text: "Critical issue",
355+
Markdown: "**Critical issue**",
356+
},
357+
CreatedAt: &referenceTimestamp,
358+
}
359+
if diff := cmp.Diff(want, finding); diff != "" {
360+
t.Errorf("CodeQuality.GetFinding mismatch (-want +got):\n%v", diff)
361+
}
362+
363+
const methodName = "GetFinding"
364+
testBadOptions(t, methodName, func() (err error) {
365+
_, _, err = client.CodeQuality.GetFinding(ctx, "\n", "\n", 1)
366+
return err
367+
})
368+
369+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
370+
got, resp, err := client.CodeQuality.GetFinding(ctx, "o", "r", 1)
371+
if got != nil {
372+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
373+
}
374+
return resp, err
375+
})
376+
}
377+
378+
func TestCodeQualityService_ListFindings_invalidOwner(t *testing.T) {
379+
t.Parallel()
380+
client, _, _ := setup(t)
381+
382+
ctx := t.Context()
383+
_, _, err := client.CodeQuality.ListFindings(ctx, "%", "r", nil)
384+
testURLParseError(t, err)
385+
}
386+
387+
func TestCodeQualityService_GetFinding_invalidOwner(t *testing.T) {
388+
t.Parallel()
389+
client, _, _ := setup(t)
390+
391+
ctx := t.Context()
392+
_, _, err := client.CodeQuality.GetFinding(ctx, "%", "r", 1)
393+
testURLParseError(t, err)
394+
}

0 commit comments

Comments
 (0)