gitea-sonarqube-bot/internal/clients/sonarqube/sonarqube_test.go
justusbunsi 685c834b61
Allow pull request naming pattern customization (#28)
Fixes: #3

Signed-off-by: Steven Kriegler <sk.bunsenbrenner@gmail.com>
2022-06-18 14:03:56 +02:00

537 lines
19 KiB
Go

package sonarqube
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"regexp"
"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) {
settings.Pattern = &settings.PatternConfig{
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
}
actual, _ := ParsePRIndex("PR-1337")
assert.Equal(t, 1337, actual, "PR index parsing is broken")
t.Cleanup(func() {
settings.Pattern = nil
})
}
func TestParsePRIndexNonIntegerFailure(t *testing.T) {
settings.Pattern = &settings.PatternConfig{
RegExp: regexp.MustCompile(`^PR-(\d+)$`),
}
_, err := ParsePRIndex("PR-invalid")
assert.EqualErrorf(t, err, "branch name 'PR-invalid' does not match regex '^PR-(\\d+)$'", "Integer parsing succeeds unexpectedly")
t.Cleanup(func() {
settings.Pattern = nil
})
}
func TestPRNameFromIndex(t *testing.T) {
settings.Pattern = &settings.PatternConfig{
Template: "PR-%d",
}
assert.Equal(t, "PR-1337", PRNameFromIndex(1337))
t.Cleanup(func() {
settings.Pattern = nil
})
}
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",
}
settings.Pattern = &settings.PatternConfig{
Template: "PR-%d",
}
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")
t.Cleanup(func() {
settings.Pattern = nil
})
}
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) {
settings.Pattern = &settings.PatternConfig{
Template: "PR-%d",
}
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")
t.Cleanup(func() {
settings.Pattern = nil
})
}
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) {
settings.Pattern = &settings.PatternConfig{
Template: "PR-%d",
}
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'")
t.Cleanup(func() {
settings.Pattern = nil
})
}
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(), "")
}