github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/model/manifest_test.go (about)

     1  // Copyright (c) 2015-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 TestIsValid(t *testing.T) {
    21  	testCases := []struct {
    22  		Title       string
    23  		manifest    *Manifest
    24  		ExpectError bool
    25  	}{
    26  		{"Invalid Id", &Manifest{Id: "some id"}, true},
    27  		{"Invalid homePageURL", &Manifest{Id: "com.company.test", HomepageURL: "some url"}, true},
    28  		{"Invalid supportURL", &Manifest{Id: "com.company.test", SupportURL: "some url"}, true},
    29  		{"Invalid ReleaseNotesURL", &Manifest{Id: "com.company.test", ReleaseNotesURL: "some url"}, true},
    30  		{"Invalid version", &Manifest{Id: "com.company.test", HomepageURL: "http://someurl.com", SupportURL: "http://someotherurl.com", Version: "version"}, true},
    31  		{"Invalid min version", &Manifest{Id: "com.company.test", HomepageURL: "http://someurl.com", SupportURL: "http://someotherurl.com", Version: "5.10.0", MinServerVersion: "version"}, true},
    32  		{"SettingSchema error", &Manifest{Id: "com.company.test", HomepageURL: "http://someurl.com", SupportURL: "http://someotherurl.com", Version: "5.10.0", MinServerVersion: "5.10.8", SettingsSchema: &PluginSettingsSchema{
    33  			Settings: []*PluginSetting{{Type: "Invalid"}},
    34  		}}, true},
    35  		{"Minimal valid manifest", &Manifest{Id: "com.company.test"}, false},
    36  		{"Happy case", &Manifest{
    37  			Id:               "com.company.test",
    38  			Name:             "thename",
    39  			Description:      "thedescription",
    40  			HomepageURL:      "http://someurl.com",
    41  			SupportURL:       "http://someotherurl.com",
    42  			ReleaseNotesURL:  "http://someotherurl.com/releases/v0.0.1",
    43  			Version:          "0.0.1",
    44  			MinServerVersion: "5.6.0",
    45  			Server: &ManifestServer{
    46  				Executable: "theexecutable",
    47  			},
    48  			Webapp: &ManifestWebapp{
    49  				BundlePath: "thebundlepath",
    50  			},
    51  			SettingsSchema: &PluginSettingsSchema{
    52  				Header: "theheadertext",
    53  				Footer: "thefootertext",
    54  				Settings: []*PluginSetting{
    55  					{
    56  						Key:         "thesetting",
    57  						DisplayName: "thedisplayname",
    58  						Type:        "dropdown",
    59  						HelpText:    "thehelptext",
    60  						Options: []*PluginOption{
    61  							{
    62  								DisplayName: "theoptiondisplayname",
    63  								Value:       "thevalue",
    64  							},
    65  						},
    66  						Default: "thedefault",
    67  					},
    68  				},
    69  			},
    70  		}, false},
    71  	}
    72  
    73  	for _, tc := range testCases {
    74  		t.Run(tc.Title, func(t *testing.T) {
    75  			err := tc.manifest.IsValid()
    76  			if tc.ExpectError {
    77  				assert.Error(t, err)
    78  			} else {
    79  				assert.NoError(t, err)
    80  			}
    81  		})
    82  	}
    83  }
    84  
    85  func TestIsValidSettingsSchema(t *testing.T) {
    86  	testCases := []struct {
    87  		Title          string
    88  		settingsSchema *PluginSettingsSchema
    89  		ExpectError    bool
    90  	}{
    91  		{"Invalid Setting", &PluginSettingsSchema{Settings: []*PluginSetting{{Type: "invalid"}}}, true},
    92  		{"Happy case", &PluginSettingsSchema{Settings: []*PluginSetting{{Type: "text"}}}, false},
    93  	}
    94  
    95  	for _, tc := range testCases {
    96  		t.Run(tc.Title, func(t *testing.T) {
    97  			err := tc.settingsSchema.isValid()
    98  			if tc.ExpectError {
    99  				assert.Error(t, err)
   100  			} else {
   101  				assert.NoError(t, err)
   102  			}
   103  		})
   104  	}
   105  }
   106  
   107  func TestSettingIsValid(t *testing.T) {
   108  	for name, test := range map[string]struct {
   109  		Setting     PluginSetting
   110  		ExpectError bool
   111  	}{
   112  		"Invalid setting type": {
   113  			PluginSetting{Type: "invalid"},
   114  			true,
   115  		},
   116  		"RegenerateHelpText error": {
   117  			PluginSetting{Type: "text", RegenerateHelpText: "some text"},
   118  			true,
   119  		},
   120  		"Placeholder error": {
   121  			PluginSetting{Type: "bool", Placeholder: "some text"},
   122  			true,
   123  		},
   124  		"Nil Options": {
   125  			PluginSetting{Type: "bool"},
   126  			false,
   127  		},
   128  		"Options error": {
   129  			PluginSetting{Type: "generated", Options: []*PluginOption{}},
   130  			true,
   131  		},
   132  		"Options displayName error": {
   133  			PluginSetting{
   134  				Type: "radio",
   135  				Options: []*PluginOption{{
   136  					Value: "some value",
   137  				}},
   138  			},
   139  			true,
   140  		},
   141  		"Options value error": {
   142  			PluginSetting{
   143  				Type: "radio",
   144  				Options: []*PluginOption{{
   145  					DisplayName: "some name",
   146  				}},
   147  			},
   148  			true,
   149  		},
   150  		"Happy case": {
   151  			PluginSetting{
   152  				Type: "radio",
   153  				Options: []*PluginOption{{
   154  					DisplayName: "Name",
   155  					Value:       "value",
   156  				}},
   157  			},
   158  			false,
   159  		},
   160  		"Valid number setting": {
   161  			PluginSetting{
   162  				Type:    "number",
   163  				Default: 10,
   164  			},
   165  			false,
   166  		},
   167  		"Placeholder is disallowed for bool settings": {
   168  			PluginSetting{
   169  				Type:        "bool",
   170  				Placeholder: "some Text",
   171  			},
   172  			true,
   173  		},
   174  		"Placeholder is allowed for text settings": {
   175  			PluginSetting{
   176  				Type:        "text",
   177  				Placeholder: "some Text",
   178  			},
   179  			false,
   180  		},
   181  		"Placeholder is allowed for long text settings": {
   182  			PluginSetting{
   183  				Type:        "longtext",
   184  				Placeholder: "some Text",
   185  			},
   186  			false,
   187  		},
   188  	} {
   189  		t.Run(name, func(t *testing.T) {
   190  			err := test.Setting.isValid()
   191  			if test.ExpectError {
   192  				assert.Error(t, err)
   193  			} else {
   194  				assert.NoError(t, err)
   195  			}
   196  		})
   197  	}
   198  }
   199  
   200  func TestConvertTypeToPluginSettingType(t *testing.T) {
   201  	testCases := []struct {
   202  		Title               string
   203  		Type                string
   204  		ExpectedSettingType PluginSettingType
   205  		ExpectError         bool
   206  	}{
   207  		{"bool", "bool", Bool, false},
   208  		{"dropdown", "dropdown", Dropdown, false},
   209  		{"generated", "generated", Generated, false},
   210  		{"radio", "radio", Radio, false},
   211  		{"text", "text", Text, false},
   212  		{"longtext", "longtext", LongText, false},
   213  		{"username", "username", Username, false},
   214  		{"custom", "custom", Custom, false},
   215  		{"invalid", "invalid", Bool, true},
   216  	}
   217  
   218  	for _, tc := range testCases {
   219  		t.Run(tc.Title, func(t *testing.T) {
   220  			settingType, err := convertTypeToPluginSettingType(tc.Type)
   221  			if !tc.ExpectError {
   222  				assert.Equal(t, settingType, tc.ExpectedSettingType)
   223  			} else {
   224  				assert.Error(t, err)
   225  			}
   226  		})
   227  	}
   228  }
   229  
   230  func TestFindManifest(t *testing.T) {
   231  	for _, tc := range []struct {
   232  		Filename       string
   233  		Contents       string
   234  		ExpectError    bool
   235  		ExpectNotExist bool
   236  	}{
   237  		{"foo", "bar", true, true},
   238  		{"plugin.json", "bar", true, false},
   239  		{"plugin.json", `{"id": "foo"}`, false, false},
   240  		{"plugin.json", `{"id": "FOO"}`, false, false},
   241  		{"plugin.yaml", `id: foo`, false, false},
   242  		{"plugin.yaml", "bar", true, false},
   243  		{"plugin.yml", `id: foo`, false, false},
   244  		{"plugin.yml", `id: FOO`, false, false},
   245  		{"plugin.yml", "bar", true, false},
   246  	} {
   247  		dir, err := ioutil.TempDir("", "mm-plugin-test")
   248  		require.NoError(t, err)
   249  		defer os.RemoveAll(dir)
   250  
   251  		path := filepath.Join(dir, tc.Filename)
   252  		f, err := os.Create(path)
   253  		require.NoError(t, err)
   254  		_, err = f.WriteString(tc.Contents)
   255  		f.Close()
   256  		require.NoError(t, err)
   257  
   258  		m, mpath, err := FindManifest(dir)
   259  		assert.True(t, (err != nil) == tc.ExpectError, tc.Filename)
   260  		assert.True(t, (err != nil && os.IsNotExist(err)) == tc.ExpectNotExist, tc.Filename)
   261  		if !tc.ExpectNotExist {
   262  			assert.Equal(t, path, mpath, tc.Filename)
   263  		} else {
   264  			assert.Empty(t, mpath, tc.Filename)
   265  		}
   266  		if !tc.ExpectError {
   267  			require.NotNil(t, m, tc.Filename)
   268  			assert.NotEmpty(t, m.Id, tc.Filename)
   269  			assert.Equal(t, strings.ToLower(m.Id), m.Id)
   270  		}
   271  	}
   272  }
   273  
   274  func TestManifestUnmarshal(t *testing.T) {
   275  	expected := Manifest{
   276  		Id:               "theid",
   277  		HomepageURL:      "https://example.com",
   278  		SupportURL:       "https://example.com/support",
   279  		IconPath:         "assets/icon.svg",
   280  		MinServerVersion: "5.6.0",
   281  		Server: &ManifestServer{
   282  			Executable: "theexecutable",
   283  			Executables: &ManifestExecutables{
   284  				LinuxAmd64:   "theexecutable-linux-amd64",
   285  				DarwinAmd64:  "theexecutable-darwin-amd64",
   286  				WindowsAmd64: "theexecutable-windows-amd64",
   287  			},
   288  		},
   289  		Webapp: &ManifestWebapp{
   290  			BundlePath: "thebundlepath",
   291  		},
   292  		SettingsSchema: &PluginSettingsSchema{
   293  			Header: "theheadertext",
   294  			Footer: "thefootertext",
   295  			Settings: []*PluginSetting{
   296  				{
   297  					Key:                "thesetting",
   298  					DisplayName:        "thedisplayname",
   299  					Type:               "dropdown",
   300  					HelpText:           "thehelptext",
   301  					RegenerateHelpText: "theregeneratehelptext",
   302  					Placeholder:        "theplaceholder",
   303  					Options: []*PluginOption{
   304  						{
   305  							DisplayName: "theoptiondisplayname",
   306  							Value:       "thevalue",
   307  						},
   308  					},
   309  					Default: "thedefault",
   310  				},
   311  			},
   312  		},
   313  	}
   314  
   315  	var yamlResult Manifest
   316  	require.NoError(t, yaml.Unmarshal([]byte(`
   317  id: theid
   318  homepage_url: https://example.com
   319  support_url: https://example.com/support
   320  icon_path: assets/icon.svg
   321  min_server_version: 5.6.0
   322  server:
   323      executable: theexecutable
   324      executables:
   325            linux-amd64: theexecutable-linux-amd64
   326            darwin-amd64: theexecutable-darwin-amd64
   327            windows-amd64: theexecutable-windows-amd64
   328  webapp:
   329      bundle_path: thebundlepath
   330  settings_schema:
   331      header: theheadertext
   332      footer: thefootertext
   333      settings:
   334          - key: thesetting
   335            display_name: thedisplayname
   336            type: dropdown
   337            help_text: thehelptext
   338            regenerate_help_text: theregeneratehelptext
   339            placeholder: theplaceholder
   340            options:
   341                - display_name: theoptiondisplayname
   342                  value: thevalue
   343            default: thedefault
   344  `), &yamlResult))
   345  	assert.Equal(t, expected, yamlResult)
   346  
   347  	var jsonResult Manifest
   348  	require.NoError(t, json.Unmarshal([]byte(`{
   349  	"id": "theid",
   350  	"homepage_url": "https://example.com",
   351  	"support_url": "https://example.com/support",
   352  	"icon_path": "assets/icon.svg",
   353  	"min_server_version": "5.6.0",
   354  	"server": {
   355  		"executable": "theexecutable",
   356  		"executables": {
   357  			"linux-amd64": "theexecutable-linux-amd64",
   358  			"darwin-amd64": "theexecutable-darwin-amd64",
   359  			"windows-amd64": "theexecutable-windows-amd64"
   360  		}
   361  	},
   362  	"webapp": {
   363  		"bundle_path": "thebundlepath"
   364  	},
   365      "settings_schema": {
   366          "header": "theheadertext",
   367          "footer": "thefootertext",
   368          "settings": [
   369  			{
   370  				"key": "thesetting",
   371  				"display_name": "thedisplayname",
   372  				"type": "dropdown",
   373  				"help_text": "thehelptext",
   374  				"regenerate_help_text": "theregeneratehelptext",
   375  				"placeholder": "theplaceholder",
   376  				"options": [
   377  					{
   378  						"display_name": "theoptiondisplayname",
   379  						"value": "thevalue"
   380  					}
   381  				],
   382  				"default": "thedefault"
   383  			}
   384  		]
   385      }
   386  	}`), &jsonResult))
   387  	assert.Equal(t, expected, jsonResult)
   388  }
   389  
   390  func TestFindManifest_FileErrors(t *testing.T) {
   391  	for _, tc := range []string{"plugin.yaml", "plugin.json"} {
   392  		dir, err := ioutil.TempDir("", "mm-plugin-test")
   393  		require.NoError(t, err)
   394  		defer os.RemoveAll(dir)
   395  
   396  		path := filepath.Join(dir, tc)
   397  		require.NoError(t, os.Mkdir(path, 0700))
   398  
   399  		m, mpath, err := FindManifest(dir)
   400  		assert.Nil(t, m)
   401  		assert.Equal(t, path, mpath)
   402  		assert.Error(t, err, tc)
   403  		assert.False(t, os.IsNotExist(err), tc)
   404  	}
   405  }
   406  
   407  func TestFindManifest_FolderPermission(t *testing.T) {
   408  	if os.Geteuid() == 0 {
   409  		t.Skip("skipping test while running as root: can't effectively remove permissions")
   410  	}
   411  
   412  	for _, tc := range []string{"plugin.yaml", "plugin.json"} {
   413  		dir, err := ioutil.TempDir("", "mm-plugin-test")
   414  		require.NoError(t, err)
   415  		defer os.RemoveAll(dir)
   416  
   417  		path := filepath.Join(dir, tc)
   418  		require.NoError(t, os.Mkdir(path, 0700))
   419  
   420  		// User does not have permission in the plugin folder
   421  		err = os.Chmod(dir, 0066)
   422  		require.NoError(t, err)
   423  
   424  		m, mpath, err := FindManifest(dir)
   425  		assert.Nil(t, m)
   426  		assert.Equal(t, "", mpath)
   427  		assert.Error(t, err, tc)
   428  		assert.False(t, os.IsNotExist(err), tc)
   429  	}
   430  }
   431  
   432  func TestManifestJson(t *testing.T) {
   433  	manifest := &Manifest{
   434  		Id: "theid",
   435  		Server: &ManifestServer{
   436  			Executable: "theexecutable",
   437  		},
   438  		Webapp: &ManifestWebapp{
   439  			BundlePath: "thebundlepath",
   440  		},
   441  		SettingsSchema: &PluginSettingsSchema{
   442  			Header: "theheadertext",
   443  			Footer: "thefootertext",
   444  			Settings: []*PluginSetting{
   445  				{
   446  					Key:                "thesetting",
   447  					DisplayName:        "thedisplayname",
   448  					Type:               "dropdown",
   449  					HelpText:           "thehelptext",
   450  					RegenerateHelpText: "theregeneratehelptext",
   451  					Placeholder:        "theplaceholder",
   452  					Options: []*PluginOption{
   453  						{
   454  							DisplayName: "theoptiondisplayname",
   455  							Value:       "thevalue",
   456  						},
   457  					},
   458  					Default: "thedefault",
   459  				},
   460  			},
   461  		},
   462  	}
   463  
   464  	json := manifest.ToJson()
   465  	newManifest := ManifestFromJson(strings.NewReader(json))
   466  	assert.Equal(t, newManifest, manifest)
   467  	assert.Equal(t, newManifest.ToJson(), json)
   468  	assert.Equal(t, ManifestFromJson(strings.NewReader("junk")), (*Manifest)(nil))
   469  
   470  	manifestList := []*Manifest{manifest}
   471  	json = ManifestListToJson(manifestList)
   472  	newManifestList := ManifestListFromJson(strings.NewReader(json))
   473  	assert.Equal(t, newManifestList, manifestList)
   474  	assert.Equal(t, ManifestListToJson(newManifestList), json)
   475  }
   476  
   477  func TestManifestHasClient(t *testing.T) {
   478  	manifest := &Manifest{
   479  		Id: "theid",
   480  		Server: &ManifestServer{
   481  			Executable: "theexecutable",
   482  		},
   483  		Webapp: &ManifestWebapp{
   484  			BundlePath: "thebundlepath",
   485  		},
   486  	}
   487  
   488  	assert.True(t, manifest.HasClient())
   489  
   490  	manifest.Webapp = nil
   491  	assert.False(t, manifest.HasClient())
   492  }
   493  
   494  func TestManifestClientManifest(t *testing.T) {
   495  	manifest := &Manifest{
   496  		Id:               "theid",
   497  		Name:             "thename",
   498  		Description:      "thedescription",
   499  		Version:          "0.0.1",
   500  		MinServerVersion: "5.6.0",
   501  		Server: &ManifestServer{
   502  			Executable: "theexecutable",
   503  		},
   504  		Webapp: &ManifestWebapp{
   505  			BundlePath: "thebundlepath",
   506  			BundleHash: []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
   507  		},
   508  		SettingsSchema: &PluginSettingsSchema{
   509  			Header: "theheadertext",
   510  			Footer: "thefootertext",
   511  			Settings: []*PluginSetting{
   512  				{
   513  					Key:                "thesetting",
   514  					DisplayName:        "thedisplayname",
   515  					Type:               "dropdown",
   516  					HelpText:           "thehelptext",
   517  					RegenerateHelpText: "theregeneratehelptext",
   518  					Placeholder:        "theplaceholder",
   519  					Options: []*PluginOption{
   520  						{
   521  							DisplayName: "theoptiondisplayname",
   522  							Value:       "thevalue",
   523  						},
   524  					},
   525  					Default: "thedefault",
   526  				},
   527  			},
   528  		},
   529  	}
   530  
   531  	sanitized := manifest.ClientManifest()
   532  
   533  	assert.Equal(t, manifest.Id, sanitized.Id)
   534  	assert.Equal(t, manifest.Version, sanitized.Version)
   535  	assert.Equal(t, manifest.MinServerVersion, sanitized.MinServerVersion)
   536  	assert.Equal(t, "/static/theid/theid_000102030405060708090a0b0c0d0e0f_bundle.js", sanitized.Webapp.BundlePath)
   537  	assert.Equal(t, manifest.Webapp.BundleHash, sanitized.Webapp.BundleHash)
   538  	assert.Equal(t, manifest.SettingsSchema, sanitized.SettingsSchema)
   539  	assert.Empty(t, sanitized.Name)
   540  	assert.Empty(t, sanitized.Description)
   541  	assert.Empty(t, sanitized.Server)
   542  
   543  	assert.NotEmpty(t, manifest.Id)
   544  	assert.NotEmpty(t, manifest.Version)
   545  	assert.NotEmpty(t, manifest.MinServerVersion)
   546  	assert.NotEmpty(t, manifest.Webapp)
   547  	assert.NotEmpty(t, manifest.Name)
   548  	assert.NotEmpty(t, manifest.Description)
   549  	assert.NotEmpty(t, manifest.Server)
   550  	assert.NotEmpty(t, manifest.SettingsSchema)
   551  }
   552  
   553  func TestManifestGetExecutableForRuntime(t *testing.T) {
   554  	testCases := []struct {
   555  		Description        string
   556  		Manifest           *Manifest
   557  		GoOs               string
   558  		GoArch             string
   559  		ExpectedExecutable string
   560  	}{
   561  		{
   562  			"no server",
   563  			&Manifest{},
   564  			"linux",
   565  			"amd64",
   566  			"",
   567  		},
   568  		{
   569  			"no executable",
   570  			&Manifest{
   571  				Server: &ManifestServer{},
   572  			},
   573  			"linux",
   574  			"amd64",
   575  			"",
   576  		},
   577  		{
   578  			"single executable",
   579  			&Manifest{
   580  				Server: &ManifestServer{
   581  					Executable: "path/to/executable",
   582  				},
   583  			},
   584  			"linux",
   585  			"amd64",
   586  			"path/to/executable",
   587  		},
   588  		{
   589  			"single executable, different runtime",
   590  			&Manifest{
   591  				Server: &ManifestServer{
   592  					Executable: "path/to/executable",
   593  				},
   594  			},
   595  			"darwin",
   596  			"amd64",
   597  			"path/to/executable",
   598  		},
   599  		{
   600  			"multiple executables, no match",
   601  			&Manifest{
   602  				Server: &ManifestServer{
   603  					Executables: &ManifestExecutables{
   604  						LinuxAmd64:   "linux-amd64/path/to/executable",
   605  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   606  						WindowsAmd64: "windows-amd64/path/to/executable",
   607  					},
   608  				},
   609  			},
   610  			"other",
   611  			"amd64",
   612  			"",
   613  		},
   614  		{
   615  			"multiple executables, linux-amd64 match",
   616  			&Manifest{
   617  				Server: &ManifestServer{
   618  					Executables: &ManifestExecutables{
   619  						LinuxAmd64:   "linux-amd64/path/to/executable",
   620  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   621  						WindowsAmd64: "windows-amd64/path/to/executable",
   622  					},
   623  				},
   624  			},
   625  			"linux",
   626  			"amd64",
   627  			"linux-amd64/path/to/executable",
   628  		},
   629  		{
   630  			"multiple executables, linux-amd64 match, single executable ignored",
   631  			&Manifest{
   632  				Server: &ManifestServer{
   633  					Executables: &ManifestExecutables{
   634  						LinuxAmd64:   "linux-amd64/path/to/executable",
   635  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   636  						WindowsAmd64: "windows-amd64/path/to/executable",
   637  					},
   638  					Executable: "path/to/executable",
   639  				},
   640  			},
   641  			"linux",
   642  			"amd64",
   643  			"linux-amd64/path/to/executable",
   644  		},
   645  		{
   646  			"multiple executables, darwin-amd64 match",
   647  			&Manifest{
   648  				Server: &ManifestServer{
   649  					Executables: &ManifestExecutables{
   650  						LinuxAmd64:   "linux-amd64/path/to/executable",
   651  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   652  						WindowsAmd64: "windows-amd64/path/to/executable",
   653  					},
   654  				},
   655  			},
   656  			"darwin",
   657  			"amd64",
   658  			"darwin-amd64/path/to/executable",
   659  		},
   660  		{
   661  			"multiple executables, windows-amd64 match",
   662  			&Manifest{
   663  				Server: &ManifestServer{
   664  					Executables: &ManifestExecutables{
   665  						LinuxAmd64:   "linux-amd64/path/to/executable",
   666  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   667  						WindowsAmd64: "windows-amd64/path/to/executable",
   668  					},
   669  				},
   670  			},
   671  			"windows",
   672  			"amd64",
   673  			"windows-amd64/path/to/executable",
   674  		},
   675  		{
   676  			"multiple executables, no match, single executable fallback",
   677  			&Manifest{
   678  				Server: &ManifestServer{
   679  					Executables: &ManifestExecutables{
   680  						LinuxAmd64:   "linux-amd64/path/to/executable",
   681  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   682  						WindowsAmd64: "windows-amd64/path/to/executable",
   683  					},
   684  					Executable: "path/to/executable",
   685  				},
   686  			},
   687  			"other",
   688  			"amd64",
   689  			"path/to/executable",
   690  		},
   691  		{
   692  			"deprecated backend field, ignored since server present",
   693  			&Manifest{
   694  				Server: &ManifestServer{
   695  					Executables: &ManifestExecutables{
   696  						LinuxAmd64:   "linux-amd64/path/to/executable",
   697  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   698  						WindowsAmd64: "windows-amd64/path/to/executable",
   699  					},
   700  				},
   701  				Backend: &ManifestServer{
   702  					Executables: &ManifestExecutables{
   703  						LinuxAmd64:   "linux-amd64/path/to/executable/backend",
   704  						DarwinAmd64:  "darwin-amd64/path/to/executable/backend",
   705  						WindowsAmd64: "windows-amd64/path/to/executable/backend",
   706  					},
   707  				},
   708  			},
   709  			"linux",
   710  			"amd64",
   711  			"linux-amd64/path/to/executable",
   712  		},
   713  		{
   714  			"deprecated backend field used, since no server present",
   715  			&Manifest{
   716  				Backend: &ManifestServer{
   717  					Executables: &ManifestExecutables{
   718  						LinuxAmd64:   "linux-amd64/path/to/executable/backend",
   719  						DarwinAmd64:  "darwin-amd64/path/to/executable/backend",
   720  						WindowsAmd64: "windows-amd64/path/to/executable/backend",
   721  					},
   722  				},
   723  			},
   724  			"linux",
   725  			"amd64",
   726  			"linux-amd64/path/to/executable/backend",
   727  		},
   728  	}
   729  
   730  	for _, testCase := range testCases {
   731  		t.Run(testCase.Description, func(t *testing.T) {
   732  			assert.Equal(
   733  				t,
   734  				testCase.ExpectedExecutable,
   735  				testCase.Manifest.GetExecutableForRuntime(testCase.GoOs, testCase.GoArch),
   736  			)
   737  		})
   738  	}
   739  }
   740  
   741  func TestManifestHasServer(t *testing.T) {
   742  	testCases := []struct {
   743  		Description string
   744  		Manifest    *Manifest
   745  		Expected    bool
   746  	}{
   747  		{
   748  			"no server",
   749  			&Manifest{},
   750  			false,
   751  		},
   752  		{
   753  			"no executable, but server still considered present",
   754  			&Manifest{
   755  				Server: &ManifestServer{},
   756  			},
   757  			true,
   758  		},
   759  		{
   760  			"single executable",
   761  			&Manifest{
   762  				Server: &ManifestServer{
   763  					Executable: "path/to/executable",
   764  				},
   765  			},
   766  			true,
   767  		},
   768  		{
   769  			"multiple executables",
   770  			&Manifest{
   771  				Server: &ManifestServer{
   772  					Executables: &ManifestExecutables{
   773  						LinuxAmd64:   "linux-amd64/path/to/executable",
   774  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   775  						WindowsAmd64: "windows-amd64/path/to/executable",
   776  					},
   777  				},
   778  			},
   779  			true,
   780  		},
   781  		{
   782  			"single executable defined via deprecated backend",
   783  			&Manifest{
   784  				Backend: &ManifestServer{
   785  					Executable: "path/to/executable",
   786  				},
   787  			},
   788  			true,
   789  		},
   790  		{
   791  			"multiple executables defined via deprecated backend",
   792  			&Manifest{
   793  				Backend: &ManifestServer{
   794  					Executables: &ManifestExecutables{
   795  						LinuxAmd64:   "linux-amd64/path/to/executable",
   796  						DarwinAmd64:  "darwin-amd64/path/to/executable",
   797  						WindowsAmd64: "windows-amd64/path/to/executable",
   798  					},
   799  				},
   800  			},
   801  			true,
   802  		},
   803  	}
   804  
   805  	for _, testCase := range testCases {
   806  		t.Run(testCase.Description, func(t *testing.T) {
   807  			assert.Equal(t, testCase.Expected, testCase.Manifest.HasServer())
   808  		})
   809  	}
   810  }
   811  
   812  func TestManifestHasWebapp(t *testing.T) {
   813  	testCases := []struct {
   814  		Description string
   815  		Manifest    *Manifest
   816  		Expected    bool
   817  	}{
   818  		{
   819  			"no webapp",
   820  			&Manifest{},
   821  			false,
   822  		},
   823  		{
   824  			"no bundle path, but webapp still considered present",
   825  			&Manifest{
   826  				Webapp: &ManifestWebapp{},
   827  			},
   828  			true,
   829  		},
   830  		{
   831  			"bundle path defined",
   832  			&Manifest{
   833  				Webapp: &ManifestWebapp{
   834  					BundlePath: "path/to/bundle",
   835  				},
   836  			},
   837  			true,
   838  		},
   839  	}
   840  
   841  	for _, testCase := range testCases {
   842  		t.Run(testCase.Description, func(t *testing.T) {
   843  			assert.Equal(t, testCase.Expected, testCase.Manifest.HasWebapp())
   844  		})
   845  	}
   846  }
   847  
   848  func TestManifestMeetMinServerVersion(t *testing.T) {
   849  	for name, test := range map[string]struct {
   850  		MinServerVersion string
   851  		ServerVersion    string
   852  		ShouldError      bool
   853  		ShouldFulfill    bool
   854  	}{
   855  		"generously fulfilled": {
   856  			MinServerVersion: "5.5.0",
   857  			ServerVersion:    "5.6.0",
   858  			ShouldError:      false,
   859  			ShouldFulfill:    true,
   860  		},
   861  		"exactly fulfilled": {
   862  			MinServerVersion: "5.6.0",
   863  			ServerVersion:    "5.6.0",
   864  			ShouldError:      false,
   865  			ShouldFulfill:    true,
   866  		},
   867  		"not fulfilled": {
   868  			MinServerVersion: "5.6.0",
   869  			ServerVersion:    "5.5.0",
   870  			ShouldError:      false,
   871  			ShouldFulfill:    false,
   872  		},
   873  		"fail to parse MinServerVersion": {
   874  			MinServerVersion: "abc",
   875  			ServerVersion:    "5.5.0",
   876  			ShouldError:      true,
   877  		},
   878  	} {
   879  		t.Run(name, func(t *testing.T) {
   880  			assert := assert.New(t)
   881  
   882  			manifest := Manifest{
   883  				MinServerVersion: test.MinServerVersion,
   884  			}
   885  			fulfilled, err := manifest.MeetMinServerVersion(test.ServerVersion)
   886  
   887  			if test.ShouldError {
   888  				assert.NotNil(err)
   889  				assert.False(fulfilled)
   890  				return
   891  			}
   892  			assert.Nil(err)
   893  			assert.Equal(test.ShouldFulfill, fulfilled)
   894  		})
   895  	}
   896  }