github.com/keys-pub/mattermost-server@v4.10.10+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  }