Improve error handling of SonarQube client
Due to unhandled errors within the SonarQube client, users may be presented with Go panics or just don't know what the root cause of a non-working bot is. Now it is possible to identify network errors, authentication issues or an incorrect bot configuration regarding SonarQube. Fixes: #20 Signed-off-by: Steven Kriegler <sk.bunsenbrenner@gmail.com>
This commit is contained in:
parent
eb3cb301fc
commit
02ad0c0bf0
|
@ -28,6 +28,7 @@ type MeasuresComponent struct {
|
||||||
type MeasuresResponse struct {
|
type MeasuresResponse struct {
|
||||||
Component MeasuresComponent `json:"component"`
|
Component MeasuresComponent `json:"component"`
|
||||||
Metrics []MeasuresComponentMetric `json:"metrics"`
|
Metrics []MeasuresComponentMetric `json:"metrics"`
|
||||||
|
Errors []Error `json:"errors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mr *MeasuresResponse) GetRenderedMarkdownTable() string {
|
func (mr *MeasuresResponse) GetRenderedMarkdownTable() string {
|
||||||
|
|
|
@ -9,6 +9,7 @@ type PullRequest struct {
|
||||||
|
|
||||||
type PullsResponse struct {
|
type PullsResponse struct {
|
||||||
PullRequests []PullRequest `json:"pullRequests"`
|
PullRequests []PullRequest `json:"pullRequests"`
|
||||||
|
Errors []Error `json:"errors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *PullsResponse) GetPullRequest(name string) *PullRequest {
|
func (r *PullsResponse) GetPullRequest(name string) *PullRequest {
|
||||||
|
|
|
@ -38,6 +38,38 @@ func GetRenderedQualityGate(qg string) string {
|
||||||
return fmt.Sprintf("**Quality Gate**: %s", status)
|
return fmt.Sprintf("**Quality Gate**: %s", status)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func retrieveDataFromApi(sdk *SonarQubeSdk, request *http.Request, wrapper interface{}) error {
|
||||||
|
request.Header.Add("Authorization", sdk.basicAuth())
|
||||||
|
rawResponse, err := sdk.client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawResponse.StatusCode == http.StatusUnauthorized {
|
||||||
|
return fmt.Errorf("missing or invalid API token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rawResponse.Body != nil {
|
||||||
|
defer rawResponse.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := sdk.bodyReader(rawResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(body, wrapper)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Message string `json:"msg"`
|
||||||
|
}
|
||||||
|
|
||||||
type SonarQubeSdkInterface interface {
|
type SonarQubeSdkInterface interface {
|
||||||
GetMeasures(string, string) (*MeasuresResponse, error)
|
GetMeasures(string, string) (*MeasuresResponse, error)
|
||||||
GetPullRequestUrl(string, int64) string
|
GetPullRequestUrl(string, int64) string
|
||||||
|
@ -52,44 +84,49 @@ type CommentComposeData struct {
|
||||||
QualityGate string
|
QualityGate string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ClientInterface interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BodyReader func(io.Reader) ([]byte, error)
|
||||||
|
type HttpRequest func(method string, target string, body io.Reader) (*http.Request, error)
|
||||||
|
|
||||||
type SonarQubeSdk struct {
|
type SonarQubeSdk struct {
|
||||||
client *http.Client
|
client ClientInterface
|
||||||
baseUrl string
|
bodyReader BodyReader
|
||||||
token string
|
httpRequest HttpRequest
|
||||||
|
baseUrl string
|
||||||
|
token string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sdk *SonarQubeSdk) GetPullRequestUrl(project string, index int64) string {
|
func (sdk *SonarQubeSdk) GetPullRequestUrl(project string, index int64) string {
|
||||||
return fmt.Sprintf("%s/dashboard?id=%s&pullRequest=%s", sdk.baseUrl, project, PRNameFromIndex(index))
|
return fmt.Sprintf("%s/dashboard?id=%s&pullRequest=%s", sdk.baseUrl, project, PRNameFromIndex(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sdk *SonarQubeSdk) fetchPullRequests(project string) *PullsResponse {
|
func (sdk *SonarQubeSdk) fetchPullRequests(project string) (*PullsResponse, error) {
|
||||||
url := fmt.Sprintf("%s/api/project_pull_requests/list?project=%s", sdk.baseUrl, project)
|
url := fmt.Sprintf("%s/api/project_pull_requests/list?project=%s", sdk.baseUrl, project)
|
||||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
request, err := sdk.httpRequest(http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Cannot initialize Request: %s", err.Error())
|
return nil, err
|
||||||
return nil
|
|
||||||
}
|
|
||||||
req.Header.Add("Authorization", sdk.basicAuth())
|
|
||||||
rawResp, _ := sdk.client.Do(req)
|
|
||||||
if rawResp.Body != nil {
|
|
||||||
defer rawResp.Body.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(rawResp.Body)
|
|
||||||
response := &PullsResponse{}
|
response := &PullsResponse{}
|
||||||
err = json.Unmarshal(body, &response)
|
err = retrieveDataFromApi(sdk, request, response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("cannot parse response from SonarQube: %s", err.Error())
|
return nil, err
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
if len(response.Errors) != 0 {
|
||||||
|
return nil, fmt.Errorf("%s", response.Errors[0].Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sdk *SonarQubeSdk) GetPullRequest(project string, index int64) (*PullRequest, error) {
|
func (sdk *SonarQubeSdk) GetPullRequest(project string, index int64) (*PullRequest, error) {
|
||||||
response := sdk.fetchPullRequests(project)
|
response, err := sdk.fetchPullRequests(project)
|
||||||
if response == nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to retrieve pull requests from SonarQube")
|
return nil, fmt.Errorf("fetching pull requests failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
name := PRNameFromIndex(index)
|
name := PRNameFromIndex(index)
|
||||||
|
@ -103,21 +140,19 @@ func (sdk *SonarQubeSdk) GetPullRequest(project string, index int64) (*PullReque
|
||||||
|
|
||||||
func (sdk *SonarQubeSdk) GetMeasures(project string, branch string) (*MeasuresResponse, error) {
|
func (sdk *SonarQubeSdk) GetMeasures(project string, branch string) (*MeasuresResponse, error) {
|
||||||
url := fmt.Sprintf("%s/api/measures/component?additionalFields=metrics&metricKeys=%s&component=%s&pullRequest=%s", sdk.baseUrl, settings.SonarQube.GetMetricsList(), project, branch)
|
url := fmt.Sprintf("%s/api/measures/component?additionalFields=metrics&metricKeys=%s&component=%s&pullRequest=%s", sdk.baseUrl, settings.SonarQube.GetMetricsList(), project, branch)
|
||||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
request, err := sdk.httpRequest(http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot initialize Request: %w", err)
|
return nil, err
|
||||||
}
|
|
||||||
req.Header.Add("Authorization", sdk.basicAuth())
|
|
||||||
rawResp, _ := sdk.client.Do(req)
|
|
||||||
if rawResp.Body != nil {
|
|
||||||
defer rawResp.Body.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body, _ := io.ReadAll(rawResp.Body)
|
|
||||||
response := &MeasuresResponse{}
|
response := &MeasuresResponse{}
|
||||||
err = json.Unmarshal(body, &response)
|
err = retrieveDataFromApi(sdk, request, response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot parse response from SonarQube: %w", err)
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(response.Errors) != 0 {
|
||||||
|
return nil, fmt.Errorf("%s", response.Errors[0].Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
|
@ -147,8 +182,10 @@ func (sdk *SonarQubeSdk) basicAuth() string {
|
||||||
|
|
||||||
func New() *SonarQubeSdk {
|
func New() *SonarQubeSdk {
|
||||||
return &SonarQubeSdk{
|
return &SonarQubeSdk{
|
||||||
client: &http.Client{},
|
client: &http.Client{},
|
||||||
baseUrl: settings.SonarQube.Url,
|
bodyReader: io.ReadAll,
|
||||||
token: settings.SonarQube.Token.Value,
|
httpRequest: http.NewRequest,
|
||||||
|
baseUrl: settings.SonarQube.Url,
|
||||||
|
token: settings.SonarQube.Token.Value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
489
internal/clients/sonarqube/sonarqube_test.go
Normal file
489
internal/clients/sonarqube/sonarqube_test.go
Normal file
|
@ -0,0 +1,489 @@
|
||||||
|
package sonarqube
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea-sonarqube-pr-bot/internal/settings"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClientMock struct {
|
||||||
|
responseError error
|
||||||
|
handler http.HandlerFunc
|
||||||
|
recoder *httptest.ResponseRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClientMock) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
c.handler.ServeHTTP(c.recoder, req)
|
||||||
|
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: c.recoder.Code,
|
||||||
|
Body: c.recoder.Result().Body,
|
||||||
|
}, c.responseError
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePRIndexSuccess(t *testing.T) {
|
||||||
|
actual, _ := ParsePRIndex("PR-1337")
|
||||||
|
assert.Equal(t, 1337, actual, "PR index parsing is broken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParsePRIndexNonIntegerFailure(t *testing.T) {
|
||||||
|
_, err := ParsePRIndex("PR-invalid")
|
||||||
|
assert.EqualErrorf(t, err, "branch name 'PR-invalid' does not match regex '^PR-(\\d+)$'", "Integer parsing succeeds unexpectedly")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPRNameFromIndex(t *testing.T) {
|
||||||
|
assert.Equal(t, "PR-1337", PRNameFromIndex(1337))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRenderedQualityGateSuccess(t *testing.T) {
|
||||||
|
actual := GetRenderedQualityGate("OK")
|
||||||
|
|
||||||
|
assert.Contains(t, actual, ":white_check_mark:", "Undetected successful quality gate during status rendering")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetRenderedQualityGateFailure(t *testing.T) {
|
||||||
|
actual := GetRenderedQualityGate("ERROR")
|
||||||
|
|
||||||
|
assert.Contains(t, actual, ":x:", "Undetected failed quality gate during status rendering")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPullRequestUrl(t *testing.T) {
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
baseUrl: "https://sonarqube.example.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := sdk.GetPullRequestUrl("test-project", 1337)
|
||||||
|
assert.Equal(t, "https://sonarqube.example.com/dashboard?id=test-project&pullRequest=PR-1337", actual, "PR Dashboard URL building broken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrieveDataFromApiSuccess(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
wrapper := &PullsResponse{}
|
||||||
|
err := retrieveDataFromApi(sdk, request, wrapper)
|
||||||
|
|
||||||
|
assert.Nil(t, err, "Successful data retrieval broken and throws error")
|
||||||
|
assert.Equal(t, "Basic dGVzdC10b2tlbjo=", request.Header.Get("Authorization"), "Authorization header not set")
|
||||||
|
assert.Equal(t, "PR-1", wrapper.PullRequests[0].Key, "Unmarshallowing into wrapper broken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrieveDataFromApiRequestError(t *testing.T) {
|
||||||
|
expected := fmt.Errorf("This error indicates an error while performing the request")
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: expected,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
err := retrieveDataFromApi(sdk, request, &PullsResponse{})
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, expected, "Undetected request performing error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrieveDataFromApiUnauthorized(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
recorder.Code = http.StatusUnauthorized
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "simulated-invalid-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: recorder,
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
err := retrieveDataFromApi(sdk, request, &PullsResponse{})
|
||||||
|
|
||||||
|
assert.Errorf(t, err, "missing or invalid API token", "Undetected unauthorized error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrieveDataFromApiBodyReadError(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||||
|
})
|
||||||
|
expected := fmt.Errorf("Error reading body content")
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: func(r io.Reader) ([]byte, error) {
|
||||||
|
return []byte(``), expected
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
err := retrieveDataFromApi(sdk, request, &PullsResponse{})
|
||||||
|
|
||||||
|
assert.ErrorIs(t, err, expected, "Undetected body processing error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrieveDataFromApiBodyUnmarshalError(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"pullReq`))
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||||
|
err := retrieveDataFromApi(sdk, request, &PullsResponse{})
|
||||||
|
|
||||||
|
assert.Errorf(t, err, "unexpected end of JSON input", "Undetected body unmarshal error")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchPullRequestsSuccess(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := sdk.fetchPullRequests("test-project")
|
||||||
|
|
||||||
|
assert.Nil(t, err, "Successful data retrieval broken and throws error")
|
||||||
|
assert.IsType(t, &PullsResponse{}, actual, "Happy path broken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchPullRequestsRequestBuildingFailure(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||||
|
})
|
||||||
|
expected := fmt.Errorf("Some simulated error")
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return nil, expected
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sdk.fetchPullRequests("test-project")
|
||||||
|
|
||||||
|
assert.Equal(t, expected, err, "Unexpected error instance returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchPullRequestsRequestError(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||||
|
})
|
||||||
|
expected := fmt.Errorf("Some simulated error")
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: expected,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sdk.fetchPullRequests("test-project")
|
||||||
|
|
||||||
|
assert.Equal(t, expected, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchPullRequestsErrorsInResponse(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"errors":[{"msg":"Project 'test-project' not found"}]}`))
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sdk.fetchPullRequests("test-project")
|
||||||
|
|
||||||
|
assert.Errorf(t, err, "Project 'test-project' not found", "Response error parsing broken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPullRequestSuccess(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := sdk.GetPullRequest("test-project", 1)
|
||||||
|
|
||||||
|
assert.Nil(t, err, "Successful data retrieval broken and throws error")
|
||||||
|
assert.IsType(t, &PullRequest{}, actual, "Happy path broken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPullRequestFetchError(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||||
|
})
|
||||||
|
expected := fmt.Errorf("Some simulated error")
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: expected,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sdk.GetPullRequest("test-project", 1)
|
||||||
|
|
||||||
|
assert.Errorf(t, err, "fetching pull requests failed", "Incorrect edge case is throwing errors")
|
||||||
|
assert.Errorf(t, err, "Some simulated error", "Unexpected error cause")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPullRequestUnknownPR(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"pullRequests":[{"key":"PR-1","title":"pr-branch","branch":"pr-branch","base":"main","status":{"qualityGateStatus":"OK","bugs":0,"vulnerabilities":0,"codeSmells":0},"analysisDate":"2022-06-12T11:23:09+0000","target":"main"}]}`))
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sdk.GetPullRequest("test-project", 1337)
|
||||||
|
|
||||||
|
assert.Errorf(t, err, "no pull request found with name 'PR-1337'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMeasuresSuccess(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := sdk.GetMeasures("test-project", "PR-1")
|
||||||
|
|
||||||
|
assert.Nil(t, err, "Successful data retrieval broken and throws error")
|
||||||
|
assert.IsType(t, &MeasuresResponse{}, actual, "Happy path broken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMeasuresRequestBuildingFailure(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||||
|
})
|
||||||
|
expected := fmt.Errorf("Some simulated error")
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return nil, expected
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sdk.GetMeasures("test-project", "PR-1")
|
||||||
|
|
||||||
|
assert.Equal(t, expected, err, "Unexpected error instance returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMeasuresRequestError(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"0","bestValue":true}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||||
|
})
|
||||||
|
expected := fmt.Errorf("Some simulated error")
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: expected,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sdk.GetMeasures("test-project", "PR-1")
|
||||||
|
|
||||||
|
assert.Equal(t, expected, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetMeasuresErrorsInResponse(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"errors":[{"msg":"Component 'non-existing-project' of pull request 'PR-1' not found"}]}`))
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sdk.GetMeasures("non-existing-project", "PR-1")
|
||||||
|
|
||||||
|
assert.Errorf(t, err, "Component 'non-existing-project' of pull request 'PR-1' not found", "Response error parsing broken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeGiteaCommentSuccess(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"10","bestValue":false}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||||
|
})
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return httptest.NewRequest(method, target, body), nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := sdk.ComposeGiteaComment(&CommentComposeData{
|
||||||
|
Key: "test-project",
|
||||||
|
PRName: "PR-1",
|
||||||
|
Url: "https://sonarqube.example.com",
|
||||||
|
QualityGate: "OK",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Nil(t, err, "Successful comment composing throwing errors")
|
||||||
|
assert.Contains(t, actual, ":white_check_mark:", "Happy path [Quality Gate] broken")
|
||||||
|
assert.Contains(t, actual, "| Metric | Current |", "Happy path [Metrics Header] broken")
|
||||||
|
assert.Contains(t, actual, "| Bugs | 10 |", "Happy path [Metrics Values] broken")
|
||||||
|
assert.Contains(t, actual, "https://sonarqube.example.com", "Happy path [Link] broken")
|
||||||
|
assert.Contains(t, actual, "/sq-bot review", "Happy path [Command] broken")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeGiteaCommentError(t *testing.T) {
|
||||||
|
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(`{"component":{"key":"test-project","name":"Test Project","qualifier":"TRK","measures":[{"metric":"bugs","value":"10","bestValue":false}],"pullRequest":"PR-1"},"metrics":[{"key":"bugs","name":"Bugs","description":"Bugs","domain":"Reliability","type":"INT","higherValuesAreBetter":false,"qualitative":false,"hidden":false,"custom":false,"bestValue":"0"}]}`))
|
||||||
|
})
|
||||||
|
expected := fmt.Errorf("Expected error from GetMeasures")
|
||||||
|
sdk := &SonarQubeSdk{
|
||||||
|
token: "test-token",
|
||||||
|
client: &ClientMock{
|
||||||
|
handler: handler,
|
||||||
|
recoder: httptest.NewRecorder(),
|
||||||
|
responseError: nil,
|
||||||
|
},
|
||||||
|
bodyReader: io.ReadAll,
|
||||||
|
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
|
||||||
|
return nil, expected
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := sdk.ComposeGiteaComment(&CommentComposeData{
|
||||||
|
Key: "test-project",
|
||||||
|
PRName: "PR-1",
|
||||||
|
Url: "https://sonarqube.example.com",
|
||||||
|
QualityGate: "OK",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Errorf(t, err, expected.Error(), "Undetected error while composing comment")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
settings.SonarQube = settings.SonarQubeConfig{
|
||||||
|
Url: "http://example.com",
|
||||||
|
Token: &settings.Token{
|
||||||
|
Value: "test-token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.IsType(t, &SonarQubeSdk{}, New(), "")
|
||||||
|
}
|
Loading…
Reference in a new issue