From a567e6fb984b2189004d7a5ddb0b48776a8ad303 Mon Sep 17 00:00:00 2001 From: Finsen Varghese Date: Tue, 23 Jan 2024 20:19:56 -0800 Subject: [PATCH] Chore: Add unit tests (#50) * feat: WIP - added router.go ginkgo unit tests * feat: WIP - added handlers.go unit tests using mocks * feat: WIP - added handlers.go unit tests using mocks * feat: added handlers.go unit tests using mocks * chore: Updated coverage badge. --------- Co-authored-by: Finsen Varghese Co-authored-by: GitHub Action --- .gitignore | 1 + README.md | 2 +- go.mod | 1 + go.sum | 3 + pkg/api/handlers/handlers.go | 39 ++++++---- pkg/api/handlers/handlers_suite_test.go | 13 ++++ pkg/api/handlers/handlers_test.go | 99 +++++++++++++++++++++++++ pkg/api/routers/routers.go | 19 +++-- 8 files changed, 152 insertions(+), 25 deletions(-) create mode 100644 pkg/api/handlers/handlers_suite_test.go create mode 100644 pkg/api/handlers/handlers_test.go diff --git a/.gitignore b/.gitignore index 3b735ec..b107cc0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +*.idea # Test binary, built with `go test -c` *.test diff --git a/README.md b/README.md index c6f5871..37c20dc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Guidewire/fern-reporter/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Guidewire/fern-reporter) -![Coverage](https://img.shields.io/badge/Coverage-60.0%25-yellow) +![Coverage](https://img.shields.io/badge/Coverage-23.0%25-red) ![Fern](https://github.com/guidewire/fern-reporter/raw/main/docs/images/logo-no-background.png) diff --git a/go.mod b/go.mod index 0d9e944..2d90a2d 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/guidewire/fern-reporter go 1.21 require ( + github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.9.1 github.com/golang-migrate/migrate/v4 v4.16.2 diff --git a/go.sum b/go.sum index 3f4b9c9..e71007c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= +github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= @@ -97,6 +99,7 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= diff --git a/pkg/api/handlers/handlers.go b/pkg/api/handlers/handlers.go index 92148d8..0d5fb03 100644 --- a/pkg/api/handlers/handlers.go +++ b/pkg/api/handlers/handlers.go @@ -9,13 +9,19 @@ import ( "github.com/guidewire/fern-reporter/pkg/models" - "github.com/guidewire/fern-reporter/pkg/db" - "github.com/gin-gonic/gin" "gorm.io/gorm" ) -func CreateTestRun(c *gin.Context) { +type Handler struct { + db *gorm.DB +} + +func NewHandler(db *gorm.DB) *Handler { + return &Handler{db: db} +} + +func (h *Handler) CreateTestRun(c *gin.Context) { var testRun models.TestRun if err := c.ShouldBindJSON(&testRun); err != nil { @@ -24,7 +30,7 @@ func CreateTestRun(c *gin.Context) { return // Stop further processing if there is a binding error } - gdb := db.GetDb() + gdb := h.db isNewRecord := testRun.ID == 0 // If it's not a new record, try to find it first @@ -83,24 +89,25 @@ func ProcessTags(db *gorm.DB, testRun *models.TestRun) error { return nil } -func GetTestRunAll(c *gin.Context) { +func (h *Handler) GetTestRunAll(c *gin.Context) { var testRuns []models.TestRun - db.GetDb().Find(&testRuns) + h.db.Find(&testRuns) c.JSON(http.StatusOK, testRuns) } -func GetTestRunByID(c *gin.Context) { +func (h *Handler) GetTestRunByID(c *gin.Context) { var testRun models.TestRun id := c.Param("id") - db.GetDb().Where("id = ?", id).First(&testRun) + h.db.Where("id = ?", id).First(&testRun) c.JSON(http.StatusOK, testRun) + } -func UpdateTestRun(c *gin.Context) { +func (h *Handler) UpdateTestRun(c *gin.Context) { var testRun models.TestRun id := c.Param("id") - db := db.GetDb() + db := h.db if err := db.Where("id = ?", id).First(&testRun).Error; err != nil { c.AbortWithStatus(http.StatusNotFound) return @@ -114,7 +121,7 @@ func UpdateTestRun(c *gin.Context) { c.JSON(http.StatusOK, &testRun) } -func DeleteTestRun(c *gin.Context) { +func (h *Handler) DeleteTestRun(c *gin.Context) { var testRun models.TestRun id := c.Param("id") if testRunID, err := strconv.Atoi(id); err != nil { @@ -124,7 +131,7 @@ func DeleteTestRun(c *gin.Context) { testRun.ID = uint64(testRunID) } - result := db.GetDb().Delete(&testRun) + result := h.db.Delete(&testRun) if result.Error != nil { // If there was an error during the delete operation c.JSON(http.StatusInternalServerError, gin.H{"error": "error deleting test run"}) @@ -138,18 +145,18 @@ func DeleteTestRun(c *gin.Context) { c.JSON(http.StatusOK, &testRun) } -func ReportTestRunAll(c *gin.Context) { +func (h *Handler) ReportTestRunAll(c *gin.Context) { var testRuns []models.TestRun - db.GetDb().Preload("SuiteRuns.SpecRuns").Find(&testRuns) + h.db.Preload("SuiteRuns.SpecRuns").Find(&testRuns) c.HTML(http.StatusOK, "test_runs.html", gin.H{ "testRuns": testRuns, }) } -func ReportTestRunById(c *gin.Context) { +func (h *Handler) ReportTestRunById(c *gin.Context) { var testRun models.TestRun id := c.Param("id") - db.GetDb().Preload("SuiteRuns.SpecRuns").Where("id = ?", id).First(&testRun) + h.db.Preload("SuiteRuns.SpecRuns").Where("id = ?", id).First(&testRun) c.HTML(http.StatusOK, "test_runs.html", gin.H{ "testRuns": []models.TestRun{testRun}, }) diff --git a/pkg/api/handlers/handlers_suite_test.go b/pkg/api/handlers/handlers_suite_test.go new file mode 100644 index 0000000..f3789b5 --- /dev/null +++ b/pkg/api/handlers/handlers_suite_test.go @@ -0,0 +1,13 @@ +package handlers_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestHandlers(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Handlers Suite") +} diff --git a/pkg/api/handlers/handlers_test.go b/pkg/api/handlers/handlers_test.go new file mode 100644 index 0000000..7e9ef12 --- /dev/null +++ b/pkg/api/handlers/handlers_test.go @@ -0,0 +1,99 @@ +package handlers_test + +import ( + "database/sql" + "encoding/json" + "net/http/httptest" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/gin-gonic/gin" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "gorm.io/driver/postgres" + "gorm.io/gorm" + + "github.com/guidewire/fern-reporter/pkg/api/handlers" + "github.com/guidewire/fern-reporter/pkg/models" +) + +var ( + db *sql.DB + gormDb *gorm.DB + mock sqlmock.Sqlmock +) + +var _ = BeforeSuite(func() { + db, mock, _ = sqlmock.New() + + dialector := postgres.New(postgres.Config{ + DSN: "sqlmock_db_0", + DriverName: "postgres", + Conn: db, + PreferSimpleProtocol: true, + }) + gormDb, _ = gorm.Open(dialector, &gorm.Config{}) + +}) + +var _ = AfterSuite(func() { + db.Close() +}) + +var _ = Describe("Handlers", func() { + Context("when GetTestRunAll handleer is invoked", func() { + It("should query db to fetch all records", func() { + + rows := sqlmock.NewRows([]string{"ID", "TestProjectName"}). + AddRow(1, "project 1"). + AddRow(2, "project 2") + + mock.ExpectQuery("SELECT (.+) FROM \"test_runs\""). + WithoutArgs(). + WillReturnRows(rows) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + handler := handlers.NewHandler(gormDb) + + handler.GetTestRunAll(c) + + Expect(w.Code).To(Equal(200)) + + var testRuns []models.TestRun + if err := json.NewDecoder(w.Body).Decode(&testRuns); err != nil { + Fail(err.Error()) + } + Expect(len(testRuns)).To(Equal(2)) + Expect(testRuns[0].TestProjectName).To(Equal("project 1")) + Expect(testRuns[1].TestProjectName).To(Equal("project 2")) + }) + }) + + Context("When GetTestRunByID handler is invoked", func() { + It("should query DB with where clause filtering by id", func() { + + rows := sqlmock.NewRows([]string{"ID", "TestProjectName"}). + AddRow(123, "project 123") + + mock.ExpectQuery("SELECT (.+) FROM \"test_runs\" WHERE id = \\$1"). + WithArgs("123"). + WillReturnRows(rows) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + c.Params = append(c.Params, gin.Param{Key: "id", Value: "123"}) + handler := handlers.NewHandler(gormDb) + handler.GetTestRunByID(c) + + Expect(w.Code).To(Equal(200)) + + var testRun models.TestRun + + if err := json.NewDecoder(w.Body).Decode(&testRun); err != nil { + Fail(err.Error()) + } + Expect(int(testRun.ID)).To(Equal(123)) + Expect(testRun.TestProjectName).To(Equal("project 123")) + }) + }) +}) diff --git a/pkg/api/routers/routers.go b/pkg/api/routers/routers.go index 5c7bf14..251b242 100644 --- a/pkg/api/routers/routers.go +++ b/pkg/api/routers/routers.go @@ -2,24 +2,27 @@ package routers import ( "github.com/guidewire/fern-reporter/pkg/api/handlers" + "github.com/guidewire/fern-reporter/pkg/db" "github.com/gin-gonic/gin" ) func RegisterRouters(router *gin.Engine) { // router.GET("/", handlers.Home) + handler := handlers.NewHandler(db.GetDb()) + api := router.Group("/api") { - testRun := api.Group("/testrun") - testRun.GET("/", handlers.GetTestRunAll) - testRun.GET("/:id", handlers.GetTestRunByID) - testRun.POST("/", handlers.CreateTestRun) - testRun.PUT("/:id", handlers.UpdateTestRun) - testRun.DELETE("/:id", handlers.DeleteTestRun) + testRun := api.Group("/testrun/") + testRun.GET("/", handler.GetTestRunAll) + testRun.GET("/:id", handler.GetTestRunByID) + testRun.POST("/", handler.CreateTestRun) + testRun.PUT("/:id", handler.UpdateTestRun) + testRun.DELETE("/:id", handler.DeleteTestRun) } reports := router.Group("/reports/testruns") { - testRunReport := reports.GET("/", handlers.ReportTestRunAll) - testRunReport.GET("/:id", handlers.ReportTestRunById) + testRunReport := reports.GET("/", handler.ReportTestRunAll) + testRunReport.GET("/:id", handler.ReportTestRunById) } }