github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/plugin/plugin_ui_test.go (about) 1 package plugin_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "html/template" 7 "net/http" 8 "testing" 9 10 "github.com/evergreen-ci/evergreen/model/user" 11 "github.com/evergreen-ci/evergreen/plugin" 12 "github.com/pkg/errors" 13 . "github.com/smartystreets/goconvey/convey" 14 ) 15 16 // ===== Mock UI Plugin ===== 17 18 // simple plugin type that has a name and ui config 19 type MockUIPlugin struct { 20 NickName string 21 Conf *plugin.PanelConfig 22 } 23 24 func (self *MockUIPlugin) Name() string { 25 return self.NickName 26 } 27 func (self *MockUIPlugin) GetAPIHandler() http.Handler { 28 return nil 29 } 30 func (self *MockUIPlugin) GetUIHandler() http.Handler { 31 return nil 32 } 33 34 func (self *MockUIPlugin) Configure(conf map[string]interface{}) error { 35 return nil 36 } 37 38 func (self *MockUIPlugin) NewCommand(commandName string) (plugin.Command, error) { 39 return nil, nil 40 } 41 func (self *MockUIPlugin) GetPanelConfig() (*plugin.PanelConfig, error) { 42 return self.Conf, nil 43 } 44 45 // ===== Tests ===== 46 47 func TestPanelManagerRegistration(t *testing.T) { 48 var ppm plugin.PanelManager 49 Convey("With a simple plugin panel manager", t, func() { 50 ppm = &plugin.SimplePanelManager{} 51 52 Convey("and a registered set of test plugins without panels", func() { 53 uselessPlugins := []plugin.UIPlugin{ 54 &MockUIPlugin{ 55 NickName: "no_ui_config", 56 Conf: nil, 57 }, 58 &MockUIPlugin{ 59 NickName: "config_with_no_panels", 60 Conf: &plugin.PanelConfig{}, 61 }, 62 } 63 err := ppm.RegisterPlugins(uselessPlugins) 64 So(err, ShouldBeNil) 65 66 Convey("no ui panel data should be returned for any scope", func() { 67 data, err := ppm.UIData(plugin.UIContext{}, plugin.TaskPage) 68 So(err, ShouldBeNil) 69 So(data["no_ui_config"], ShouldBeNil) 70 So(data["config_with_no_panels"], ShouldBeNil) 71 data, err = ppm.UIData(plugin.UIContext{}, plugin.BuildPage) 72 So(err, ShouldBeNil) 73 So(data["no_ui_config"], ShouldBeNil) 74 So(data["config_with_no_panels"], ShouldBeNil) 75 data, err = ppm.UIData(plugin.UIContext{}, plugin.VersionPage) 76 So(err, ShouldBeNil) 77 So(data["no_ui_config"], ShouldBeNil) 78 So(data["config_with_no_panels"], ShouldBeNil) 79 }) 80 }) 81 82 Convey("registering a plugin panel with no page should fail", func() { 83 badPanelPlugins := []plugin.UIPlugin{ 84 &MockUIPlugin{ 85 NickName: "bad_panel", 86 Conf: &plugin.PanelConfig{ 87 Panels: []plugin.UIPanel{ 88 {PanelHTML: "<marquee> PANEL </marquee>"}, 89 }, 90 }, 91 }, 92 } 93 err := ppm.RegisterPlugins(badPanelPlugins) 94 So(err, ShouldNotBeNil) 95 So(err.Error(), ShouldContainSubstring, "Page") 96 }) 97 98 Convey("registering the same plugin name twice should fail", func() { 99 conflictingPlugins := []plugin.UIPlugin{ 100 &MockUIPlugin{ 101 NickName: "a", 102 Conf: nil, 103 }, 104 &MockUIPlugin{ 105 NickName: "a", 106 Conf: &plugin.PanelConfig{}, 107 }, 108 } 109 err := ppm.RegisterPlugins(conflictingPlugins) 110 So(err, ShouldNotBeNil) 111 So(err.Error(), ShouldContainSubstring, "already") 112 }) 113 114 Convey("registering more than one data function to the same page "+ 115 "for the same plugin should fail", func() { 116 dataPlugins := []plugin.UIPlugin{ 117 &MockUIPlugin{ 118 NickName: "data_function_fan", 119 Conf: &plugin.PanelConfig{ 120 Panels: []plugin.UIPanel{ 121 { 122 Page: plugin.TaskPage, 123 DataFunc: func(context plugin.UIContext) (interface{}, error) { 124 return 100, nil 125 }}, 126 { 127 Page: plugin.TaskPage, 128 DataFunc: func(context plugin.UIContext) (interface{}, error) { 129 return nil, errors.New("this function just errors") 130 }}, 131 }, 132 }, 133 }, 134 } 135 err := ppm.RegisterPlugins(dataPlugins) 136 So(err, ShouldNotBeNil) 137 So(err.Error(), ShouldContainSubstring, "function is already registered") 138 }) 139 }) 140 } 141 142 func TestPanelManagerRetrieval(t *testing.T) { 143 var ppm plugin.PanelManager 144 145 Convey("With a simple plugin panel manager", t, func() { 146 ppm = &plugin.SimplePanelManager{} 147 148 Convey("and a registered set of test plugins with panels", func() { 149 // These 3 plugins exist to check the sort output of the manager. 150 // For consistency, plugin panels and includes are ordered by plugin name 151 // and then by the order of their declaration in the Panels array. 152 // This test asserts that the panels in A come before B which come 153 // before C, even though they are not in the plugin array in that order. 154 testPlugins := []plugin.UIPlugin{ 155 &MockUIPlugin{ 156 NickName: "A_the_first_letter", 157 Conf: &plugin.PanelConfig{ 158 Panels: []plugin.UIPanel{ 159 { 160 Page: plugin.TaskPage, 161 Position: plugin.PageCenter, 162 Includes: []template.HTML{ 163 "0", 164 "1", 165 }, 166 PanelHTML: "0", 167 DataFunc: func(context plugin.UIContext) (interface{}, error) { 168 return 1000, nil 169 }, 170 }, 171 { 172 Page: plugin.TaskPage, 173 Position: plugin.PageCenter, 174 Includes: []template.HTML{ 175 "2", 176 "3", 177 }, 178 PanelHTML: "1", 179 }, 180 { 181 Page: plugin.TaskPage, 182 Position: plugin.PageLeft, 183 Includes: []template.HTML{ 184 "4", 185 }, 186 PanelHTML: "X", 187 }, 188 }, 189 }, 190 }, 191 &MockUIPlugin{ 192 NickName: "C_the_third_letter", 193 Conf: &plugin.PanelConfig{ 194 Panels: []plugin.UIPanel{ 195 { 196 Page: plugin.TaskPage, 197 Position: plugin.PageCenter, 198 Includes: []template.HTML{ 199 "7", 200 "8", 201 }, 202 PanelHTML: "3", 203 DataFunc: func(context plugin.UIContext) (interface{}, error) { 204 return 2112, nil 205 }, 206 }, 207 }, 208 }, 209 }, 210 &MockUIPlugin{ 211 NickName: "B_the_middle_letter", 212 Conf: &plugin.PanelConfig{ 213 Panels: []plugin.UIPanel{ 214 { 215 Page: plugin.TaskPage, 216 Position: plugin.PageCenter, 217 Includes: []template.HTML{ 218 "5", 219 }, 220 PanelHTML: "2", 221 DataFunc: func(context plugin.UIContext) (interface{}, error) { 222 return 1776, nil 223 }, 224 }, 225 { 226 Page: plugin.TaskPage, 227 Position: plugin.PageLeft, 228 Includes: []template.HTML{ 229 "6", 230 }, 231 PanelHTML: "Z", 232 }, 233 }, 234 }, 235 }, 236 } 237 238 err := ppm.RegisterPlugins(testPlugins) 239 So(err, ShouldBeNil) 240 241 Convey("retrieved includes for the task page should be in correct "+ 242 "stable alphabetical order by plugin name", func() { 243 includes, err := ppm.Includes(plugin.TaskPage) 244 So(err, ShouldBeNil) 245 So(includes, ShouldNotBeNil) 246 247 // includes == [0 1 2 ... ] 248 for i := 1; i < len(includes); i++ { 249 So(includes[i], ShouldBeGreaterThan, includes[i-1]) 250 } 251 }) 252 Convey("retrieved panel HTML for the task page should be in correct "+ 253 "stable alphabetical order by plugin name", func() { 254 panels, err := ppm.Panels(plugin.TaskPage) 255 So(err, ShouldBeNil) 256 So(len(panels.Right), ShouldEqual, 0) 257 So(len(panels.Left), ShouldBeGreaterThan, 0) 258 So(len(panels.Center), ShouldBeGreaterThan, 0) 259 260 // left == [X Z] 261 So(panels.Left[0], ShouldBeLessThan, panels.Left[1]) 262 263 // center == [0 1 2 3] 264 for i := 1; i < len(panels.Center); i++ { 265 So(panels.Center[i], ShouldBeGreaterThan, panels.Center[i-1]) 266 } 267 }) 268 Convey("data functions populate the results map with their return values", func() { 269 uiData, err := ppm.UIData(plugin.UIContext{}, plugin.TaskPage) 270 So(err, ShouldBeNil) 271 So(len(uiData), ShouldBeGreaterThan, 0) 272 So(uiData["A_the_first_letter"], ShouldEqual, 1000) 273 So(uiData["B_the_middle_letter"], ShouldEqual, 1776) 274 So(uiData["C_the_third_letter"], ShouldEqual, 2112) 275 }) 276 }) 277 }) 278 } 279 280 func TestPluginUIDataFunctionErrorHandling(t *testing.T) { 281 var ppm plugin.PanelManager 282 283 Convey("With a simple plugin panel manager", t, func() { 284 ppm = &plugin.SimplePanelManager{} 285 286 Convey("and a set of plugins, some with erroring data functions", func() { 287 errorPlugins := []plugin.UIPlugin{ 288 &MockUIPlugin{ 289 NickName: "error1", 290 Conf: &plugin.PanelConfig{ 291 Panels: []plugin.UIPanel{ 292 { 293 Page: plugin.TaskPage, 294 Position: plugin.PageCenter, 295 DataFunc: func(context plugin.UIContext) (interface{}, error) { 296 return nil, errors.New("Error #1") 297 }, 298 }, 299 }, 300 }, 301 }, 302 &MockUIPlugin{ 303 NickName: "error2", 304 Conf: &plugin.PanelConfig{ 305 Panels: []plugin.UIPanel{ 306 { 307 Page: plugin.TaskPage, 308 Position: plugin.PageCenter, 309 DataFunc: func(context plugin.UIContext) (interface{}, error) { 310 return nil, errors.New("Error #2") 311 }, 312 }, 313 }, 314 }, 315 }, 316 &MockUIPlugin{ 317 NickName: "error3 not found", 318 Conf: &plugin.PanelConfig{ 319 Panels: []plugin.UIPanel{ 320 { 321 Page: plugin.TaskPage, 322 Position: plugin.PageCenter, 323 DataFunc: func(_ plugin.UIContext) (interface{}, error) { 324 return nil, errors.New("Error") 325 }, 326 }, 327 }, 328 }, 329 }, 330 &MockUIPlugin{ 331 NickName: "good", 332 Conf: &plugin.PanelConfig{ 333 Panels: []plugin.UIPanel{ 334 { 335 Page: plugin.TaskPage, 336 Position: plugin.PageCenter, 337 DataFunc: func(_ plugin.UIContext) (interface{}, error) { 338 return "fine", nil 339 }, 340 }, 341 }, 342 }, 343 }, 344 } 345 err := ppm.RegisterPlugins(errorPlugins) 346 So(err, ShouldBeNil) 347 data, err := ppm.UIData(plugin.UIContext{}, plugin.TaskPage) 348 So(err, ShouldNotBeNil) 349 350 Convey("non-broken functions should succeed", func() { 351 So(data["good"], ShouldEqual, "fine") 352 }) 353 354 Convey("and reasonable error messages should be produced for failures", func() { 355 So(err.Error(), ShouldContainSubstring, "error1") 356 So(err.Error(), ShouldContainSubstring, "Error #1") 357 So(err.Error(), ShouldContainSubstring, "error2") 358 So(err.Error(), ShouldContainSubstring, "Error #2") 359 So(err.Error(), ShouldContainSubstring, "error3") 360 }) 361 }) 362 Convey("and a plugin that panics", func() { 363 errorPlugins := []plugin.UIPlugin{ 364 &MockUIPlugin{ 365 NickName: "busted", 366 Conf: &plugin.PanelConfig{ 367 Panels: []plugin.UIPanel{ 368 { 369 Page: plugin.TaskPage, 370 Position: plugin.PageCenter, 371 DataFunc: func(_ plugin.UIContext) (interface{}, error) { 372 panic("BOOM") 373 }, 374 }, 375 }, 376 }, 377 }, 378 &MockUIPlugin{ 379 NickName: "good", 380 Conf: &plugin.PanelConfig{ 381 Panels: []plugin.UIPanel{ 382 { 383 Page: plugin.TaskPage, 384 Position: plugin.PageCenter, 385 DataFunc: func(_ plugin.UIContext) (interface{}, error) { 386 return "still fine", nil 387 }, 388 }, 389 }, 390 }, 391 }, 392 } 393 394 Convey("reasonable error messages should be produced", func() { 395 err := ppm.RegisterPlugins(errorPlugins) 396 So(err, ShouldBeNil) 397 data, err := ppm.UIData(plugin.UIContext{}, plugin.TaskPage) 398 So(err, ShouldNotBeNil) 399 So(data["good"], ShouldEqual, "still fine") 400 So(err.Error(), ShouldContainSubstring, "panic") 401 So(err.Error(), ShouldContainSubstring, "BOOM") 402 So(err.Error(), ShouldContainSubstring, "busted") 403 }) 404 }) 405 }) 406 } 407 408 func TestUIDataInjection(t *testing.T) { 409 var ppm plugin.PanelManager 410 411 Convey("With a simple plugin panel manager", t, func() { 412 ppm = &plugin.SimplePanelManager{} 413 414 Convey("and a registered set of test plugins with injection needs", func() { 415 funcPlugins := []plugin.UIPlugin{ 416 &MockUIPlugin{ 417 NickName: "combine", 418 Conf: &plugin.PanelConfig{ 419 Panels: []plugin.UIPanel{ 420 { 421 Page: plugin.TaskPage, 422 Position: plugin.PageCenter, 423 DataFunc: func(ctx plugin.UIContext) (interface{}, error) { 424 return ctx.Task.Id + ctx.Build.Id + ctx.Version.Id, nil 425 }, 426 }, 427 }, 428 }, 429 }, 430 &MockUIPlugin{ 431 NickName: "userhttpapiserver", 432 Conf: &plugin.PanelConfig{ 433 Panels: []plugin.UIPanel{ 434 { 435 Page: plugin.TaskPage, 436 Position: plugin.PageCenter, 437 DataFunc: func(ctx plugin.UIContext) (interface{}, error) { 438 return fmt.Sprintf("%v.%v@%v", ctx.User.Email, ctx.Settings.ApiUrl), nil 439 }, 440 }, 441 }, 442 }, 443 }, 444 } 445 err := ppm.RegisterPlugins(funcPlugins) 446 So(err, ShouldBeNil) 447 }) 448 }) 449 } 450 451 func TestUserInjection(t *testing.T) { 452 Convey("With a dbUser and a request", t, func() { 453 u := &user.DBUser{Id: "name1"} 454 r, err := http.NewRequest("GET", "/", bytes.NewBufferString("{}")) 455 So(err, ShouldBeNil) 456 457 Convey("the user should possible to set and retrieve", func() { 458 plugin.SetUser(u, r) 459 So(plugin.GetUser(r), ShouldResemble, u) 460 }) 461 }) 462 }