Skip to content

Commit 3b3cbf1

Browse files
Add support for Dependabot alert endpoints (#2554)
1 parent 9598613 commit 3b3cbf1

File tree

5 files changed

+920
-0
lines changed

5 files changed

+920
-0
lines changed

github/dependabot_alerts.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright 2022 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"context"
10+
"fmt"
11+
)
12+
13+
// Dependency reprensents the vulnerable dependency.
14+
type Dependency struct {
15+
Package *VulnerabilityPackage `json:"package,omitempty"`
16+
ManifestPath *string `json:"manifest_path,omitempty"`
17+
Scope *string `json:"scope,omitempty"`
18+
}
19+
20+
// AdvisoryCVSs represents the advisory pertaining to the Common Vulnerability Scoring System.
21+
type AdvisoryCVSs struct {
22+
Score *float64 `json:"score,omitempty"`
23+
VectorString *string `json:"vector_string,omitempty"`
24+
}
25+
26+
// AdvisoryCWEs reprensent the advisory pertaining to Common Weakness Enumeration.
27+
type AdvisoryCWEs struct {
28+
CWEID *string `json:"cwe_id,omitempty"`
29+
Name *string `json:"name,omitempty"`
30+
}
31+
32+
// DependabotSecurityAdvisory represents the GitHub Security Advisory.
33+
type DependabotSecurityAdvisory struct {
34+
GHSAID *string `json:"ghsa_id,omitempty"`
35+
CVEID *string `json:"cve_id,omitempty"`
36+
Summary *string `json:"summary,omitempty"`
37+
Description *string `json:"description,omitempty"`
38+
Vulnerabilities []*AdvisoryVulnerability `json:"vulnerabilities,omitempty"`
39+
Severity *string `json:"severity,omitempty"`
40+
CVSs *AdvisoryCVSs `json:"cvss,omitempty"`
41+
CWEs []*AdvisoryCWEs `json:"cwes,omitempty"`
42+
Identifiers []*AdvisoryIdentifier `json:"identifiers,omitempty"`
43+
References []*AdvisoryReference `json:"references,omitempty"`
44+
PublishedAt *Timestamp `json:"published_at,omitempty"`
45+
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
46+
WithdrawnAt *Timestamp `json:"withdrawn_at,omitempty"`
47+
}
48+
49+
// DependabotAlert represents a Dependabot alert.
50+
type DependabotAlert struct {
51+
Number *int `json:"number,omitempty"`
52+
State *string `json:"state,omitempty"`
53+
Dependency *Dependency `json:"dependency,omitempty"`
54+
SecurityAdvisory *DependabotSecurityAdvisory `json:"security_advisory,omitempty"`
55+
SecurityVulnerability *AdvisoryVulnerability `json:"security_vulnerability,omitempty"`
56+
URL *string `json:"url,omitempty"`
57+
HTMLURL *string `json:"html_url,omitempty"`
58+
CreatedAt *Timestamp `json:"created_at,omitempty"`
59+
UpdatedAt *Timestamp `json:"updated_at,omitempty"`
60+
DismissedAt *Timestamp `json:"dismissed_at,omitempty"`
61+
DismissedBy *User `json:"dismissed_by,omitempty"`
62+
DismissedReason *string `json:"dismissed_reason,omitempty"`
63+
DismissedComment *string `json:"dismissed_comment,omitempty"`
64+
FixedAt *Timestamp `json:"fixed_at,omitempty"`
65+
}
66+
67+
// ListAlertsOptions specifies the optional parameters to the DependabotService.ListRepoAlerts
68+
// and DependabotService.ListOrgAlerts methods.
69+
type ListAlertsOptions struct {
70+
State *string `url:"state,omitempty"`
71+
Severity *string `url:"severity,omitempty"`
72+
Ecosystem *string `url:"ecosystem,omitempty"`
73+
Package *string `url:"package,omitempty"`
74+
Scope *string `url:"scope,omitempty"`
75+
Sort *string `url:"sort,omitempty"`
76+
Direction *string `url:"direction,omitempty"`
77+
78+
ListCursorOptions
79+
}
80+
81+
func (s *DependabotService) listAlerts(ctx context.Context, url string, opts *ListAlertsOptions) ([]*DependabotAlert, *Response, error) {
82+
u, err := addOptions(url, opts)
83+
if err != nil {
84+
return nil, nil, err
85+
}
86+
87+
req, err := s.client.NewRequest("GET", u, nil)
88+
if err != nil {
89+
return nil, nil, err
90+
}
91+
92+
var alerts []*DependabotAlert
93+
resp, err := s.client.Do(ctx, req, &alerts)
94+
if err != nil {
95+
return nil, resp, err
96+
}
97+
98+
return alerts, resp, nil
99+
}
100+
101+
// ListRepoAlerts lists all Dependabot alerts of a repository.
102+
//
103+
// GitHub API docs: https://docs.github.com/en/rest/dependabot/alerts#list-dependabot-alerts-for-a-repository
104+
func (s *DependabotService) ListRepoAlerts(ctx context.Context, owner, repo string, opts *ListAlertsOptions) ([]*DependabotAlert, *Response, error) {
105+
url := fmt.Sprintf("repos/%v/%v/dependabot/alerts", owner, repo)
106+
return s.listAlerts(ctx, url, opts)
107+
}
108+
109+
// ListOrgAlerts lists all Dependabot alerts of an organization.
110+
//
111+
// GitHub API docs: https://docs.github.com/en/rest/dependabot/alerts#list-dependabot-alerts-for-an-organization
112+
func (s *DependabotService) ListOrgAlerts(ctx context.Context, org string, opts *ListAlertsOptions) ([]*DependabotAlert, *Response, error) {
113+
url := fmt.Sprintf("orgs/%v/dependabot/alerts", org)
114+
return s.listAlerts(ctx, url, opts)
115+
}
116+
117+
// GetRepoAlert gets a single repository Dependabot alert.
118+
//
119+
// GitHub API docs: https://docs.github.com/en/rest/dependabot/alerts#get-a-dependabot-alert
120+
func (s *DependabotService) GetRepoAlert(ctx context.Context, owner, repo string, number int) (*DependabotAlert, *Response, error) {
121+
url := fmt.Sprintf("repos/%v/%v/dependabot/alerts/%v", owner, repo, number)
122+
req, err := s.client.NewRequest("GET", url, nil)
123+
if err != nil {
124+
return nil, nil, err
125+
}
126+
127+
alert := new(DependabotAlert)
128+
resp, err := s.client.Do(ctx, req, alert)
129+
if err != nil {
130+
return nil, resp, err
131+
}
132+
133+
return alert, resp, nil
134+
}

github/dependabot_alerts_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright 2022 The go-github AUTHORS. All rights reserved.
2+
//
3+
// Use of this source code is governed by a BSD-style
4+
// license that can be found in the LICENSE file.
5+
6+
package github
7+
8+
import (
9+
"context"
10+
"fmt"
11+
"net/http"
12+
"testing"
13+
14+
"github.com/google/go-cmp/cmp"
15+
)
16+
17+
func TestDependabotService_ListRepoAlerts(t *testing.T) {
18+
client, mux, _, teardown := setup()
19+
defer teardown()
20+
21+
mux.HandleFunc("/repos/o/r/dependabot/alerts", func(w http.ResponseWriter, r *http.Request) {
22+
testMethod(t, r, "GET")
23+
testFormValues(t, r, values{"state": "open"})
24+
fmt.Fprint(w, `[{"number":1,"state":"open"},{"number":42,"state":"fixed"}]`)
25+
})
26+
27+
opts := &ListAlertsOptions{State: String("open")}
28+
ctx := context.Background()
29+
alerts, _, err := client.Dependabot.ListRepoAlerts(ctx, "o", "r", opts)
30+
if err != nil {
31+
t.Errorf("Dependabot.ListRepoAlerts returned error: %v", err)
32+
}
33+
34+
want := []*DependabotAlert{
35+
{Number: Int(1), State: String("open")},
36+
{Number: Int(42), State: String("fixed")},
37+
}
38+
if !cmp.Equal(alerts, want) {
39+
t.Errorf("Dependabot.ListRepoAlerts returned %+v, want %+v", alerts, want)
40+
}
41+
42+
const methodName = "ListRepoAlerts"
43+
testBadOptions(t, methodName, func() (err error) {
44+
_, _, err = client.Dependabot.ListRepoAlerts(ctx, "\n", "\n", opts)
45+
return err
46+
})
47+
48+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
49+
got, resp, err := client.Dependabot.ListRepoAlerts(ctx, "o", "r", opts)
50+
if got != nil {
51+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
52+
}
53+
return resp, err
54+
})
55+
}
56+
57+
func TestDependabotService_GetRepoAlert(t *testing.T) {
58+
client, mux, _, teardown := setup()
59+
defer teardown()
60+
61+
mux.HandleFunc("/repos/o/r/dependabot/alerts/42", func(w http.ResponseWriter, r *http.Request) {
62+
testMethod(t, r, "GET")
63+
fmt.Fprint(w, `{"number":42,"state":"fixed"}`)
64+
})
65+
66+
ctx := context.Background()
67+
alert, _, err := client.Dependabot.GetRepoAlert(ctx, "o", "r", 42)
68+
if err != nil {
69+
t.Errorf("Dependabot.GetRepoAlert returned error: %v", err)
70+
}
71+
72+
want := &DependabotAlert{
73+
Number: Int(42),
74+
State: String("fixed"),
75+
}
76+
if !cmp.Equal(alert, want) {
77+
t.Errorf("Dependabot.GetRepoAlert returned %+v, want %+v", alert, want)
78+
}
79+
80+
const methodName = "GetRepoAlert"
81+
testBadOptions(t, methodName, func() (err error) {
82+
_, _, err = client.Dependabot.GetRepoAlert(ctx, "\n", "\n", 0)
83+
return err
84+
})
85+
86+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
87+
got, resp, err := client.Dependabot.GetRepoAlert(ctx, "o", "r", 42)
88+
if got != nil {
89+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
90+
}
91+
return resp, err
92+
})
93+
}
94+
95+
func TestDependabotService_ListOrgAlerts(t *testing.T) {
96+
client, mux, _, teardown := setup()
97+
defer teardown()
98+
99+
mux.HandleFunc("/orgs/o/dependabot/alerts", func(w http.ResponseWriter, r *http.Request) {
100+
testMethod(t, r, "GET")
101+
testFormValues(t, r, values{"state": "open"})
102+
fmt.Fprint(w, `[{"number":1,"state":"open"},{"number":42,"state":"fixed"}]`)
103+
})
104+
105+
opts := &ListAlertsOptions{State: String("open")}
106+
ctx := context.Background()
107+
alerts, _, err := client.Dependabot.ListOrgAlerts(ctx, "o", opts)
108+
if err != nil {
109+
t.Errorf("Dependabot.ListOrgAlerts returned error: %v", err)
110+
}
111+
112+
want := []*DependabotAlert{
113+
{Number: Int(1), State: String("open")},
114+
{Number: Int(42), State: String("fixed")},
115+
}
116+
if !cmp.Equal(alerts, want) {
117+
t.Errorf("Dependabot.ListOrgAlerts returned %+v, want %+v", alerts, want)
118+
}
119+
120+
const methodName = "ListOrgAlerts"
121+
testBadOptions(t, methodName, func() (err error) {
122+
_, _, err = client.Dependabot.ListOrgAlerts(ctx, "\n", opts)
123+
return err
124+
})
125+
126+
testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) {
127+
got, resp, err := client.Dependabot.ListOrgAlerts(ctx, "o", opts)
128+
if got != nil {
129+
t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got)
130+
}
131+
return resp, err
132+
})
133+
}

0 commit comments

Comments
 (0)