- revise testing of widget enpoints

- add validators for widgets
- revise implementation of widget endpoints
- clean up testdata, serializers and response
- improve documentation for swaggo
This commit is contained in:
Sonja Happ 2019-09-06 15:10:25 +02:00
parent 7c7488ee00
commit e3651e34f0
9 changed files with 594 additions and 254 deletions

View file

@ -1,23 +1,5 @@
package common
import "github.com/jinzhu/gorm/dialects/postgres"
type WidgetResponse struct {
ID uint `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Width uint `json:"width"`
Height uint `json:"height"`
MinWidth uint `json:"minWidth"`
MinHeight uint `json:"minHeight"`
X int `json:"x"`
Y int `json:"y"`
Z int `json:"z"`
DashboardID uint `json:"dashboardID"`
IsLocked bool `json:"isLocked"`
CustomProperties postgres.Jsonb `json:"customProperties"`
}
type FileResponse struct {
Name string `json:"name"`
ID uint `json:"id"`
@ -52,14 +34,6 @@ type ResponseMsgSignal struct {
Signal SignalResponse `json:"signal"`
}
type ResponseMsgWidgets struct {
Widgets []WidgetResponse `json:"widgets"`
}
type ResponseMsgWidget struct {
Widget WidgetResponse `json:"widget"`
}
type ResponseMsgFiles struct {
Files []FileResponse `json:"files"`
}

View file

@ -4,47 +4,6 @@ import (
"github.com/gin-gonic/gin"
)
// Widget/s Serializers
type WidgetsSerializer struct {
Ctx *gin.Context
Widgets []Widget
}
func (self *WidgetsSerializer) Response() []WidgetResponse {
response := []WidgetResponse{}
for _, widget := range self.Widgets {
serializer := WidgetSerializer{self.Ctx, widget}
response = append(response, serializer.Response())
}
return response
}
type WidgetSerializer struct {
Ctx *gin.Context
Widget
}
func (self *WidgetSerializer) Response() WidgetResponse {
response := WidgetResponse{
ID: self.ID,
Name: self.Name,
Type: self.Type,
Width: self.Width,
Height: self.Height,
MinWidth: self.MinWidth,
MinHeight: self.MinHeight,
X: self.X,
Y: self.Y,
Z: self.Z,
DashboardID: self.DashboardID,
IsLocked: self.IsLocked,
CustomProperties: self.CustomProperties,
}
return response
}
// File/s Serializers
type FilesSerializerNoAssoc struct {

View file

@ -265,7 +265,6 @@ var FileD = File{
// Widgets
var customPropertiesA = json.RawMessage(`{"property1" : "testValue1A", "property2" : "testValue2A", "property3" : 42}`)
var customPropertiesB = json.RawMessage(`{"property1" : "testValue1B", "property2" : "testValue2B", "property3" : 43}`)
var customPropertiesC = json.RawMessage(`{"property1" : "testValue1C", "property2" : "testValue2C", "property3" : 44}`)
var WidgetA = Widget{
Name: "Widget_A",
@ -281,21 +280,6 @@ var WidgetA = Widget{
CustomProperties: postgres.Jsonb{customPropertiesA},
}
var WidgetA_response = WidgetResponse{
ID: 1,
Name: WidgetA.Name,
Type: WidgetA.Type,
Width: WidgetA.Width,
Height: WidgetA.Height,
MinWidth: WidgetA.MinWidth,
MinHeight: WidgetA.MinHeight,
X: WidgetA.X,
Y: WidgetA.Y,
Z: WidgetA.Z,
IsLocked: WidgetA.IsLocked,
CustomProperties: WidgetA.CustomProperties,
}
var WidgetB = Widget{
Name: "Widget_B",
Type: "slider",
@ -305,66 +289,7 @@ var WidgetB = Widget{
MinWidth: 50,
X: 100,
Y: -40,
Z: 0,
Z: -1,
IsLocked: false,
CustomProperties: postgres.Jsonb{customPropertiesB},
}
var WidgetB_response = WidgetResponse{
ID: 2,
Name: WidgetB.Name,
Type: WidgetB.Type,
Width: WidgetB.Width,
Height: WidgetB.Height,
MinWidth: WidgetB.MinWidth,
MinHeight: WidgetB.MinHeight,
X: WidgetB.X,
Y: WidgetB.Y,
Z: WidgetB.Z,
IsLocked: WidgetB.IsLocked,
CustomProperties: WidgetB.CustomProperties,
}
var WidgetC = Widget{
Name: "Widget_C",
Type: "bargraph",
Height: 30,
Width: 100,
MinHeight: 20,
MinWidth: 50,
X: 11,
Y: 12,
Z: 13,
IsLocked: false,
CustomProperties: postgres.Jsonb{customPropertiesC},
}
var WidgetC_response = WidgetResponse{
ID: 3,
Name: WidgetC.Name,
Type: WidgetC.Type,
Width: WidgetC.Width,
Height: WidgetC.Height,
MinWidth: WidgetC.MinWidth,
MinHeight: WidgetC.MinHeight,
X: WidgetC.X,
Y: WidgetC.Y,
Z: WidgetC.Z,
IsLocked: WidgetC.IsLocked,
CustomProperties: WidgetC.CustomProperties,
}
var WidgetCUpdated_response = WidgetResponse{
ID: 3,
Name: "Widget_CUpdated",
Type: WidgetC.Type,
Height: 35,
Width: 110,
MinHeight: WidgetC.MinHeight,
MinWidth: WidgetC.MinWidth,
X: WidgetC.X,
Y: WidgetC.Y,
Z: WidgetC.Z,
IsLocked: WidgetC.IsLocked,
CustomProperties: WidgetC.CustomProperties,
}

View file

@ -56,3 +56,11 @@ type ResponseDashboards struct {
type ResponseDashboard struct {
dashboard common.Dashboard
}
type ResponseWidgets struct {
widgets []common.Widget
}
type ResponseWidget struct {
widget common.Widget
}

View file

@ -1,6 +1,7 @@
package widget
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
@ -22,11 +23,10 @@ func RegisterWidgetEndpoints(r *gin.RouterGroup) {
// @ID getWidgets
// @Produce json
// @Tags widgets
// @Success 200 {array} common.WidgetResponse "Array of widgets to which belong to dashboard"
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Success 200 {object} docs.ResponseWidgets "Widgets to which belong to dashboard"
// @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param dashboardID query int true "Dashboard ID"
// @Router /widgets [get]
func getWidgets(c *gin.Context) {
@ -43,9 +43,8 @@ func getWidgets(c *gin.Context) {
return
}
serializer := common.WidgetsSerializer{c, widgets}
c.JSON(http.StatusOK, gin.H{
"widgets": serializer.Response(),
"widgets": widgets,
})
}
@ -55,51 +54,51 @@ func getWidgets(c *gin.Context) {
// @Accept json
// @Produce json
// @Tags widgets
// @Param inputWidget body common.ResponseMsgWidget true "Widget to be added incl. ID of dashboard"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param inputWidget body widget.validNewWidget true "Widget to be added incl. ID of dashboard"
// @Success 200 {object} docs.ResponseWidget "Widget that was added"
// @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error"
// @Router /widgets [post]
func addWidget(c *gin.Context) {
var newWidgetData common.ResponseMsgWidget
err := c.BindJSON(&newWidgetData)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
var req addWidgetRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
"success": false,
"message": fmt.Sprintf("%v", err),
})
return
}
var newWidget Widget
newWidget.Name = newWidgetData.Widget.Name
newWidget.Type = newWidgetData.Widget.Type
newWidget.Height = newWidgetData.Widget.Height
newWidget.Width = newWidgetData.Widget.Width
newWidget.MinHeight = newWidgetData.Widget.MinHeight
newWidget.MinWidth = newWidgetData.Widget.MinWidth
newWidget.X = newWidgetData.Widget.X
newWidget.Y = newWidgetData.Widget.Y
newWidget.Z = newWidgetData.Widget.Z
newWidget.CustomProperties = newWidgetData.Widget.CustomProperties
newWidget.IsLocked = newWidgetData.Widget.IsLocked
newWidget.DashboardID = newWidgetData.Widget.DashboardID
// Validate the request
if err := req.validate(); err != nil {
c.JSON(http.StatusUnprocessableEntity, gin.H{
"success": false,
"message": fmt.Sprintf("%v", err),
})
return
}
// Create the new widget from the request
newWidget := req.createWidget()
// Check if user is allowed to modify selected dashboard (scenario)
ok, _ := dashboard.CheckPermissions(c, common.Create, "body", int(newWidget.DashboardID))
if !ok {
return
}
err = newWidget.addToDashboard()
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
})
err := newWidget.addToDashboard()
if err != nil {
common.ProvideErrorResponse(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"widget": newWidget.Widget,
})
}
// updateWidget godoc
@ -108,37 +107,60 @@ func addWidget(c *gin.Context) {
// @Tags widgets
// @Accept json
// @Produce json
// @Param inputWidget body common.ResponseMsgWidget true "Widget to be updated"
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Param inputWidget body widget.validUpdatedWidget true "Widget to be updated"
// @Success 200 {object} docs.ResponseWidget "Widget that was updated"
// @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param widgetID path int true "Widget ID"
// @Router /widgets/{widgetID} [put]
func updateWidget(c *gin.Context) {
ok, w := CheckPermissions(c, common.Update, -1)
ok, oldWidget := CheckPermissions(c, common.Update, -1)
if !ok {
return
}
var modifiedWidget common.ResponseMsgWidget
err := c.BindJSON(&modifiedWidget)
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
var req updateWidgetRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
"success": false,
"message": fmt.Sprintf("%v", err),
})
return
}
err = w.update(modifiedWidget.Widget)
if common.ProvideErrorResponse(c, err) == false {
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
// Validate the request
if err := req.validate(); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": fmt.Sprintf("%v", err),
})
return
}
// Create the updatedScenario from oldScenario
updatedWidget, err := req.updatedWidget(oldWidget)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": fmt.Sprintf("%v", err),
})
return
}
// Update the widget in the DB
err = oldWidget.update(updatedWidget)
if err != nil {
common.ProvideErrorResponse(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"widget": updatedWidget.Widget,
})
}
// getWidget godoc
@ -146,11 +168,11 @@ func updateWidget(c *gin.Context) {
// @ID getWidget
// @Tags widgets
// @Produce json
// @Success 200 {object} common.WidgetResponse "Requested widget."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Success 200 {object} docs.ResponseWidget "Widget that was requested"
// @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param widgetID path int true "Widget ID"
// @Router /widgets/{widgetID} [get]
func getWidget(c *gin.Context) {
@ -160,9 +182,8 @@ func getWidget(c *gin.Context) {
return
}
serializer := common.WidgetSerializer{c, w.Widget}
c.JSON(http.StatusOK, gin.H{
"widget": serializer.Response(),
"widget": w.Widget,
})
}
@ -171,11 +192,11 @@ func getWidget(c *gin.Context) {
// @ID deleteWidget
// @Tags widgets
// @Produce json
// @Success 200 "OK."
// @Failure 401 "Unauthorized Access"
// @Failure 403 "Access forbidden."
// @Failure 404 "Not found"
// @Failure 500 "Internal server error"
// @Success 200 {object} docs.ResponseWidget "Widget that was deleted"
// @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param widgetID path int true "Widget ID"
// @Router /widgets/{widgetID} [delete]
func deleteWidget(c *gin.Context) {
@ -191,6 +212,6 @@ func deleteWidget(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"message": "OK.",
"widget": w.Widget,
})
}

View file

@ -1,8 +1,6 @@
package widget
import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
)
@ -21,7 +19,7 @@ func (w *Widget) ByID(id uint) error {
db := common.GetDB()
err := db.Find(w, id).Error
if err != nil {
return fmt.Errorf("Widget with id=%v does not exist", id)
return err
}
return nil
}
@ -46,7 +44,7 @@ func (w *Widget) addToDashboard() error {
return err
}
func (w *Widget) update(modifiedWidget common.WidgetResponse) error {
func (w *Widget) update(modifiedWidget Widget) error {
db := common.GetDB()
err := db.Model(w).Updates(map[string]interface{}{

View file

@ -17,7 +17,10 @@ func CheckPermissions(c *gin.Context, operation common.CRUD, widgetIDBody int) (
err := common.ValidateRole(c, common.ModelWidget, operation)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, "Access denied (role validation failed).")
c.JSON(http.StatusUnprocessableEntity, gin.H{
"success": false,
"message": fmt.Sprintf("Access denied (role validation failed): %v", err),
})
return false, w
}
@ -25,9 +28,10 @@ func CheckPermissions(c *gin.Context, operation common.CRUD, widgetIDBody int) (
if widgetIDBody < 0 {
widgetID, err = strconv.Atoi(c.Param("widgetID"))
if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of widgetID path parameter")
c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg,
"success": false,
"message": fmt.Sprintf("Bad request. No or incorrect format of widgetID path parameter"),
})
return false, w
}

View file

@ -1,65 +1,407 @@
package widget
import (
"encoding/json"
"testing"
"github.com/gin-gonic/gin"
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/dashboard"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
"github.com/jinzhu/gorm/dialects/postgres"
"github.com/stretchr/testify/assert"
"os"
"testing"
)
// Test /widgets endpoints
func TestWidgetEndpoints(t *testing.T) {
var router *gin.Engine
var db *gorm.DB
var token string
type WidgetRequest struct {
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Width uint `json:"width,omitempty"`
Height uint `json:"height,omitempty"`
MinWidth uint `json:"minWidth,omitempty"`
MinHeight uint `json:"minHeight,omitempty"`
X int `json:"x,omitempty"`
Y int `json:"y,omitempty"`
Z int `json:"z,omitempty"`
DashboardID uint `json:"dashboardID,omitempty"`
IsLocked bool `json:"isLocked,omitempty"`
CustomProperties postgres.Jsonb `json:"customProperties,omitempty"`
}
var myWidgets = []common.WidgetResponse{common.WidgetA_response, common.WidgetB_response}
var msgWidgets = common.ResponseMsgWidgets{Widgets: myWidgets}
var msgWdg = common.ResponseMsgWidget{Widget: common.WidgetC_response}
var msgWdgupdated = common.ResponseMsgWidget{Widget: common.WidgetCUpdated_response}
type DashboardRequest struct {
Name string `json:"name,omitempty"`
Grid int `json:"grid,omitempty"`
ScenarioID uint `json:"scenarioID,omitempty"`
}
db := common.DummyInitDB()
type ScenarioRequest struct {
Name string `json:"name,omitempty"`
Running bool `json:"running,omitempty"`
StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
}
func addScenarioAndDashboard(token string) (scenarioID uint, dashboardID uint) {
// POST $newScenario
newScenario := ScenarioRequest{
Name: common.ScenarioA.Name,
Running: common.ScenarioA.Running,
StartParameters: common.ScenarioA.StartParameters,
}
_, resp, _ := common.NewTestEndpoint(router, token,
"/api/scenarios", "POST", common.KeyModels{"scenario": newScenario})
// Read newScenario's ID from the response
newScenarioID, _ := common.GetResponseID(resp)
// test POST dashboards/ $newDashboard
newDashboard := DashboardRequest{
Name: common.DashboardA.Name,
Grid: common.DashboardA.Grid,
ScenarioID: uint(newScenarioID),
}
_, resp, _ = common.NewTestEndpoint(router, token,
"/api/dashboards", "POST", common.KeyModels{"dashboard": newDashboard})
// Read newDashboard's ID from the response
newDashboardID, _ := common.GetResponseID(resp)
return uint(newScenarioID), uint(newDashboardID)
}
func TestMain(m *testing.M) {
db = common.DummyInitDB()
defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default()
router = gin.Default()
api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.RegisterAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true))
RegisterWidgetEndpoints(api.Group("/widgets"))
// scenario endpoints required here to first add a scenario to the DB
// that can be associated with a new dashboard
scenario.RegisterScenarioEndpoints(api.Group("/scenarios"))
// dashboard endpoints required here to first add a dashboard to the DB
// that can be associated with a new widget
dashboard.RegisterDashboardEndpoints(api.Group("/dashboards"))
credjson, _ := json.Marshal(common.CredUser)
msgOKjson, _ := json.Marshal(common.MsgOK)
msgWidgetsjson, _ := json.Marshal(msgWidgets)
msgWdgjson, _ := json.Marshal(msgWdg)
msgWdgupdatedjson, _ := json.Marshal(msgWdgupdated)
os.Exit(m.Run())
}
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
func TestAddWidget(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// test GET widgets
common.TestEndpoint(t, router, token, "/api/widgets?dashboardID=1", "GET", nil, 200, msgWidgetsjson)
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
// test POST widgets
common.TestEndpoint(t, router, token, "/api/widgets", "POST", msgWdgjson, 200, msgOKjson)
_, dashboardID := addScenarioAndDashboard(token)
// test GET widgets/:widgetID to check if previous POST worked correctly
common.TestEndpoint(t, router, token, "/api/widgets/3", "GET", nil, 200, msgWdgjson)
// test POST widgets/ $newWidget
newWidget := WidgetRequest{
Name: common.WidgetA.Name,
Type: common.WidgetA.Type,
Width: common.WidgetA.Width,
Height: common.WidgetA.Height,
MinWidth: common.WidgetA.MinWidth,
MinHeight: common.WidgetA.MinHeight,
X: common.WidgetA.X,
Y: common.WidgetA.Y,
Z: common.WidgetA.Z,
IsLocked: common.WidgetA.IsLocked,
CustomProperties: common.WidgetA.CustomProperties,
DashboardID: dashboardID,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/widgets", "POST", common.KeyModels{"widget": newWidget})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// test PUT widgets/:widgetID
common.TestEndpoint(t, router, token, "/api/widgets/3", "PUT", msgWdgupdatedjson, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/widgets/3", "GET", nil, 200, msgWdgupdatedjson)
// Compare POST's response with the newWidget
err = common.CompareResponse(resp, common.KeyModels{"widget": newWidget})
assert.NoError(t, err)
// test DELETE widgets/:widgetID
common.TestEndpoint(t, router, token, "/api/widgets/3", "DELETE", nil, 200, msgOKjson)
common.TestEndpoint(t, router, token, "/api/widgets?dashboardID=1", "GET", nil, 200, msgWidgetsjson)
// Read newWidget's ID from the response
newWidgetID, err := common.GetResponseID(resp)
assert.NoError(t, err)
// TODO add testing for other return codes
// Get the newWidget
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/widgets/%v", newWidgetID), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare GET's response with the newWidget
err = common.CompareResponse(resp, common.KeyModels{"widget": newWidget})
assert.NoError(t, err)
// try to POST a malformed widget
// Required fields are missing
malformedNewWidget := WidgetRequest{
Name: "ThisIsAMalformedDashboard",
}
// this should NOT work and return a unprocessable entity 442 status code
code, resp, err = common.NewTestEndpoint(router, token,
"/api/widgets", "POST", common.KeyModels{"widget": malformedNewWidget})
assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
}
func TestUpdateWidget(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
_, dashboardID := addScenarioAndDashboard(token)
// test POST widgets/ $newWidget
newWidget := WidgetRequest{
Name: common.WidgetA.Name,
Type: common.WidgetA.Type,
Width: common.WidgetA.Width,
Height: common.WidgetA.Height,
MinWidth: common.WidgetA.MinWidth,
MinHeight: common.WidgetA.MinHeight,
X: common.WidgetA.X,
Y: common.WidgetA.Y,
Z: common.WidgetA.Z,
IsLocked: common.WidgetA.IsLocked,
CustomProperties: common.WidgetA.CustomProperties,
DashboardID: dashboardID,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/widgets", "POST", common.KeyModels{"widget": newWidget})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newWidget's ID from the response
newWidgetID, err := common.GetResponseID(resp)
assert.NoError(t, err)
updatedWidget := WidgetRequest{
Name: common.WidgetB.Name,
Type: common.WidgetB.Type,
Width: common.WidgetB.Width,
Height: common.WidgetB.Height,
MinWidth: common.WidgetB.MinWidth,
MinHeight: common.WidgetB.MinHeight,
CustomProperties: common.WidgetA.CustomProperties,
}
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/widgets/%v", newWidgetID), "PUT", common.KeyModels{"widget": updatedWidget})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare PUT's response with the updatedWidget
err = common.CompareResponse(resp, common.KeyModels{"widget": updatedWidget})
assert.NoError(t, err)
// Get the updatedWidget
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/widgets/%v", newWidgetID), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare GET's response with the updatedWidget
err = common.CompareResponse(resp, common.KeyModels{"widget": updatedWidget})
assert.NoError(t, err)
// try to update a widget that does not exist (should return not found 404 status code)
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/widgets/%v", newWidgetID+1), "PUT", common.KeyModels{"widget": updatedWidget})
assert.NoError(t, err)
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
}
func TestDeleteWidget(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
_, dashboardID := addScenarioAndDashboard(token)
// test POST widgets/ $newWidget
newWidget := WidgetRequest{
Name: common.WidgetA.Name,
Type: common.WidgetA.Type,
Width: common.WidgetA.Width,
Height: common.WidgetA.Height,
MinWidth: common.WidgetA.MinWidth,
MinHeight: common.WidgetA.MinHeight,
X: common.WidgetA.X,
Y: common.WidgetA.Y,
Z: common.WidgetA.Z,
IsLocked: common.WidgetA.IsLocked,
CustomProperties: common.WidgetA.CustomProperties,
DashboardID: dashboardID,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/widgets", "POST", common.KeyModels{"widget": newWidget})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newWidget's ID from the response
newWidgetID, err := common.GetResponseID(resp)
assert.NoError(t, err)
// Count the number of all the widgets returned for dashboard
initialNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/widgets?dashboardID=%v", dashboardID), "GET", nil)
assert.NoError(t, err)
// Delete the added newWidget
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/widgets/%v", newWidgetID), "DELETE", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare DELETE's response with the newWidget
err = common.CompareResponse(resp, common.KeyModels{"widget": newWidget})
assert.NoError(t, err)
// Again count the number of all the widgets returned for dashboard
finalNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/widgets?dashboardID=%v", dashboardID), "GET", nil)
assert.NoError(t, err)
assert.Equal(t, initialNumber-1, finalNumber)
}
func TestGetAllWidgetsOfDashboard(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
_, dashboardID := addScenarioAndDashboard(token)
// Count the number of all the widgets returned for dashboard
initialNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/widgets?dashboardID=%v", dashboardID), "GET", nil)
assert.NoError(t, err)
// test POST widgets/ $newWidget
newWidgetA := WidgetRequest{
Name: common.WidgetA.Name,
Type: common.WidgetA.Type,
Width: common.WidgetA.Width,
Height: common.WidgetA.Height,
MinWidth: common.WidgetA.MinWidth,
MinHeight: common.WidgetA.MinHeight,
X: common.WidgetA.X,
Y: common.WidgetA.Y,
Z: common.WidgetA.Z,
IsLocked: common.WidgetA.IsLocked,
CustomProperties: common.WidgetA.CustomProperties,
DashboardID: dashboardID,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/widgets", "POST", common.KeyModels{"widget": newWidgetA})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
newWidgetB := WidgetRequest{
Name: common.WidgetB.Name,
Type: common.WidgetB.Type,
Width: common.WidgetB.Width,
Height: common.WidgetB.Height,
MinWidth: common.WidgetB.MinWidth,
MinHeight: common.WidgetB.MinHeight,
X: common.WidgetB.X,
Y: common.WidgetB.Y,
Z: common.WidgetB.Z,
IsLocked: common.WidgetB.IsLocked,
CustomProperties: common.WidgetB.CustomProperties,
DashboardID: dashboardID,
}
code, resp, err = common.NewTestEndpoint(router, token,
"/api/widgets", "POST", common.KeyModels{"widget": newWidgetB})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Again count the number of all the widgets returned for dashboard
finalNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/widgets?dashboardID=%v", dashboardID), "GET", nil)
assert.NoError(t, err)
assert.Equal(t, initialNumber+2, finalNumber)
}
//// Test /widgets endpoints
//func TestWidgetEndpoints(t *testing.T) {
//
// var token string
//
// var myWidgets = []common.WidgetResponse{common.WidgetA_response, common.WidgetB_response}
// var msgWidgets = common.ResponseMsgWidgets{Widgets: myWidgets}
// var msgWdg = common.ResponseMsgWidget{Widget: common.WidgetC_response}
// var msgWdgupdated = common.ResponseMsgWidget{Widget: common.WidgetCUpdated_response}
//
// db := common.DummyInitDB()
// defer db.Close()
// common.DummyPopulateDB(db)
//
// router := gin.Default()
// api := router.Group("/api")
//
// // All endpoints require authentication except when someone wants to
// // login (POST /authenticate)
// user.RegisterAuthenticate(api.Group("/authenticate"))
//
// api.Use(user.Authentication(true))
//
// RegisterWidgetEndpoints(api.Group("/widgets"))
//
// credjson, _ := json.Marshal(common.CredUser)
// msgOKjson, _ := json.Marshal(common.MsgOK)
// msgWidgetsjson, _ := json.Marshal(msgWidgets)
// msgWdgjson, _ := json.Marshal(msgWdg)
// msgWdgupdatedjson, _ := json.Marshal(msgWdgupdated)
//
// token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200)
//
// // test GET widgets
// common.TestEndpoint(t, router, token, "/api/widgets?dashboardID=1", "GET", nil, 200, msgWidgetsjson)
//
// // test POST widgets
// common.TestEndpoint(t, router, token, "/api/widgets", "POST", msgWdgjson, 200, msgOKjson)
//
// // test GET widgets/:widgetID to check if previous POST worked correctly
// common.TestEndpoint(t, router, token, "/api/widgets/3", "GET", nil, 200, msgWdgjson)
//
// // test PUT widgets/:widgetID
// common.TestEndpoint(t, router, token, "/api/widgets/3", "PUT", msgWdgupdatedjson, 200, msgOKjson)
// common.TestEndpoint(t, router, token, "/api/widgets/3", "GET", nil, 200, msgWdgupdatedjson)
//
// // test DELETE widgets/:widgetID
// common.TestEndpoint(t, router, token, "/api/widgets/3", "DELETE", nil, 200, msgOKjson)
// common.TestEndpoint(t, router, token, "/api/widgets?dashboardID=1", "GET", nil, 200, msgWidgetsjson)
//
// // TODO add testing for other return codes
//
//}

View file

@ -0,0 +1,109 @@
package widget
import (
"encoding/json"
"github.com/jinzhu/gorm/dialects/postgres"
"github.com/nsf/jsondiff"
"gopkg.in/go-playground/validator.v9"
)
var validate *validator.Validate
type validNewWidget struct {
Name string `form:"name" validate:"required"`
Type string `form:"type" validate:"required"`
Width uint `form:"width" validate:"required"`
Height uint `form:"height" validate:"required"`
MinWidth uint `form:"minWidth" validate:"omitempty"`
MinHeight uint `form:"minHeight" validate:"omitempty"`
X int `form:"x" validate:"omitempty"`
Y int `form:"y" validate:"omitempty"`
Z int `form:"z" validate:"omitempty"`
DashboardID uint `form:"dashboardID" validate:"required"`
IsLocked bool `form:"isLocked" validate:"omitempty"`
CustomProperties postgres.Jsonb `form:"customProperties" validate:"omitempty"`
}
type validUpdatedWidget struct {
Name string `form:"name" validate:"omitempty"`
Type string `form:"type" validate:"omitempty"`
Width uint `form:"width" validate:"omitempty"`
Height uint `form:"height" validate:"omitempty"`
MinWidth uint `form:"minWidth" validate:"omitempty"`
MinHeight uint `form:"minHeight" validate:"omitempty"`
X int `form:"x" validate:"omitempty"`
Y int `form:"y" validate:"omitempty"`
Z int `form:"z" validate:"omitempty"`
IsLocked bool `form:"isLocked" validate:"omitempty"`
CustomProperties postgres.Jsonb `form:"customProperties" validate:"omitempty"`
}
type addWidgetRequest struct {
validNewWidget `json:"widget"`
}
type updateWidgetRequest struct {
validUpdatedWidget `json:"widget"`
}
func (r *addWidgetRequest) validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *validUpdatedWidget) validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *addWidgetRequest) createWidget() Widget {
var s Widget
s.Name = r.Name
s.Type = r.Type
s.Width = r.Width
s.Height = r.Height
s.MinWidth = r.MinWidth
s.MinHeight = r.MinHeight
s.X = r.X
s.Y = r.Y
s.Z = r.Z
s.IsLocked = r.IsLocked
s.CustomProperties = r.CustomProperties
s.DashboardID = r.DashboardID
return s
}
func (r *updateWidgetRequest) updatedWidget(oldWidget Widget) (Widget, error) {
// Use the old Widget as a basis for the updated Widget `s`
s := oldWidget
if r.Name != "" {
s.Name = r.Name
}
s.Type = r.Type
s.Width = r.Width
s.Height = r.Height
s.MinWidth = r.MinWidth
s.MinHeight = r.MinHeight
s.X = r.X
s.Y = r.Y
s.Z = r.Z
s.IsLocked = r.IsLocked
// only update custom props if not empty
var emptyJson postgres.Jsonb
// Serialize empty json and params
emptyJson_ser, _ := json.Marshal(emptyJson)
customprops_ser, _ := json.Marshal(r.CustomProperties)
opts := jsondiff.DefaultConsoleOptions()
diff, _ := jsondiff.Compare(emptyJson_ser, customprops_ser, &opts)
if diff.String() != "FullMatch" {
s.CustomProperties = r.CustomProperties
}
return s, nil
}