diff --git a/internal/clients/sonarqube/measures.go b/internal/clients/sonarqube/measures.go index 08bb24c..ba74210 100644 --- a/internal/clients/sonarqube/measures.go +++ b/internal/clients/sonarqube/measures.go @@ -28,6 +28,7 @@ type MeasuresComponent struct { type MeasuresResponse struct { Component MeasuresComponent `json:"component"` Metrics []MeasuresComponentMetric `json:"metrics"` + Errors []Error `json:"errors"` } func (mr *MeasuresResponse) GetRenderedMarkdownTable() string { diff --git a/internal/clients/sonarqube/pulls.go b/internal/clients/sonarqube/pulls.go index 567ef8d..90fb0cc 100644 --- a/internal/clients/sonarqube/pulls.go +++ b/internal/clients/sonarqube/pulls.go @@ -9,6 +9,7 @@ type PullRequest struct { type PullsResponse struct { PullRequests []PullRequest `json:"pullRequests"` + Errors []Error `json:"errors"` } func (r *PullsResponse) GetPullRequest(name string) *PullRequest { diff --git a/internal/clients/sonarqube/sonarqube.go b/internal/clients/sonarqube/sonarqube.go index c834345..249349e 100644 --- a/internal/clients/sonarqube/sonarqube.go +++ b/internal/clients/sonarqube/sonarqube.go @@ -38,6 +38,38 @@ func GetRenderedQualityGate(qg string) string { 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 { GetMeasures(string, string) (*MeasuresResponse, error) GetPullRequestUrl(string, int64) string @@ -52,44 +84,49 @@ type CommentComposeData struct { 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 { - client *http.Client - baseUrl string - token string + client ClientInterface + bodyReader BodyReader + httpRequest HttpRequest + baseUrl string + token string } func (sdk *SonarQubeSdk) GetPullRequestUrl(project string, index int64) string { 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) - req, err := http.NewRequest(http.MethodGet, url, nil) + request, err := sdk.httpRequest(http.MethodGet, url, nil) if err != nil { - log.Printf("Cannot initialize Request: %s", err.Error()) - return nil - } - req.Header.Add("Authorization", sdk.basicAuth()) - rawResp, _ := sdk.client.Do(req) - if rawResp.Body != nil { - defer rawResp.Body.Close() + return nil, err } - body, _ := io.ReadAll(rawResp.Body) response := &PullsResponse{} - err = json.Unmarshal(body, &response) + err = retrieveDataFromApi(sdk, request, response) if err != nil { - log.Printf("cannot parse response from SonarQube: %s", err.Error()) - return nil + return nil, err } - 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) { - response := sdk.fetchPullRequests(project) - if response == nil { - return nil, fmt.Errorf("unable to retrieve pull requests from SonarQube") + response, err := sdk.fetchPullRequests(project) + if err != nil { + return nil, fmt.Errorf("fetching pull requests failed: %w", err) } 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) { 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 { - return nil, fmt.Errorf("cannot initialize Request: %w", err) - } - req.Header.Add("Authorization", sdk.basicAuth()) - rawResp, _ := sdk.client.Do(req) - if rawResp.Body != nil { - defer rawResp.Body.Close() + return nil, err } - body, _ := io.ReadAll(rawResp.Body) response := &MeasuresResponse{} - err = json.Unmarshal(body, &response) + err = retrieveDataFromApi(sdk, request, response) 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 @@ -147,8 +182,10 @@ func (sdk *SonarQubeSdk) basicAuth() string { func New() *SonarQubeSdk { return &SonarQubeSdk{ - client: &http.Client{}, - baseUrl: settings.SonarQube.Url, - token: settings.SonarQube.Token.Value, + client: &http.Client{}, + bodyReader: io.ReadAll, + httpRequest: http.NewRequest, + baseUrl: settings.SonarQube.Url, + token: settings.SonarQube.Token.Value, } } diff --git a/internal/clients/sonarqube/sonarqube_test.go b/internal/clients/sonarqube/sonarqube_test.go new file mode 100644 index 0000000..bb9ef98 --- /dev/null +++ b/internal/clients/sonarqube/sonarqube_test.go @@ -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(), "") +}