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:
parent
af38a31bbe
commit
10d2d3def8
|
@ -1,13 +1,17 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"gitea-sonarqube-pr-bot/internal/api"
|
"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"
|
"gitea-sonarqube-pr-bot/internal/settings"
|
||||||
|
|
||||||
|
"github.com/fvbock/endless"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,7 +31,7 @@ func main() {
|
||||||
Name: "gitea-sonarqube-pr-bot",
|
Name: "gitea-sonarqube-pr-bot",
|
||||||
Usage: "Improve your experience with SonarQube and Gitea",
|
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.`,
|
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)
|
err := app.Run(os.Args)
|
||||||
|
@ -35,3 +39,13 @@ func main() {
|
||||||
log.Fatal(err)
|
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)
|
||||||
|
}
|
||||||
|
|
|
@ -1,28 +1,38 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"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/gin-gonic/gin"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
sonarQubeWebhookHandler SonarQubeWebhookHandlerInferface
|
|
||||||
giteaWebhookHandler GiteaWebhookHandlerInferface
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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) {
|
type validGiteaEndpointHeader struct {
|
||||||
r.POST("/hooks/sonarqube", func(c *gin.Context) {
|
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{}
|
h := validSonarQubeEndpointHeader{}
|
||||||
|
|
||||||
if err := c.ShouldBindHeader(&h); err != nil {
|
if err := c.ShouldBindHeader(&h); err != nil {
|
||||||
|
@ -30,16 +40,8 @@ func addSonarQubeEndpoint(r *gin.Engine) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sonarQubeWebhookHandler.Handle(c.Writer, c.Request)
|
s.sonarQubeWebhookHandler.Handle(c.Writer, c.Request)
|
||||||
})
|
}).POST("/hooks/gitea", func(c *gin.Context) {
|
||||||
}
|
|
||||||
|
|
||||||
type validGiteaEndpointHeader struct {
|
|
||||||
GiteaEvent string `header:"X-Gitea-Event" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func addGiteaEndpoint(r *gin.Engine) {
|
|
||||||
r.POST("/hooks/gitea", func(c *gin.Context) {
|
|
||||||
h := validGiteaEndpointHeader{}
|
h := validGiteaEndpointHeader{}
|
||||||
|
|
||||||
if err := c.ShouldBindHeader(&h); err != nil {
|
if err := c.ShouldBindHeader(&h); err != nil {
|
||||||
|
@ -49,9 +51,9 @@ func addGiteaEndpoint(r *gin.Engine) {
|
||||||
|
|
||||||
switch h.GiteaEvent {
|
switch h.GiteaEvent {
|
||||||
case "pull_request":
|
case "pull_request":
|
||||||
giteaWebhookHandler.HandleSynchronize(c.Writer, c.Request)
|
s.giteaWebhookHandler.HandleSynchronize(c.Writer, c.Request)
|
||||||
case "issue_comment":
|
case "issue_comment":
|
||||||
giteaWebhookHandler.HandleComment(c.Writer, c.Request)
|
s.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",
|
||||||
|
@ -60,36 +62,14 @@ func addGiteaEndpoint(r *gin.Engine) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupRouter() *gin.Engine {
|
func New(giteaHandler GiteaWebhookHandlerInferface, sonarQubeHandler SonarQubeWebhookHandlerInferface) *ApiServer {
|
||||||
r := gin.New()
|
s := &ApiServer{
|
||||||
|
Engine: gin.New(),
|
||||||
|
giteaWebhookHandler: giteaHandler,
|
||||||
|
sonarQubeWebhookHandler: sonarQubeHandler,
|
||||||
|
}
|
||||||
|
|
||||||
r.Use(gin.Recovery())
|
s.setup()
|
||||||
r.Use(gin.LoggerWithConfig(gin.LoggerConfig{
|
|
||||||
SkipPaths: []string{"/ping", "/favicon.ico"},
|
|
||||||
}))
|
|
||||||
|
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
return s
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,76 +82,71 @@ func (h *SQSdkMock) ComposeGiteaComment(data *sqSdk.CommentComposeData) (string,
|
||||||
|
|
||||||
// SETUP: mute logs
|
// SETUP: mute logs
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
sonarQubeWebhookHandler = nil
|
|
||||||
giteaWebhookHandler = nil
|
|
||||||
|
|
||||||
gin.SetMode(gin.TestMode)
|
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) {
|
func TestNonAPIRoutes(t *testing.T) {
|
||||||
router := setupRouter()
|
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.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()
|
w = httptest.NewRecorder()
|
||||||
req, _ = http.NewRequest("GET", "/ping", nil)
|
req, _ = http.NewRequest("GET", "/ping", nil)
|
||||||
router.ServeHTTP(w, req)
|
router.Engine.ServeHTTP(w, req)
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSonarQubeAPIRouteMissingProjectHeader(t *testing.T) {
|
func TestSonarQubeAPIRouteMissingProjectHeader(t *testing.T) {
|
||||||
router := setupRouter()
|
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.ServeHTTP(w, req)
|
router.Engine.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSonarQubeAPIRouteProcessing(t *testing.T) {
|
func TestSonarQubeAPIRouteProcessing(t *testing.T) {
|
||||||
router := setupRouter()
|
|
||||||
|
|
||||||
sonarQubeHandlerMock := new(SonarQubeHandlerMock)
|
sonarQubeHandlerMock := new(SonarQubeHandlerMock)
|
||||||
sonarQubeHandlerMock.On("Handle", mock.Anything, mock.Anything).Return(nil)
|
sonarQubeHandlerMock.On("Handle", mock.Anything, mock.Anything).Return(nil)
|
||||||
sonarQubeWebhookHandler = sonarQubeHandlerMock
|
|
||||||
|
router := New(new(GiteaHandlerMock), 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(`{}`)))
|
||||||
req.Header.Add("X-SonarQube-Project", "gitea-sonarqube-bot")
|
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)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
sonarQubeHandlerMock.AssertNumberOfCalls(t, "Handle", 1)
|
sonarQubeHandlerMock.AssertNumberOfCalls(t, "Handle", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGiteaAPIRouteMissingEventHeader(t *testing.T) {
|
func TestGiteaAPIRouteMissingEventHeader(t *testing.T) {
|
||||||
router := setupRouter()
|
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(`{}`)))
|
||||||
router.ServeHTTP(w, req)
|
router.Engine.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGiteaAPIRouteSynchronizeProcessing(t *testing.T) {
|
func TestGiteaAPIRouteSynchronizeProcessing(t *testing.T) {
|
||||||
router := setupRouter()
|
|
||||||
|
|
||||||
giteaHandlerMock := new(GiteaHandlerMock)
|
giteaHandlerMock := new(GiteaHandlerMock)
|
||||||
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
|
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
|
||||||
giteaHandlerMock.On("HandleComment", 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()
|
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")
|
req.Header.Add("X-Gitea-Event", "pull_request")
|
||||||
router.ServeHTTP(w, req)
|
router.Engine.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 1)
|
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 1)
|
||||||
|
@ -159,17 +154,16 @@ func TestGiteaAPIRouteSynchronizeProcessing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGiteaAPIRouteCommentProcessing(t *testing.T) {
|
func TestGiteaAPIRouteCommentProcessing(t *testing.T) {
|
||||||
router := setupRouter()
|
|
||||||
|
|
||||||
giteaHandlerMock := new(GiteaHandlerMock)
|
giteaHandlerMock := new(GiteaHandlerMock)
|
||||||
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
|
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
|
||||||
giteaHandlerMock.On("HandleComment", 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()
|
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", "issue_comment")
|
req.Header.Add("X-Gitea-Event", "issue_comment")
|
||||||
router.ServeHTTP(w, req)
|
router.Engine.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
|
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
|
||||||
|
@ -177,17 +171,16 @@ func TestGiteaAPIRouteCommentProcessing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGiteaAPIRouteUnknownEvent(t *testing.T) {
|
func TestGiteaAPIRouteUnknownEvent(t *testing.T) {
|
||||||
router := setupRouter()
|
|
||||||
|
|
||||||
giteaHandlerMock := new(GiteaHandlerMock)
|
giteaHandlerMock := new(GiteaHandlerMock)
|
||||||
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
|
giteaHandlerMock.On("HandleSynchronize", mock.Anything, mock.Anything).Return(nil)
|
||||||
giteaHandlerMock.On("HandleComment", 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()
|
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", "unknown")
|
req.Header.Add("X-Gitea-Event", "unknown")
|
||||||
router.ServeHTTP(w, req)
|
router.Engine.ServeHTTP(w, req)
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, w.Code)
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
|
giteaHandlerMock.AssertNumberOfCalls(t, "HandleSynchronize", 0)
|
||||||
|
|
|
@ -11,5 +11,8 @@ sonar.exclusions=**/*_test.go,contrib/**,docker/**,docs/**,helm/**
|
||||||
sonar.tests=.
|
sonar.tests=.
|
||||||
sonar.test.inclusions=**/*_test.go
|
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.tests.reportPaths=test-report.out
|
||||||
sonar.go.coverage.reportPaths=cover.out
|
sonar.go.coverage.reportPaths=cover.out
|
||||||
|
|
Loading…
Reference in a new issue