Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add method to update codescanning alert #2639

41 changes: 41 additions & 0 deletions github/code-scanning.go
Expand Up @@ -173,6 +173,22 @@ type SarifAnalysis struct {
ToolName *string `json:"tool_name,omitempty"`
}

// CodeScanningAlertState specifies the state of a code scanning alert.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning
type CodeScanningAlertState struct {
// State sets the state of the code scanning alert and is a required field.
// You must also provide DismissedReason when you set the state to "dismissed".
// State can be one of: "open", "dismissed".
State string `json:"state"`
// DismissedReason represents the reason for dismissing or closing the alert.
// It is required when the state is "dismissed".
// It can be one of: "false positive", "won't fix", "used in tests".
DismissedReason *string `json:"dismissed_reason,omitempty"`
// DismissedComment is associated with the dismissal of the alert.
DismissedComment *string `json:"dismissed_comment,omitempty"`
}

// SarifID identifies a sarif analysis upload.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning
Expand Down Expand Up @@ -261,6 +277,31 @@ func (s *CodeScanningService) GetAlert(ctx context.Context, owner, repo string,
return a, resp, nil
}

// UpdateAlert updates the state of a single code scanning alert for a repository.
//
// You must use an access token with the security_events scope to use this endpoint.
// GitHub Apps must have the security_events read permission to use this endpoint.
//
// The security alert_id is the number at the end of the security alert's URL.
//
// GitHub API docs: https://docs.github.com/en/rest/code-scanning?apiVersion=2022-11-28#update-a-code-scanning-alert
func (s *CodeScanningService) UpdateAlert(ctx context.Context, owner, repo string, id int64, stateInfo *CodeScanningAlertState) (*Alert, *Response, error) {
u := fmt.Sprintf("repos/%v/%v/code-scanning/alerts/%v", owner, repo, id)

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

a := new(Alert)
resp, err := s.client.Do(ctx, req, a)
if err != nil {
return nil, resp, err
}

return a, resp, nil
}

// UploadSarif uploads the result of code scanning job to GitHub.
//
// For the parameter sarif, you must first compress your SARIF file using gzip and then translate the contents of the file into a Base64 encoding string.
Expand Down
117 changes: 117 additions & 0 deletions github/code-scanning_test.go
Expand Up @@ -613,6 +613,123 @@ func TestCodeScanningService_ListAlertsForRepo(t *testing.T) {
})
}

func TestCodeScanningService_UpdateAlert(t *testing.T) {
client, mux, _, teardown := setup()
defer teardown()
mux.HandleFunc("/repos/o/r/code-scanning/alerts/88", func(w http.ResponseWriter, r *http.Request) {
testMethod(t, r, "PATCH")
fmt.Fprint(w, `{"rule_id":"js/useless-expression",
"rule_severity":"warning",
"rule_description":"Expression has no effect",
"tool": {
"name": "CodeQL",
"guid": null,
"version": "1.4.0"
},
"rule": {
"id": "useless expression",
"severity": "warning",
"description": "Expression has no effect",
"name": "useless expression",
"full_description": "Expression has no effect",
"help": "Expression has no effect"
},
"most_recent_instance": {
"ref": "refs/heads/main",
"state": "dismissed",
"commit_sha": "abcdefg12345",
"message": {
"text": "This path depends on a user-provided value."
},
"location": {
"path": "spec-main/api-session-spec.ts",
"start_line": 917,
"end_line": 917,
"start_column": 7,
"end_column": 18
},
"classifications": [
"test"
]
},
"created_at":"2019-01-02T15:04:05Z",
"state":"dismissed",
"dismissed_reason": "false positive",
"dismissed_comment": "This alert is not actually correct as sanitizer is used",
"closed_by":null,
"closed_at":null,
"url":"https://api.github.com/repos/o/r/code-scanning/alerts/88",
"html_url":"https://github.com/o/r/security/code-scanning/88"}`)
})

ctx := context.Background()
dismissedComment := String("This alert is not actually correct as sanitizer is used")
dismissedReason := String("false positive")
state := String("dismissed")
stateInfo := &CodeScanningAlertState{State: *state, DismissedReason: dismissedReason, DismissedComment: dismissedComment}
alert, _, err := client.CodeScanning.UpdateAlert(ctx, "o", "r", 88, stateInfo)
if err != nil {
t.Errorf("CodeScanning.UpdateAlert returned error: %v", err)
}

date := Timestamp{time.Date(2019, time.January, 02, 15, 04, 05, 0, time.UTC)}
want := &Alert{
RuleID: String("js/useless-expression"),
RuleSeverity: String("warning"),
RuleDescription: String("Expression has no effect"),
Tool: &Tool{Name: String("CodeQL"), GUID: nil, Version: String("1.4.0")},
Rule: &Rule{
ID: String("useless expression"),
Severity: String("warning"),
Description: String("Expression has no effect"),
Name: String("useless expression"),
FullDescription: String("Expression has no effect"),
Help: String("Expression has no effect"),
},
CreatedAt: &date,
State: state,
DismissedReason: dismissedReason,
DismissedComment: dismissedComment,
ClosedBy: nil,
ClosedAt: nil,
URL: String("https://api.github.com/repos/o/r/code-scanning/alerts/88"),
HTMLURL: String("https://github.com/o/r/security/code-scanning/88"),
MostRecentInstance: &MostRecentInstance{
Ref: String("refs/heads/main"),
State: String("dismissed"),
CommitSHA: String("abcdefg12345"),
Message: &Message{
Text: String("This path depends on a user-provided value."),
},
Location: &Location{
Path: String("spec-main/api-session-spec.ts"),
StartLine: Int(917),
EndLine: Int(917),
StartColumn: Int(7),
EndColumn: Int(18),
},
Classifications: []string{"test"},
},
}
if !cmp.Equal(alert, want) {
t.Errorf("CodeScanning.UpdateAlert returned %+v, want %+v", alert, want)
}

const methodName = "UpdateAlert"
testBadOptions(t, methodName, func() (err error) {
_, _, err = client.CodeScanning.UpdateAlert(ctx, "\n", "\n", -88, stateInfo)
return err
})

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

func TestCodeScanningService_GetAlert(t *testing.T) {
client, mux, _, teardown := setup()
defer teardown()
Expand Down
16 changes: 16 additions & 0 deletions github/github-accessors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions github/github-accessors_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.