Use object structure for ApiServer

The API entrypoint was not fully testable due to production API client
instantiation. This instantiation is now done within the command
entrypoint and this file is excluded from coverage analysis in
SonarQube.

Signed-off-by: Steven Kriegler <sk.bunsenbrenner@gmail.com>
This commit is contained in:
justusbunsi 2022-05-21 18:10:20 +02:00
parent af38a31bbe
commit 10d2d3def8
No known key found for this signature in database
GPG key ID: 82B29BF2507F9F8B
4 changed files with 72 additions and 82 deletions

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)

View file

@ -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