Introduce better test case structure

Signed-off-by: Steven Kriegler <sk.bunsenbrenner@gmail.com>
This commit is contained in:
justusbunsi 2022-07-12 11:20:08 +02:00
parent 51211d77cd
commit 54beca9c25
No known key found for this signature in database
GPG key ID: 82B29BF2507F9F8B
8 changed files with 1169 additions and 1127 deletions

View file

@ -6,12 +6,14 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestIsValidBotCommentForInvalidComment(t *testing.T) { func TestIsValidBotComment(t *testing.T) {
assert.False(t, IsValidBotComment(""), "Undetected missing action prefix") t.Run("Valid", func(t *testing.T) {
assert.False(t, IsValidBotComment("/sq-bot invalid-command"), "Undetected invalid bot command") assert.True(t, IsValidBotComment("/sq-bot review"), "Correct bot comment not recognized")
assert.False(t, IsValidBotComment("Some context with /sq-bot review within"), "Incorrect bot prefix detected inside random comment") })
}
func TestIsValidBotCommentForValidComment(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
assert.True(t, IsValidBotComment("/sq-bot review"), "Correct bot comment not recognized") assert.False(t, IsValidBotComment(""), "Undetected missing action prefix")
assert.False(t, IsValidBotComment("/sq-bot invalid-command"), "Undetected invalid bot command")
assert.False(t, IsValidBotComment("Some context with /sq-bot review within"), "Incorrect bot prefix detected inside random comment")
})
} }

File diff suppressed because one or more lines are too long

View file

@ -93,105 +93,115 @@ func TestMain(m *testing.M) {
} }
func TestNonAPIRoutes(t *testing.T) { func TestNonAPIRoutes(t *testing.T) {
router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) t.Run("favicon", func(t *testing.T) {
router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock))
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/favicon.ico", nil) req, _ := http.NewRequest("GET", "/favicon.ico", nil)
router.Engine.ServeHTTP(w, req) router.Engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusNoContent, w.Code) assert.Equal(t, http.StatusNoContent, w.Code)
})
w = httptest.NewRecorder() t.Run("ping", func(t *testing.T) {
req, _ = http.NewRequest("GET", "/ping", nil) router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock))
router.Engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code) w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/ping", nil)
router.Engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
})
} }
func TestSonarQubeAPIRouteMissingProjectHeader(t *testing.T) { func TestSonarQubeAPIRoute(t *testing.T) {
router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) t.Run("Missing project header", func(t *testing.T) {
router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock))
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`))) req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`)))
router.Engine.ServeHTTP(w, req) router.Engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusNotFound, w.Code)
})
t.Run("Processing", func(t *testing.T) {
sonarQubeHandlerMock := new(SonarQubeHandlerMock)
sonarQubeHandlerMock.On("Handle", mock.IsType(&http.Request{}))
router := New(new(GiteaHandlerMock), sonarQubeHandlerMock)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`)))
req.Header.Add("X-SonarQube-Project", "gitea-sonarqube-bot")
router.Engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
sonarQubeHandlerMock.AssertNumberOfCalls(t, "Handle", 1)
sonarQubeHandlerMock.AssertExpectations(t)
})
} }
func TestSonarQubeAPIRouteProcessing(t *testing.T) { func TestGiteaAPIRoute(t *testing.T) {
sonarQubeHandlerMock := new(SonarQubeHandlerMock) t.Run("Missing event header", func(t *testing.T) {
sonarQubeHandlerMock.On("Handle", mock.IsType(&http.Request{})) router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock))
router := New(new(GiteaHandlerMock), sonarQubeHandlerMock) w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
router.Engine.ServeHTTP(w, req)
w := httptest.NewRecorder() assert.Equal(t, http.StatusNotFound, w.Code)
req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`))) })
req.Header.Add("X-SonarQube-Project", "gitea-sonarqube-bot")
router.Engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code) t.Run("Processing synchronize", func(t *testing.T) {
sonarQubeHandlerMock.AssertNumberOfCalls(t, "Handle", 1) giteaHandlerMock := new(GiteaHandlerMock)
sonarQubeHandlerMock.AssertExpectations(t) giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
} giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe()
func TestGiteaAPIRouteMissingEventHeader(t *testing.T) { router := New(giteaHandlerMock, new(SonarQubeHandlerMock))
router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock))
w := httptest.NewRecorder()
w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) req.Header.Add("X-Gitea-Event", "pull_request")
router.Engine.ServeHTTP(w, req) router.Engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code) assert.Equal(t, http.StatusOK, w.Code)
} giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 1)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0)
func TestGiteaAPIRouteSynchronizeProcessing(t *testing.T) { giteaHandlerMock.AssertExpectations(t)
giteaHandlerMock := new(GiteaHandlerMock) })
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe() t.Run("Processing comment", func(t *testing.T) {
giteaHandlerMock := new(GiteaHandlerMock)
router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe()
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) router := New(giteaHandlerMock, new(SonarQubeHandlerMock))
req.Header.Add("X-Gitea-Event", "pull_request")
router.Engine.ServeHTTP(w, req) w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
assert.Equal(t, http.StatusOK, w.Code) req.Header.Add("X-Gitea-Event", "issue_comment")
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 1) router.Engine.ServeHTTP(w, req)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0)
giteaHandlerMock.AssertExpectations(t) assert.Equal(t, http.StatusOK, w.Code)
} giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 1)
func TestGiteaAPIRouteCommentProcessing(t *testing.T) { giteaHandlerMock.AssertExpectations(t)
giteaHandlerMock := new(GiteaHandlerMock) })
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe()
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil) t.Run("Unknown event", func(t *testing.T) {
giteaHandlerMock := new(GiteaHandlerMock)
router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe()
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe()
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) router := New(giteaHandlerMock, new(SonarQubeHandlerMock))
req.Header.Add("X-Gitea-Event", "issue_comment")
router.Engine.ServeHTTP(w, req) w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
assert.Equal(t, http.StatusOK, w.Code) req.Header.Add("X-Gitea-Event", "unknown")
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0) router.Engine.ServeHTTP(w, req)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 1)
giteaHandlerMock.AssertExpectations(t) assert.Equal(t, http.StatusOK, w.Code)
} giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0)
func TestGiteaAPIRouteUnknownEvent(t *testing.T) { giteaHandlerMock.AssertExpectations(t)
giteaHandlerMock := new(GiteaHandlerMock) })
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Maybe()
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Maybe()
router := New(giteaHandlerMock, new(SonarQubeHandlerMock))
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
req.Header.Add("X-Gitea-Event", "unknown")
router.Engine.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0)
giteaHandlerMock.AssertExpectations(t)
} }

View file

@ -10,37 +10,38 @@ func getRequestData() []byte {
return []byte(`{"serverUrl":"https://example.com","status":"SUCCESS","analysedAt":"2022-05-15T16:45:31+0000","revision":"378080777919s07657a07f7a3e2d05dc75f64edd","changedAt":"2022-05-15T16:41:39+0000","project":{"key":"gitea-sonarqube-bot","name":"Gitea SonarQube Bot","url":"https://example.com/dashboard?id=gitea-sonarqube-bot"},"branch":{"name":"PR-1822","type":"PULL_REQUEST","isMain":false,"url":"https://example.com/dashboard?id=gitea-sonarqube-bot&pullRequest=PR-1822"},"qualityGate":{"name":"GiteaSonarQubeBot","status":"OK","conditions":[{"metric":"new_reliability_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_security_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_maintainability_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_security_hotspots_reviewed","operator":"LESS_THAN","status":"OK","errorThreshold":"100"}]},"properties":{"sonar.analysis.sqbot":"378080777919s07657a07f7a3e2d05dc75f64edd"}}`) return []byte(`{"serverUrl":"https://example.com","status":"SUCCESS","analysedAt":"2022-05-15T16:45:31+0000","revision":"378080777919s07657a07f7a3e2d05dc75f64edd","changedAt":"2022-05-15T16:41:39+0000","project":{"key":"gitea-sonarqube-bot","name":"Gitea SonarQube Bot","url":"https://example.com/dashboard?id=gitea-sonarqube-bot"},"branch":{"name":"PR-1822","type":"PULL_REQUEST","isMain":false,"url":"https://example.com/dashboard?id=gitea-sonarqube-bot&pullRequest=PR-1822"},"qualityGate":{"name":"GiteaSonarQubeBot","status":"OK","conditions":[{"metric":"new_reliability_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_security_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_maintainability_rating","operator":"GREATER_THAN","value":"1","status":"OK","errorThreshold":"1"},{"metric":"new_security_hotspots_reviewed","operator":"LESS_THAN","status":"OK","errorThreshold":"100"}]},"properties":{"sonar.analysis.sqbot":"378080777919s07657a07f7a3e2d05dc75f64edd"}}`)
} }
func TestIsValidWebhookSuccess(t *testing.T) { func TestIsValidWebhook(t *testing.T) {
actual, _ := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component") t.Run("Success", func(t *testing.T) {
assert.True(t, actual, "Expected successful webhook signature validation") actual, _ := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component")
} assert.True(t, actual, "Expected successful webhook signature validation")
})
func TestIsValidWebhookNothingConfiguredOrProvidedSuccess(t *testing.T) { t.Run("Nothing configured or provided", func(t *testing.T) {
actual, _ := isValidWebhook(getRequestData(), "", "", "test-component") actual, _ := isValidWebhook(getRequestData(), "", "", "test-component")
assert.True(t, actual, "Webhook signature validation not skipped") assert.True(t, actual, "Webhook signature validation not skipped")
} })
func TestIsValidWebhookSignatureDecodingFailure(t *testing.T) { t.Run("Signature decoding error", func(t *testing.T) {
actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "invalid-signature", "test-component") actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "invalid-signature", "test-component")
assert.False(t, actual) assert.False(t, actual)
assert.EqualError(t, err, "Error decoding signature for test-component webhook.", "Undetected signature encoding error") assert.EqualError(t, err, "Error decoding signature for test-component webhook.", "Undetected signature encoding error")
} })
func TestIsValidWebhookSignatureMismatchFailure(t *testing.T) { t.Run("Signature mismatch", func(t *testing.T) {
actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "fde6a666b7a1a46c27efb1961c17b46b6cf7aa13db5560e5ac95e801a18a92f3", "test-component") actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "fde6a666b7a1a46c27efb1961c17b46b6cf7aa13db5560e5ac95e801a18a92f3", "test-component")
assert.False(t, actual) assert.False(t, actual)
assert.EqualError(t, err, "Signature header does not match the received test-component webhook content. Request rejected.", "Undetected signature mismatch") assert.EqualError(t, err, "Signature header does not match the received test-component webhook content. Request rejected.", "Undetected signature mismatch")
// assert.EqualError(t, err, "Signature header received but no test-component webhook secret configured. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (1)") })
}
func TestIsValidWebhookEmptySecretConfigurationFailure(t *testing.T) { t.Run("Empty secret configuration", func(t *testing.T) {
actual, err := isValidWebhook(getRequestData(), "", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component") actual, err := isValidWebhook(getRequestData(), "", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467", "test-component")
assert.False(t, actual) assert.False(t, actual)
assert.EqualError(t, err, "Signature header received but no test-component webhook secret configured. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (1)") assert.EqualError(t, err, "Signature header received but no test-component webhook secret configured. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (1)")
} })
func TestIsValidWebhookEmptySignatureConfigurationFailure(t *testing.T) { t.Run("Empty signature configuration", func(t *testing.T) {
actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "", "test-component") actual, err := isValidWebhook(getRequestData(), "sonarqube-test-webhook-secret", "", "test-component")
assert.False(t, actual) assert.False(t, actual)
assert.EqualError(t, err, "test-component webhook secret configured but no signature header received. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (2)") assert.EqualError(t, err, "test-component webhook secret configured but no signature header received. Request rejected due to possible configuration mismatch.", "Undetected configuration mismatch (2)")
})
} }

View file

@ -34,137 +34,139 @@ func withValidSonarQubeRequestData(t *testing.T, jsonBody []byte) (*http.Request
return req, rr, handler return req, rr, handler
} }
func TestHandleSonarQubeWebhookProjectMapped(t *testing.T) { func TestHandleSonarQubeWebhook(t *testing.T) {
settings.Pattern = &settings.PatternConfig{ t.Run("With mapped Project", func(t *testing.T) {
RegExp: regexp.MustCompile(`^PR-(\d+)$`), settings.Pattern = &settings.PatternConfig{
} RegExp: regexp.MustCompile(`^PR-(\d+)$`),
settings.SonarQube = settings.SonarQubeConfig{ }
Webhook: &settings.Webhook{ settings.SonarQube = settings.SonarQubeConfig{
Secret: "", Webhook: &settings.Webhook{
}, Secret: "",
}
settings.Projects = []settings.Project{
{
SonarQube: struct{ Key string }{
Key: "pr-bot",
}, },
}, }
} settings.Projects = []settings.Project{
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) {
handler.ServeHTTP(rr, req) SonarQube: struct{ Key string }{
Key: "pr-bot",
},
},
}
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code) assert.Equal(t, http.StatusOK, rr.Code)
assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String())
t.Cleanup(func() { t.Cleanup(func() {
settings.Pattern = nil settings.Pattern = nil
}) })
} })
func TestHandleSonarQubeWebhookProjectNotMapped(t *testing.T) { t.Run("Without mapped project", func(t *testing.T) {
settings.Projects = []settings.Project{ settings.Projects = []settings.Project{
{ {
SonarQube: struct{ Key string }{ SonarQube: struct{ Key string }{
Key: "another-project", Key: "another-project",
}, },
}, },
} }
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code) assert.Equal(t, http.StatusOK, rr.Code)
assert.Equal(t, `{"message": "Project 'pr-bot' not in configured list. Request ignored."}`, rr.Body.String()) assert.Equal(t, `{"message": "Project 'pr-bot' not in configured list. Request ignored."}`, rr.Body.String())
} })
func TestHandleSonarQubeWebhookInvalidJSONBody(t *testing.T) { t.Run("With invalid JSON body", func(t *testing.T) {
settings.Projects = []settings.Project{ settings.Projects = []settings.Project{
{ {
SonarQube: struct{ Key string }{ SonarQube: struct{ Key string }{
Key: "pr-bot", Key: "pr-bot",
}, },
}, },
} }
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": ["invalid-server-url-content"] }`)) req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": ["invalid-server-url-content"] }`))
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusUnprocessableEntity, rr.Code) assert.Equal(t, http.StatusUnprocessableEntity, rr.Code)
assert.Equal(t, `{"message": "Error parsing POST body."}`, rr.Body.String()) assert.Equal(t, `{"message": "Error parsing POST body."}`, rr.Body.String())
} })
func TestHandleSonarQubeWebhookInvalidWebhookSignature(t *testing.T) { t.Run("With invalid webhook signature", func(t *testing.T) {
settings.SonarQube = settings.SonarQubeConfig{ settings.SonarQube = settings.SonarQubeConfig{
Webhook: &settings.Webhook{ Webhook: &settings.Webhook{
Secret: "sonarqube-test-webhook-secret", Secret: "sonarqube-test-webhook-secret",
}, },
} }
settings.Projects = []settings.Project{ settings.Projects = []settings.Project{
{ {
SonarQube: struct{ Key string }{ SonarQube: struct{ Key string }{
Key: "pr-bot", Key: "pr-bot",
}, },
}, },
} }
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
req.Header.Set("X-Sonar-Webhook-HMAC-SHA256", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467") req.Header.Set("X-Sonar-Webhook-HMAC-SHA256", "647f2395d30b1b7efcb58d9338be5b69c2addb54faf6bde6314a57ea28f45467")
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusPreconditionFailed, rr.Code) assert.Equal(t, http.StatusPreconditionFailed, rr.Code)
assert.Equal(t, `{"message": "Webhook validation failed. Request rejected."}`, rr.Body.String()) assert.Equal(t, `{"message": "Webhook validation failed. Request rejected."}`, rr.Body.String())
} })
func TestHandleSonarQubeWebhookForPullRequest(t *testing.T) { t.Run("Running for Pull Request", func(t *testing.T) {
settings.Pattern = &settings.PatternConfig{ settings.Pattern = &settings.PatternConfig{
RegExp: regexp.MustCompile(`^PR-(\d+)$`), RegExp: regexp.MustCompile(`^PR-(\d+)$`),
} }
settings.SonarQube = settings.SonarQubeConfig{ settings.SonarQube = settings.SonarQubeConfig{
Webhook: &settings.Webhook{ Webhook: &settings.Webhook{
Secret: "", Secret: "",
}, },
} }
settings.Projects = []settings.Project{ settings.Projects = []settings.Project{
{ {
SonarQube: struct{ Key string }{ SonarQube: struct{ Key string }{
Key: "pr-bot", Key: "pr-bot",
}, },
}, },
} }
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code) assert.Equal(t, http.StatusOK, rr.Code)
assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String())
t.Cleanup(func() { t.Cleanup(func() {
settings.Pattern = nil settings.Pattern = nil
}) })
} })
func TestHandleSonarQubeWebhookForBranch(t *testing.T) { t.Run("Running for branch", func(t *testing.T) {
settings.Pattern = &settings.PatternConfig{ settings.Pattern = &settings.PatternConfig{
RegExp: regexp.MustCompile(`^PR-(\d+)$`), RegExp: regexp.MustCompile(`^PR-(\d+)$`),
} }
settings.SonarQube = settings.SonarQubeConfig{ settings.SonarQube = settings.SonarQubeConfig{
Webhook: &settings.Webhook{ Webhook: &settings.Webhook{
Secret: "", Secret: "",
}, },
} }
settings.Projects = []settings.Project{ settings.Projects = []settings.Project{
{ {
SonarQube: struct{ Key string }{ SonarQube: struct{ Key string }{
Key: "pr-bot", Key: "pr-bot",
}, },
}, },
} }
req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "BRANCH", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)) req, rr, handler := withValidSonarQubeRequestData(t, []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "BRANCH", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`))
handler.ServeHTTP(rr, req) handler.ServeHTTP(rr, req)
assert.Equal(t, http.StatusOK, rr.Code) assert.Equal(t, http.StatusOK, rr.Code)
assert.Equal(t, `{"message": "Ignore Hook for non-PR analysis."}`, rr.Body.String()) assert.Equal(t, `{"message": "Ignore Hook for non-PR analysis."}`, rr.Body.String())
t.Cleanup(func() { t.Cleanup(func() {
settings.Pattern = nil settings.Pattern = nil
})
}) })
} }

View file

@ -28,29 +28,31 @@ func (c *ClientMock) Do(req *http.Request) (*http.Response, error) {
}, c.responseError }, c.responseError
} }
func TestParsePRIndexSuccess(t *testing.T) { func TestParsePRIndex(t *testing.T) {
settings.Pattern = &settings.PatternConfig{ t.Run("Success", func(t *testing.T) {
RegExp: regexp.MustCompile(`^PR-(\d+)$`), settings.Pattern = &settings.PatternConfig{
} RegExp: regexp.MustCompile(`^PR-(\d+)$`),
}
actual, _ := ParsePRIndex("PR-1337") actual, _ := ParsePRIndex("PR-1337")
assert.Equal(t, 1337, actual, "PR index parsing is broken") assert.Equal(t, 1337, actual, "PR index parsing is broken")
t.Cleanup(func() { t.Cleanup(func() {
settings.Pattern = nil settings.Pattern = nil
})
}) })
}
func TestParsePRIndexNonIntegerFailure(t *testing.T) { t.Run("No integer value", func(t *testing.T) {
settings.Pattern = &settings.PatternConfig{ settings.Pattern = &settings.PatternConfig{
RegExp: regexp.MustCompile(`^PR-(\d+)$`), RegExp: regexp.MustCompile(`^PR-(\d+)$`),
} }
_, err := ParsePRIndex("PR-invalid") _, err := ParsePRIndex("PR-invalid")
assert.EqualErrorf(t, err, "branch name 'PR-invalid' does not match regex '^PR-(\\d+)$'", "Integer parsing succeeds unexpectedly") assert.EqualErrorf(t, err, "branch name 'PR-invalid' does not match regex '^PR-(\\d+)$'", "Integer parsing succeeds unexpectedly")
t.Cleanup(func() { t.Cleanup(func() {
settings.Pattern = nil settings.Pattern = nil
})
}) })
} }
@ -66,16 +68,14 @@ func TestPRNameFromIndex(t *testing.T) {
}) })
} }
func TestGetRenderedQualityGateSuccess(t *testing.T) { func TestGetRenderedQualityGate(t *testing.T) {
actual := GetRenderedQualityGate("OK") t.Run("Passed", func(t *testing.T) {
assert.Contains(t, GetRenderedQualityGate("OK"), ":white_check_mark:", "Undetected successful quality gate during status rendering")
})
assert.Contains(t, actual, ":white_check_mark:", "Undetected successful quality gate during status rendering") t.Run("Failed", func(t *testing.T) {
} assert.Contains(t, GetRenderedQualityGate("ERROR"), ":x:", "Undetected failed 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) { func TestGetPullRequestUrl(t *testing.T) {
@ -94,435 +94,445 @@ func TestGetPullRequestUrl(t *testing.T) {
}) })
} }
func TestRetrieveDataFromApiSuccess(t *testing.T) { func TestRetrieveDataFromApi(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Run("Success", func(t *testing.T) {
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"}]}`)) 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")
}) })
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) t.Run("Internal error", func(t *testing.T) {
wrapper := &PullsResponse{} expected := fmt.Errorf("This error indicates an error while performing the request")
err := retrieveDataFromApi(sdk, request, wrapper) 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,
}
assert.Nil(t, err, "Successful data retrieval broken and throws error") request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
assert.Equal(t, "Basic dGVzdC10b2tlbjo=", request.Header.Get("Authorization"), "Authorization header not set") err := retrieveDataFromApi(sdk, request, &PullsResponse{})
assert.Equal(t, "PR-1", wrapper.PullRequests[0].Key, "Unmarshallowing into wrapper broken")
}
func TestRetrieveDataFromApiRequestError(t *testing.T) { assert.ErrorIs(t, err, expected, "Undetected request performing error")
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) t.Run("Unauthorized", func(t *testing.T) {
err := retrieveDataFromApi(sdk, request, &PullsResponse{}) 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,
}
assert.Errorf(t, err, "missing or invalid API token", "Undetected unauthorized error") request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
} err := retrieveDataFromApi(sdk, request, &PullsResponse{})
func TestRetrieveDataFromApiBodyReadError(t *testing.T) { assert.Errorf(t, err, "missing or invalid API token", "Undetected unauthorized error")
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) t.Run("Body read error", func(t *testing.T) {
err := retrieveDataFromApi(sdk, request, &PullsResponse{}) 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
},
}
assert.ErrorIs(t, err, expected, "Undetected body processing error") request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
} err := retrieveDataFromApi(sdk, request, &PullsResponse{})
func TestRetrieveDataFromApiBodyUnmarshalError(t *testing.T) { assert.ErrorIs(t, err, expected, "Undetected body processing error")
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) t.Run("Unmarshal error", func(t *testing.T) {
err := retrieveDataFromApi(sdk, request, &PullsResponse{}) 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,
}
assert.Errorf(t, err, "unexpected end of JSON input", "Undetected body unmarshal error") request := httptest.NewRequest(http.MethodGet, "http://example.com", nil)
} err := retrieveDataFromApi(sdk, request, &PullsResponse{})
func TestFetchPullRequestsSuccess(t *testing.T) { assert.Errorf(t, err, "unexpected end of JSON input", "Undetected body unmarshal error")
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) { func TestFetchPullRequests(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Run("Success", func(t *testing.T) {
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"}]}`)) 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")
}) })
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) t.Run("Building failure", func(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
},
}
assert.Errorf(t, err, "fetching pull requests failed", "Incorrect edge case is throwing errors") _, err := sdk.fetchPullRequests("test-project")
assert.Errorf(t, err, "Some simulated error", "Unexpected error cause")
}
func TestGetPullRequestUnknownPR(t *testing.T) { assert.Equal(t, expected, err, "Unexpected error instance returned")
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) t.Run("Internal error", func(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
},
}
assert.Errorf(t, err, "no pull request found with name 'PR-1337'") _, err := sdk.fetchPullRequests("test-project")
t.Cleanup(func() { assert.Equal(t, expected, err)
settings.Pattern = nil })
t.Run("Errors in response", func(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 TestGetMeasuresSuccess(t *testing.T) { func TestGetPullRequest(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Run("Success", func(t *testing.T) {
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"}]}`)) 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
})
}) })
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") t.Run("Fetch error", func(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
},
}
assert.Nil(t, err, "Successful data retrieval broken and throws error") _, err := sdk.GetPullRequest("test-project", 1)
assert.IsType(t, &MeasuresResponse{}, actual, "Happy path broken")
assert.Errorf(t, err, "fetching pull requests failed", "Incorrect edge case is throwing errors")
assert.Errorf(t, err, "Some simulated error", "Unexpected error cause")
})
t.Run("Unknown PR", func(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 TestGetMeasuresRequestBuildingFailure(t *testing.T) { func TestGetMeasures(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Run("Success", func(t *testing.T) {
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"}]}`)) 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")
}) })
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") t.Run("Building failure", func(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
},
}
assert.Equal(t, expected, err, "Unexpected error instance returned") _, err := sdk.GetMeasures("test-project", "PR-1")
assert.Equal(t, expected, err, "Unexpected error instance returned")
})
t.Run("Request error", func(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)
})
t.Run("Errors in response", func(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 TestGetMeasuresRequestError(t *testing.T) { func TestComposeGiteaComment(t *testing.T) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Run("Success", func(t *testing.T) {
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"}]}`)) 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("Some simulated error") })
sdk := &SonarQubeSdk{ sdk := &SonarQubeSdk{
token: "test-token", token: "test-token",
client: &ClientMock{ client: &ClientMock{
handler: handler, handler: handler,
recoder: httptest.NewRecorder(), recoder: httptest.NewRecorder(),
responseError: expected, responseError: nil,
}, },
bodyReader: io.ReadAll, bodyReader: io.ReadAll,
httpRequest: func(method, target string, body io.Reader) (*http.Request, error) { httpRequest: func(method, target string, body io.Reader) (*http.Request, error) {
return httptest.NewRequest(method, target, body), nil return httptest.NewRequest(method, target, body), nil
}, },
} }
_, err := sdk.GetMeasures("test-project", "PR-1") actual, err := sdk.ComposeGiteaComment(&CommentComposeData{
Key: "test-project",
PRName: "PR-1",
Url: "https://sonarqube.example.com",
QualityGate: "OK",
})
assert.Equal(t, expected, err) 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")
func TestGetMeasuresErrorsInResponse(t *testing.T) { assert.Contains(t, actual, "| Bugs | 10 |", "Happy path [Metrics Values] broken")
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Contains(t, actual, "https://sonarqube.example.com", "Happy path [Link] broken")
w.Write([]byte(`{"errors":[{"msg":"Component 'non-existing-project' of pull request 'PR-1' not found"}]}`)) assert.Contains(t, actual, "/sq-bot review", "Happy path [Command] broken")
})
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") t.Run("Error", func(t *testing.T) {
assert.Contains(t, actual, ":white_check_mark:", "Happy path [Quality Gate] broken") handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Contains(t, actual, "| Metric | Current |", "Happy path [Metrics Header] broken") 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"}]}`))
assert.Contains(t, actual, "| Bugs | 10 |", "Happy path [Metrics Values] broken") })
assert.Contains(t, actual, "https://sonarqube.example.com", "Happy path [Link] broken") expected := fmt.Errorf("Expected error from GetMeasures")
assert.Contains(t, actual, "/sq-bot review", "Happy path [Command] broken") 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
},
}
func TestComposeGiteaCommentError(t *testing.T) { _, err := sdk.ComposeGiteaComment(&CommentComposeData{
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { Key: "test-project",
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"}]}`)) PRName: "PR-1",
Url: "https://sonarqube.example.com",
QualityGate: "OK",
})
assert.Errorf(t, err, expected.Error(), "Undetected error while composing comment")
}) })
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) { func TestNew(t *testing.T) {

View file

@ -10,8 +10,9 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var defaultConfig []byte = []byte( func defaultConfig() []byte {
`gitea: return []byte(
`gitea:
url: https://example.com/gitea url: https://example.com/gitea
token: token:
value: d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565 value: d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565
@ -34,6 +35,7 @@ namingPattern:
regex: "^PR-(\\d+)$" regex: "^PR-(\\d+)$"
template: "PR-%d" template: "PR-%d"
`) `)
}
func WriteConfigFile(t *testing.T, content []byte) string { func WriteConfigFile(t *testing.T, content []byte) string {
dir := os.TempDir() dir := os.TempDir()
@ -48,78 +50,157 @@ func WriteConfigFile(t *testing.T, content []byte) string {
return config return config
} }
func TestLoadWithMissingFile(t *testing.T) { func TestLoad(t *testing.T) {
assert.Panics(t, func() { Load(path.Join(os.TempDir(), "config.yaml")) }, "No panic while reading missing file") t.Run("Missing file", func(t *testing.T) {
} assert.Panics(t, func() { Load(path.Join(os.TempDir(), "config.yaml")) }, "No panic while reading missing file")
})
func TestLoadWithExistingFile(t *testing.T) { t.Run("Existing file", func(t *testing.T) {
c := WriteConfigFile(t, defaultConfig) c := WriteConfigFile(t, defaultConfig())
assert.NotPanics(t, func() { Load(c) }, "Unexpected panic while reading existing file")
})
assert.NotPanics(t, func() { Load(c) }, "Unexpected panic while reading existing file") t.Run("File references", func(t *testing.T) {
} giteaWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-gitea")
_ = ioutil.WriteFile(giteaWebhookSecretFile, []byte(`gitea-totally-secret`), 0444)
func TestLoadGiteaStructure(t *testing.T) { giteaTokenFile := path.Join(os.TempDir(), "token-secret-gitea")
c := WriteConfigFile(t, defaultConfig) _ = ioutil.WriteFile(giteaTokenFile, []byte(`d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565`), 0444)
Load(c)
expected := GiteaConfig{ sonarqubeWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-sonarqube")
Url: "https://example.com/gitea", _ = ioutil.WriteFile(sonarqubeWebhookSecretFile, []byte(`sonarqube-totally-secret`), 0444)
Token: &Token{
Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
},
Webhook: &Webhook{
Secret: "haxxor-gitea-secret",
},
}
assert.EqualValues(t, expected, Gitea) sonarqubeTokenFile := path.Join(os.TempDir(), "token-secret-sonarqube")
} _ = ioutil.WriteFile(sonarqubeTokenFile, []byte(`a09eb5785b25bb2cbacf48808a677a0709f02d8e`), 0444)
func TestLoadGiteaStructureInjectedEnvs(t *testing.T) { c := WriteConfigFile(t, []byte(
os.Setenv("PRBOT_GITEA_WEBHOOK_SECRET", "injected-webhook-secret") `gitea:
os.Setenv("PRBOT_GITEA_TOKEN_VALUE", "injected-token") url: https://example.com/gitea
c := WriteConfigFile(t, defaultConfig) token:
Load(c) value: fake-gitea-token
sonarqube:
url: https://example.com/sonarqube
token:
value: fake-sonarqube-token
projects:
- sonarqube:
key: gitea-sonarqube-bot
gitea:
owner: example-organization
name: pr-bot
`))
os.Setenv("PRBOT_GITEA_WEBHOOK_SECRETFILE", giteaWebhookSecretFile)
os.Setenv("PRBOT_GITEA_TOKEN_FILE", giteaTokenFile)
os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE", sonarqubeWebhookSecretFile)
os.Setenv("PRBOT_SONARQUBE_TOKEN_FILE", sonarqubeTokenFile)
expected := GiteaConfig{ expectedGitea := GiteaConfig{
Url: "https://example.com/gitea", Url: "https://example.com/gitea",
Token: &Token{ Token: &Token{
Value: "injected-token", Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
}, file: giteaTokenFile,
Webhook: &Webhook{ },
Secret: "injected-webhook-secret", Webhook: &Webhook{
}, Secret: "gitea-totally-secret",
} secretFile: giteaWebhookSecretFile,
},
}
assert.EqualValues(t, expected, Gitea) expectedSonarQube := SonarQubeConfig{
Url: "https://example.com/sonarqube",
Token: &Token{
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
file: sonarqubeTokenFile,
},
Webhook: &Webhook{
Secret: "sonarqube-totally-secret",
secretFile: sonarqubeWebhookSecretFile,
},
AdditionalMetrics: []string{},
}
t.Cleanup(func() { Load(c)
os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRET") assert.EqualValues(t, expectedGitea, Gitea)
os.Unsetenv("PRBOT_GITEA_TOKEN_VALUE") assert.EqualValues(t, expectedSonarQube, SonarQube)
t.Cleanup(func() {
os.Remove(giteaWebhookSecretFile)
os.Remove(giteaTokenFile)
os.Remove(sonarqubeWebhookSecretFile)
os.Remove(sonarqubeTokenFile)
os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRETFILE")
os.Unsetenv("PRBOT_GITEA_TOKEN_FILE")
os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE")
os.Unsetenv("PRBOT_SONARQUBE_TOKEN_FILE")
})
}) })
} }
func TestLoadSonarQubeStructure(t *testing.T) { func TestLoadGitea(t *testing.T) {
c := WriteConfigFile(t, defaultConfig) t.Run("Default", func(t *testing.T) {
Load(c) c := WriteConfigFile(t, defaultConfig())
Load(c)
expected := SonarQubeConfig{ expected := GiteaConfig{
Url: "https://example.com/sonarqube", Url: "https://example.com/gitea",
Token: &Token{ Token: &Token{
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e", Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
}, },
Webhook: &Webhook{ Webhook: &Webhook{
Secret: "haxxor-sonarqube-secret", Secret: "haxxor-gitea-secret",
}, },
} }
assert.EqualValues(t, expected, SonarQube) assert.EqualValues(t, expected, Gitea)
assert.EqualValues(t, expected.GetMetricsList(), "bugs,vulnerabilities,code_smells") })
t.Run("Injected envs", func(t *testing.T) {
os.Setenv("PRBOT_GITEA_WEBHOOK_SECRET", "injected-webhook-secret")
os.Setenv("PRBOT_GITEA_TOKEN_VALUE", "injected-token")
c := WriteConfigFile(t, defaultConfig())
Load(c)
expected := GiteaConfig{
Url: "https://example.com/gitea",
Token: &Token{
Value: "injected-token",
},
Webhook: &Webhook{
Secret: "injected-webhook-secret",
},
}
assert.EqualValues(t, expected, Gitea)
t.Cleanup(func() {
os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRET")
os.Unsetenv("PRBOT_GITEA_TOKEN_VALUE")
})
})
} }
func TestLoadSonarQubeStructureWithAdditionalMetrics(t *testing.T) { func TestLoadSonarQube(t *testing.T) {
c := WriteConfigFile(t, []byte( t.Run("Default", func(t *testing.T) {
`gitea: c := WriteConfigFile(t, defaultConfig())
Load(c)
expected := SonarQubeConfig{
Url: "https://example.com/sonarqube",
Token: &Token{
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
},
Webhook: &Webhook{
Secret: "haxxor-sonarqube-secret",
},
}
assert.EqualValues(t, expected, SonarQube)
assert.EqualValues(t, expected.GetMetricsList(), "bugs,vulnerabilities,code_smells")
})
t.Run("Additional metrics", func(t *testing.T) {
c := WriteConfigFile(t, []byte(
`gitea:
url: https://example.com/gitea url: https://example.com/gitea
token: token:
value: fake-gitea-token value: fake-gitea-token
@ -135,147 +216,74 @@ projects:
owner: example-organization owner: example-organization
name: pr-bot name: pr-bot
`)) `))
Load(c) Load(c)
expected := SonarQubeConfig{ expected := SonarQubeConfig{
Url: "https://example.com/sonarqube", Url: "https://example.com/sonarqube",
Token: &Token{ Token: &Token{
Value: "fake-sonarqube-token", Value: "fake-sonarqube-token",
}, },
Webhook: &Webhook{ Webhook: &Webhook{
Secret: "", Secret: "",
}, },
AdditionalMetrics: []string{ AdditionalMetrics: []string{
"new_security_hotspots", "new_security_hotspots",
}, },
} }
assert.EqualValues(t, expected, SonarQube) assert.EqualValues(t, expected, SonarQube)
assert.EqualValues(t, expected.AdditionalMetrics, []string{"new_security_hotspots"}) assert.EqualValues(t, expected.AdditionalMetrics, []string{"new_security_hotspots"})
assert.EqualValues(t, "bugs,vulnerabilities,code_smells,new_security_hotspots", SonarQube.GetMetricsList()) assert.EqualValues(t, "bugs,vulnerabilities,code_smells,new_security_hotspots", SonarQube.GetMetricsList())
} })
func TestLoadSonarQubeStructureInjectedEnvs(t *testing.T) { t.Run("Injected envs", func(t *testing.T) {
os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRET", "injected-webhook-secret") os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRET", "injected-webhook-secret")
os.Setenv("PRBOT_SONARQUBE_TOKEN_VALUE", "injected-token") os.Setenv("PRBOT_SONARQUBE_TOKEN_VALUE", "injected-token")
c := WriteConfigFile(t, defaultConfig) c := WriteConfigFile(t, defaultConfig())
Load(c) Load(c)
expected := SonarQubeConfig{ expected := SonarQubeConfig{
Url: "https://example.com/sonarqube", Url: "https://example.com/sonarqube",
Token: &Token{ Token: &Token{
Value: "injected-token", Value: "injected-token",
}, },
Webhook: &Webhook{ Webhook: &Webhook{
Secret: "injected-webhook-secret", Secret: "injected-webhook-secret",
}, },
} }
assert.EqualValues(t, expected, SonarQube) assert.EqualValues(t, expected, SonarQube)
t.Cleanup(func() { t.Cleanup(func() {
os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRET") os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRET")
os.Unsetenv("PRBOT_SONARQUBE_TOKEN_VALUE") os.Unsetenv("PRBOT_SONARQUBE_TOKEN_VALUE")
})
}) })
} }
func TestLoadStructureWithFileReferenceResolving(t *testing.T) { func TestLoadProjects(t *testing.T) {
giteaWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-gitea") t.Run("Default", func(t *testing.T) {
_ = ioutil.WriteFile(giteaWebhookSecretFile, []byte(`gitea-totally-secret`), 0444) c := WriteConfigFile(t, defaultConfig())
Load(c)
giteaTokenFile := path.Join(os.TempDir(), "token-secret-gitea") expectedProjects := []Project{
_ = ioutil.WriteFile(giteaTokenFile, []byte(`d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565`), 0444) {
SonarQube: struct{ Key string }{
Key: "gitea-sonarqube-bot",
},
Gitea: GiteaRepository{
Owner: "example-organization",
Name: "pr-bot",
},
},
}
sonarqubeWebhookSecretFile := path.Join(os.TempDir(), "webhook-secret-sonarqube") assert.EqualValues(t, expectedProjects, Projects)
_ = ioutil.WriteFile(sonarqubeWebhookSecretFile, []byte(`sonarqube-totally-secret`), 0444)
sonarqubeTokenFile := path.Join(os.TempDir(), "token-secret-sonarqube")
_ = ioutil.WriteFile(sonarqubeTokenFile, []byte(`a09eb5785b25bb2cbacf48808a677a0709f02d8e`), 0444)
c := WriteConfigFile(t, []byte(
`gitea:
url: https://example.com/gitea
token:
value: fake-gitea-token
sonarqube:
url: https://example.com/sonarqube
token:
value: fake-sonarqube-token
projects:
- sonarqube:
key: gitea-sonarqube-bot
gitea:
owner: example-organization
name: pr-bot
`))
os.Setenv("PRBOT_GITEA_WEBHOOK_SECRETFILE", giteaWebhookSecretFile)
os.Setenv("PRBOT_GITEA_TOKEN_FILE", giteaTokenFile)
os.Setenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE", sonarqubeWebhookSecretFile)
os.Setenv("PRBOT_SONARQUBE_TOKEN_FILE", sonarqubeTokenFile)
expectedGitea := GiteaConfig{
Url: "https://example.com/gitea",
Token: &Token{
Value: "d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565",
file: giteaTokenFile,
},
Webhook: &Webhook{
Secret: "gitea-totally-secret",
secretFile: giteaWebhookSecretFile,
},
}
expectedSonarQube := SonarQubeConfig{
Url: "https://example.com/sonarqube",
Token: &Token{
Value: "a09eb5785b25bb2cbacf48808a677a0709f02d8e",
file: sonarqubeTokenFile,
},
Webhook: &Webhook{
Secret: "sonarqube-totally-secret",
secretFile: sonarqubeWebhookSecretFile,
},
AdditionalMetrics: []string{},
}
Load(c)
assert.EqualValues(t, expectedGitea, Gitea)
assert.EqualValues(t, expectedSonarQube, SonarQube)
t.Cleanup(func() {
os.Remove(giteaWebhookSecretFile)
os.Remove(giteaTokenFile)
os.Remove(sonarqubeWebhookSecretFile)
os.Remove(sonarqubeTokenFile)
os.Unsetenv("PRBOT_GITEA_WEBHOOK_SECRETFILE")
os.Unsetenv("PRBOT_GITEA_TOKEN_FILE")
os.Unsetenv("PRBOT_SONARQUBE_WEBHOOK_SECRETFILE")
os.Unsetenv("PRBOT_SONARQUBE_TOKEN_FILE")
}) })
}
func TestLoadProjectsStructure(t *testing.T) { t.Run("Empty mapping", func(t *testing.T) {
c := WriteConfigFile(t, defaultConfig) invalidConfig := []byte(
Load(c) `gitea:
expectedProjects := []Project{
{
SonarQube: struct{ Key string }{
Key: "gitea-sonarqube-bot",
},
Gitea: GiteaRepository{
Owner: "example-organization",
Name: "pr-bot",
},
},
}
assert.EqualValues(t, expectedProjects, Projects)
}
func TestLoadProjectsStructureWithNoMapping(t *testing.T) {
invalidConfig := []byte(
`gitea:
url: https://example.com/gitea url: https://example.com/gitea
token: token:
value: d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565 value: d0fcdeb5eaa99c506831f9eb4e63fc7cc484a565
@ -289,26 +297,28 @@ sonarqube:
secret: haxxor-sonarqube-secret secret: haxxor-sonarqube-secret
projects: [] projects: []
`) `)
c := WriteConfigFile(t, invalidConfig) c := WriteConfigFile(t, invalidConfig)
assert.Panics(t, func() { Load(c) }, "No panic for empty project mapping that is required") assert.Panics(t, func() { Load(c) }, "No panic for empty project mapping that is required")
})
} }
func TestLoadNamingPatternStructure(t *testing.T) { func TestLoadNamingPattern(t *testing.T) {
c := WriteConfigFile(t, defaultConfig) t.Run("Default", func(t *testing.T) {
Load(c) c := WriteConfigFile(t, defaultConfig())
Load(c)
expected := &PatternConfig{ expected := &PatternConfig{
RegExp: regexp.MustCompile(`^PR-(\d+)$`), RegExp: regexp.MustCompile(`^PR-(\d+)$`),
Template: "PR-%d", Template: "PR-%d",
} }
assert.EqualValues(t, expected, Pattern) assert.EqualValues(t, expected, Pattern)
} })
func TestLoadNamingPatternStructureWithInternalDefaults(t *testing.T) { t.Run("Internal defaults", func(t *testing.T) {
c := WriteConfigFile(t, []byte( c := WriteConfigFile(t, []byte(
`gitea: `gitea:
url: https://example.com/gitea url: https://example.com/gitea
token: token:
value: fake-gitea-token value: fake-gitea-token
@ -324,48 +334,49 @@ projects:
owner: example-organization owner: example-organization
name: pr-bot name: pr-bot
`)) `))
Load(c) Load(c)
expected := &PatternConfig{ expected := &PatternConfig{
RegExp: regexp.MustCompile(`^PR-(\d+)$`), RegExp: regexp.MustCompile(`^PR-(\d+)$`),
Template: "PR-%d", Template: "PR-%d",
} }
assert.EqualValues(t, expected, Pattern) assert.EqualValues(t, expected, Pattern)
} })
func TestLoadNamingPatternStructureInjectedEnvs(t *testing.T) { t.Run("Injected envs", func(t *testing.T) {
os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest") os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest")
os.Setenv("PRBOT_NAMINGPATTERN_TEMPLATE", "test-%d-pullrequest") os.Setenv("PRBOT_NAMINGPATTERN_TEMPLATE", "test-%d-pullrequest")
c := WriteConfigFile(t, defaultConfig) c := WriteConfigFile(t, defaultConfig())
Load(c) Load(c)
expected := &PatternConfig{ expected := &PatternConfig{
RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`), RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`),
Template: "test-%d-pullrequest", Template: "test-%d-pullrequest",
} }
assert.EqualValues(t, expected, Pattern) assert.EqualValues(t, expected, Pattern)
t.Cleanup(func() { t.Cleanup(func() {
os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX") os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX")
os.Unsetenv("PRBOT_NAMINGPATTERN_TEMPLATE") os.Unsetenv("PRBOT_NAMINGPATTERN_TEMPLATE")
}) })
} })
func TestLoadNamingPatternStructureMixedInput(t *testing.T) { t.Run("Mixed input", func(t *testing.T) {
os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest") os.Setenv("PRBOT_NAMINGPATTERN_REGEX", "test-(\\d+)-pullrequest")
c := WriteConfigFile(t, defaultConfig) c := WriteConfigFile(t, defaultConfig())
Load(c) Load(c)
expected := &PatternConfig{ expected := &PatternConfig{
RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`), RegExp: regexp.MustCompile(`test-(\d+)-pullrequest`),
Template: "PR-%d", Template: "PR-%d",
} }
assert.EqualValues(t, expected, Pattern) assert.EqualValues(t, expected, Pattern)
t.Cleanup(func() { t.Cleanup(func() {
os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX") os.Unsetenv("PRBOT_NAMINGPATTERN_REGEX")
})
}) })
} }

View file

@ -10,47 +10,49 @@ import (
) )
func TestNewWebhook(t *testing.T) { func TestNewWebhook(t *testing.T) {
settings.Pattern = &settings.PatternConfig{ t.Run("Success", func(t *testing.T) {
RegExp: regexp.MustCompile(`^PR-(\d+)$`), settings.Pattern = &settings.PatternConfig{
} RegExp: regexp.MustCompile(`^PR-(\d+)$`),
}
raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": { "sonar.analysis.sqbot": "a84442009c09b1adc278b6bb80a3853419f54007" } }`) raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "PR-1337", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": { "sonar.analysis.sqbot": "a84442009c09b1adc278b6bb80a3853419f54007" } }`)
response, ok := New(raw) response, ok := New(raw)
assert.NotNil(t, response) assert.NotNil(t, response)
assert.Equal(t, 1337, response.PRIndex) assert.Equal(t, 1337, response.PRIndex)
assert.Equal(t, "a84442009c09b1adc278b6bb80a3853419f54007", response.Properties.OriginalCommit) assert.Equal(t, "a84442009c09b1adc278b6bb80a3853419f54007", response.Properties.OriginalCommit)
assert.True(t, ok) assert.True(t, ok)
t.Cleanup(func() { t.Cleanup(func() {
settings.Pattern = nil settings.Pattern = nil
})
}) })
}
func TestNewWebhookInvalidJSON(t *testing.T) { t.Run("Invalid JSON", func(t *testing.T) {
raw := []byte(`{ "serverUrl": ["invalid-server-url-content"] }`) raw := []byte(`{ "serverUrl": ["invalid-server-url-content"] }`)
_, ok := New(raw) _, ok := New(raw)
assert.False(t, ok) assert.False(t, ok)
} })
func TestNewWebhookInvalidBranchName(t *testing.T) { t.Run("Invalid branch name", func(t *testing.T) {
settings.Pattern = &settings.PatternConfig{ settings.Pattern = &settings.PatternConfig{
RegExp: regexp.MustCompile(`^PR-(\d+)$`), RegExp: regexp.MustCompile(`^PR-(\d+)$`),
} }
raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "invalid", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`) raw := []byte(`{ "serverUrl": "https://example.com/sonarqube", "taskId": "AXouyxDpizdp4B1K", "status": "SUCCESS", "analysedAt": "2021-05-21T12:12:07+0000", "revision": "f84442009c09b1adc278b6aa80a3853419f54007", "changedAt": "2021-05-21T12:12:07+0000", "project": { "key": "pr-bot", "name": "PR Bot", "url": "https://example.com/sonarqube/dashboard?id=pr-bot" }, "branch": { "name": "invalid", "type": "PULL_REQUEST", "isMain": false, "url": "https://example.com/sonarqube/dashboard?id=pr-bot&pullRequest=PR-1337" }, "qualityGate": { "name": "PR Bot", "status": "OK", "conditions": [ { "metric": "new_reliability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_maintainability_rating", "operator": "GREATER_THAN", "value": "1", "status": "OK", "errorThreshold": "1" }, { "metric": "new_security_hotspots_reviewed", "operator": "LESS_THAN", "status": "NO_VALUE", "errorThreshold": "100" } ] }, "properties": {} }`)
_, ok := New(raw) _, ok := New(raw)
assert.False(t, ok) assert.False(t, ok)
t.Cleanup(func() { t.Cleanup(func() {
settings.Pattern = nil settings.Pattern = nil
})
}) })
} }
func TestWebhookGetRevision(t *testing.T) { func TestWebhookGetRevision(t *testing.T) {
t.Run("Default revision", func(t *testing.T) { t.Run("Default", func(t *testing.T) {
w := Webhook{ w := Webhook{
Revision: "225fa0306c0ab83297d0cb5db0717b194ccb2e76", Revision: "225fa0306c0ab83297d0cb5db0717b194ccb2e76",
} }
@ -58,7 +60,7 @@ func TestWebhookGetRevision(t *testing.T) {
assert.Equal(t, w.Revision, w.GetRevision()) assert.Equal(t, w.Revision, w.GetRevision())
}) })
t.Run("Default revision due to incomplete properties", func(t *testing.T) { t.Run("Incomplete properties", func(t *testing.T) {
w := Webhook{ w := Webhook{
Revision: "225fa0306c0ab83297d0cb5db0717b194ccb2e76", Revision: "225fa0306c0ab83297d0cb5db0717b194ccb2e76",
Properties: &properties{}, Properties: &properties{},