Change of data model: associate files with scenario instead of widgets and component configuration #34

This commit is contained in:
Sonja Happ 2020-05-26 11:27:00 +02:00
parent 0e54b7ba33
commit 8b60430d84
10 changed files with 231 additions and 561 deletions

View file

@ -142,6 +142,8 @@ func TestScenarioAssociations(t *testing.T) {
configB := ComponentConfiguration{} configB := ComponentConfiguration{}
dashboardA := Dashboard{} dashboardA := Dashboard{}
dashboardB := Dashboard{} dashboardB := Dashboard{}
fileA := File{}
fileB := File{}
// add scenarios to DB // add scenarios to DB
assert.NoError(t, DBpool.Create(&scenarioA).Error) assert.NoError(t, DBpool.Create(&scenarioA).Error)
@ -159,6 +161,10 @@ func TestScenarioAssociations(t *testing.T) {
assert.NoError(t, DBpool.Create(&dashboardA).Error) assert.NoError(t, DBpool.Create(&dashboardA).Error)
assert.NoError(t, DBpool.Create(&dashboardB).Error) assert.NoError(t, DBpool.Create(&dashboardB).Error)
// add files to DB
assert.NoError(t, DBpool.Create(&fileA).Error)
assert.NoError(t, DBpool.Create(&fileB).Error)
// add many-to-many associations between users and scenarios // add many-to-many associations between users and scenarios
// User HM Scenarios, Scenario HM Users (Many-to-Many) // User HM Scenarios, Scenario HM Users (Many-to-Many)
assert.NoError(t, DBpool.Model(&scenarioA).Association("Users").Append(&userA).Error) assert.NoError(t, DBpool.Model(&scenarioA).Association("Users").Append(&userA).Error)
@ -166,6 +172,10 @@ func TestScenarioAssociations(t *testing.T) {
assert.NoError(t, DBpool.Model(&scenarioB).Association("Users").Append(&userA).Error) assert.NoError(t, DBpool.Model(&scenarioB).Association("Users").Append(&userA).Error)
assert.NoError(t, DBpool.Model(&scenarioB).Association("Users").Append(&userB).Error) assert.NoError(t, DBpool.Model(&scenarioB).Association("Users").Append(&userB).Error)
// add Component Configuration has many files associations
assert.NoError(t, DBpool.Model(&scenarioA).Association("Files").Append(&fileA).Error)
assert.NoError(t, DBpool.Model(&scenarioA).Association("Files").Append(&fileB).Error)
// add scenario has many component configurations associations // add scenario has many component configurations associations
assert.NoError(t, DBpool.Model(&scenarioA).Association("ComponentConfigurations").Append(&configA).Error) assert.NoError(t, DBpool.Model(&scenarioA).Association("ComponentConfigurations").Append(&configA).Error)
assert.NoError(t, DBpool.Model(&scenarioA).Association("ComponentConfigurations").Append(&configB).Error) assert.NoError(t, DBpool.Model(&scenarioA).Association("ComponentConfigurations").Append(&configB).Error)
@ -200,6 +210,14 @@ func TestScenarioAssociations(t *testing.T) {
assert.Fail(t, "Scenario Associations", assert.Fail(t, "Scenario Associations",
"Expected to have %v Dashboards. Has %v.", 2, len(dashboards)) "Expected to have %v Dashboards. Has %v.", 2, len(dashboards))
} }
// Get files of scenario1
var files []File
assert.NoError(t, DBpool.Model(&scenario1).Related(&files, "Files").Error)
if len(files) != 2 {
assert.Fail(t, "Scenario Associations",
"Expected to have %v Files. Has %v.", 2, len(files))
}
} }
func TestICAssociations(t *testing.T) { func TestICAssociations(t *testing.T) {
@ -249,10 +267,6 @@ func TestComponentConfigurationAssociations(t *testing.T) {
outSignalB := Signal{Direction: "out"} outSignalB := Signal{Direction: "out"}
inSignalA := Signal{Direction: "in"} inSignalA := Signal{Direction: "in"}
inSignalB := Signal{Direction: "in"} inSignalB := Signal{Direction: "in"}
fileA := File{}
fileB := File{}
fileC := File{}
fileD := File{}
icA := InfrastructureComponent{} icA := InfrastructureComponent{}
icB := InfrastructureComponent{} icB := InfrastructureComponent{}
@ -266,12 +280,6 @@ func TestComponentConfigurationAssociations(t *testing.T) {
assert.NoError(t, DBpool.Create(&inSignalA).Error) assert.NoError(t, DBpool.Create(&inSignalA).Error)
assert.NoError(t, DBpool.Create(&inSignalB).Error) assert.NoError(t, DBpool.Create(&inSignalB).Error)
// add files to DB
assert.NoError(t, DBpool.Create(&fileA).Error)
assert.NoError(t, DBpool.Create(&fileB).Error)
assert.NoError(t, DBpool.Create(&fileC).Error)
assert.NoError(t, DBpool.Create(&fileD).Error)
// add ICs to DB // add ICs to DB
assert.NoError(t, DBpool.Create(&icA).Error) assert.NoError(t, DBpool.Create(&icA).Error)
assert.NoError(t, DBpool.Create(&icB).Error) assert.NoError(t, DBpool.Create(&icB).Error)
@ -282,10 +290,6 @@ func TestComponentConfigurationAssociations(t *testing.T) {
assert.NoError(t, DBpool.Model(&configA).Association("OutputMapping").Append(&outSignalA).Error) assert.NoError(t, DBpool.Model(&configA).Association("OutputMapping").Append(&outSignalA).Error)
assert.NoError(t, DBpool.Model(&configA).Association("OutputMapping").Append(&outSignalB).Error) assert.NoError(t, DBpool.Model(&configA).Association("OutputMapping").Append(&outSignalB).Error)
// add Component Configuration has many files associations
assert.NoError(t, DBpool.Model(&configA).Association("Files").Append(&fileC).Error)
assert.NoError(t, DBpool.Model(&configA).Association("Files").Append(&fileD).Error)
// associate Component Configurations with IC // associate Component Configurations with IC
assert.NoError(t, DBpool.Model(&icA).Association("ComponentConfigurations").Append(&configA).Error) assert.NoError(t, DBpool.Model(&icA).Association("ComponentConfigurations").Append(&configA).Error)
assert.NoError(t, DBpool.Model(&icA).Association("ComponentConfigurations").Append(&configB).Error) assert.NoError(t, DBpool.Model(&icA).Association("ComponentConfigurations").Append(&configB).Error)
@ -306,13 +310,6 @@ func TestComponentConfigurationAssociations(t *testing.T) {
"Expected to have %v Output Signals. Has %v.", 2, len(signals)) "Expected to have %v Output Signals. Has %v.", 2, len(signals))
} }
// Get files of config1
var files []File
assert.NoError(t, DBpool.Model(&config1).Related(&files, "Files").Error)
if len(files) != 2 {
assert.Fail(t, "ComponentConfiguration Associations",
"Expected to have %v Files. Has %v.", 2, len(files))
}
} }
func TestDashboardAssociations(t *testing.T) { func TestDashboardAssociations(t *testing.T) {
@ -358,35 +355,13 @@ func TestWidgetAssociations(t *testing.T) {
// create copies of global test data // create copies of global test data
widgetA := Widget{} widgetA := Widget{}
widgetB := Widget{} widgetB := Widget{}
fileA := File{}
fileB := File{}
fileC := File{}
fileD := File{}
// add widgets to DB // add widgets to DB
assert.NoError(t, DBpool.Create(&widgetA).Error) assert.NoError(t, DBpool.Create(&widgetA).Error)
assert.NoError(t, DBpool.Create(&widgetB).Error) assert.NoError(t, DBpool.Create(&widgetB).Error)
// add files to DB
assert.NoError(t, DBpool.Create(&fileA).Error)
assert.NoError(t, DBpool.Create(&fileB).Error)
assert.NoError(t, DBpool.Create(&fileC).Error)
assert.NoError(t, DBpool.Create(&fileD).Error)
// add widget has many files associations to DB
assert.NoError(t, DBpool.Model(&widgetA).Association("Files").Append(&fileA).Error)
assert.NoError(t, DBpool.Model(&widgetA).Association("Files").Append(&fileB).Error)
var widget1 Widget var widget1 Widget
assert.NoError(t, DBpool.Find(&widget1, 1).Error, fmt.Sprintf("Find Widget with ID=1")) assert.NoError(t, DBpool.Find(&widget1, 1).Error, fmt.Sprintf("Find Widget with ID=1"))
// Get files of widget
var files []File
assert.NoError(t, DBpool.Model(&widget1).Related(&files, "Files").Error)
if len(files) != 2 {
assert.Fail(t, "Widget Associations",
"Expected to have %v Files. Has %v.", 2, len(files))
}
} }
func TestFileAssociations(t *testing.T) { func TestFileAssociations(t *testing.T) {

View file

@ -69,6 +69,8 @@ type Scenario struct {
ComponentConfigurations []ComponentConfiguration `json:"-" gorm:"foreignkey:ScenarioID" ` ComponentConfigurations []ComponentConfiguration `json:"-" gorm:"foreignkey:ScenarioID" `
// Dashboards that belong to the Scenario // Dashboards that belong to the Scenario
Dashboards []Dashboard `json:"-" gorm:"foreignkey:ScenarioID" ` Dashboards []Dashboard `json:"-" gorm:"foreignkey:ScenarioID" `
// Files that belong to the Scenario (for example images, models, etc.)
Files []File `json:"-" gorm:"foreignkey:ScenarioID"`
} }
// ComponentConfiguration data model // ComponentConfiguration data model
@ -90,8 +92,6 @@ type ComponentConfiguration struct {
OutputMapping []Signal `json:"-" gorm:"foreignkey:ConfigID"` OutputMapping []Signal `json:"-" gorm:"foreignkey:ConfigID"`
// Mapping of input signals of the Component Configuration, order of signals is important // Mapping of input signals of the Component Configuration, order of signals is important
InputMapping []Signal `json:"-" gorm:"foreignkey:ConfigID"` InputMapping []Signal `json:"-" gorm:"foreignkey:ConfigID"`
// Files of Component Configuration (can be CIM and other ComponentConfiguration file formats)
Files []File `json:"-" gorm:"foreignkey:ConfigID"`
// Currently selected FileID // Currently selected FileID
SelectedFileID uint `json:"selectedFileID" gorm:"default:0"` SelectedFileID uint `json:"selectedFileID" gorm:"default:0"`
} }
@ -178,8 +178,6 @@ type Widget struct {
CustomProperties postgres.Jsonb `json:"customProperties"` CustomProperties postgres.Jsonb `json:"customProperties"`
// ID of dashboard to which widget belongs // ID of dashboard to which widget belongs
DashboardID uint `json:"dashboardID"` DashboardID uint `json:"dashboardID"`
// Files that belong to widget (for example images)
Files []File `json:"-" gorm:"foreignkey:WidgetID"`
// IDs of signals that widget uses // IDs of signals that widget uses
SignalIDs pq.Int64Array `json:"signalIDs" gorm:"type:integer[]"` SignalIDs pq.Int64Array `json:"signalIDs" gorm:"type:integer[]"`
} }
@ -195,10 +193,8 @@ type File struct {
Size uint `json:"size"` Size uint `json:"size"`
// Last modification time of file // Last modification time of file
Date string `json:"date"` Date string `json:"date"`
// ID of Component Configuration to which file belongs // ID of Scenario to which file belongs
ConfigID uint `json:"configID"` ScenarioID uint `json:"scenarioID"`
// ID of widget to which file belongs
WidgetID uint `json:"widgetID"`
// File itself // File itself
FileData []byte `json:"-" gorm:"column:FileData"` FileData []byte `json:"-" gorm:"column:FileData"`
} }

View file

@ -1,6 +1,6 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT // GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag at // This file was generated by swaggo/swag at
// 2020-03-13 17:14:16.841484501 +0100 CET m=+0.100488476 // 2020-05-26 11:22:58.121837163 +0200 CEST m=+0.130733859
package docs package docs
@ -751,7 +751,7 @@ var doc = `{
"tags": [ "tags": [
"files" "files"
], ],
"summary": "Get all files of a specific component configuration or widget", "summary": "Get all files of a specific scenario",
"operationId": "getFiles", "operationId": "getFiles",
"parameters": [ "parameters": [
{ {
@ -761,24 +761,17 @@ var doc = `{
"in": "header", "in": "header",
"required": true "required": true
}, },
{
"type": "string",
"description": "Set to config for files of component configuration, set to widget for files of widget",
"name": "objectType",
"in": "query",
"required": true
},
{ {
"type": "integer", "type": "integer",
"description": "ID of either config or widget of which files are requested", "description": "Scenario ID",
"name": "objectID", "name": "scenarioID",
"in": "query", "in": "query",
"required": true "required": true
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Files which belong to config or widget", "description": "Files which belong to scenario",
"schema": { "schema": {
"type": "object", "type": "object",
"$ref": "#/definitions/docs.ResponseFiles" "$ref": "#/definitions/docs.ResponseFiles"
@ -822,7 +815,7 @@ var doc = `{
"tags": [ "tags": [
"files" "files"
], ],
"summary": "Add a file to a specific component config or widget", "summary": "Add a file to a specific scenario",
"operationId": "addFile", "operationId": "addFile",
"parameters": [ "parameters": [
{ {
@ -839,17 +832,10 @@ var doc = `{
"in": "formData", "in": "formData",
"required": true "required": true
}, },
{
"type": "string",
"description": "Set to config for files of component config, set to widget for files of widget",
"name": "objectType",
"in": "query",
"required": true
},
{ {
"type": "integer", "type": "integer",
"description": "ID of either config or widget of which files are requested", "description": "ID of scenario to which file shall be added",
"name": "objectID", "name": "scenarioID",
"in": "query", "in": "query",
"required": true "required": true
} }
@ -3248,10 +3234,6 @@ var doc = `{
"database.File": { "database.File": {
"type": "object", "type": "object",
"properties": { "properties": {
"configID": {
"description": "ID of Component Configuration to which file belongs",
"type": "integer"
},
"date": { "date": {
"description": "Last modification time of file", "description": "Last modification time of file",
"type": "string" "type": "string"
@ -3263,6 +3245,10 @@ var doc = `{
"description": "Name of file", "description": "Name of file",
"type": "string" "type": "string"
}, },
"scenarioID": {
"description": "ID of Scenario to which file belongs",
"type": "integer"
},
"size": { "size": {
"description": "Size of file (in byte)", "description": "Size of file (in byte)",
"type": "integer" "type": "integer"
@ -3270,16 +3256,16 @@ var doc = `{
"type": { "type": {
"description": "Type of file (MIME type)", "description": "Type of file (MIME type)",
"type": "string" "type": "string"
},
"widgetID": {
"description": "ID of widget to which file belongs",
"type": "integer"
} }
} }
}, },
"database.InfrastructureComponent": { "database.InfrastructureComponent": {
"type": "object", "type": "object",
"properties": { "properties": {
"category": {
"description": "Category of IC (simulator, gateway, database, etc.)",
"type": "string"
},
"host": { "host": {
"description": "Host if the IC", "description": "Host if the IC",
"type": "string" "type": "string"
@ -3287,8 +3273,8 @@ var doc = `{
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"modelType": { "name": {
"description": "Model type supported by the IC", "description": "Name of the IC",
"type": "string" "type": "string"
}, },
"properties": { "properties": {
@ -3307,6 +3293,10 @@ var doc = `{
"description": "Time of last state update", "description": "Time of last state update",
"type": "string" "type": "string"
}, },
"type": {
"description": "Type of IC (RTDS, VILLASnode, RTDS, etc.)",
"type": "string"
},
"uptime": { "uptime": {
"description": "Uptime of the IC", "description": "Uptime of the IC",
"type": "integer" "type": "integer"
@ -3659,16 +3649,20 @@ var doc = `{
"infrastructure_component.validNewIC": { "infrastructure_component.validNewIC": {
"type": "object", "type": "object",
"required": [ "required": [
"Category",
"Host", "Host",
"Name",
"Type", "Type",
"Properties",
"UUID" "UUID"
], ],
"properties": { "properties": {
"Category": {
"type": "string"
},
"Host": { "Host": {
"type": "string" "type": "string"
}, },
"Type": { "Name": {
"type": "string" "type": "string"
}, },
"Properties": { "Properties": {
@ -3677,6 +3671,9 @@ var doc = `{
"State": { "State": {
"type": "string" "type": "string"
}, },
"Type": {
"type": "string"
},
"UUID": { "UUID": {
"type": "string" "type": "string"
} }
@ -3685,10 +3682,13 @@ var doc = `{
"infrastructure_component.validUpdatedIC": { "infrastructure_component.validUpdatedIC": {
"type": "object", "type": "object",
"properties": { "properties": {
"Category": {
"type": "string"
},
"Host": { "Host": {
"type": "string" "type": "string"
}, },
"Type": { "Name": {
"type": "string" "type": "string"
}, },
"Properties": { "Properties": {
@ -3697,6 +3697,9 @@ var doc = `{
"State": { "State": {
"type": "string" "type": "string"
}, },
"Type": {
"type": "string"
},
"UUID": { "UUID": {
"type": "string" "type": "string"
} }

View file

@ -736,7 +736,7 @@
"tags": [ "tags": [
"files" "files"
], ],
"summary": "Get all files of a specific component configuration or widget", "summary": "Get all files of a specific scenario",
"operationId": "getFiles", "operationId": "getFiles",
"parameters": [ "parameters": [
{ {
@ -746,24 +746,17 @@
"in": "header", "in": "header",
"required": true "required": true
}, },
{
"type": "string",
"description": "Set to config for files of component configuration, set to widget for files of widget",
"name": "objectType",
"in": "query",
"required": true
},
{ {
"type": "integer", "type": "integer",
"description": "ID of either config or widget of which files are requested", "description": "Scenario ID",
"name": "objectID", "name": "scenarioID",
"in": "query", "in": "query",
"required": true "required": true
} }
], ],
"responses": { "responses": {
"200": { "200": {
"description": "Files which belong to config or widget", "description": "Files which belong to scenario",
"schema": { "schema": {
"type": "object", "type": "object",
"$ref": "#/definitions/docs.ResponseFiles" "$ref": "#/definitions/docs.ResponseFiles"
@ -807,7 +800,7 @@
"tags": [ "tags": [
"files" "files"
], ],
"summary": "Add a file to a specific component config or widget", "summary": "Add a file to a specific scenario",
"operationId": "addFile", "operationId": "addFile",
"parameters": [ "parameters": [
{ {
@ -824,17 +817,10 @@
"in": "formData", "in": "formData",
"required": true "required": true
}, },
{
"type": "string",
"description": "Set to config for files of component config, set to widget for files of widget",
"name": "objectType",
"in": "query",
"required": true
},
{ {
"type": "integer", "type": "integer",
"description": "ID of either config or widget of which files are requested", "description": "ID of scenario to which file shall be added",
"name": "objectID", "name": "scenarioID",
"in": "query", "in": "query",
"required": true "required": true
} }
@ -3233,10 +3219,6 @@
"database.File": { "database.File": {
"type": "object", "type": "object",
"properties": { "properties": {
"configID": {
"description": "ID of Component Configuration to which file belongs",
"type": "integer"
},
"date": { "date": {
"description": "Last modification time of file", "description": "Last modification time of file",
"type": "string" "type": "string"
@ -3248,6 +3230,10 @@
"description": "Name of file", "description": "Name of file",
"type": "string" "type": "string"
}, },
"scenarioID": {
"description": "ID of Scenario to which file belongs",
"type": "integer"
},
"size": { "size": {
"description": "Size of file (in byte)", "description": "Size of file (in byte)",
"type": "integer" "type": "integer"
@ -3255,16 +3241,16 @@
"type": { "type": {
"description": "Type of file (MIME type)", "description": "Type of file (MIME type)",
"type": "string" "type": "string"
},
"widgetID": {
"description": "ID of widget to which file belongs",
"type": "integer"
} }
} }
}, },
"database.InfrastructureComponent": { "database.InfrastructureComponent": {
"type": "object", "type": "object",
"properties": { "properties": {
"category": {
"description": "Category of IC (simulator, gateway, database, etc.)",
"type": "string"
},
"host": { "host": {
"description": "Host if the IC", "description": "Host if the IC",
"type": "string" "type": "string"
@ -3272,8 +3258,8 @@
"id": { "id": {
"type": "integer" "type": "integer"
}, },
"modelType": { "name": {
"description": "Model type supported by the IC", "description": "Name of the IC",
"type": "string" "type": "string"
}, },
"properties": { "properties": {
@ -3292,6 +3278,10 @@
"description": "Time of last state update", "description": "Time of last state update",
"type": "string" "type": "string"
}, },
"type": {
"description": "Type of IC (RTDS, VILLASnode, RTDS, etc.)",
"type": "string"
},
"uptime": { "uptime": {
"description": "Uptime of the IC", "description": "Uptime of the IC",
"type": "integer" "type": "integer"
@ -3644,16 +3634,20 @@
"infrastructure_component.validNewIC": { "infrastructure_component.validNewIC": {
"type": "object", "type": "object",
"required": [ "required": [
"Category",
"Host", "Host",
"Modeltype", "Name",
"Properties", "Type",
"UUID" "UUID"
], ],
"properties": { "properties": {
"Category": {
"type": "string"
},
"Host": { "Host": {
"type": "string" "type": "string"
}, },
"Modeltype": { "Name": {
"type": "string" "type": "string"
}, },
"Properties": { "Properties": {
@ -3662,6 +3656,9 @@
"State": { "State": {
"type": "string" "type": "string"
}, },
"Type": {
"type": "string"
},
"UUID": { "UUID": {
"type": "string" "type": "string"
} }
@ -3670,10 +3667,13 @@
"infrastructure_component.validUpdatedIC": { "infrastructure_component.validUpdatedIC": {
"type": "object", "type": "object",
"properties": { "properties": {
"Category": {
"type": "string"
},
"Host": { "Host": {
"type": "string" "type": "string"
}, },
"Modeltype": { "Name": {
"type": "string" "type": "string"
}, },
"Properties": { "Properties": {
@ -3682,6 +3682,9 @@
"State": { "State": {
"type": "string" "type": "string"
}, },
"Type": {
"type": "string"
},
"UUID": { "UUID": {
"type": "string" "type": "string"
} }

View file

@ -115,9 +115,6 @@ definitions:
type: object type: object
database.File: database.File:
properties: properties:
configID:
description: ID of Component Configuration to which file belongs
type: integer
date: date:
description: Last modification time of file description: Last modification time of file
type: string type: string
@ -126,25 +123,28 @@ definitions:
name: name:
description: Name of file description: Name of file
type: string type: string
scenarioID:
description: ID of Scenario to which file belongs
type: integer
size: size:
description: Size of file (in byte) description: Size of file (in byte)
type: integer type: integer
type: type:
description: Type of file (MIME type) description: Type of file (MIME type)
type: string type: string
widgetID:
description: ID of widget to which file belongs
type: integer
type: object type: object
database.InfrastructureComponent: database.InfrastructureComponent:
properties: properties:
category:
description: Category of IC (simulator, gateway, database, etc.)
type: string
host: host:
description: Host if the IC description: Host if the IC
type: string type: string
id: id:
type: integer type: integer
modelType: name:
description: Model type supported by the IC description: Name of the IC
type: string type: string
properties: properties:
description: Properties of IC as JSON string description: Properties of IC as JSON string
@ -158,6 +158,9 @@ definitions:
stateUpdateAt: stateUpdateAt:
description: Time of last state update description: Time of last state update
type: string type: string
type:
description: Type of IC (RTDS, VILLASnode, RTDS, etc.)
type: string
uptime: uptime:
description: Uptime of the IC description: Uptime of the IC
type: integer type: integer
@ -398,32 +401,41 @@ definitions:
type: object type: object
infrastructure_component.validNewIC: infrastructure_component.validNewIC:
properties: properties:
Category:
type: string
Host: Host:
type: string type: string
Modeltype: Name:
type: string type: string
Properties: Properties:
type: string type: string
State: State:
type: string type: string
Type:
type: string
UUID: UUID:
type: string type: string
required: required:
- Category
- Host - Host
- Modeltype - Name
- Properties - Type
- UUID - UUID
type: object type: object
infrastructure_component.validUpdatedIC: infrastructure_component.validUpdatedIC:
properties: properties:
Category:
type: string
Host: Host:
type: string type: string
Modeltype: Name:
type: string type: string
Properties: Properties:
type: string type: string
State: State:
type: string type: string
Type:
type: string
UUID: UUID:
type: string type: string
type: object type: object
@ -1150,22 +1162,16 @@ paths:
name: Authorization name: Authorization
required: true required: true
type: string type: string
- description: Set to config for files of component configuration, set to widget - description: Scenario ID
for files of widget
in: query in: query
name: objectType name: scenarioID
required: true
type: string
- description: ID of either config or widget of which files are requested
in: query
name: objectID
required: true required: true
type: integer type: integer
produces: produces:
- application/json - application/json
responses: responses:
"200": "200":
description: Files which belong to config or widget description: Files which belong to scenario
schema: schema:
$ref: '#/definitions/docs.ResponseFiles' $ref: '#/definitions/docs.ResponseFiles'
type: object type: object
@ -1184,7 +1190,7 @@ paths:
schema: schema:
$ref: '#/definitions/docs.ResponseError' $ref: '#/definitions/docs.ResponseError'
type: object type: object
summary: Get all files of a specific component configuration or widget summary: Get all files of a specific scenario
tags: tags:
- files - files
post: post:
@ -1207,15 +1213,9 @@ paths:
name: inputFile name: inputFile
required: true required: true
type: file type: file
- description: Set to config for files of component config, set to widget for - description: ID of scenario to which file shall be added
files of widget
in: query in: query
name: objectType name: scenarioID
required: true
type: string
- description: ID of either config or widget of which files are requested
in: query
name: objectID
required: true required: true
type: integer type: integer
produces: produces:
@ -1246,7 +1246,7 @@ paths:
schema: schema:
$ref: '#/definitions/docs.ResponseError' $ref: '#/definitions/docs.ResponseError'
type: object type: object
summary: Add a file to a specific component config or widget summary: Add a file to a specific scenario
tags: tags:
- files - files
/files/{fileID}: /files/{fileID}:

View file

@ -24,14 +24,11 @@ package file
import ( import (
"fmt" "fmt"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper"
"net/http" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario"
"strconv"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"net/http"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/component-configuration"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/widget"
) )
func RegisterFileEndpoints(r *gin.RouterGroup) { func RegisterFileEndpoints(r *gin.RouterGroup) {
@ -43,57 +40,28 @@ func RegisterFileEndpoints(r *gin.RouterGroup) {
} }
// getFiles godoc // getFiles godoc
// @Summary Get all files of a specific component configuration or widget // @Summary Get all files of a specific scenario
// @ID getFiles // @ID getFiles
// @Tags files // @Tags files
// @Produce json // @Produce json
// @Success 200 {object} docs.ResponseFiles "Files which belong to config or widget" // @Success 200 {object} docs.ResponseFiles "Files which belong to scenario"
// @Failure 404 {object} docs.ResponseError "Not found" // @Failure 404 {object} docs.ResponseError "Not found"
// @Failure 422 {object} docs.ResponseError "Unprocessable entity" // @Failure 422 {object} docs.ResponseError "Unprocessable entity"
// @Failure 500 {object} docs.ResponseError "Internal server error" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param Authorization header string true "Authorization token" // @Param Authorization header string true "Authorization token"
// @Param objectType query string true "Set to config for files of component configuration, set to widget for files of widget" // @Param scenarioID query int true "Scenario ID"
// @Param objectID query int true "ID of either config or widget of which files are requested"
// @Router /files [get] // @Router /files [get]
func getFiles(c *gin.Context) { func getFiles(c *gin.Context) {
var err error ok, so := scenario.CheckPermissions(c, database.Read, "query", -1)
objectType := c.Request.URL.Query().Get("objectType")
if objectType != "config" && objectType != "widget" {
helper.BadRequestError(c, fmt.Sprintf("Object type not supported for files: %s", objectType))
return
}
objectID_s := c.Request.URL.Query().Get("objectID")
objectID, err := strconv.Atoi(objectID_s)
if err != nil {
helper.BadRequestError(c, fmt.Sprintf("Error on ID conversion: %s", err.Error()))
return
}
//Check access
var ok bool
var m component_configuration.ComponentConfiguration
var w widget.Widget
if objectType == "config" {
ok, m = component_configuration.CheckPermissions(c, database.Read, "body", objectID)
} else {
ok, w = widget.CheckPermissions(c, database.Read, objectID)
}
if !ok { if !ok {
return return
} }
// get meta data of files // get meta data of files
db := database.GetDB() db := database.GetDB()
var files []database.File var files []database.File
err := db.Order("ID asc").Model(so).Related(&files, "Files").Error
if objectType == "config" {
err = db.Order("ID asc").Model(&m).Related(&files, "Files").Error
} else {
err = db.Order("ID asc").Model(&w).Related(&files, "Files").Error
}
if !helper.DBError(c, err) { if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"files": files}) c.JSON(http.StatusOK, gin.H{"files": files})
} }
@ -101,7 +69,7 @@ func getFiles(c *gin.Context) {
} }
// addFile godoc // addFile godoc
// @Summary Add a file to a specific component config or widget // @Summary Add a file to a specific scenario
// @ID addFile // @ID addFile
// @Tags files // @Tags files
// @Produce json // @Produce json
@ -118,36 +86,14 @@ func getFiles(c *gin.Context) {
// @Failure 500 {object} docs.ResponseError "Internal server error" // @Failure 500 {object} docs.ResponseError "Internal server error"
// @Param Authorization header string true "Authorization token" // @Param Authorization header string true "Authorization token"
// @Param inputFile formData file true "File to be uploaded" // @Param inputFile formData file true "File to be uploaded"
// @Param objectType query string true "Set to config for files of component config, set to widget for files of widget" // @Param scenarioID query int true "ID of scenario to which file shall be added"
// @Param objectID query int true "ID of either config or widget of which files are requested"
// @Router /files [post] // @Router /files [post]
func addFile(c *gin.Context) { func addFile(c *gin.Context) {
objectType := c.Request.URL.Query().Get("objectType") ok, so := scenario.CheckPermissions(c, database.Read, "query", -1)
if objectType != "config" && objectType != "widget" { if !ok {
helper.BadRequestError(c, fmt.Sprintf("Object type not supported for files: %s", objectType))
return return
} }
objectID_s := c.Request.URL.Query().Get("objectID")
objectID, err := strconv.Atoi(objectID_s)
if err != nil {
helper.BadRequestError(c, fmt.Sprintf("Error on ID conversion: %s", err.Error()))
return
}
// Check access
var ok bool
if objectType == "config" {
ok, _ = component_configuration.CheckPermissions(c, database.Update, "body", objectID)
if !ok {
return
}
} else {
ok, _ = widget.CheckPermissions(c, database.Update, objectID)
if !ok {
return
}
}
// Extract file from POST request form // Extract file from POST request form
file_header, err := c.FormFile("file") file_header, err := c.FormFile("file")
@ -157,7 +103,7 @@ func addFile(c *gin.Context) {
} }
var newFile File var newFile File
err = newFile.register(file_header, objectType, uint(objectID)) err = newFile.register(file_header, so.ID)
if !helper.DBError(c, err) { if !helper.DBError(c, err) {
c.JSON(http.StatusOK, gin.H{"file": newFile.File}) c.JSON(http.StatusOK, gin.H{"file": newFile.File})
} }

View file

@ -22,6 +22,7 @@
package file package file
import ( import (
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"io/ioutil" "io/ioutil"
"mime/multipart" "mime/multipart"
@ -31,8 +32,6 @@ import (
"time" "time"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/component-configuration"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/widget"
) )
type File struct { type File struct {
@ -69,37 +68,14 @@ func (f *File) download(c *gin.Context) error {
return nil return nil
} }
func (f *File) register(fileHeader *multipart.FileHeader, objectType string, objectID uint) error { func (f *File) register(fileHeader *multipart.FileHeader, scenarioID uint) error {
// Obtain properties of file // Obtain properties of file
f.Type = fileHeader.Header.Get("Content-Type") f.Type = fileHeader.Header.Get("Content-Type")
f.Name = filepath.Base(fileHeader.Filename) f.Name = filepath.Base(fileHeader.Filename)
//f.Path = filepath.Join(getFolderName(objectType, objectID), f.Name)
f.Size = uint(fileHeader.Size) f.Size = uint(fileHeader.Size)
f.Date = time.Now().String() f.Date = time.Now().String()
f.ScenarioID = scenarioID
var m component_configuration.ComponentConfiguration
var w widget.Widget
var err error
if objectType == "config" {
// check if config exists
err = m.ByID(objectID)
f.WidgetID = 0
f.ConfigID = objectID
if err != nil {
return err
}
} else {
// check if widget exists
f.WidgetID = objectID
f.ConfigID = 0
err = w.ByID(uint(objectID))
if err != nil {
return err
}
}
// set file data // set file data
fileContent, err := fileHeader.Open() fileContent, err := fileHeader.Open()
@ -116,22 +92,18 @@ func (f *File) register(fileHeader *multipart.FileHeader, objectType string, obj
return err return err
} }
// Create association to config or widget // Create association to scenario
if objectType == "config" { db := database.GetDB()
db := database.GetDB()
err := db.Model(&m).Association("Files").Append(f).Error var so scenario.Scenario
if err != nil { err = so.ByID(scenarioID)
return err if err != nil {
} return err
} else {
db := database.GetDB()
err := db.Model(&w).Association("Files").Append(f).Error
if err != nil {
return err
}
} }
return nil err = db.Model(&so).Association("Files").Append(f).Error
return err
} }
func (f *File) update(fileHeader *multipart.FileHeader) error { func (f *File) update(fileHeader *multipart.FileHeader) error {
@ -156,32 +128,19 @@ func (f *File) delete() error {
db := database.GetDB() db := database.GetDB()
if f.WidgetID > 0 { // remove association between file and scenario
// remove association between file and widget var so scenario.Scenario
var w widget.Widget err := so.ByID(f.ScenarioID)
err := w.ByID(f.WidgetID) if err != nil {
if err != nil { return err
return err }
} err = db.Model(&so).Association("Files").Delete(f).Error
err = db.Model(&w).Association("Files").Delete(f).Error if err != nil {
if err != nil { return err
return err
}
} else {
// remove association between file and config
var m component_configuration.ComponentConfiguration
err := m.ByID(f.ConfigID)
if err != nil {
return err
}
err = db.Model(&m).Association("Files").Delete(f).Error
if err != nil {
return err
}
} }
// delete file from DB // delete file from DB
err := db.Delete(f).Error err = db.Delete(f).Error
return err return err
} }

View file

@ -25,8 +25,7 @@ import (
"fmt" "fmt"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/component-configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/widget"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -50,16 +49,9 @@ func checkPermissions(c *gin.Context, operation database.CRUD) (bool, File) {
return false, f return false, f
} }
if f.ConfigID > 0 { ok, _ := scenario.CheckPermissions(c, operation, "body", int(f.ScenarioID))
ok, _ := component_configuration.CheckPermissions(c, operation, "body", int(f.ConfigID)) if !ok {
if !ok { return false, f
return false, f
}
} else {
ok, _ := widget.CheckPermissions(c, operation, int(f.WidgetID))
if !ok {
return false, f
}
} }
return true, f return true, f

View file

@ -27,12 +27,8 @@ import (
"git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/database" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/component-configuration"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/dashboard"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/infrastructure-component"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/scenario"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/user" "git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/user"
"git.rwth-aachen.de/acs/public/villas/web-backend-go/routes/widget"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jinzhu/gorm/dialects/postgres" "github.com/jinzhu/gorm/dialects/postgres"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -47,72 +43,18 @@ import (
var router *gin.Engine var router *gin.Engine
type ConfigRequest struct {
Name string `json:"name,omitempty"`
ScenarioID uint `json:"scenarioID,omitempty"`
ICID uint `json:"icID,omitempty"`
StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
}
type ICRequest struct {
UUID string `json:"uuid,omitempty"`
Host string `json:"host,omitempty"`
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Category string `json:"category,omitempty"`
State string `json:"state,omitempty"`
Properties postgres.Jsonb `json:"properties,omitempty"`
}
type ScenarioRequest struct { type ScenarioRequest struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Running bool `json:"running,omitempty"` Running bool `json:"running,omitempty"`
StartParameters postgres.Jsonb `json:"startParameters,omitempty"` StartParameters postgres.Jsonb `json:"startParameters,omitempty"`
} }
type DashboardRequest struct { func addScenario() (scenarioID uint) {
Name string `json:"name,omitempty"`
Grid int `json:"grid,omitempty"`
ScenarioID uint `json:"scenarioID,omitempty"`
}
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"`
}
func addScenarioAndICAndConfigAndDashboardAndWidget() (scenarioID uint, ICID uint, configID uint, dashboardID uint, widgetID uint) {
// authenticate as admin // authenticate as admin
token, _ := helper.AuthenticateForTest(router, token, _ := helper.AuthenticateForTest(router,
"/api/authenticate", "POST", helper.AdminCredentials) "/api/authenticate", "POST", helper.AdminCredentials)
// POST $newICA
newICA := ICRequest{
UUID: helper.ICA.UUID,
Host: helper.ICA.Host,
Type: helper.ICA.Type,
Name: helper.ICA.Name,
Category: helper.ICA.Category,
State: helper.ICA.State,
Properties: helper.ICA.Properties,
}
_, resp, _ := helper.TestEndpoint(router, token,
"/api/ic", "POST", helper.KeyModels{"ic": newICA})
// Read newIC's ID from the response
newICID, _ := helper.GetResponseID(resp)
// authenticate as normal user // authenticate as normal user
token, _ = helper.AuthenticateForTest(router, token, _ = helper.AuthenticateForTest(router,
"/api/authenticate", "POST", helper.UserACredentials) "/api/authenticate", "POST", helper.UserACredentials)
@ -123,63 +65,17 @@ func addScenarioAndICAndConfigAndDashboardAndWidget() (scenarioID uint, ICID uin
Running: helper.ScenarioA.Running, Running: helper.ScenarioA.Running,
StartParameters: helper.ScenarioA.StartParameters, StartParameters: helper.ScenarioA.StartParameters,
} }
_, resp, _ = helper.TestEndpoint(router, token, _, resp, _ := helper.TestEndpoint(router, token,
"/api/scenarios", "POST", helper.KeyModels{"scenario": newScenario}) "/api/scenarios", "POST", helper.KeyModels{"scenario": newScenario})
// Read newScenario's ID from the response // Read newScenario's ID from the response
newScenarioID, _ := helper.GetResponseID(resp) newScenarioID, _ := helper.GetResponseID(resp)
// POST new component config
newConfig := ConfigRequest{
Name: helper.ConfigA.Name,
ScenarioID: uint(newScenarioID),
ICID: uint(newICID),
StartParameters: helper.ConfigA.StartParameters,
}
_, resp, _ = helper.TestEndpoint(router, token,
"/api/configs", "POST", helper.KeyModels{"config": newConfig})
// Read newConfig's ID from the response
newConfigID, _ := helper.GetResponseID(resp)
// POST new dashboard
newDashboard := DashboardRequest{
Name: helper.DashboardA.Name,
Grid: helper.DashboardA.Grid,
ScenarioID: uint(newScenarioID),
}
_, resp, _ = helper.TestEndpoint(router, token,
"/api/dashboards", "POST", helper.KeyModels{"dashboard": newDashboard})
// Read newDashboard's ID from the response
newDashboardID, _ := helper.GetResponseID(resp)
// POST new widget
newWidget := WidgetRequest{
Name: helper.WidgetA.Name,
Type: helper.WidgetA.Type,
Width: helper.WidgetA.Width,
Height: helper.WidgetA.Height,
MinWidth: helper.WidgetA.MinWidth,
MinHeight: helper.WidgetA.MinHeight,
X: helper.WidgetA.X,
Y: helper.WidgetA.Y,
Z: helper.WidgetA.Z,
IsLocked: helper.WidgetA.IsLocked,
CustomProperties: helper.WidgetA.CustomProperties,
DashboardID: uint(newDashboardID),
}
_, resp, _ = helper.TestEndpoint(router, token,
"/api/widgets", "POST", helper.KeyModels{"widget": newWidget})
// Read newWidget's ID from the response
newWidgetID, _ := helper.GetResponseID(resp)
// add the guest user to the new scenario // add the guest user to the new scenario
_, resp, _ = helper.TestEndpoint(router, token, _, resp, _ = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil) fmt.Sprintf("/api/scenarios/%v/user?username=User_C", newScenarioID), "PUT", nil)
return uint(newScenarioID), uint(newICID), uint(newConfigID), uint(newDashboardID), uint(newWidgetID) return uint(newScenarioID)
} }
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
@ -198,21 +94,8 @@ func TestMain(m *testing.M) {
user.RegisterAuthenticate(api.Group("/authenticate")) user.RegisterAuthenticate(api.Group("/authenticate"))
api.Use(user.Authentication(true)) api.Use(user.Authentication(true))
// component-configuration endpoints required here to first add a config to the DB
// that can be associated with a new file
component_configuration.RegisterComponentConfigurationEndpoints(api.Group("/configs"))
// scenario endpoints required here to first add a scenario to the DB // scenario endpoints required here to first add a scenario to the DB
// that can be associated with a new component config
scenario.RegisterScenarioEndpoints(api.Group("/scenarios")) scenario.RegisterScenarioEndpoints(api.Group("/scenarios"))
// IC endpoints required here to first add a IC to the DB
// that can be associated with a new component config
infrastructure_component.RegisterICEndpoints(api.Group("/ic"))
// 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"))
// widget endpoints required here to first add a widget to the DB
// that can be associated with a new file
widget.RegisterWidgetEndpoints(api.Group("/widgets"))
RegisterFileEndpoints(api.Group("/files")) RegisterFileEndpoints(api.Group("/files"))
@ -226,7 +109,7 @@ func TestAddFile(t *testing.T) {
// prepare the content of the DB for testing // prepare the content of the DB for testing
// using the respective endpoints of the API // using the respective endpoints of the API
_, _, configID, _, widgetID := addScenarioAndICAndConfigAndDashboardAndWidget() scenarioID := addScenario()
// authenticate as userB who has no access to the elements in the DB // authenticate as userB who has no access to the elements in the DB
token, err := helper.AuthenticateForTest(router, token, err := helper.AuthenticateForTest(router,
@ -235,17 +118,10 @@ func TestAddFile(t *testing.T) {
emptyBuf := &bytes.Buffer{} emptyBuf := &bytes.Buffer{}
// try to POST to a component config to which UserB has no access // try to POST to a scenario to which UserB has no access
// should return a 422 unprocessable entity error // should return a 422 unprocessable entity error
code, resp, err := helper.TestEndpoint(router, token, code, resp, err := helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=config", configID), "POST", emptyBuf) fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), "POST", emptyBuf)
assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
// try to POST to a widget to which UserB has no access
// should return a 422 unprocessable entity error
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=widget", widgetID), "POST", emptyBuf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
@ -254,24 +130,17 @@ func TestAddFile(t *testing.T) {
"/api/authenticate", "POST", helper.UserACredentials) "/api/authenticate", "POST", helper.UserACredentials)
assert.NoError(t, err) assert.NoError(t, err)
// try to POST to an invalid object type // try to POST without a scenario ID
// should return a bad request error // should return a bad request error
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=wrongtype", widgetID), "POST", emptyBuf) fmt.Sprintf("/api/files"), "POST", emptyBuf)
assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
// try to POST without an object ID
// should return a bad request error
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files?objectType=config"), "POST", emptyBuf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
// try to POST an invalid file // try to POST an invalid file
// should return a bad request // should return a bad request
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=config", configID), "POST", emptyBuf) fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), "POST", emptyBuf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
@ -297,11 +166,10 @@ func TestAddFile(t *testing.T) {
contentType := bodyWriter.FormDataContentType() contentType := bodyWriter.FormDataContentType()
bodyWriter.Close() bodyWriter.Close()
//req, err := http.NewRequest("POST", "/api/files?objectID=1&objectType=widget", bodyBuf)
// Create the request // Create the request
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, err := http.NewRequest("POST", fmt.Sprintf("/api/files?objectID=%v&objectType=config", configID), bodyBuf) req, err := http.NewRequest("POST", fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), bodyBuf)
assert.NoError(t, err, "create request") assert.NoError(t, err, "create request")
req.Header.Set("Content-Type", contentType) req.Header.Set("Content-Type", contentType)
@ -342,7 +210,7 @@ func TestUpdateFile(t *testing.T) {
// prepare the content of the DB for testing // prepare the content of the DB for testing
// using the respective endpoints of the API // using the respective endpoints of the API
_, _, configID, _, _ := addScenarioAndICAndConfigAndDashboardAndWidget() scenarioID := addScenario()
// authenticate as normal user // authenticate as normal user
token, err := helper.AuthenticateForTest(router, token, err := helper.AuthenticateForTest(router,
@ -370,11 +238,10 @@ func TestUpdateFile(t *testing.T) {
contentType := bodyWriter.FormDataContentType() contentType := bodyWriter.FormDataContentType()
bodyWriter.Close() bodyWriter.Close()
//req, err := http.NewRequest("POST", "/api/files?objectID=1&objectType=widget", bodyBuf)
// Create the POST request // Create the POST request
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, err := http.NewRequest("POST", fmt.Sprintf("/api/files?objectID=%v&objectType=config", configID), bodyBuf) req, err := http.NewRequest("POST", fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), bodyBuf)
assert.NoError(t, err, "create request") assert.NoError(t, err, "create request")
req.Header.Set("Content-Type", contentType) req.Header.Set("Content-Type", contentType)
@ -476,7 +343,7 @@ func TestDeleteFile(t *testing.T) {
// prepare the content of the DB for testing // prepare the content of the DB for testing
// using the respective endpoints of the API // using the respective endpoints of the API
_, _, configID, _, widgetID := addScenarioAndICAndConfigAndDashboardAndWidget() scenarioID := addScenario()
// authenticate as normal user // authenticate as normal user
token, err := helper.AuthenticateForTest(router, token, err := helper.AuthenticateForTest(router,
@ -506,7 +373,7 @@ func TestDeleteFile(t *testing.T) {
// Create the request // Create the request
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, err := http.NewRequest("POST", fmt.Sprintf("/api/files?objectID=%v&objectType=config", configID), bodyBuf) req, err := http.NewRequest("POST", fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), bodyBuf)
assert.NoError(t, err, "create request") assert.NoError(t, err, "create request")
req.Header.Set("Content-Type", contentType) req.Header.Set("Content-Type", contentType)
req.Header.Add("Authorization", "Bearer "+token) req.Header.Add("Authorization", "Bearer "+token)
@ -516,7 +383,7 @@ func TestDeleteFile(t *testing.T) {
newFileID, err := helper.GetResponseID(w.Body) newFileID, err := helper.GetResponseID(w.Body)
assert.NoError(t, err) assert.NoError(t, err)
// add a second file to a widget, this time to a widget // add a second file to a scenario
bodyBuf2 := &bytes.Buffer{} bodyBuf2 := &bytes.Buffer{}
bodyWriter2 := multipart.NewWriter(bodyBuf2) bodyWriter2 := multipart.NewWriter(bodyBuf2)
fileWriter2, err := bodyWriter2.CreateFormFile("file", "testuploadfile.txt") fileWriter2, err := bodyWriter2.CreateFormFile("file", "testuploadfile.txt")
@ -529,7 +396,7 @@ func TestDeleteFile(t *testing.T) {
// Create the request // Create the request
w2 := httptest.NewRecorder() w2 := httptest.NewRecorder()
req2, err := http.NewRequest("POST", fmt.Sprintf("/api/files?objectID=%v&objectType=widget", widgetID), bodyBuf2) req2, err := http.NewRequest("POST", fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), bodyBuf2)
assert.NoError(t, err, "create request") assert.NoError(t, err, "create request")
req2.Header.Set("Content-Type", contentType2) req2.Header.Set("Content-Type", contentType2)
req2.Header.Add("Authorization", "Bearer "+token) req2.Header.Add("Authorization", "Bearer "+token)
@ -544,28 +411,21 @@ func TestDeleteFile(t *testing.T) {
"/api/authenticate", "POST", helper.UserBCredentials) "/api/authenticate", "POST", helper.UserBCredentials)
assert.NoError(t, err) assert.NoError(t, err)
// try to DELETE file of component config to which userB has no access // try to DELETE file from scenario to which userB has no access
// should return an unprocessable entity error // should return an unprocessable entity error
code, resp, err := helper.TestEndpoint(router, token, code, resp, err := helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files/%v", newFileID), "DELETE", nil) fmt.Sprintf("/api/files/%v", newFileID), "DELETE", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
// try to DELETE file of widget to which userB has no access
// should return an unprocessable entity error
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files/%v", newFileID2), "DELETE", nil)
assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
// authenticate as normal user // authenticate as normal user
token, err = helper.AuthenticateForTest(router, token, err = helper.AuthenticateForTest(router,
"/api/authenticate", "POST", helper.UserACredentials) "/api/authenticate", "POST", helper.UserACredentials)
assert.NoError(t, err) assert.NoError(t, err)
// Count the number of all files returned for component config // Count the number of all files returned for scenario
initialNumber, err := helper.LengthOfResponse(router, token, initialNumber, err := helper.LengthOfResponse(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=config", configID), "GET", nil) fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), "GET", nil)
assert.NoError(t, err) assert.NoError(t, err)
// try to DELETE non-existing fileID // try to DELETE non-existing fileID
@ -580,20 +440,13 @@ func TestDeleteFile(t *testing.T) {
"/api/authenticate", "POST", helper.GuestCredentials) "/api/authenticate", "POST", helper.GuestCredentials)
assert.NoError(t, err) assert.NoError(t, err)
// try to DELETE file of component config as guest // try to DELETE file of scenario as guest
// should return an unprocessable entity error // should return an unprocessable entity error
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files/%v", newFileID), "DELETE", nil) fmt.Sprintf("/api/files/%v", newFileID), "DELETE", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
// try to DELETE file of widget as guest
// should return an unprocessable entity error
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files/%v", newFileID2), "DELETE", nil)
assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
// authenticate as normal user // authenticate as normal user
token, err = helper.AuthenticateForTest(router, token, err = helper.AuthenticateForTest(router,
"/api/authenticate", "POST", helper.UserACredentials) "/api/authenticate", "POST", helper.UserACredentials)
@ -611,15 +464,15 @@ func TestDeleteFile(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 200, code, "Response body: \n%v\n", resp) assert.Equalf(t, 200, code, "Response body: \n%v\n", resp)
// Again count the number of all the files returned for component config // Again count the number of all the files returned for scenario
finalNumber, err := helper.LengthOfResponse(router, token, finalNumber, err := helper.LengthOfResponse(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=config", configID), "GET", nil) fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), "GET", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, initialNumber-1, finalNumber) assert.Equal(t, initialNumber-2, finalNumber)
} }
func TestGetAllFilesOfConfig(t *testing.T) { func TestGetAllFilesOfScenario(t *testing.T) {
database.DropTables() database.DropTables()
database.MigrateModels() database.MigrateModels()
@ -627,24 +480,17 @@ func TestGetAllFilesOfConfig(t *testing.T) {
// prepare the content of the DB for testing // prepare the content of the DB for testing
// using the respective endpoints of the API // using the respective endpoints of the API
_, _, ConfigID, _, widgetID := addScenarioAndICAndConfigAndDashboardAndWidget() scenarioID := addScenario()
// authenticate as userB who has no access to the elements in the DB // authenticate as userB who has no access to the elements in the DB
token, err := helper.AuthenticateForTest(router, token, err := helper.AuthenticateForTest(router,
"/api/authenticate", "POST", helper.UserBCredentials) "/api/authenticate", "POST", helper.UserBCredentials)
assert.NoError(t, err) assert.NoError(t, err)
// try to get all files for component config to which userB has not access // try to get all files for scenario to which userB has not access
// should return unprocessable entity error // should return unprocessable entity error
code, resp, err := helper.TestEndpoint(router, token, code, resp, err := helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=config", ConfigID), "GET", nil) fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
// try to get all files for widget to which userB has not access
// should return unprocessable entity error
code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=widget", widgetID), "GET", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 422, code, "Response body: \n%v\n", resp) assert.Equalf(t, 422, code, "Response body: \n%v\n", resp)
@ -653,26 +499,15 @@ func TestGetAllFilesOfConfig(t *testing.T) {
"/api/authenticate", "POST", helper.UserACredentials) "/api/authenticate", "POST", helper.UserACredentials)
assert.NoError(t, err) assert.NoError(t, err)
//try to get all files for unsupported object type; should return a bad request error //try to get all files with missing scenario ID; should return a bad request error
code, resp, err = helper.TestEndpoint(router, token, code, resp, err = helper.TestEndpoint(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=wrongtype", ConfigID), "GET", nil) fmt.Sprintf("/api/files"), "GET", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp) assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
//try to get all files with missing object ID; should return a bad request error // Count the number of all files returned for scenario
code, resp, err = helper.TestEndpoint(router, token, initialNumber, err := helper.LengthOfResponse(router, token,
fmt.Sprintf("/api/files?objectType=config"), "GET", nil) fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), "GET", nil)
assert.NoError(t, err)
assert.Equalf(t, 400, code, "Response body: \n%v\n", resp)
// Count the number of all files returned for component config
initialNumberConfig, err := helper.LengthOfResponse(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=config", ConfigID), "GET", nil)
assert.NoError(t, err)
// Count the number of all files returned for widget
initialNumberWidget, err := helper.LengthOfResponse(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=widget", widgetID), "GET", nil)
assert.NoError(t, err) assert.NoError(t, err)
// create a testfile.txt in local folder // create a testfile.txt in local folder
@ -685,94 +520,55 @@ func TestGetAllFilesOfConfig(t *testing.T) {
assert.NoError(t, err, "opening file") assert.NoError(t, err, "opening file")
defer fh.Close() defer fh.Close()
// test POST a file to component config and widget // test POST a file to scenario
bodyBufConfig1 := &bytes.Buffer{} bodyBuf1 := &bytes.Buffer{}
bodyBufWidget1 := &bytes.Buffer{} bodyWriter1 := multipart.NewWriter(bodyBuf1)
bodyWriterConfig1 := multipart.NewWriter(bodyBufConfig1) fileWriter1, err := bodyWriter1.CreateFormFile("file", "testuploadfile.txt")
bodyWriterWidget1 := multipart.NewWriter(bodyBufWidget1)
fileWriterConfig1, err := bodyWriterConfig1.CreateFormFile("file", "testuploadfile.txt")
assert.NoError(t, err, "writing to buffer")
fileWriterWidget1, err := bodyWriterWidget1.CreateFormFile("file", "testuploadfile.txt")
assert.NoError(t, err, "writing to buffer") assert.NoError(t, err, "writing to buffer")
// io copy // io copy
_, err = io.Copy(fileWriterConfig1, fh) _, err = io.Copy(fileWriter1, fh)
assert.NoError(t, err, "IO copy") assert.NoError(t, err, "IO copy")
_, err = io.Copy(fileWriterWidget1, fh) contentType1 := bodyWriter1.FormDataContentType()
assert.NoError(t, err, "IO copy") bodyWriter1.Close()
contentTypeConfig1 := bodyWriterConfig1.FormDataContentType()
contentTypeWidget1 := bodyWriterWidget1.FormDataContentType()
bodyWriterConfig1.Close()
bodyWriterWidget1.Close()
// Create the request for component config // Create the request
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, err := http.NewRequest("POST", fmt.Sprintf("/api/files?objectID=%v&objectType=config", ConfigID), bodyBufConfig1) req, err := http.NewRequest("POST", fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), bodyBuf1)
assert.NoError(t, err, "create request") assert.NoError(t, err, "create request")
req.Header.Set("Content-Type", contentTypeConfig1) req.Header.Set("Content-Type", contentType1)
req.Header.Add("Authorization", "Bearer "+token) req.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w, req) router.ServeHTTP(w, req)
assert.Equalf(t, 200, w.Code, "Response body: \n%v\n", w.Body) assert.Equalf(t, 200, w.Code, "Response body: \n%v\n", w.Body)
// Create the request for widget // POST a second file to scenario
w2 := httptest.NewRecorder()
req2, err := http.NewRequest("POST", fmt.Sprintf("/api/files?objectID=%v&objectType=widget", widgetID), bodyBufWidget1)
assert.NoError(t, err, "create request")
req2.Header.Set("Content-Type", contentTypeWidget1)
req2.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w2, req2)
assert.Equalf(t, 200, w2.Code, "Response body: \n%v\n", w2.Body)
// POST a second file to component config and widget
// open a second file handle // open a second file handle
fh2, err := os.Open("testfile.txt") fh2, err := os.Open("testfile.txt")
assert.NoError(t, err, "opening file") assert.NoError(t, err, "opening file")
defer fh2.Close() defer fh2.Close()
bodyBufConfig2 := &bytes.Buffer{} bodyBuf2 := &bytes.Buffer{}
bodyBufWidget2 := &bytes.Buffer{} bodyWriter2 := multipart.NewWriter(bodyBuf2)
bodyWriterConfig2 := multipart.NewWriter(bodyBufConfig2) fileWriter2, err := bodyWriter2.CreateFormFile("file", "testuploadfile2.txt")
bodyWriterWidget2 := multipart.NewWriter(bodyBufWidget2)
fileWriterConfig2, err := bodyWriterConfig2.CreateFormFile("file", "testuploadfile2.txt")
assert.NoError(t, err, "writing to buffer")
fileWriterWidget2, err := bodyWriterWidget2.CreateFormFile("file", "testuploadfile2.txt")
assert.NoError(t, err, "writing to buffer") assert.NoError(t, err, "writing to buffer")
// io copy // io copy
_, err = io.Copy(fileWriterConfig2, fh2) _, err = io.Copy(fileWriter2, fh2)
assert.NoError(t, err, "IO copy") assert.NoError(t, err, "IO copy")
_, err = io.Copy(fileWriterWidget2, fh2) contentType2 := bodyWriter2.FormDataContentType()
assert.NoError(t, err, "IO copy") bodyWriter2.Close()
contentTypeConfig2 := bodyWriterConfig2.FormDataContentType()
contentTypeWidget2 := bodyWriterWidget2.FormDataContentType()
bodyWriterConfig2.Close()
bodyWriterWidget2.Close()
w3 := httptest.NewRecorder() w2 := httptest.NewRecorder()
req3, err := http.NewRequest("POST", fmt.Sprintf("/api/files?objectID=%v&objectType=config", ConfigID), bodyBufConfig2) req2, err := http.NewRequest("POST", fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), bodyBuf2)
assert.NoError(t, err, "create request") assert.NoError(t, err, "create request")
req3.Header.Set("Content-Type", contentTypeConfig2) req2.Header.Set("Content-Type", contentType2)
req3.Header.Add("Authorization", "Bearer "+token) req2.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w3, req3) router.ServeHTTP(w2, req2)
assert.Equalf(t, 200, w3.Code, "Response body: \n%v\n", w3.Body) assert.Equalf(t, 200, w2.Code, "Response body: \n%v\n", w2.Body)
w4 := httptest.NewRecorder() // Again count the number of all the files returned for scenario
req4, err := http.NewRequest("POST", fmt.Sprintf("/api/files?objectID=%v&objectType=widget", widgetID), bodyBufWidget2) finalNumber, err := helper.LengthOfResponse(router, token,
assert.NoError(t, err, "create request") fmt.Sprintf("/api/files?scenarioID=%v", scenarioID), "GET", nil)
req4.Header.Set("Content-Type", contentTypeWidget2)
req4.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w4, req4)
assert.Equalf(t, 200, w4.Code, "Response body: \n%v\n", w4.Body)
// Again count the number of all the files returned for component config
finalNumberConfig, err := helper.LengthOfResponse(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=config", ConfigID), "GET", nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, initialNumberConfig+2, finalNumberConfig) assert.Equal(t, initialNumber+2, finalNumber)
// Again count the number of all the files returned for widget
finalNumberWidget, err := helper.LengthOfResponse(router, token,
fmt.Sprintf("/api/files?objectID=%v&objectType=widget", widgetID), "GET", nil)
assert.NoError(t, err)
assert.Equal(t, initialNumberWidget+2, finalNumberWidget)
} }

View file

@ -264,9 +264,9 @@ func AddTestData(basePath string, router *gin.Engine) (*bytes.Buffer, error) {
contentType := bodyWriter.FormDataContentType() contentType := bodyWriter.FormDataContentType()
bodyWriter.Close() bodyWriter.Close()
// Create the request and add file to component config // Create the request and add file to scenario
w1 := httptest.NewRecorder() w1 := httptest.NewRecorder()
req1, _ := http.NewRequest("POST", basePath+"/files?objectID=1&objectType=config", bodyBuf) req1, _ := http.NewRequest("POST", basePath+"/files?scenarioID=1", bodyBuf)
req1.Header.Set("Content-Type", contentType) req1.Header.Set("Content-Type", contentType)
req1.Header.Add("Authorization", "Bearer "+token) req1.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w1, req1) router.ServeHTTP(w1, req1)
@ -283,9 +283,9 @@ func AddTestData(basePath string, router *gin.Engine) (*bytes.Buffer, error) {
contentType = bodyWriter.FormDataContentType() contentType = bodyWriter.FormDataContentType()
bodyWriter.Close() bodyWriter.Close()
// Create the request and add file to widget // Create the request and add a second file to scenario
w2 := httptest.NewRecorder() w2 := httptest.NewRecorder()
req2, _ := http.NewRequest("POST", basePath+"/files?objectID=1&objectType=widget", bodyBuf) req2, _ := http.NewRequest("POST", basePath+"/files?scenarioID=1", bodyBuf)
req2.Header.Set("Content-Type", contentType) req2.Header.Set("Content-Type", contentType)
req2.Header.Add("Authorization", "Bearer "+token) req2.Header.Add("Authorization", "Bearer "+token)
router.ServeHTTP(w2, req2) router.ServeHTTP(w2, req2)