diff --git a/cmd/gitea-sonarqube-bot/main.go b/cmd/gitea-sonarqube-bot/main.go index 8c08d20..5c9eb4d 100644 --- a/cmd/gitea-sonarqube-bot/main.go +++ b/cmd/gitea-sonarqube-bot/main.go @@ -1,13 +1,17 @@ package main import ( + "fmt" "log" "os" "path" "gitea-sonarqube-pr-bot/internal/api" + giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea" + sonarQubeSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube" "gitea-sonarqube-pr-bot/internal/settings" + "github.com/fvbock/endless" "github.com/urfave/cli/v2" ) @@ -27,7 +31,7 @@ func main() { Name: "gitea-sonarqube-pr-bot", Usage: "Improve your experience with SonarQube and Gitea", Description: `By default, gitea-sonarqube-pr-bot will start running the webserver if no arguments are passed.`, - Action: api.Serve, + Action: serveApi, } err := app.Run(os.Args) @@ -35,3 +39,13 @@ func main() { log.Fatal(err) } } + +func serveApi(c *cli.Context) error { + fmt.Println("Hi! I'm the Gitea-SonarQube-PR bot. At your service.") + + giteaHandler := api.NewGiteaWebhookHandler(giteaSdk.New(), sonarQubeSdk.New()) + sqHandler := api.NewSonarQubeWebhookHandler(giteaSdk.New(), sonarQubeSdk.New()) + server := api.New(giteaHandler, sqHandler) + + return endless.ListenAndServe(":3000", server.Engine) +} diff --git a/internal/api/gitea.go b/internal/api/gitea.go index c96ffbc..13660b1 100644 --- a/internal/api/gitea.go +++ b/internal/api/gitea.go @@ -12,6 +12,11 @@ import ( 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 { giteaSdk giteaSdk.GiteaSdkInterface sqSdk sqSdk.SonarQubeSdkInterface @@ -88,7 +93,7 @@ func (h *GiteaWebhookHandler) HandleComment(rw http.ResponseWriter, r *http.Requ 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{ giteaSdk: g, sqSdk: sq, diff --git a/internal/api/main.go b/internal/api/main.go index aa92dfe..7ae869f 100644 --- a/internal/api/main.go +++ b/internal/api/main.go @@ -1,32 +1,38 @@ package api import ( - "fmt" "net/http" - giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea" - sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube" - - "github.com/fvbock/endless" "github.com/gin-gonic/gin" - "github.com/urfave/cli/v2" ) -func addPingApi(r *gin.Engine) { - r.GET("/ping", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "pong", - }) - }) -} - type validSonarQubeEndpointHeader struct { SonarQubeProject string `header:"X-SonarQube-Project" binding:"required"` } -func addSonarQubeEndpoint(r *gin.Engine) { - webhookHandler := NewSonarQubeWebhookHandler(giteaSdk.New(), sqSdk.New()) - r.POST("/hooks/sonarqube", func(c *gin.Context) { +type validGiteaEndpointHeader struct { + GiteaEvent string `header:"X-Gitea-Event" binding:"required"` +} + +type ApiServer struct { + Engine *gin.Engine + sonarQubeWebhookHandler SonarQubeWebhookHandlerInferface + giteaWebhookHandler GiteaWebhookHandlerInferface +} + +func (s *ApiServer) setup() { + s.Engine.Use(gin.Recovery()) + s.Engine.Use(gin.LoggerWithConfig(gin.LoggerConfig{ + SkipPaths: []string{"/ping", "/favicon.ico"}, + })) + + s.Engine.GET("/favicon.ico", func(c *gin.Context) { + c.Status(http.StatusNoContent) + }).GET("/ping", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "pong", + }) + }).POST("/hooks/sonarqube", func(c *gin.Context) { h := validSonarQubeEndpointHeader{} if err := c.ShouldBindHeader(&h); err != nil { @@ -34,17 +40,8 @@ func addSonarQubeEndpoint(r *gin.Engine) { return } - webhookHandler.Handle(c.Writer, c.Request) - }) -} - -type validGiteaEndpointHeader struct { - GiteaEvent string `header:"X-Gitea-Event" binding:"required"` -} - -func addGiteaEndpoint(r *gin.Engine) { - webhookHandler := NewGiteaWebhookHandler(giteaSdk.New(), sqSdk.New()) - r.POST("/hooks/gitea", func(c *gin.Context) { + s.sonarQubeWebhookHandler.Handle(c.Writer, c.Request) + }).POST("/hooks/gitea", func(c *gin.Context) { h := validGiteaEndpointHeader{} if err := c.ShouldBindHeader(&h); err != nil { @@ -54,9 +51,9 @@ func addGiteaEndpoint(r *gin.Engine) { switch h.GiteaEvent { case "pull_request": - webhookHandler.HandleSynchronize(c.Writer, c.Request) + s.giteaWebhookHandler.HandleSynchronize(c.Writer, c.Request) case "issue_comment": - webhookHandler.HandleComment(c.Writer, c.Request) + s.giteaWebhookHandler.HandleComment(c.Writer, c.Request) default: c.JSON(http.StatusOK, gin.H{ "message": "ignore unknown event", @@ -65,23 +62,14 @@ func addGiteaEndpoint(r *gin.Engine) { }) } -func Serve(c *cli.Context) error { - fmt.Println("Hi! I'm the Gitea-SonarQube-PR bot. At your service.") +func New(giteaHandler GiteaWebhookHandlerInferface, sonarQubeHandler SonarQubeWebhookHandlerInferface) *ApiServer { + s := &ApiServer{ + Engine: gin.New(), + giteaWebhookHandler: giteaHandler, + sonarQubeWebhookHandler: sonarQubeHandler, + } - r := gin.New() + s.setup() - r.Use(gin.Recovery()) - r.Use(gin.LoggerWithConfig(gin.LoggerConfig{ - SkipPaths: []string{"/ping", "/favicon.ico"}, - })) - - addPingApi(r) - addSonarQubeEndpoint(r) - addGiteaEndpoint(r) - - r.GET("/favicon.ico", func(c *gin.Context) { - c.Status(http.StatusNoContent) - }) - - return endless.ListenAndServe(":3000", r) + return s } diff --git a/internal/api/main_test.go b/internal/api/main_test.go index 8b27e47..046972a 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -1,26 +1,41 @@ package api import ( + "bytes" "io/ioutil" "log" + "net/http" + "net/http/httptest" "os" "testing" giteaSdk "gitea-sonarqube-pr-bot/internal/clients/gitea" sqSdk "gitea-sonarqube-pr-bot/internal/clients/sonarqube" "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" ) -// Default SDK mocking -type HandlerPartialMock struct { +type SonarQubeHandlerMock struct { mock.Mock } -func (h *HandlerPartialMock) fetchDetails(w *webhook.Webhook) { - h.Called(w) +func (h *SonarQubeHandlerMock) Handle(rw http.ResponseWriter, r *http.Request) { + 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 { @@ -65,12 +80,109 @@ func (h *SQSdkMock) ComposeGiteaComment(data *sqSdk.CommentComposeData) (string, return "", nil } -func defaultMockPreparation(h *HandlerPartialMock) { - h.On("fetchDetails", mock.Anything).Return(nil) -} - // SETUP: mute logs func TestMain(m *testing.M) { + gin.SetMode(gin.TestMode) log.SetOutput(ioutil.Discard) os.Exit(m.Run()) } + +func TestNonAPIRoutes(t *testing.T) { + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/favicon.ico", nil) + router.Engine.ServeHTTP(w, req) + assert.Equal(t, http.StatusNoContent, 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) { + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`))) + router.Engine.ServeHTTP(w, req) + + assert.Equal(t, http.StatusNotFound, w.Code) +} + +func TestSonarQubeAPIRouteProcessing(t *testing.T) { + sonarQubeHandlerMock := new(SonarQubeHandlerMock) + sonarQubeHandlerMock.On("Handle", mock.Anything, mock.Anything).Return(nil) + + 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) +} + +func TestGiteaAPIRouteMissingEventHeader(t *testing.T) { + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) + router.Engine.ServeHTTP(w, req) + + assert.Equal(t, http.StatusNotFound, w.Code) +} + +func TestGiteaAPIRouteSynchronizeProcessing(t *testing.T) { + giteaHandlerMock := new(GiteaHandlerMock) + giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil) + giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil) + + router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) + req.Header.Add("X-Gitea-Event", "pull_request") + router.Engine.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) { + giteaHandlerMock := new(GiteaHandlerMock) + giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil) + giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil) + + router := New(giteaHandlerMock, new(SonarQubeHandlerMock)) + + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) + req.Header.Add("X-Gitea-Event", "issue_comment") + router.Engine.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) { + giteaHandlerMock := new(GiteaHandlerMock) + giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil) + giteaHandlerMock.On("HandleComment", mock.Anything, mock.Anything).Return(nil) + + 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) +} diff --git a/internal/api/sonarqube.go b/internal/api/sonarqube.go index 77fbf30..6a73d49 100644 --- a/internal/api/sonarqube.go +++ b/internal/api/sonarqube.go @@ -14,10 +14,13 @@ import ( webhook "gitea-sonarqube-pr-bot/internal/webhooks/sonarqube" ) +type SonarQubeWebhookHandlerInferface interface { + Handle(rw http.ResponseWriter, r *http.Request) +} + type SonarQubeWebhookHandler struct { - fetchDetails func(w *webhook.Webhook) - giteaSdk giteaSdk.GiteaSdkInterface - sqSdk sqSdk.SonarQubeSdkInterface + giteaSdk giteaSdk.GiteaSdkInterface + sqSdk sqSdk.SonarQubeSdkInterface } 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) { - if strings.ToLower(w.Branch.Type) != "pull_request" { - log.Println("Ignore Hook for non-PR") - return - } - - h.fetchDetails(w) - status := giteaSdk.StatusOK if w.QualityGate.Status != "OK" { 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 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."}`) h.processData(w, settings.Projects[pIdx].Gitea) } -func fetchDetails(w *webhook.Webhook) { - 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 { +func NewSonarQubeWebhookHandler(g giteaSdk.GiteaSdkInterface, sq sqSdk.SonarQubeSdkInterface) SonarQubeWebhookHandlerInferface { return &SonarQubeWebhookHandler{ - fetchDetails: fetchDetails, - giteaSdk: g, - sqSdk: sq, + giteaSdk: g, + sqSdk: sq, } } diff --git a/internal/api/sonarqube_test.go b/internal/api/sonarqube_test.go index ce5011e..d0643ef 100644 --- a/internal/api/sonarqube_test.go +++ b/internal/api/sonarqube_test.go @@ -11,12 +11,8 @@ import ( "github.com/stretchr/testify/assert" ) -func withValidSonarQubeRequestData(t *testing.T, mockPreparation func(*HandlerPartialMock), jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc, *HandlerPartialMock) { - partialMock := new(HandlerPartialMock) - mockPreparation(partialMock) - +func withValidSonarQubeRequestData(t *testing.T, jsonBody []byte) (*http.Request, *httptest.ResponseRecorder, http.HandlerFunc) { webhookHandler := NewSonarQubeWebhookHandler(new(GiteaSdkMock), new(SQSdkMock)) - webhookHandler.fetchDetails = partialMock.fetchDetails req, err := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer(jsonBody)) if err != nil { @@ -27,7 +23,7 @@ func withValidSonarQubeRequestData(t *testing.T, mockPreparation func(*HandlerPa rr := httptest.NewRecorder() handler := http.HandlerFunc(webhookHandler.Handle) - return req, rr, handler, partialMock + return req, rr, handler } 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) 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) 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) 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) assert.Equal(t, http.StatusOK, rr.Code) assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) - partialMock.AssertNumberOfCalls(t, "fetchDetails", 1) } 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) assert.Equal(t, http.StatusOK, rr.Code) - assert.Equal(t, `{"message": "Processing data. See bot logs for details."}`, rr.Body.String()) - partialMock.AssertNumberOfCalls(t, "fetchDetails", 0) + assert.Equal(t, `{"message": "Ignore Hook for non-PR analysis."}`, rr.Body.String()) } diff --git a/sonar-project.properties b/sonar-project.properties index d12c673..a1d7469 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -11,5 +11,8 @@ sonar.exclusions=**/*_test.go,contrib/**,docker/**,docs/**,helm/** sonar.tests=. sonar.test.inclusions=**/*_test.go +# Entrypoint of the application and not properly testable +sonar.coverage.exclusions=cmd/gitea-sonarqube-bot/main.go + sonar.go.tests.reportPaths=test-report.out sonar.go.coverage.reportPaths=cover.out