Merge branch 'main' into webhook-secret-validation
This commit is contained in:
commit
71b16fa296
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,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
|
||||||
|
@ -105,7 +110,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,
|
||||||
|
|
|
@ -1,32 +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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func addPingApi(r *gin.Engine) {
|
|
||||||
r.GET("/ping", func(c *gin.Context) {
|
|
||||||
c.JSON(http.StatusOK, gin.H{
|
|
||||||
"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) {
|
type validGiteaEndpointHeader struct {
|
||||||
webhookHandler := NewSonarQubeWebhookHandler(giteaSdk.New(), sqSdk.New())
|
GiteaEvent string `header:"X-Gitea-Event" binding:"required"`
|
||||||
r.POST("/hooks/sonarqube", func(c *gin.Context) {
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
@ -34,17 +40,8 @@ func addSonarQubeEndpoint(r *gin.Engine) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
webhookHandler.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) {
|
|
||||||
webhookHandler := NewGiteaWebhookHandler(giteaSdk.New(), sqSdk.New())
|
|
||||||
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 {
|
||||||
|
@ -54,9 +51,9 @@ func addGiteaEndpoint(r *gin.Engine) {
|
||||||
|
|
||||||
switch h.GiteaEvent {
|
switch h.GiteaEvent {
|
||||||
case "pull_request":
|
case "pull_request":
|
||||||
webhookHandler.HandleSynchronize(c.Writer, c.Request)
|
s.giteaWebhookHandler.HandleSynchronize(c.Writer, c.Request)
|
||||||
case "issue_comment":
|
case "issue_comment":
|
||||||
webhookHandler.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",
|
||||||
|
@ -65,23 +62,14 @@ func addGiteaEndpoint(r *gin.Engine) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Serve(c *cli.Context) error {
|
func New(giteaHandler GiteaWebhookHandlerInferface, sonarQubeHandler SonarQubeWebhookHandlerInferface) *ApiServer {
|
||||||
fmt.Println("Hi! I'm the Gitea-SonarQube-PR bot. At your service.")
|
s := &ApiServer{
|
||||||
|
Engine: gin.New(),
|
||||||
|
giteaWebhookHandler: giteaHandler,
|
||||||
|
sonarQubeWebhookHandler: sonarQubeHandler,
|
||||||
|
}
|
||||||
|
|
||||||
r := gin.New()
|
s.setup()
|
||||||
|
|
||||||
r.Use(gin.Recovery())
|
return s
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,109 @@ 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) {
|
||||||
|
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 := 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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -104,19 +100,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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -43,7 +39,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)
|
||||||
|
@ -58,7 +54,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)
|
||||||
|
@ -74,7 +70,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)
|
||||||
|
@ -116,12 +112,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) {
|
||||||
|
@ -138,10 +133,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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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/main.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