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/main.go b/internal/api/main.go index f71d4ef..7ae869f 100644 --- a/internal/api/main.go +++ b/internal/api/main.go @@ -1,28 +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" -) - -var ( - sonarQubeWebhookHandler SonarQubeWebhookHandlerInferface - giteaWebhookHandler GiteaWebhookHandlerInferface ) type validSonarQubeEndpointHeader struct { SonarQubeProject string `header:"X-SonarQube-Project" binding:"required"` } -func addSonarQubeEndpoint(r *gin.Engine) { - 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 { @@ -30,16 +40,8 @@ func addSonarQubeEndpoint(r *gin.Engine) { return } - sonarQubeWebhookHandler.Handle(c.Writer, c.Request) - }) -} - -type validGiteaEndpointHeader struct { - GiteaEvent string `header:"X-Gitea-Event" binding:"required"` -} - -func addGiteaEndpoint(r *gin.Engine) { - 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 { @@ -49,9 +51,9 @@ func addGiteaEndpoint(r *gin.Engine) { switch h.GiteaEvent { case "pull_request": - giteaWebhookHandler.HandleSynchronize(c.Writer, c.Request) + s.giteaWebhookHandler.HandleSynchronize(c.Writer, c.Request) case "issue_comment": - giteaWebhookHandler.HandleComment(c.Writer, c.Request) + s.giteaWebhookHandler.HandleComment(c.Writer, c.Request) default: c.JSON(http.StatusOK, gin.H{ "message": "ignore unknown event", @@ -60,36 +62,14 @@ func addGiteaEndpoint(r *gin.Engine) { }) } -func setupRouter() *gin.Engine { - r := gin.New() +func New(giteaHandler GiteaWebhookHandlerInferface, sonarQubeHandler SonarQubeWebhookHandlerInferface) *ApiServer { + s := &ApiServer{ + Engine: gin.New(), + giteaWebhookHandler: giteaHandler, + sonarQubeWebhookHandler: sonarQubeHandler, + } - r.Use(gin.Recovery()) - r.Use(gin.LoggerWithConfig(gin.LoggerConfig{ - SkipPaths: []string{"/ping", "/favicon.ico"}, - })) + s.setup() - r.GET("/ping", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "pong", - }) - }) - r.GET("/favicon.ico", func(c *gin.Context) { - 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 s } diff --git a/internal/api/main_test.go b/internal/api/main_test.go index f4fc87f..046972a 100644 --- a/internal/api/main_test.go +++ b/internal/api/main_test.go @@ -82,76 +82,71 @@ func (h *SQSdkMock) ComposeGiteaComment(data *sqSdk.CommentComposeData) (string, // SETUP: mute logs func TestMain(m *testing.M) { - sonarQubeWebhookHandler = nil - giteaWebhookHandler = nil - gin.SetMode(gin.TestMode) log.SetOutput(ioutil.Discard) os.Exit(m.Run()) } func TestNonAPIRoutes(t *testing.T) { - router := setupRouter() + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/favicon.ico", nil) - router.ServeHTTP(w, req) + router.Engine.ServeHTTP(w, req) assert.Equal(t, http.StatusNoContent, w.Code) w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/ping", nil) - router.ServeHTTP(w, req) + router.Engine.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) } func TestSonarQubeAPIRouteMissingProjectHeader(t *testing.T) { - router := setupRouter() + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/hooks/sonarqube", bytes.NewBuffer([]byte(`{}`))) - router.ServeHTTP(w, req) + router.Engine.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 + + 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.ServeHTTP(w, req) + router.Engine.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) sonarQubeHandlerMock.AssertNumberOfCalls(t, "Handle", 1) } func TestGiteaAPIRouteMissingEventHeader(t *testing.T) { - router := setupRouter() + router := New(new(GiteaHandlerMock), new(SonarQubeHandlerMock)) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/hooks/gitea", bytes.NewBuffer([]byte(`{}`))) - router.ServeHTTP(w, req) + router.Engine.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 + + 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.ServeHTTP(w, req) + router.Engine.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 1) @@ -159,17 +154,16 @@ func TestGiteaAPIRouteSynchronizeProcessing(t *testing.T) { } 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 + + 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.ServeHTTP(w, req) + router.Engine.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0) @@ -177,17 +171,16 @@ func TestGiteaAPIRouteCommentProcessing(t *testing.T) { } 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 + + 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.ServeHTTP(w, req) + router.Engine.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0) diff --git a/sonar-project.properties b/sonar-project.properties index d12c673..dd8a6e0 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.go + sonar.go.tests.reportPaths=test-report.out sonar.go.coverage.reportPaths=cover.out