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  }