- revise testing of signal endpoints

- revise documentation for swaggo
- clean up testdata, serializers and responses
- add validators for signal endpoints
- revise signal endpoint implementations
This commit is contained in:
Sonja Happ 2019-09-06 16:01:59 +02:00
parent 8882f6069c
commit 7544c863de
9 changed files with 548 additions and 218 deletions

View file

@ -12,28 +12,12 @@ type FileResponse struct {
SimulationModelID uint `json:"simulationModelID"` SimulationModelID uint `json:"simulationModelID"`
} }
type SignalResponse struct {
Name string `json:"name"`
Unit string `json:"unit"`
Index uint `json:"index"`
Direction string `json:"direction"`
SimulationModelID uint `json:"simulationModelID"`
}
// Response messages // Response messages
type ResponseMsg struct { type ResponseMsg struct {
Message string `json:"message"` Message string `json:"message"`
} }
type ResponseMsgSignals struct {
Signals []SignalResponse `json:"signals"`
}
type ResponseMsgSignal struct {
Signal SignalResponse `json:"signal"`
}
type ResponseMsgFiles struct { type ResponseMsgFiles struct {
Files []FileResponse `json:"files"` Files []FileResponse `json:"files"`
} }

View file

@ -40,35 +40,3 @@ func (self *FileSerializerNoAssoc) Response() FileResponse {
} }
return response return response
} }
// Signal/s Serializers
type SignalsSerializer struct {
Ctx *gin.Context
Signals []Signal
}
func (self *SignalsSerializer) Response() []SignalResponse {
response := []SignalResponse{}
for _, s := range self.Signals {
serializer := SignalSerializer{self.Ctx, s}
response = append(response, serializer.Response())
}
return response
}
type SignalSerializer struct {
Ctx *gin.Context
Signal
}
func (self *SignalSerializer) Response() SignalResponse {
response := SignalResponse{
Name: self.Name,
Unit: self.Unit,
Direction: self.Direction,
SimulationModelID: self.SimulationModelID,
Index: self.Index,
}
return response
}

View file

@ -117,13 +117,6 @@ var OutSignalA = Signal{
Unit: "V", Unit: "V",
} }
var OutSignalA_response = SignalResponse{
Name: OutSignalA.Name,
Direction: OutSignalA.Direction,
Index: OutSignalA.Index,
Unit: OutSignalA.Unit,
}
var OutSignalB = Signal{ var OutSignalB = Signal{
Name: "outSignal_B", Name: "outSignal_B",
Direction: "out", Direction: "out",
@ -131,13 +124,6 @@ var OutSignalB = Signal{
Unit: "V", Unit: "V",
} }
var OutSignalB_response = SignalResponse{
Name: OutSignalB.Name,
Direction: OutSignalB.Direction,
Index: OutSignalB.Index,
Unit: OutSignalB.Unit,
}
var InSignalA = Signal{ var InSignalA = Signal{
Name: "inSignal_A", Name: "inSignal_A",
Direction: "in", Direction: "in",
@ -145,13 +131,6 @@ var InSignalA = Signal{
Unit: "A", Unit: "A",
} }
var InSignalA_response = SignalResponse{
Name: InSignalA.Name,
Direction: InSignalA.Direction,
Index: InSignalA.Index,
Unit: InSignalA.Unit,
}
var InSignalB = Signal{ var InSignalB = Signal{
Name: "inSignal_B", Name: "inSignal_B",
Direction: "in", Direction: "in",
@ -159,41 +138,6 @@ var InSignalB = Signal{
Unit: "A", Unit: "A",
} }
var InSignalB_response = SignalResponse{
Name: InSignalB.Name,
Direction: InSignalB.Direction,
Index: InSignalB.Index,
Unit: InSignalB.Unit,
}
var InSignalC = Signal{
Name: "inSignal_C",
Direction: "in",
Index: 2,
Unit: "A",
}
var InSignalC_response = SignalResponse{
Name: InSignalC.Name,
Direction: InSignalC.Direction,
Index: InSignalC.Index,
Unit: InSignalC.Unit,
}
var InSignalCUpdated = Signal{
Name: "inSignalupdated_C",
Direction: InSignalC.Direction,
Index: InSignalC.Index,
Unit: "Ohm",
}
var InSignalCUpdated_response = SignalResponse{
Name: InSignalCUpdated.Name,
Direction: InSignalCUpdated.Direction,
Index: InSignalCUpdated.Index,
Unit: InSignalCUpdated.Unit,
}
// Dashboards // Dashboards
var DashboardA = Dashboard{ var DashboardA = Dashboard{

View file

@ -64,3 +64,11 @@ type ResponseWidgets struct {
type ResponseWidget struct { type ResponseWidget struct {
widget common.Widget widget common.Widget
} }
type ResponseSignals struct {
signals []common.Signal
}
type ResponseSignal struct {
signal common.Signal
}

View file

@ -1,6 +1,7 @@
package signal package signal
import ( import (
"fmt"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -24,11 +25,10 @@ func RegisterSignalEndpoints(r *gin.RouterGroup) {
// @Tags signals // @Tags signals
// @Param direction query string true "Direction of signal (in or out)" // @Param direction query string true "Direction of signal (in or out)"
// @Param modelID query string true "Model ID of signals to be obtained" // @Param modelID query string true "Model ID of signals to be obtained"
// @Success 200 {array} common.Signal "Requested signals." // @Success 200 {object} docs.ResponseSignals "Signals which belong to simulation model"
// @Failure 401 "Unauthorized Access" // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 403 "Access forbidden." // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 404 "Not found" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Failure 500 "Internal server error"
// @Router /signals [get] // @Router /signals [get]
func getSignals(c *gin.Context) { func getSignals(c *gin.Context) {
@ -44,9 +44,9 @@ func getSignals(c *gin.Context) {
} else if direction == "out" { } else if direction == "out" {
mapping = "OutputMapping" mapping = "OutputMapping"
} else { } else {
errormsg := "Bad request. Direction has to be in or out"
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "success": false,
"error": "Bad request. Direction has to be in or out",
}) })
return return
} }
@ -58,9 +58,8 @@ func getSignals(c *gin.Context) {
return return
} }
serializer := common.SignalsSerializer{c, sigs}
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"signals": serializer.Response(), "signals": sigs,
}) })
} }
@ -70,31 +69,35 @@ func getSignals(c *gin.Context) {
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Tags signals // @Tags signals
// @Param inputSignal body common.ResponseMsgSignal true "A signal to be added to the model incl. direction and model ID to which signal shall be added" // @Param inputSignal body signal.validNewSignal true "A signal to be added to the model incl. direction and model ID to which signal shall be added"
// @Success 200 "OK." // @Success 200 {object} docs.ResponseSignal "Signal that was added"
// @Failure 401 "Unauthorized Access" // @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 403 "Access forbidden." // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 404 "Not found" // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 "Internal server error" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Router /signals [post] // @Router /signals [post]
func addSignal(c *gin.Context) { func addSignal(c *gin.Context) {
var newSignalData common.ResponseMsgSignal var req addSignalRequest
err := c.BindJSON(&newSignalData) if err := c.ShouldBindJSON(&req); err != nil {
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "success": false,
"message": fmt.Sprintf("%v", err),
}) })
return return
} }
var newSignal Signal // Validate the request
newSignal.Index = newSignalData.Signal.Index if err := req.validate(); err != nil {
newSignal.SimulationModelID = newSignalData.Signal.SimulationModelID c.JSON(http.StatusUnprocessableEntity, gin.H{
newSignal.Direction = newSignalData.Signal.Direction "success": false,
newSignal.Unit = newSignalData.Signal.Unit "message": fmt.Sprintf("%v", err),
newSignal.Name = newSignalData.Signal.Name })
return
}
// Create the new signal from the request
newSignal := req.createSignal()
ok, _ := simulationmodel.CheckPermissions(c, common.Update, "body", int(newSignal.SimulationModelID)) ok, _ := simulationmodel.CheckPermissions(c, common.Update, "body", int(newSignal.SimulationModelID))
if !ok { if !ok {
@ -102,12 +105,15 @@ func addSignal(c *gin.Context) {
} }
// Add signal to model // Add signal to model
err = newSignal.addToSimulationModel() err := newSignal.addToSimulationModel()
if common.ProvideErrorResponse(c, err) == false { if err != nil {
c.JSON(http.StatusOK, gin.H{ common.ProvideErrorResponse(c, err)
"message": "OK.", return
})
} }
c.JSON(http.StatusOK, gin.H{
"signal": newSignal.Signal,
})
} }
// updateSignal godoc // updateSignal godoc
@ -115,35 +121,58 @@ func addSignal(c *gin.Context) {
// @ID updateSignal // @ID updateSignal
// @Tags signals // @Tags signals
// @Produce json // @Produce json
// @Success 200 "OK." // @Param inputSignal body signal.validUpdatedSignal true "A signal to be updated"
// @Failure 401 "Unauthorized Access" // @Success 200 {object} docs.ResponseSignal "Signal that was updated"
// @Failure 403 "Access forbidden." // @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 404 "Not found" // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 500 "Internal server error" // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param signalID path int true "ID of signal to be updated" // @Param signalID path int true "ID of signal to be updated"
// @Router /signals/{signalID} [put] // @Router /signals/{signalID} [put]
func updateSignal(c *gin.Context) { func updateSignal(c *gin.Context) {
ok, sig := checkPermissions(c, common.Delete) ok, oldSignal := checkPermissions(c, common.Delete)
if !ok { if !ok {
return return
} }
var modifiedSignal common.ResponseMsgSignal var req updateSignalRequest
err := c.BindJSON(&modifiedSignal) if err := c.ShouldBindJSON(&req); err != nil {
if err != nil {
errormsg := "Bad request. Error binding form data to JSON: " + err.Error()
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "success": false,
"message": fmt.Sprintf("%v", err),
}) })
return return
} }
err = sig.update(modifiedSignal.Signal) // Validate the request
if common.ProvideErrorResponse(c, err) == false { if err := req.validate(); err != nil {
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"message": "OK.", "success": false,
"message": fmt.Sprintf("%v", err),
}) })
return
} }
// Create the updatedSignal from oldDashboard
updatedSignal, err := req.updatedSignal(oldSignal)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"success": false,
"message": fmt.Sprintf("%v", err),
})
return
}
// Update the signal in the DB
err = oldSignal.update(updatedSignal)
if err != nil {
common.ProvideErrorResponse(c, err)
return
}
c.JSON(http.StatusOK, gin.H{
"signal": updatedSignal.Signal,
})
} }
// getSignal godoc // getSignal godoc
@ -151,11 +180,11 @@ func updateSignal(c *gin.Context) {
// @ID getSignal // @ID getSignal
// @Tags signals // @Tags signals
// @Produce json // @Produce json
// @Success 200 "OK." // @Success 200 {object} docs.ResponseSignal "Signal that was requested"
// @Failure 401 "Unauthorized Access" // @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 403 "Access forbidden." // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 404 "Not found" // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 "Internal server error" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param signalID path int true "ID of signal to be obtained" // @Param signalID path int true "ID of signal to be obtained"
// @Router /signals/{signalID} [get] // @Router /signals/{signalID} [get]
func getSignal(c *gin.Context) { func getSignal(c *gin.Context) {
@ -164,9 +193,8 @@ func getSignal(c *gin.Context) {
return return
} }
serializer := common.SignalSerializer{c, sig.Signal}
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"signal": serializer.Response(), "signal": sig.Signal,
}) })
} }
@ -175,11 +203,11 @@ func getSignal(c *gin.Context) {
// @ID deleteSignal // @ID deleteSignal
// @Tags signals // @Tags signals
// @Produce json // @Produce json
// @Success 200 "OK." // @Success 200 {object} docs.ResponseSignal "Signal that was deleted"
// @Failure 401 "Unauthorized Access" // @Failure 400 {object} docs.ResponseError "Bad request"
// @Failure 403 "Access forbidden." // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 404 "Not found" // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 "Internal server error" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param signalID path int true "ID of signal to be deleted" // @Param signalID path int true "ID of signal to be deleted"
// @Router /signals/{signalID} [delete] // @Router /signals/{signalID} [delete]
func deleteSignal(c *gin.Context) { func deleteSignal(c *gin.Context) {
@ -190,10 +218,12 @@ func deleteSignal(c *gin.Context) {
} }
err := sig.delete() err := sig.delete()
if common.ProvideErrorResponse(c, err) == false { if err != nil {
c.JSON(http.StatusOK, gin.H{ common.ProvideErrorResponse(c, err)
"message": "OK.", return
})
} }
c.JSON(http.StatusOK, gin.H{
"signal": sig.Signal,
})
} }

View file

@ -1,8 +1,6 @@
package signal package signal
import ( import (
"fmt"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
) )
@ -21,7 +19,7 @@ func (s *Signal) byID(id uint) error {
db := common.GetDB() db := common.GetDB()
err := db.Find(s, id).Error err := db.Find(s, id).Error
if err != nil { if err != nil {
return fmt.Errorf("Signal with id=%v does not exist", id) return err
} }
return nil return nil
} }
@ -64,7 +62,7 @@ func (s *Signal) addToSimulationModel() error {
return err return err
} }
func (s *Signal) update(modifiedSignal common.SignalResponse) error { func (s *Signal) update(modifiedSignal Signal) error {
db := common.GetDB() db := common.GetDB()
err := db.Model(s).Updates(map[string]interface{}{ err := db.Model(s).Updates(map[string]interface{}{

View file

@ -17,15 +17,18 @@ func checkPermissions(c *gin.Context, operation common.CRUD) (bool, Signal) {
err := common.ValidateRole(c, common.ModelSignal, operation) err := common.ValidateRole(c, common.ModelSignal, operation)
if err != nil { 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, sig return false, sig
} }
signalID, err := strconv.Atoi(c.Param("signalID")) signalID, err := strconv.Atoi(c.Param("signalID"))
if err != nil { if err != nil {
errormsg := fmt.Sprintf("Bad request. No or incorrect format of signalID path parameter")
c.JSON(http.StatusBadRequest, gin.H{ c.JSON(http.StatusBadRequest, gin.H{
"error": errormsg, "success": false,
"error": fmt.Sprintf("Bad request. No or incorrect format of signalID path parameter"),
}) })
return false, sig return false, sig
} }

View file

@ -1,73 +1,395 @@
package signal package signal
import ( import (
"encoding/json" "fmt"
"testing"
"github.com/gin-gonic/gin"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common" "git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/common"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/scenario"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulationmodel"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/simulator"
"git.rwth-aachen.de/acs/public/villas/villasweb-backend-go/routes/user" "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 /models endpoints var router *gin.Engine
func TestSignalEndpoints(t *testing.T) { var db *gorm.DB
var token string type SignalRequest struct {
Name string `json:"name,omitempty"`
Unit string `json:"unit,omitempty"`
Index uint `json:"index,omitempty"`
Direction string `json:"direction,omitempty"`
SimulationModelID uint `json:"simulationModelID,omitempty"`
}
var myInSignals = []common.SignalResponse{common.InSignalA_response, common.InSignalB_response} type SimulationModelRequest struct {
var myOutSignals = []common.SignalResponse{common.OutSignalA_response, common.OutSignalB_response} Name string `json:"name,omitempty"`
var msgInSignals = common.ResponseMsgSignals{Signals: myInSignals} ScenarioID uint `json:"scenarioID,omitempty"`
var msgInSignalC = common.ResponseMsgSignal{Signal: common.InSignalC_response} SimulatorID uint `json:"simulatorID,omitempty"`
var msgInSignalCupdated = common.ResponseMsgSignal{Signal: common.InSignalCUpdated_response} StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
var msgOutSignals = common.ResponseMsgSignals{Signals: myOutSignals} }
db := common.DummyInitDB() type SimulatorRequest struct {
UUID string `json:"uuid,omitempty"`
Host string `json:"host,omitempty"`
Modeltype string `json:"modelType,omitempty"`
State string `json:"state,omitempty"`
Properties postgres.Jsonb `json:"properties,omitempty"`
}
type ScenarioRequest struct {
Name string `json:"name,omitempty"`
Running bool `json:"running,omitempty"`
StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
}
func addScenarioAndSimulatorAndSimulationModel() (scenarioID uint, simulatorID uint, simulationModelID uint) {
// authenticate as admin
token, _ := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.AdminCredentials)
// POST $newSimulatorA
newSimulatorA := SimulatorRequest{
UUID: common.SimulatorA.UUID,
Host: common.SimulatorA.Host,
Modeltype: common.SimulatorA.Modeltype,
State: common.SimulatorA.State,
Properties: common.SimulatorA.Properties,
}
_, resp, _ := common.NewTestEndpoint(router, token,
"/api/simulators", "POST", common.KeyModels{"simulator": newSimulatorA})
// Read newSimulator's ID from the response
newSimulatorID, _ := common.GetResponseID(resp)
// authenticate as normal user
token, _ = common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
// 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 models/ $newSimulationModel
newSimulationModel := SimulationModelRequest{
Name: common.SimulationModelA.Name,
ScenarioID: uint(newScenarioID),
SimulatorID: uint(newSimulatorID),
StartParameters: common.SimulationModelA.StartParameters,
}
_, resp, _ = common.NewTestEndpoint(router, token,
"/api/models", "POST", common.KeyModels{"model": newSimulationModel})
// Read newSimulationModel's ID from the response
newSimulationModelID, _ := common.GetResponseID(resp)
return uint(newScenarioID), uint(newSimulatorID), uint(newSimulationModelID)
}
func TestMain(m *testing.M) {
db = common.DummyInitDB()
defer db.Close() defer db.Close()
common.DummyPopulateDB(db)
router := gin.Default() router = gin.Default()
api := router.Group("/api") api := router.Group("/api")
// All endpoints require authentication except when someone wants to
// login (POST /authenticate)
user.RegisterAuthenticate(api.Group("/authenticate")) user.RegisterAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true)) api.Use(user.Authentication(true))
// simulationmodel endpoints required here to first add a simulation to the DB
// that can be associated with a new signal model
simulationmodel.RegisterSimulationModelEndpoints(api.Group("/models"))
// scenario endpoints required here to first add a scenario to the DB
// that can be associated with a new simulation model
scenario.RegisterScenarioEndpoints(api.Group("/scenarios"))
// simulator endpoints required here to first add a simulator to the DB
// that can be associated with a new simulation model
simulator.RegisterSimulatorEndpoints(api.Group("/simulators"))
RegisterSignalEndpoints(api.Group("/signals")) RegisterSignalEndpoints(api.Group("/signals"))
credjson, _ := json.Marshal(common.CredUser) os.Exit(m.Run())
msgOKjson, _ := json.Marshal(common.MsgOK) }
msgInSignalsjson, _ := json.Marshal(msgInSignals)
msgOutSignalsjson, _ := json.Marshal(msgOutSignals)
inSignalCjson, _ := json.Marshal(msgInSignalC)
inSignalCupdatedjson, _ := json.Marshal(msgInSignalCupdated)
token = common.AuthenticateForTest(t, router, "/api/authenticate", "POST", credjson, 200) func TestAddSignal(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// test GET signals // prepare the content of the DB for testing
common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=in", "GET", nil, 200, msgInSignalsjson) // by adding a scenario and a simulator to the DB
common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=out", "GET", nil, 200, msgOutSignalsjson) // using the respective endpoints of the API
_, _, simulationModelID := addScenarioAndSimulatorAndSimulationModel()
// test POST signals // authenticate as normal user
common.TestEndpoint(t, router, token, "/api/signals", "POST", inSignalCjson, 200, msgOKjson) token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
// test GET signals/:signalID // test POST signals/ $newSignal
common.TestEndpoint(t, router, token, "/api/signals/5", "GET", nil, 200, inSignalCjson) newSignal := SignalRequest{
Name: common.InSignalA.Name,
Unit: common.InSignalA.Unit,
Direction: common.InSignalA.Direction,
Index: 1,
SimulationModelID: simulationModelID,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/signals", "POST", common.KeyModels{"signal": newSignal})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// test PUT signals/:signalID // Compare POST's response with the newSignal
common.TestEndpoint(t, router, token, "/api/signals/5", "PUT", inSignalCupdatedjson, 200, msgOKjson) err = common.CompareResponse(resp, common.KeyModels{"signal": newSignal})
common.TestEndpoint(t, router, token, "/api/signals/5", "GET", nil, 200, inSignalCupdatedjson) assert.NoError(t, err)
// test DELETE signals/:signalID // Read newSignal's ID from the response
common.TestEndpoint(t, router, token, "/api/signals/5", "DELETE", nil, 200, msgOKjson) newSignalID, err := common.GetResponseID(resp)
common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=in", "GET", nil, 200, msgInSignalsjson) assert.NoError(t, err)
common.TestEndpoint(t, router, token, "/api/signals?modelID=1&direction=out", "GET", nil, 200, msgOutSignalsjson)
// TODO test GET models/:ModelID to check if POST and DELETE adapt InputLength correctly?? // Get the newSignal
//common.TestEndpoint(t, router, token, "/api/models/1", "GET", nil, 200, string(msgModelAUpdated2json)) code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/signals/%v", newSignalID), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// TODO add testing for other return codes // Compare GET's response with the newSSignal
err = common.CompareResponse(resp, common.KeyModels{"signal": newSignal})
assert.NoError(t, err)
// try to POST a malformed signal
// Required fields are missing
malformedNewSignal := SignalRequest{
Name: "ThisIsAMalformedRequest",
}
// this should NOT work and return a unprocessable entity 442 status code
code, resp, err = common.NewTestEndpoint(router, token,
"/api/signals", "POST", common.KeyModels{"model": malformedNewSignal})
assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
}
func TestUpdateSignal(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// prepare the content of the DB for testing
// by adding a scenario and a simulator to the DB
// using the respective endpoints of the API
_, _, simulationModelID := addScenarioAndSimulatorAndSimulationModel()
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
// test POST signals/ $newSignal
newSignal := SignalRequest{
Name: common.InSignalA.Name,
Unit: common.InSignalA.Unit,
Direction: common.InSignalA.Direction,
Index: 1,
SimulationModelID: simulationModelID,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/signals", "POST", common.KeyModels{"signal": newSignal})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newSignal's ID from the response
newSignalID, err := common.GetResponseID(resp)
assert.NoError(t, err)
updatedSignal := SignalRequest{
Name: common.InSignalB.Name,
Unit: common.InSignalB.Unit,
Index: 1,
}
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/signals/%v", newSignalID), "PUT", common.KeyModels{"signal": updatedSignal})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare PUT's response with the updatedSignal
err = common.CompareResponse(resp, common.KeyModels{"signal": updatedSignal})
assert.NoError(t, err)
// Get the updatedSignal
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/signals/%v", newSignalID), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare GET's response with the updatedSignal
err = common.CompareResponse(resp, common.KeyModels{"signal": updatedSignal})
assert.NoError(t, err)
// try to update a signal that does not exist (should return not found 404 status code)
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/signals/%v", newSignalID+1), "PUT", common.KeyModels{"signal": updatedSignal})
assert.NoError(t, err)
assert.Equalf(t, 404, code, "Response body: \n%v\n", resp)
}
func TestDeleteSignal(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// prepare the content of the DB for testing
// by adding a scenario and a simulator to the DB
// using the respective endpoints of the API
_, _, simulationModelID := addScenarioAndSimulatorAndSimulationModel()
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
// test POST signals/ $newSignal
newSignal := SignalRequest{
Name: common.InSignalA.Name,
Unit: common.InSignalA.Unit,
Direction: common.InSignalA.Direction,
Index: 1,
SimulationModelID: simulationModelID,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/signals", "POST", common.KeyModels{"signal": newSignal})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Read newSignal's ID from the response
newSignalID, err := common.GetResponseID(resp)
assert.NoError(t, err)
// Count the number of all the input signals returned for simulation model
initialNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/signals?modelID=%v&direction=in", simulationModelID), "GET", nil)
assert.NoError(t, err)
// add an output signal to make sure that counting of input signals works
newSignalout := SignalRequest{
Name: common.OutSignalA.Name,
Unit: common.OutSignalA.Unit,
Direction: common.OutSignalA.Direction,
Index: 1,
SimulationModelID: simulationModelID,
}
code, resp, err = common.NewTestEndpoint(router, token,
"/api/signals", "POST", common.KeyModels{"signal": newSignalout})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Delete the added newSignal
code, resp, err = common.NewTestEndpoint(router, token,
fmt.Sprintf("/api/signals/%v", newSignalID), "DELETE", nil)
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Compare DELETE's response with the newSignal
err = common.CompareResponse(resp, common.KeyModels{"signal": newSignal})
assert.NoError(t, err)
// Again count the number of all the input signals returned for simulation model
finalNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/signals?modelID=%v&direction=in", simulationModelID), "GET", nil)
assert.NoError(t, err)
assert.Equal(t, initialNumber-1, finalNumber)
}
func TestGetAllInputSignalsOfSimulationModel(t *testing.T) {
common.DropTables(db)
common.MigrateModels(db)
common.DummyAddOnlyUserTableWithAdminAndUsersDB(db)
// prepare the content of the DB for testing
// by adding a scenario and a simulator to the DB
// using the respective endpoints of the API
_, _, simulationModelID := addScenarioAndSimulatorAndSimulationModel()
// authenticate as normal user
token, err := common.NewAuthenticateForTest(router,
"/api/authenticate", "POST", common.UserACredentials)
assert.NoError(t, err)
// Count the number of all the input signals returned for simulation model
initialNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/signals?modelID=%v&direction=in", simulationModelID), "GET", nil)
assert.NoError(t, err)
// test POST signals/ $newSignal
newSignalA := SignalRequest{
Name: common.InSignalA.Name,
Unit: common.InSignalA.Unit,
Direction: common.InSignalA.Direction,
Index: 1,
SimulationModelID: simulationModelID,
}
code, resp, err := common.NewTestEndpoint(router, token,
"/api/signals", "POST", common.KeyModels{"signal": newSignalA})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// add a second input signal
newSignalB := SignalRequest{
Name: common.InSignalB.Name,
Unit: common.InSignalB.Unit,
Direction: common.InSignalB.Direction,
Index: 2,
SimulationModelID: simulationModelID,
}
code, resp, err = common.NewTestEndpoint(router, token,
"/api/signals", "POST", common.KeyModels{"signal": newSignalB})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// add an output signal
newSignalAout := SignalRequest{
Name: common.OutSignalA.Name,
Unit: common.OutSignalA.Unit,
Direction: common.OutSignalA.Direction,
Index: 1,
SimulationModelID: simulationModelID,
}
code, resp, err = common.NewTestEndpoint(router, token,
"/api/signals", "POST", common.KeyModels{"signal": newSignalAout})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// add a second output signal
newSignalBout := SignalRequest{
Name: common.OutSignalB.Name,
Unit: common.OutSignalB.Unit,
Direction: common.OutSignalB.Direction,
Index: 1,
SimulationModelID: simulationModelID,
}
code, resp, err = common.NewTestEndpoint(router, token,
"/api/signals", "POST", common.KeyModels{"signal": newSignalBout})
assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Again count the number of all the input signals returned for simulation model
finalNumber, err := common.LengthOfResponse(router, token,
fmt.Sprintf("/api/signals?modelID=%v&direction=in", simulationModelID), "GET", nil)
assert.NoError(t, err)
assert.Equal(t, initialNumber+2, finalNumber)
} }

View file

@ -0,0 +1,73 @@
package signal
import (
"gopkg.in/go-playground/validator.v9"
)
var validate *validator.Validate
type validNewSignal struct {
Name string `form:"Name" validate:"required"`
Unit string `form:"unit" validate:"omitempty"`
Index uint `form:"index" validate:"required"`
Direction string `form:"direction" validate:"required,oneof=in out"`
SimulationModelID uint `form:"simulationModelID" validate:"required"`
}
type validUpdatedSignal struct {
Name string `form:"Name" validate:"omitempty"`
Unit string `form:"unit" validate:"omitempty"`
Index uint `form:"index" validate:"omitempty"`
}
type addSignalRequest struct {
validNewSignal `json:"signal"`
}
type updateSignalRequest struct {
validUpdatedSignal `json:"signal"`
}
func (r *addSignalRequest) validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *validUpdatedSignal) validate() error {
validate = validator.New()
errs := validate.Struct(r)
return errs
}
func (r *addSignalRequest) createSignal() Signal {
var s Signal
s.Name = r.Name
s.Unit = r.Unit
s.Index = r.Index
s.Direction = r.Direction
s.SimulationModelID = r.SimulationModelID
return s
}
func (r *updateSignalRequest) updatedSignal(oldSignal Signal) (Signal, error) {
// Use the old Signal as a basis for the updated Signal `s`
s := oldSignal
if r.Name != "" {
s.Name = r.Name
}
if r.Index != 0 {
// TODO this implies that we start indexing at 1
s.Index = r.Index
}
if r.Unit != "" {
s.Unit = r.Unit
}
return s, nil
}