Merge branch 'main' into webhook-secret-validation

This commit is contained in:
justusbunsi 2022-05-21 18:30:24 +02:00
commit 71b16fa296
7 changed files with 204 additions and 90 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

@ -13,6 +13,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
@ -105,7 +110,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,

View file

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

View file

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

View file

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

View file

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

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/main.go
sonar.go.tests.reportPaths=test-report.out
sonar.go.coverage.reportPaths=cover.out