github.com/arduino/arduino-cloud-cli@v0.0.0-20240517070944-e7a449561083/internal/template/load_test.go (about) 1 // This file is part of arduino-cloud-cli. 2 // 3 // Copyright (C) 2021 ARDUINO SA (http://www.arduino.cc/) 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published 7 // by the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 package template 19 20 import ( 21 "context" 22 "testing" 23 24 iotclient "github.com/arduino/iot-client-go" 25 "github.com/gofrs/uuid" 26 "github.com/google/go-cmp/cmp" 27 ) 28 29 const ( 30 // Real IDs will be UUIDs v4 like this: 9231a50b-8680-4489-a465-2b769fc310cb 31 // Here we use these text strings to improve test errors readability 32 switchyID = "switchy-id" 33 relayID = "relay-id" 34 blinkSpeedID = "blink_speed-id" 35 36 thingOverriddenID = "thing-overridden-id" 37 switchyOverriddenID = "switchy-overridden-id" 38 ) 39 40 var ( 41 dashboardTemplateTest = map[string]interface{}{ 42 "id": "home-security-alarm-dashboard", 43 "name": "Home Security Alarm", 44 "widgets": []interface{}{ 45 map[string]interface{}{ 46 "type": "Messenger", "name": "message_update", 47 "variables": []interface{}{map[string]interface{}{"thing_id": "home-security-alarm", "variable_id": "message_update"}}, 48 }, 49 map[string]interface{}{ 50 "type": "Switch", "name": "light_alarm", 51 "variables": []interface{}{map[string]interface{}{"thing_id": "home-security-alarm", "variable_id": "light_alarm"}}, 52 "options": map[string]interface{}{"showLabels": true}, 53 }, 54 }, 55 } 56 57 dashboardDetailed = &iotclient.Dashboardv2{ 58 Name: "dashboard", 59 Widgets: []iotclient.Widget{ 60 {Name: "Switch-name", Height: 1, HeightMobile: 2, Width: 3, WidthMobile: 4, 61 X: 5, XMobile: 6, Y: 7, YMobile: 8, Options: map[string]interface{}{"showLabels": true}, 62 Type: "Switch", 63 }, 64 }, 65 } 66 67 dashboardNoOptions = &iotclient.Dashboardv2{ 68 Name: "dashboard-no-options", 69 Widgets: []iotclient.Widget{ 70 {Name: "Switch-name", Height: 1, HeightMobile: 2, Width: 3, WidthMobile: 4, 71 X: 5, XMobile: 6, Y: 7, YMobile: 8, Options: map[string]interface{}{}, 72 Type: "Switch", 73 }, 74 }, 75 } 76 77 dashboardWithVariable = &iotclient.Dashboardv2{ 78 Name: "dashboard-with-variable", 79 Widgets: []iotclient.Widget{ 80 {Name: "Switch-name", Height: 1, HeightMobile: 2, Width: 3, WidthMobile: 4, 81 X: 5, XMobile: 6, Y: 7, YMobile: 8, Options: map[string]interface{}{"showLabels": true}, Type: "Switch", 82 Variables: []string{switchyID}, 83 }, 84 }, 85 } 86 87 dashboardVariableOverride = &iotclient.Dashboardv2{ 88 Name: "dashboard-with-variable", 89 Widgets: []iotclient.Widget{ 90 {Name: "Switch-name", Height: 1, HeightMobile: 2, Width: 3, WidthMobile: 4, 91 X: 5, XMobile: 6, Y: 7, YMobile: 8, Options: map[string]interface{}{"showLabels": true}, Type: "Switch", 92 Variables: []string{switchyOverriddenID}, 93 }, 94 }, 95 } 96 97 dashboardTwoWidgets = &iotclient.Dashboardv2{ 98 Name: "dashboard-two-widgets", 99 Widgets: []iotclient.Widget{ 100 {Name: "blink_speed", Height: 7, Width: 8, 101 X: 7, Y: 5, Options: map[string]interface{}{"min": float64(0), "max": float64(5000)}, Type: "Slider", 102 Variables: []string{blinkSpeedID}, 103 }, 104 {Name: "relay_2", Height: 5, Width: 5, 105 X: 5, Y: 0, Options: map[string]interface{}{"showLabels": true}, Type: "Switch", 106 Variables: []string{relayID}, 107 }, 108 }, 109 } 110 ) 111 112 func TestLoadTemplate(t *testing.T) { 113 tests := []struct { 114 name string 115 file string 116 override map[string]string 117 want map[string]interface{} 118 }{ 119 120 { 121 name: "yaml dashboard template", 122 file: "testdata/home-security-dashboard.yaml", 123 want: dashboardTemplateTest, 124 }, 125 126 { 127 name: "json dashboard template", 128 file: "testdata/home-security-dashboard.json", 129 want: dashboardTemplateTest, 130 }, 131 } 132 133 for _, tt := range tests { 134 t.Run(tt.name, func(t *testing.T) { 135 var got map[string]interface{} 136 err := loadTemplate(tt.file, &got) 137 if err != nil { 138 t.Errorf("%v", err) 139 } 140 if !cmp.Equal(got, tt.want) { 141 t.Errorf("Wrong template received, diff:\n%s", cmp.Diff(tt.want, got)) 142 } 143 }) 144 } 145 } 146 147 type thingShowTest struct{} 148 149 func (t *thingShowTest) ThingShow(ctx context.Context, thingID string) (*iotclient.ArduinoThing, error) { 150 if thingID == thingOverriddenID { 151 return &iotclient.ArduinoThing{ 152 Properties: []iotclient.ArduinoProperty{ 153 {Id: switchyOverriddenID, VariableName: "switchy"}, 154 }, 155 }, nil 156 } 157 return &iotclient.ArduinoThing{ 158 Properties: []iotclient.ArduinoProperty{ 159 {Id: switchyID, VariableName: "switchy"}, 160 {Id: relayID, VariableName: "relay_2"}, 161 {Id: blinkSpeedID, VariableName: "blink_speed"}, 162 }, 163 }, nil 164 } 165 166 func TestLoadDashboard(t *testing.T) { 167 mockThingShow := &thingShowTest{} 168 tests := []struct { 169 name string 170 file string 171 override map[string]string 172 want *iotclient.Dashboardv2 173 }{ 174 { 175 name: "dashboard detailed", 176 file: "testdata/dashboard-detailed.yaml", 177 override: nil, 178 want: dashboardDetailed, 179 }, 180 181 { 182 name: "dashboard with wrong options to be filtered out", 183 file: "testdata/dashboard-wrong-options.yaml", 184 override: nil, 185 want: dashboardDetailed, 186 }, 187 188 { 189 name: "dashboard without options, should have a not nil map", 190 file: "testdata/dashboard-no-options.yaml", 191 override: nil, 192 want: dashboardNoOptions, 193 }, 194 195 { 196 name: "dashboard with variable, mocked variable id is concatenation of thing_id and variable_id", 197 file: "testdata/dashboard-with-variable.yaml", 198 override: nil, 199 want: dashboardWithVariable, 200 }, 201 202 { 203 name: "dashboard with variable, thing is overridden", 204 file: "testdata/dashboard-with-variable.yaml", 205 override: map[string]string{"thing": thingOverriddenID}, 206 want: dashboardVariableOverride, 207 }, 208 209 { 210 name: "dashboard with two widgets", 211 file: "testdata/dashboard-two-widgets.yaml", 212 override: nil, 213 want: dashboardTwoWidgets, 214 }, 215 } 216 217 for _, tt := range tests { 218 t.Run(tt.name, func(t *testing.T) { 219 got, err := LoadDashboard(context.TODO(), tt.file, tt.override, mockThingShow) 220 if err != nil { 221 t.Errorf("%v", err) 222 } 223 224 for i := range got.Widgets { 225 // check widget id generation 226 id := got.Widgets[i].Id 227 _, err := uuid.FromString(id) 228 if err != nil { 229 t.Errorf("Widget ID is not a valid UUID: %s", id) 230 } 231 // Remove generated id to be able to compare the widget with the expected one 232 got.Widgets[i].Id = "" 233 } 234 235 if !cmp.Equal(got, tt.want) { 236 t.Errorf("Wrong template received, diff:\n%s", cmp.Diff(tt.want, got)) 237 } 238 }) 239 } 240 }