Rewrite API entrypoint to be testable

The current code base regarding API entrypoint is not testable as it
directly connects to Gitea when creating the API endpoints. This
prevented my from writing tests in the past for that part.

As the SonarQube quality gate broke due to changes in the API entrypoint
logic, tests are now required to satisfy the quality gate.

Therefore, the instantiation of the API handlers is now decoupled from
building the bot API endpoints and follows the same interface wrapper
strategy as used for the Gitea API client. This makes it testable.

Now, tests are written for the most parts of the API entrypoint. I've
also noticed that there was much overhead within the tests for a
non-implemented function `fetchDetails`. So I dropped that function for
now.

Signed-off-by: Steven Kriegler <sk.bunsenbrenner@gmail.com>
This commit is contained in:
justusbunsi 2022-05-21 17:23:07 +02:00
parent 7f5c3390c4
commit af38a31bbe
No known key found for this signature in database
GPG key ID: 82B29BF2507F9F8B
5 changed files with 185 additions and 61 deletions

View file

@ -12,6 +12,11 @@ import (
webhook "gitea-sonarqube-pr-bot/internal/webhooks/gitea" webhook "gitea-sonarqube-pr-bot/internal/webhooks/gitea"
) )
type GiteaWebhookHandlerInferface interface {
HandleSynchronize(rw http.ResponseWriter, r *http.Request)
HandleComment(rw http.ResponseWriter, r *http.Request)
}
type GiteaWebhookHandler struct { type GiteaWebhookHandler struct {
giteaSdk giteaSdk.GiteaSdkInterface giteaSdk giteaSdk.GiteaSdkInterface
sqSdk sqSdk.SonarQubeSdkInterface sqSdk sqSdk.SonarQubeSdkInterface
@ -88,7 +93,7 @@ func (h *GiteaWebhookHandler) HandleComment(rw http.ResponseWriter, r *http.Requ
w.ProcessData(h.giteaSdk, h.sqSdk) w.ProcessData(h.giteaSdk, h.sqSdk)
} }
func NewGiteaWebhookHandler(g giteaSdk.GiteaSdkInterface, sq sqSdk.SonarQubeSdkInterface) *GiteaWebhookHandler { func NewGiteaWebhookHandler(g giteaSdk.GiteaSdkInterface, sq sqSdk.SonarQubeSdkInterface) GiteaWebhookHandlerInferface {
return &GiteaWebhookHandler{ return &GiteaWebhookHandler{
giteaSdk: g, giteaSdk: g,
sqSdk: sq, sqSdk: sq,

View file

@ -12,20 +12,16 @@ import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
func addPingApi(r *gin.Engine) { var (
r.GET("/ping", func(c *gin.Context) { sonarQubeWebhookHandler SonarQubeWebhookHandlerInferface
c.JSON(http.StatusOK, gin.H{ giteaWebhookHandler GiteaWebhookHandlerInferface
"message": "pong", )
})
})
}
type validSonarQubeEndpointHeader struct { type validSonarQubeEndpointHeader struct {
SonarQubeProject string `header:"X-SonarQube-Project" binding:"required"` SonarQubeProject string `header:"X-SonarQube-Project" binding:"required"`
} }
func addSonarQubeEndpoint(r *gin.Engine) { func addSonarQubeEndpoint(r *gin.Engine) {
webhookHandler := NewSonarQubeWebhookHandler(giteaSdk.New(), sqSdk.New())
r.POST("/hooks/sonarqube", func(c *gin.Context) { r.POST("/hooks/sonarqube", func(c *gin.Context) {
h := validSonarQubeEndpointHeader{} h := validSonarQubeEndpointHeader{}
@ -34,7 +30,7 @@ func addSonarQubeEndpoint(r *gin.Engine) {
return return
} }
webhookHandler.Handle(c.Writer, c.Request) sonarQubeWebhookHandler.Handle(c.Writer, c.Request)
}) })
} }
@ -43,7 +39,6 @@ type validGiteaEndpointHeader struct {
} }
func addGiteaEndpoint(r *gin.Engine) { func addGiteaEndpoint(r *gin.Engine) {
webhookHandler := NewGiteaWebhookHandler(giteaSdk.New(), sqSdk.New())
r.POST("/hooks/gitea", func(c *gin.Context) { r.POST("/hooks/gitea", func(c *gin.Context) {
h := validGiteaEndpointHeader{} h := validGiteaEndpointHeader{}
@ -54,9 +49,9 @@ func addGiteaEndpoint(r *gin.Engine) {
switch h.GiteaEvent { switch h.GiteaEvent {
case "pull_request": case "pull_request":
webhookHandler.HandleSynchronize(c.Writer, c.Request) giteaWebhookHandler.HandleSynchronize(c.Writer, c.Request)
case "issue_comment": case "issue_comment":
webhookHandler.HandleComment(c.Writer, c.Request) giteaWebhookHandler.HandleComment(c.Writer, c.Request)
default: default:
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"message": "ignore unknown event", "message": "ignore unknown event",
@ -65,9 +60,7 @@ func addGiteaEndpoint(r *gin.Engine) {
}) })
} }
func Serve(c *cli.Context) error { func setupRouter() *gin.Engine {
fmt.Println("Hi! I'm the Gitea-SonarQube-PR bot. At your service.")
r := gin.New() r := gin.New()
r.Use(gin.Recovery()) r.Use(gin.Recovery())
@ -75,13 +68,28 @@ func Serve(c *cli.Context) error {
SkipPaths: []string{"/ping", "/favicon.ico"}, SkipPaths: []string{"/ping", "/favicon.ico"},
})) }))
addPingApi(r) r.GET("/ping", func(c *gin.Context) {
addSonarQubeEndpoint(r) c.JSON(http.StatusOK, gin.H{
addGiteaEndpoint(r) "message": "pong",
})
})
r.GET("/favicon.ico", func(c *gin.Context) { r.GET("/favicon.ico", func(c *gin.Context) {
c.Status(http.StatusNoContent) c.Status(http.StatusNoContent)
}) })
addSonarQubeEndpoint(r)
addGiteaEndpoint(r)
return r
}
func Serve(c *cli.Context) error {
fmt.Println("Hi! I'm the Gitea-SonarQube-PR bot. At your service.")
sonarQubeWebhookHandler = NewSonarQubeWebhookHandler(giteaSdk.New(), sqSdk.New())
giteaWebhookHandler = NewGiteaWebhookHandler(giteaSdk.New(), sqSdk.New())
r := setupRouter()
return endless.ListenAndServe(":3000", r) return endless.ListenAndServe(":3000", r)
} }

View file

@ -1,26 +1,41 @@
package api package api
import ( import (
"bytes"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http"
"net/http/httptest"
"os" "os"
"testing" "testing"
giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea" giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea"
sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube" sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube"
"gitea-sonarqube-pr-bot/internal/settings" "gitea-sonarqube-pr-bot/internal/settings"
webhook "gitea-sonarqube-pr-bot/internal/webhooks/sonarqube"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
) )
// Default SDK mocking type SonarQubeHandlerMock struct {
type HandlerPartialMock struct {
mock.Mock mock.Mock
} }
func (h *HandlerPartialMock) fetchDetails(w *webhook.Webhook) { func (h *SonarQubeHandlerMock) Handle(rw http.ResponseWriter, r *http.Request) {
h.Called(w) h.Called(rw, r)
}
type GiteaHandlerMock struct {
mock.Mock
}
func (h *GiteaHandlerMock) HandleSynchronize(rw http.ResponseWriter, r *http.Request) {
h.Called(rw, r)
}
func (h *GiteaHandlerMock) HandleComment(rw http.ResponseWriter, r *http.Request) {
h.Called(rw, r)
} }
type GiteaSdkMock struct { type GiteaSdkMock struct {
@ -65,12 +80,116 @@ func (h *SQSdkMock) ComposeGiteaComment(data *sqSdk.CommentComposeData) (string,
return "", nil return "", nil
} }
func defaultMockPreparation(h *HandlerPartialMock) {
h.On("fetchDetails", mock.Anything).Return(nil)
}
// SETUP: mute logs // SETUP: mute logs
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
sonarQubeWebhookHandler = nil
giteaWebhookHandler = nil
gin.SetMode(gin.TestMode)
log.SetOutput(ioutil.Discard) log.SetOutput(ioutil.Discard)
os.Exit(m.Run()) os.Exit(m.Run())
} }
func TestNonAPIRoutes(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/favicon.ico", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNoContent, w.Code)
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/ping", nil)
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestSonarQubeAPIRouteMissingProjectHeader(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`)))
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestSonarQubeAPIRouteProcessing(t *testing.T) {
router := setupRouter()
sonarQubeHandlerMock := new(SonarQubeHandlerMock)
sonarQubeHandlerMock.On("Handle", mock.Anything, mock.Anything).Return(nil)
sonarQubeWebhookHandler = sonarQubeHandlerMock
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`)))
req.Header.Add("X-SonarQube-Project", "gitea-sonarqube-bot")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
sonarQubeHandlerMock.AssertNumberOfCalls(t, "Handle", 1)
}
func TestGiteaAPIRouteMissingEventHeader(t *testing.T) {
router := setupRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusNotFound, w.Code)
}
func TestGiteaAPIRouteSynchronizeProcessing(t *testing.T) {
router := setupRouter()
giteaHandlerMock := new(GiteaHandlerMock)
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil)
giteaWebhookHandler = giteaHandlerMock
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
req.Header.Add("X-Gitea-Event", "pull_request")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 1)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0)
}
func TestGiteaAPIRouteCommentProcessing(t *testing.T) {
router := setupRouter()
giteaHandlerMock := new(GiteaHandlerMock)
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil)
giteaWebhookHandler = giteaHandlerMock
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
req.Header.Add("X-Gitea-Event", "issue_comment")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 1)
}
func TestGiteaAPIRouteUnknownEvent(t *testing.T) {
router := setupRouter()
giteaHandlerMock := new(GiteaHandlerMock)
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil)
giteaWebhookHandler = giteaHandlerMock
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`)))
req.Header.Add("X-Gitea-Event", "unknown")
router.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
giteaHandlerMock.AssertNumberOfCalls(t, "HandleComment", 0)
}

View file

@ -14,10 +14,13 @@ import (
webhook "gitea-sonarqube-pr-bot/internal/webhooks/sonarqube" webhook "gitea-sonarqube-pr-bot/internal/webhooks/sonarqube"
) )
type SonarQubeWebhookHandlerInferface interface {
Handle(rw http.ResponseWriter, r *http.Request)
}
type SonarQubeWebhookHandler struct { type SonarQubeWebhookHandler struct {
fetchDetails func(w *webhook.Webhook) giteaSdk giteaSdk.GiteaSdkInterface
giteaSdk giteaSdk.GiteaSdkInterface sqSdk sqSdk.SonarQubeSdkInterface
sqSdk sqSdk.SonarQubeSdkInterface
} }
func (*SonarQubeWebhookHandler) inProjectsMapping(p []settings.Project, n string) (bool, int) { func (*SonarQubeWebhookHandler) inProjectsMapping(p []settings.Project, n string) (bool, int) {
@ -31,13 +34,6 @@ func (*SonarQubeWebhookHandler) inProjectsMapping(p []settings.Project, n string
} }
func (h *SonarQubeWebhookHandler) processData(w *webhook.Webhook, repo settings.GiteaRepository) { func (h *SonarQubeWebhookHandler) processData(w *webhook.Webhook, repo settings.GiteaRepository) {
if strings.ToLower(w.Branch.Type) != "pull_request" {
log.Println("Ignore Hook for non-PR")
return
}
h.fetchDetails(w)
status := giteaSdk.StatusOK status := giteaSdk.StatusOK
if w.QualityGate.Status != "OK" { if w.QualityGate.Status != "OK" {
status = giteaSdk.StatusFailure status = giteaSdk.StatusFailure
@ -96,19 +92,21 @@ func (h *SonarQubeWebhookHandler) Handle(rw http.ResponseWriter, r *http.Request
// Send response to SonarQube at this point to ensure being within 10 seconds limit of webhook response timeout // Send response to SonarQube at this point to ensure being within 10 seconds limit of webhook response timeout
rw.WriteHeader(http.StatusOK) rw.WriteHeader(http.StatusOK)
if strings.ToLower(w.Branch.Type) != "pull_request" {
io.WriteString(rw, `{"message": "Ignore Hook for non-PR analysis."}`)
log.Println("Ignore Hook for non-PR analysis")
return
}
io.WriteString(rw, `{"message": "Processing data. See bot logs for details."}`) io.WriteString(rw, `{"message": "Processing data. See bot logs for details."}`)
h.processData(w, settings.Projects[pIdx].Gitea) h.processData(w, settings.Projects[pIdx].Gitea)
} }
func fetchDetails(w *webhook.Webhook) { func NewSonarQubeWebhookHandler(g giteaSdk.GiteaSdkInterface, sq sqSdk.SonarQubeSdkInterface) SonarQubeWebhookHandlerInferface {
log.Printf("This method will load additional data from SonarQube based on PR %d", w.PRIndex)
}
func NewSonarQubeWebhookHandler(g giteaSdk.GiteaSdkInterface, sq sqSdk.SonarQubeSdkInterface) *SonarQubeWebhookHandler {
return &SonarQubeWebhookHandler{ return &SonarQubeWebhookHandler{
fetchDetails: fetchDetails, giteaSdk: g,
giteaSdk: g, sqSdk: sq,
sqSdk: sq,
} }
} }

View file

@ -11,12 +11,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func withValidSonarQubeRequestData(t *testing.T, mockPreparation func(*HandlerPartialMock), jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc, *HandlerPartialMock) { func withValidSonarQubeRequestData(t *testing.T, jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc) {
partialMock := new(HandlerPartialMock)
mockPreparation(partialMock)
webhookHandler := NewSonarQubeWebhookHandler(new(GiteaSdkMock), new(SQSdkMock)) webhookHandler := NewSonarQubeWebhookHandler(new(GiteaSdkMock), new(SQSdkMock))
webhookHandler.fetchDetails = partialMock.fetchDetails
req, err := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer(jsonBody)) req, err := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer(jsonBody))
if err != nil { if err != nil {
@ -27,7 +23,7 @@ func withValidSonarQubeRequestData(t *testing.T, mockPreparation func(*HandlerPa
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
handler := http.HandlerFunc(webhookHandler.Handle) handler := http.HandlerFunc(webhookHandler.Handle)
return req, rr, handler, partialMock return req, rr, handler
} }
func TestHandleSonarQubeWebhookProjectMapped(t *testing.T) { func TestHandleSonarQubeWebhookProjectMapped(t *testing.T) {
@ -38,7 +34,7 @@ func TestHandleSonarQubeWebhookProjectMapped(t *testing.T) {
}, },
}, },
} }
req, rr, handler, _ := withValidSonarQubeRequestData(t, defaultMockPreparation, []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)
@ -53,7 +49,7 @@ func TestHandleSonarQubeWebhookProjectNotMapped(t *testing.T) {
}, },
}, },
} }
req, rr, handler, _ := withValidSonarQubeRequestData(t, defaultMockPreparation, []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)
@ -69,7 +65,7 @@ func TestHandleSonarQubeWebhookInvalidJSONBody(t *testing.T) {
}, },
} }
req, rr, handler, _ := withValidSonarQubeRequestData(t, defaultMockPreparation, []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)
@ -85,12 +81,11 @@ func TestHandleSonarQubeWebhookForPullRequest(t *testing.T) {
}, },
} }
req, rr, handler, partialMock := withValidSonarQubeRequestData(t, defaultMockPreparation, []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())
partialMock.AssertNumberOfCalls(t, "fetchDetails", 1)
} }
func TestHandleSonarQubeWebhookForBranch(t *testing.T) { func TestHandleSonarQubeWebhookForBranch(t *testing.T) {
@ -102,10 +97,9 @@ func TestHandleSonarQubeWebhookForBranch(t *testing.T) {
}, },
} }
req, rr, handler, partialMock := withValidSonarQubeRequestData(t, defaultMockPreparation, []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": "Processing data. See bot logs for details."}`, rr.Body.String()) assert.Equal(t, `{"message": "Ignore Hook for non-PR analysis."}`, rr.Body.String())
partialMock.AssertNumberOfCalls(t, "fetchDetails", 0)
} }