github.com/nhannv/mattermost-server@v5.11.1+incompatible/model/manifest_test.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package model
     5  
     6  import (
     7  	"encoding/json"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"testing"
    13  
    14  	"gopkg.in/yaml.v2"
    15  
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  )
    19  
    20  func TestFindManifest(t *testing.T) {
    21  	for _, tc := range []struct {
    22  		Filename       string
    23  		Contents       string
    24  		ExpectError    bool
    25  		ExpectNotExist bool
    26  	}{
    27  		{"foo", "bar", true, true},
    28  		{"plugin.json", "bar", true, false},
    29  		{"plugin.json", `{"id": "foo"}`, false, false},
    30  		{"plugin.json", `{"id": "FOO"}`, false, false},
    31  		{"plugin.yaml", `id: foo`, false, false},
    32  		{"plugin.yaml", "bar", true, false},
    33  		{"plugin.yml", `id: foo`, false, false},
    34  		{"plugin.yml", `id: FOO`, false, false},
    35  		{"plugin.yml", "bar", true, false},
    36  	} {
    37  		dir, err := ioutil.TempDir("", "mm-plugin-test")
    38  		require.NoError(t, err)
    39  		defer os.RemoveAll(dir)
    40  
    41  		path := filepath.Join(dir, tc.Filename)
    42  		f, err := os.Create(path)
    43  		require.NoError(t, err)
    44  		_, err = f.WriteString(tc.Contents)
    45  		f.Close()
    46  		require.NoError(t, err)
    47  
    48  		m, mpath, err := FindManifest(dir)
    49  		assert.True(t, (err != nil) == tc.ExpectError, tc.Filename)
    50  		assert.True(t, (err != nil && os.IsNotExist(err)) == tc.ExpectNotExist, tc.Filename)
    51  		if !tc.ExpectNotExist {
    52  			assert.Equal(t, path, mpath, tc.Filename)
    53  		} else {
    54  			assert.Empty(t, mpath, tc.Filename)
    55  		}
    56  		if !tc.ExpectError {
    57  			require.NotNil(t, m, tc.Filename)
    58  			assert.NotEmpty(t, m.Id, tc.Filename)
    59  			assert.Equal(t, strings.ToLower(m.Id), m.Id)
    60  		}
    61  	}
    62  }
    63  
    64  func TestManifestUnmarshal(t *testing.T) {
    65  	expected := Manifest{
    66  		Id:               "theid",
    67  		MinServerVersion: "5.6.0",
    68  		Server: &ManifestServer{
    69  			Executable: "theexecutable",
    70  			Executables: &ManifestExecutables{
    71  				LinuxAmd64:   "theexecutable-linux-amd64",
    72  				DarwinAmd64:  "theexecutable-darwin-amd64",
    73  				WindowsAmd64: "theexecutable-windows-amd64",
    74  			},
    75  		},
    76  		Webapp: &ManifestWebapp{
    77  			BundlePath: "thebundlepath",
    78  		},
    79  		SettingsSchema: &PluginSettingsSchema{
    80  			Header: "theheadertext",
    81  			Footer: "thefootertext",
    82  			Settings: []*PluginSetting{
    83  				&PluginSetting{
    84  					Key:                "thesetting",
    85  					DisplayName:        "thedisplayname",
    86  					Type:               "dropdown",
    87  					HelpText:           "thehelptext",
    88  					RegenerateHelpText: "theregeneratehelptext",
    89  					Placeholder:        "theplaceholder",
    90  					Options: []*PluginOption{
    91  						&PluginOption{
    92  							DisplayName: "theoptiondisplayname",
    93  							Value:       "thevalue",
    94  						},
    95  					},
    96  					Default: "thedefault",
    97  				},
    98  			},
    99  		},
   100  	}
   101  
   102  	var yamlResult Manifest
   103  	require.NoError(t, yaml.Unmarshal([]byte(`
   104  id: theid
   105  min_server_version: 5.6.0
   106  server:
   107      executable: theexecutable
   108      executables:
   109            linux-amd64: theexecutable-linux-amd64
   110            darwin-amd64: theexecutable-darwin-amd64
   111            windows-amd64: theexecutable-windows-amd64
   112  webapp:
   113      bundle_path: thebundlepath
   114  settings_schema:
   115      header: theheadertext
   116      footer: thefootertext
   117      settings:
   118          - key: thesetting
   119            display_name: thedisplayname
   120            type: dropdown
   121            help_text: thehelptext
   122            regenerate_help_text: theregeneratehelptext
   123            placeholder: theplaceholder
   124            options:
   125                - display_name: theoptiondisplayname
   126                  value: thevalue
   127            default: thedefault
   128  `), &yamlResult))
   129  	assert.Equal(t, expected, yamlResult)
   130  
   131  	var jsonResult Manifest
   132  	require.NoError(t, json.Unmarshal([]byte(`{
   133  	"id": "theid",
   134    "min_server_version": "5.6.0",
   135  	"server": {
   136  		"executable": "theexecutable",
   137  		"executables": {
   138  			"linux-amd64": "theexecutable-linux-amd64",
   139  			"darwin-amd64": "theexecutable-darwin-amd64",
   140  			"windows-amd64": "theexecutable-windows-amd64"
   141  		}
   142  	},
   143  	"webapp": {
   144  		"bundle_path": "thebundlepath"
   145  	},
   146      "settings_schema": {
   147          "header": "theheadertext",
   148          "footer": "thefootertext",
   149          "settings": [
   150  			{
   151  				"key": "thesetting",
   152  				"display_name": "thedisplayname",
   153  				"type": "dropdown",
   154  				"help_text": "thehelptext",
   155  				"regenerate_help_text": "theregeneratehelptext",
   156  				"placeholder": "theplaceholder",
   157  				"options": [
   158  					{
   159  						"display_name": "theoptiondisplayname",
   160  						"value": "thevalue"
   161  					}
   162  				],
   163  				"default": "thedefault"
   164  			}
   165  		]
   166      }
   167  	}`), &jsonResult))
   168  	assert.Equal(t, expected, jsonResult)
   169  }
   170  
   171  func TestFindManifest_FileErrors(t *testing.T) {
   172  	for _, tc := range []string{"plugin.yaml", "plugin.json"} {
   173  		dir, err := ioutil.TempDir("", "mm-plugin-test")
   174  		require.NoError(t, err)
   175  		defer os.RemoveAll(dir)
   176  
   177  		path := filepath.Join(dir, tc)
   178  		require.NoError(t, os.Mkdir(path, 0700))
   179  
   180  		m, mpath, err := FindManifest(dir)
   181  		assert.Nil(t, m)
   182  		assert.Equal(t, path, mpath)
   183  		assert.Error(t, err, tc)
   184  		assert.False(t, os.IsNotExist(err), tc)
   185  	}
   186  }
   187  
   188  func TestFindManifest_FolderPermission(t *testing.T) {
   189  	if os.Geteuid() == 0 {
   190  		t.Skip("skipping test while running as root: can't effectively remove permissions")
   191  	}
   192  
   193  	for _, tc := range []string{"plugin.yaml", "plugin.json"} {
   194  		dir, err := ioutil.TempDir("", "mm-plugin-test")
   195  		require.NoError(t, err)
   196  		defer os.RemoveAll(dir)
   197  
   198  		path := filepath.Join(dir, tc)
   199  		require.NoError(t, os.Mkdir(path, 0700))
   200  
   201  		// User does not have permission in the plugin folder
   202  		err = os.Chmod(dir, 0066)
   203  		require.NoError(t, err)
   204  
   205  		m, mpath, err := FindManifest(dir)
   206  		assert.Nil(t, m)
   207  		assert.Equal(t, "", mpath)
   208  		assert.Error(t, err, tc)
   209  		assert.False(t, os.IsNotExist(err), tc)
   210  	}
   211  }
   212  
   213  func TestManifestJson(t *testing.T) {
   214  	manifest := &Manifest{
   215  		Id: "theid",
   216  		Server: &ManifestServer{
   217  			Executable: "theexecutable",
   218  		},
   219  		Webapp: &ManifestWebapp{
   220  			BundlePath: "thebundlepath",
   221  		},
   222  		SettingsSchema: &PluginSettingsSchema{
   223  			Header: "theheadertext",
   224  			Footer: "thefootertext",
   225  			Settings: []*PluginSetting{
   226  				&PluginSetting{
   227  					Key:                "thesetting",
   228  					DisplayName:        "thedisplayname",
   229  					Type:               "dropdown",
   230  					HelpText:           "thehelptext",
   231  					RegenerateHelpText: "theregeneratehelptext",
   232  					Placeholder:        "theplaceholder",
   233  					Options: []*PluginOption{
   234  						&PluginOption{
   235  							DisplayName: "theoptiondisplayname",
   236  							Value:       "thevalue",
   237  						},
   238  					},
   239  					Default: "thedefault",
   240  				},
   241  			},
   242  		},
   243  	}
   244  
   245  	json := manifest.ToJson()
   246  	newManifest := ManifestFromJson(strings.NewReader(json))
   247  	assert.Equal(t, newManifest, manifest)
   248  	assert.Equal(t, newManifest.ToJson(), json)
   249  	assert.Equal(t, ManifestFromJson(strings.NewReader("junk")), (*Manifest)(nil))
   250  
   251  	manifestList := []*Manifest{manifest}
   252  	json = ManifestListToJson(manifestList)
   253  	newManifestList := ManifestListFromJson(strings.NewReader(json))
   254  	assert.Equal(t, newManifestList, manifestList)
   255  	assert.Equal(t, ManifestListToJson(newManifestList), json)
   256  }
   257  
   258  func TestManifestHasClient(t *testing.T) {
   259  	manifest := &Manifest{
   260  		Id: "theid",
   261  		Server: &ManifestServer{
   262  			Executable: "theexecutable",
   263  		},
   264  		Webapp: &ManifestWebapp{
   265  			BundlePath: "thebundlepath",
   266  		},
   267  	}
   268  
   269  	assert.True(t, manifest.HasClient())
   270  
   271  	manifest.Webapp = nil
   272  	assert.False(t, manifest.HasClient())
   273  }
   274  
   275  func TestManifestClientManifest(t *testing.T) {
   276  	manifest := &Manifest{
   277  		Id:               "theid",
   278  		Name:             "thename",
   279  		Description:      "thedescription",
   280  		Version:          "0.0.1",
   281  		MinServerVersion: "5.6.0",
   282  		Server: &ManifestServer{
   283  			Executable: "theexecutable",
   284  		},
   285  		Webapp: &ManifestWebapp{
   286  			BundlePath: "thebundlepath",
   287  			BundleHash: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
   288  		},
   289  		SettingsSchema: &PluginSettingsSchema{
   290  			Header: "theheadertext",
   291  			Footer: "thefootertext",
   292  			Settings: []*PluginSetting{
   293  				&PluginSetting{
   294  					Key:                "thesetting",
   295  					DisplayName:        "thedisplayname",
   296  					Type:               "dropdown",
   297  					HelpText:           "thehelptext",
   298  					RegenerateHelpText: "theregeneratehelptext",
   299  					Placeholder:        "theplaceholder",
   300  					Options: []*PluginOption{
   301  						&PluginOption{
   302  							DisplayName: "theoptiondisplayname",
   303  							Value:       "thevalue",
   304  						},
   305  					},
   306  					Default: "thedefault",
   307  				},
   308  			},
   309  		},
   310  	}
   311  
   312  	sanitized := manifest.ClientManifest()
   313  
   314  	assert.Equal(t, manifest.Id, sanitized.Id)
   315  	assert.Equal(t, manifest.Version, sanitized.Version)
   316  	assert.Equal(t, manifest.MinServerVersion, sanitized.MinServerVersion)
   317  	assert.Equal(t, "/static/theid/theid_000102030405060708090a0b0c0d0e0f_bundle.js", sanitized.Webapp.BundlePath)
   318  	assert.Equal(t, manifest.Webapp.BundleHash, sanitized.Webapp.BundleHash)
   319  	assert.Equal(t, manifest.SettingsSchema, sanitized.SettingsSchema)
   320  	assert.Empty(t, sanitized.Name)
   321  	assert.Empty(t, sanitized.Description)
   322  	assert.Empty(t, sanitized.Server)
   323  
   324  	assert.NotEmpty(t, manifest.Id)
   325  	assert.NotEmpty(t, manifest.Version)
   326  	assert.NotEmpty(t, manifest.MinServerVersion)
   327  	assert.NotEmpty(t, manifest.Webapp)
   328  	assert.NotEmpty(t, manifest.Name)
   329  	assert.NotEmpty(t, manifest.Description)
   330  	assert.NotEmpty(t, manifest.Server)
   331  	assert.NotEmpty(t, manifest.SettingsSchema)
   332  }
   333  
   334  func TestManifestGetExecutableForRuntime(t *testing.T) {
   335  	testCases := []struct {
   336  		Description        string
   337  		Manifest           *Manifest
   338  		GoOs               string
   339  		GoArch             string
   340  		ExpectedExecutable string
   341  	}{
   342  		{
   343  			"no server",
   344  			&Manifest{},
   345  			"linux",
   346  			"amd64",
   347  			"",
   348  		},
   349  		{
   350  			"no executable",
   351  			&Manifest{
   352  				Server: &ManifestServer{},
   353  			},
   354  			"linux",
   355  			"amd64",
   356  			"",
   357  		},
   358  		{
   359  			"single executable",
   360  			&Manifest{
   361  				Server: &ManifestServer{
   362  					Executable: "path/to/executable",
   363  				},
   364  			},
   365  			"linux",
   366  			"amd64",
   367  			"path/to/executable",
   368  		},
   369  		{
   370  			"single executable, different runtime",
   371  			&Manifest{
   372  				Server: &ManifestServer{
   373  					Executable: "path/to/executable",
   374  				},
   375  			},
   376  			"darwin",
   377  			"amd64",
   378  			"path/to/executable",
   379  		},
   380  		{
   381  			"multiple executables, no match",
   382  			&Manifest{
   383  				Server: &ManifestServer{
   384  					Executables: &ManifestExecutables{
   385  						LinuxAmd64:   "linux-amd64/path/to/executable",
   386  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   387  						WindowsAmd64: "windows-amd64/path/to/executable",
   388  					},
   389  				},
   390  			},
   391  			"other",
   392  			"amd64",
   393  			"",
   394  		},
   395  		{
   396  			"multiple executables, linux-amd64 match",
   397  			&Manifest{
   398  				Server: &ManifestServer{
   399  					Executables: &ManifestExecutables{
   400  						LinuxAmd64:   "linux-amd64/path/to/executable",
   401  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   402  						WindowsAmd64: "windows-amd64/path/to/executable",
   403  					},
   404  				},
   405  			},
   406  			"linux",
   407  			"amd64",
   408  			"linux-amd64/path/to/executable",
   409  		},
   410  		{
   411  			"multiple executables, linux-amd64 match, single executable ignored",
   412  			&Manifest{
   413  				Server: &ManifestServer{
   414  					Executables: &ManifestExecutables{
   415  						LinuxAmd64:   "linux-amd64/path/to/executable",
   416  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   417  						WindowsAmd64: "windows-amd64/path/to/executable",
   418  					},
   419  					Executable: "path/to/executable",
   420  				},
   421  			},
   422  			"linux",
   423  			"amd64",
   424  			"linux-amd64/path/to/executable",
   425  		},
   426  		{
   427  			"multiple executables, darwin-amd64 match",
   428  			&Manifest{
   429  				Server: &ManifestServer{
   430  					Executables: &ManifestExecutables{
   431  						LinuxAmd64:   "linux-amd64/path/to/executable",
   432  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   433  						WindowsAmd64: "windows-amd64/path/to/executable",
   434  					},
   435  				},
   436  			},
   437  			"darwin",
   438  			"amd64",
   439  			"darwin-amd64/path/to/executable",
   440  		},
   441  		{
   442  			"multiple executables, windows-amd64 match",
   443  			&Manifest{
   444  				Server: &ManifestServer{
   445  					Executables: &ManifestExecutables{
   446  						LinuxAmd64:   "linux-amd64/path/to/executable",
   447  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   448  						WindowsAmd64: "windows-amd64/path/to/executable",
   449  					},
   450  				},
   451  			},
   452  			"windows",
   453  			"amd64",
   454  			"windows-amd64/path/to/executable",
   455  		},
   456  		{
   457  			"multiple executables, no match, single executable fallback",
   458  			&Manifest{
   459  				Server: &ManifestServer{
   460  					Executables: &ManifestExecutables{
   461  						LinuxAmd64:   "linux-amd64/path/to/executable",
   462  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   463  						WindowsAmd64: "windows-amd64/path/to/executable",
   464  					},
   465  					Executable: "path/to/executable",
   466  				},
   467  			},
   468  			"other",
   469  			"amd64",
   470  			"path/to/executable",
   471  		},
   472  		{
   473  			"deprecated backend field, ignored since server present",
   474  			&Manifest{
   475  				Server: &ManifestServer{
   476  					Executables: &ManifestExecutables{
   477  						LinuxAmd64:   "linux-amd64/path/to/executable",
   478  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   479  						WindowsAmd64: "windows-amd64/path/to/executable",
   480  					},
   481  				},
   482  				Backend: &ManifestServer{
   483  					Executables: &ManifestExecutables{
   484  						LinuxAmd64:   "linux-amd64/path/to/executable/backend",
   485  						DarwinAmd64:  "darwin-amd64/path/to/executable/backend",
   486  						WindowsAmd64: "windows-amd64/path/to/executable/backend",
   487  					},
   488  				},
   489  			},
   490  			"linux",
   491  			"amd64",
   492  			"linux-amd64/path/to/executable",
   493  		},
   494  		{
   495  			"deprecated backend field used, since no server present",
   496  			&Manifest{
   497  				Backend: &ManifestServer{
   498  					Executables: &ManifestExecutables{
   499  						LinuxAmd64:   "linux-amd64/path/to/executable/backend",
   500  						DarwinAmd64:  "darwin-amd64/path/to/executable/backend",
   501  						WindowsAmd64: "windows-amd64/path/to/executable/backend",
   502  					},
   503  				},
   504  			},
   505  			"linux",
   506  			"amd64",
   507  			"linux-amd64/path/to/executable/backend",
   508  		},
   509  	}
   510  
   511  	for _, testCase := range testCases {
   512  		t.Run(testCase.Description, func(t *testing.T) {
   513  			assert.Equal(
   514  				t,
   515  				testCase.ExpectedExecutable,
   516  				testCase.Manifest.GetExecutableForRuntime(testCase.GoOs, testCase.GoArch),
   517  			)
   518  		})
   519  	}
   520  }
   521  
   522  func TestManifestHasServer(t *testing.T) {
   523  	testCases := []struct {
   524  		Description string
   525  		Manifest    *Manifest
   526  		Expected    bool
   527  	}{
   528  		{
   529  			"no server",
   530  			&Manifest{},
   531  			false,
   532  		},
   533  		{
   534  			"no executable, but server still considered present",
   535  			&Manifest{
   536  				Server: &ManifestServer{},
   537  			},
   538  			true,
   539  		},
   540  		{
   541  			"single executable",
   542  			&Manifest{
   543  				Server: &ManifestServer{
   544  					Executable: "path/to/executable",
   545  				},
   546  			},
   547  			true,
   548  		},
   549  		{
   550  			"multiple executables",
   551  			&Manifest{
   552  				Server: &ManifestServer{
   553  					Executables: &ManifestExecutables{
   554  						LinuxAmd64:   "linux-amd64/path/to/executable",
   555  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   556  						WindowsAmd64: "windows-amd64/path/to/executable",
   557  					},
   558  				},
   559  			},
   560  			true,
   561  		},
   562  		{
   563  			"single executable defined via deprecated backend",
   564  			&Manifest{
   565  				Backend: &ManifestServer{
   566  					Executable: "path/to/executable",
   567  				},
   568  			},
   569  			true,
   570  		},
   571  		{
   572  			"multiple executables defined via deprecated backend",
   573  			&Manifest{
   574  				Backend: &ManifestServer{
   575  					Executables: &ManifestExecutables{
   576  						LinuxAmd64:   "linux-amd64/path/to/executable",
   577  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   578  						WindowsAmd64: "windows-amd64/path/to/executable",
   579  					},
   580  				},
   581  			},
   582  			true,
   583  		},
   584  	}
   585  
   586  	for _, testCase := range testCases {
   587  		t.Run(testCase.Description, func(t *testing.T) {
   588  			assert.Equal(t, testCase.Expected, testCase.Manifest.HasServer())
   589  		})
   590  	}
   591  }
   592  
   593  func TestManifestHasWebapp(t *testing.T) {
   594  	testCases := []struct {
   595  		Description string
   596  		Manifest    *Manifest
   597  		Expected    bool
   598  	}{
   599  		{
   600  			"no webapp",
   601  			&Manifest{},
   602  			false,
   603  		},
   604  		{
   605  			"no bundle path, but webapp still considered present",
   606  			&Manifest{
   607  				Webapp: &ManifestWebapp{},
   608  			},
   609  			true,
   610  		},
   611  		{
   612  			"bundle path defined",
   613  			&Manifest{
   614  				Webapp: &ManifestWebapp{
   615  					BundlePath: "path/to/bundle",
   616  				},
   617  			},
   618  			true,
   619  		},
   620  	}
   621  
   622  	for _, testCase := range testCases {
   623  		t.Run(testCase.Description, func(t *testing.T) {
   624  			assert.Equal(t, testCase.Expected, testCase.Manifest.HasWebapp())
   625  		})
   626  	}
   627  }
   628  
   629  func TestManifestMeetMinServerVersion(t *testing.T) {
   630  	for name, test := range map[string]struct {
   631  		MinServerVersion string
   632  		ServerVersion    string
   633  		ShouldError      bool
   634  		ShouldFulfill    bool
   635  	}{
   636  		"generously fulfilled": {
   637  			MinServerVersion: "5.5.0",
   638  			ServerVersion:    "5.6.0",
   639  			ShouldError:      false,
   640  			ShouldFulfill:    true,
   641  		},
   642  		"exactly fulfilled": {
   643  			MinServerVersion: "5.6.0",
   644  			ServerVersion:    "5.6.0",
   645  			ShouldError:      false,
   646  			ShouldFulfill:    true,
   647  		},
   648  		"not fulfilled": {
   649  			MinServerVersion: "5.6.0",
   650  			ServerVersion:    "5.5.0",
   651  			ShouldError:      false,
   652  			ShouldFulfill:    false,
   653  		},
   654  		"fail to parse MinServerVersion": {
   655  			MinServerVersion: "abc",
   656  			ServerVersion:    "5.5.0",
   657  			ShouldError:      true,
   658  		},
   659  	} {
   660  		t.Run(name, func(t *testing.T) {
   661  			assert := assert.New(t)
   662  
   663  			manifest := Manifest{
   664  				MinServerVersion: test.MinServerVersion,
   665  			}
   666  			fulfilled, err := manifest.MeetMinServerVersion(test.ServerVersion)
   667  
   668  			if test.ShouldError {
   669  				assert.NotNil(err)
   670  				assert.False(fulfilled)
   671  				return
   672  			}
   673  			assert.Nil(err)
   674  			assert.Equal(test.ShouldFulfill, fulfilled)
   675  		})
   676  	}
   677  }