github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+incompatible/plugin/pluginenv/environment_test.go (about) 1 package pluginenv 2 3 import ( 4 "context" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "net/http/httptest" 9 "os" 10 "path/filepath" 11 "sync" 12 "testing" 13 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/mock" 16 "github.com/stretchr/testify/require" 17 18 "github.com/mattermost/mattermost-server/model" 19 "github.com/mattermost/mattermost-server/plugin" 20 "github.com/mattermost/mattermost-server/plugin/plugintest" 21 ) 22 23 type MockProvider struct { 24 mock.Mock 25 } 26 27 func (m *MockProvider) API(manifest *model.Manifest) (plugin.API, error) { 28 ret := m.Called() 29 if ret.Get(0) == nil { 30 return nil, ret.Error(1) 31 } 32 return ret.Get(0).(plugin.API), ret.Error(1) 33 } 34 35 func (m *MockProvider) Supervisor(bundle *model.BundleInfo) (plugin.Supervisor, error) { 36 ret := m.Called() 37 if ret.Get(0) == nil { 38 return nil, ret.Error(1) 39 } 40 return ret.Get(0).(plugin.Supervisor), ret.Error(1) 41 } 42 43 type MockSupervisor struct { 44 mock.Mock 45 } 46 47 func (m *MockSupervisor) Start(api plugin.API) error { 48 return m.Called(api).Error(0) 49 } 50 51 func (m *MockSupervisor) Stop() error { 52 return m.Called().Error(0) 53 } 54 55 func (m *MockSupervisor) Hooks() plugin.Hooks { 56 return m.Called().Get(0).(plugin.Hooks) 57 } 58 59 func initTmpDir(t *testing.T, files map[string]string) string { 60 success := false 61 dir, err := ioutil.TempDir("", "mm-plugin-test") 62 require.NoError(t, err) 63 defer func() { 64 if !success { 65 os.RemoveAll(dir) 66 } 67 }() 68 69 for name, contents := range files { 70 path := filepath.Join(dir, name) 71 parent := filepath.Dir(path) 72 require.NoError(t, os.MkdirAll(parent, 0700)) 73 f, err := os.Create(path) 74 require.NoError(t, err) 75 _, err = f.WriteString(contents) 76 f.Close() 77 require.NoError(t, err) 78 } 79 80 success = true 81 return dir 82 } 83 84 func TestNew_MissingOptions(t *testing.T) { 85 dir := initTmpDir(t, map[string]string{ 86 "foo/plugin.json": `{"id": "foo"}`, 87 }) 88 defer os.RemoveAll(dir) 89 90 var provider MockProvider 91 defer provider.AssertExpectations(t) 92 93 env, err := New( 94 APIProvider(provider.API), 95 ) 96 assert.Nil(t, env) 97 assert.Error(t, err) 98 } 99 100 func TestEnvironment(t *testing.T) { 101 dir := initTmpDir(t, map[string]string{ 102 ".foo/plugin.json": `{"id": "foo"}`, 103 "foo/bar": "asdf", 104 "foo/plugin.json": `{"id": "foo", "backend": {}}`, 105 "bar/zxc": "qwer", 106 "baz/plugin.yaml": "id: baz", 107 "bad/plugin.json": "asd", 108 "qwe": "asd", 109 }) 110 defer os.RemoveAll(dir) 111 112 webappDir := "notarealdirectory" 113 114 var provider MockProvider 115 defer provider.AssertExpectations(t) 116 117 env, err := New( 118 SearchPath(dir), 119 WebappPath(webappDir), 120 APIProvider(provider.API), 121 SupervisorProvider(provider.Supervisor), 122 ) 123 require.NoError(t, err) 124 defer env.Shutdown() 125 126 plugins, err := env.Plugins() 127 assert.NoError(t, err) 128 assert.Len(t, plugins, 3) 129 130 activePlugins := env.ActivePlugins() 131 assert.Len(t, activePlugins, 0) 132 133 assert.Error(t, env.ActivatePlugin("x")) 134 135 var api struct{ plugin.API } 136 var supervisor MockSupervisor 137 defer supervisor.AssertExpectations(t) 138 var hooks plugintest.Hooks 139 defer hooks.AssertExpectations(t) 140 141 provider.On("API").Return(&api, nil) 142 provider.On("Supervisor").Return(&supervisor, nil) 143 144 supervisor.On("Start", &api).Return(nil) 145 supervisor.On("Stop").Return(nil) 146 supervisor.On("Hooks").Return(&hooks) 147 148 assert.NoError(t, env.ActivatePlugin("foo")) 149 assert.Equal(t, env.ActivePluginIds(), []string{"foo"}) 150 activePlugins = env.ActivePlugins() 151 assert.Len(t, activePlugins, 1) 152 assert.NoError(t, env.ActivatePlugin("foo")) 153 assert.True(t, env.IsPluginActive("foo")) 154 155 hooks.On("OnDeactivate").Return(nil) 156 assert.NoError(t, env.DeactivatePlugin("foo")) 157 assert.Error(t, env.DeactivatePlugin("foo")) 158 assert.False(t, env.IsPluginActive("foo")) 159 160 assert.NoError(t, env.ActivatePlugin("foo")) 161 assert.Equal(t, env.ActivePluginIds(), []string{"foo"}) 162 163 assert.Equal(t, env.SearchPath(), dir) 164 assert.Equal(t, env.WebappPath(), webappDir) 165 166 assert.Empty(t, env.Shutdown()) 167 } 168 169 func TestEnvironment_DuplicatePluginError(t *testing.T) { 170 dir := initTmpDir(t, map[string]string{ 171 "foo/plugin.json": `{"id": "foo"}`, 172 "foo2/plugin.json": `{"id": "foo"}`, 173 }) 174 defer os.RemoveAll(dir) 175 176 var provider MockProvider 177 defer provider.AssertExpectations(t) 178 179 env, err := New( 180 SearchPath(dir), 181 APIProvider(provider.API), 182 SupervisorProvider(provider.Supervisor), 183 ) 184 require.NoError(t, err) 185 defer env.Shutdown() 186 187 assert.Error(t, env.ActivatePlugin("foo")) 188 assert.Empty(t, env.ActivePluginIds()) 189 } 190 191 func TestEnvironment_BadSearchPathError(t *testing.T) { 192 var provider MockProvider 193 defer provider.AssertExpectations(t) 194 195 env, err := New( 196 SearchPath("thissearchpathshouldnotexist!"), 197 APIProvider(provider.API), 198 SupervisorProvider(provider.Supervisor), 199 ) 200 require.NoError(t, err) 201 defer env.Shutdown() 202 203 assert.Error(t, env.ActivatePlugin("foo")) 204 assert.Empty(t, env.ActivePluginIds()) 205 } 206 207 func TestEnvironment_ActivatePluginErrors(t *testing.T) { 208 dir := initTmpDir(t, map[string]string{ 209 "foo/plugin.json": `{"id": "foo", "backend": {}}`, 210 }) 211 defer os.RemoveAll(dir) 212 213 var provider MockProvider 214 215 env, err := New( 216 SearchPath(dir), 217 APIProvider(provider.API), 218 SupervisorProvider(provider.Supervisor), 219 ) 220 require.NoError(t, err) 221 defer env.Shutdown() 222 223 var api struct{ plugin.API } 224 var supervisor MockSupervisor 225 var hooks plugintest.Hooks 226 227 for name, setup := range map[string]func(){ 228 "SupervisorProviderError": func() { 229 provider.On("Supervisor").Return(nil, fmt.Errorf("test error")) 230 }, 231 "APIProviderError": func() { 232 provider.On("API").Return(plugin.API(nil), fmt.Errorf("test error")) 233 provider.On("Supervisor").Return(&supervisor, nil) 234 }, 235 "SupervisorError": func() { 236 provider.On("API").Return(&api, nil) 237 provider.On("Supervisor").Return(&supervisor, nil) 238 239 supervisor.On("Start", &api).Return(fmt.Errorf("test error")) 240 }, 241 } { 242 t.Run(name, func(t *testing.T) { 243 supervisor.Mock = mock.Mock{} 244 hooks.Mock = mock.Mock{} 245 provider.Mock = mock.Mock{} 246 setup() 247 assert.Error(t, env.ActivatePlugin("foo")) 248 assert.Empty(t, env.ActivePluginIds()) 249 supervisor.AssertExpectations(t) 250 hooks.AssertExpectations(t) 251 provider.AssertExpectations(t) 252 }) 253 } 254 } 255 256 func TestEnvironment_ShutdownError(t *testing.T) { 257 dir := initTmpDir(t, map[string]string{ 258 "foo/plugin.json": `{"id": "foo", "backend": {}}`, 259 }) 260 defer os.RemoveAll(dir) 261 262 var provider MockProvider 263 defer provider.AssertExpectations(t) 264 265 env, err := New( 266 SearchPath(dir), 267 APIProvider(provider.API), 268 SupervisorProvider(provider.Supervisor), 269 ) 270 require.NoError(t, err) 271 defer env.Shutdown() 272 273 var api struct{ plugin.API } 274 var supervisor MockSupervisor 275 defer supervisor.AssertExpectations(t) 276 var hooks plugintest.Hooks 277 defer hooks.AssertExpectations(t) 278 279 provider.On("API").Return(&api, nil) 280 provider.On("Supervisor").Return(&supervisor, nil) 281 282 supervisor.On("Start", &api).Return(nil) 283 supervisor.On("Stop").Return(fmt.Errorf("test error")) 284 supervisor.On("Hooks").Return(&hooks) 285 286 hooks.On("OnDeactivate").Return(fmt.Errorf("test error")) 287 288 assert.NoError(t, env.ActivatePlugin("foo")) 289 assert.Equal(t, env.ActivePluginIds(), []string{"foo"}) 290 assert.Len(t, env.Shutdown(), 2) 291 } 292 293 func TestEnvironment_ConcurrentHookInvocations(t *testing.T) { 294 dir := initTmpDir(t, map[string]string{ 295 "foo/plugin.json": `{"id": "foo", "backend": {}}`, 296 }) 297 defer os.RemoveAll(dir) 298 299 var provider MockProvider 300 defer provider.AssertExpectations(t) 301 302 var api struct{ plugin.API } 303 var supervisor MockSupervisor 304 defer supervisor.AssertExpectations(t) 305 var hooks plugintest.Hooks 306 defer hooks.AssertExpectations(t) 307 308 env, err := New( 309 SearchPath(dir), 310 APIProvider(provider.API), 311 SupervisorProvider(provider.Supervisor), 312 ) 313 require.NoError(t, err) 314 defer env.Shutdown() 315 316 provider.On("API").Return(&api, nil) 317 provider.On("Supervisor").Return(&supervisor, nil) 318 319 supervisor.On("Start", &api).Return(nil) 320 supervisor.On("Stop").Return(nil) 321 supervisor.On("Hooks").Return(&hooks) 322 323 ch := make(chan bool) 324 325 hooks.On("OnDeactivate").Return(nil) 326 hooks.On("ServeHTTP", mock.AnythingOfType("*httptest.ResponseRecorder"), mock.AnythingOfType("*http.Request")).Run(func(args mock.Arguments) { 327 r := args.Get(1).(*http.Request) 328 if r.URL.Path == "/1" { 329 <-ch 330 } else { 331 ch <- true 332 } 333 }) 334 335 assert.NoError(t, env.ActivatePlugin("foo")) 336 337 rec := httptest.NewRecorder() 338 339 wg := sync.WaitGroup{} 340 wg.Add(2) 341 342 go func() { 343 req, err := http.NewRequest("GET", "/1", nil) 344 require.NoError(t, err) 345 env.Hooks().ServeHTTP(rec, req.WithContext(context.WithValue(context.Background(), "plugin_id", "foo"))) 346 wg.Done() 347 }() 348 349 go func() { 350 req, err := http.NewRequest("GET", "/2", nil) 351 require.NoError(t, err) 352 env.Hooks().ServeHTTP(rec, req.WithContext(context.WithValue(context.Background(), "plugin_id", "foo"))) 353 wg.Done() 354 }() 355 356 wg.Wait() 357 } 358 359 func TestEnvironment_HooksForPlugins(t *testing.T) { 360 dir := initTmpDir(t, map[string]string{ 361 "foo/plugin.json": `{"id": "foo", "backend": {}}`, 362 }) 363 defer os.RemoveAll(dir) 364 365 var provider MockProvider 366 defer provider.AssertExpectations(t) 367 368 env, err := New( 369 SearchPath(dir), 370 APIProvider(provider.API), 371 SupervisorProvider(provider.Supervisor), 372 ) 373 require.NoError(t, err) 374 defer env.Shutdown() 375 376 var api struct{ plugin.API } 377 var supervisor MockSupervisor 378 defer supervisor.AssertExpectations(t) 379 var hooks plugintest.Hooks 380 defer hooks.AssertExpectations(t) 381 382 provider.On("API").Return(&api, nil) 383 provider.On("Supervisor").Return(&supervisor, nil) 384 385 supervisor.On("Start", &api).Return(nil) 386 supervisor.On("Stop").Return(nil) 387 supervisor.On("Hooks").Return(&hooks) 388 389 hooks.On("OnDeactivate").Return(nil) 390 hooks.On("ExecuteCommand", mock.AnythingOfType("*model.CommandArgs")).Return(&model.CommandResponse{ 391 Text: "bar", 392 }, nil) 393 394 assert.NoError(t, env.ActivatePlugin("foo")) 395 assert.Equal(t, env.ActivePluginIds(), []string{"foo"}) 396 397 resp, appErr, err := env.HooksForPlugin("foo").ExecuteCommand(&model.CommandArgs{ 398 Command: "/foo", 399 }) 400 assert.Equal(t, "bar", resp.Text) 401 assert.Nil(t, appErr) 402 assert.NoError(t, err) 403 404 assert.Empty(t, env.Shutdown()) 405 }