get.porter.sh/porter@v1.3.0/pkg/porter/plugins_test.go (about)

     1  package porter
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"testing"
    10  
    11  	"get.porter.sh/porter/pkg/pkgmgmt"
    12  	"get.porter.sh/porter/pkg/pkgmgmt/client"
    13  	"get.porter.sh/porter/pkg/plugins"
    14  	"get.porter.sh/porter/pkg/printer"
    15  	"get.porter.sh/porter/pkg/test"
    16  	"get.porter.sh/porter/pkg/yaml"
    17  	"get.porter.sh/porter/tests"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/require"
    20  	"github.com/xeipuuv/gojsonschema"
    21  )
    22  
    23  func TestPorter_PrintPlugins(t *testing.T) {
    24  	t.Run("plaintext", func(t *testing.T) {
    25  		ctx := context.Background()
    26  		p := NewTestPorter(t)
    27  		defer p.Close()
    28  
    29  		opts := PrintPluginsOptions{
    30  			PrintOptions: printer.PrintOptions{
    31  				Format: printer.FormatPlaintext,
    32  			},
    33  		}
    34  		err := p.PrintPlugins(ctx, opts)
    35  
    36  		require.Nil(t, err)
    37  
    38  		got := p.TestConfig.TestContext.GetOutput()
    39  		test.CompareGoldenFile(t, "testdata/plugins/list-output.txt", got)
    40  	})
    41  
    42  	t.Run("yaml", func(t *testing.T) {
    43  		ctx := context.Background()
    44  		p := NewTestPorter(t)
    45  		defer p.Close()
    46  
    47  		opts := PrintPluginsOptions{
    48  			PrintOptions: printer.PrintOptions{
    49  				Format: printer.FormatYaml,
    50  			},
    51  		}
    52  		err := p.PrintPlugins(ctx, opts)
    53  
    54  		require.Nil(t, err)
    55  		expected := `- name: plugin1
    56    versioninfo:
    57      version: v1.0
    58      commit: abc123
    59      author: Porter Authors
    60    implementations:
    61      - type: storage
    62        name: blob
    63      - type: storage
    64        name: mongo
    65  - name: plugin2
    66    versioninfo:
    67      version: v1.0
    68      commit: abc123
    69      author: Porter Authors
    70    implementations:
    71      - type: storage
    72        name: blob
    73      - type: storage
    74        name: mongo
    75  - name: unknown
    76    versioninfo:
    77      version: v1.0
    78      commit: abc123
    79      author: Porter Authors
    80    implementations: []
    81  `
    82  		actual := p.TestConfig.TestContext.GetOutput()
    83  		assert.Equal(t, expected, actual)
    84  	})
    85  
    86  	t.Run("json", func(t *testing.T) {
    87  		ctx := context.Background()
    88  		p := NewTestPorter(t)
    89  		defer p.Close()
    90  
    91  		opts := PrintPluginsOptions{
    92  			PrintOptions: printer.PrintOptions{
    93  				Format: printer.FormatJson,
    94  			},
    95  		}
    96  		err := p.PrintPlugins(ctx, opts)
    97  
    98  		require.Nil(t, err)
    99  		expected := `[
   100    {
   101      "name": "plugin1",
   102      "version": "v1.0",
   103      "commit": "abc123",
   104      "author": "Porter Authors",
   105      "implementations": [
   106        {
   107          "type": "storage",
   108          "implementation": "blob"
   109        },
   110        {
   111          "type": "storage",
   112          "implementation": "mongo"
   113        }
   114      ]
   115    },
   116    {
   117      "name": "plugin2",
   118      "version": "v1.0",
   119      "commit": "abc123",
   120      "author": "Porter Authors",
   121      "implementations": [
   122        {
   123          "type": "storage",
   124          "implementation": "blob"
   125        },
   126        {
   127          "type": "storage",
   128          "implementation": "mongo"
   129        }
   130      ]
   131    },
   132    {
   133      "name": "unknown",
   134      "version": "v1.0",
   135      "commit": "abc123",
   136      "author": "Porter Authors",
   137      "implementations": null
   138    }
   139  ]
   140  `
   141  		actual := p.TestConfig.TestContext.GetOutput()
   142  		assert.Equal(t, expected, actual)
   143  	})
   144  }
   145  
   146  func TestPorter_ShowPlugin(t *testing.T) {
   147  	t.Run("plaintext", func(t *testing.T) {
   148  		ctx := context.Background()
   149  
   150  		p := NewTestPorter(t)
   151  		defer p.Close()
   152  
   153  		opts := ShowPluginOptions{Name: "plugin1"}
   154  		opts.Format = printer.FormatPlaintext
   155  		err := p.ShowPlugin(ctx, opts)
   156  		require.NoError(t, err, "ShowPlugin failed")
   157  
   158  		expected := `Name: plugin1
   159  Version: v1.0
   160  Commit: abc123
   161  Author: Porter Authors
   162  
   163  ---------------------------
   164    Type     Implementation  
   165  ---------------------------
   166    storage  blob            
   167    storage  mongo           
   168  `
   169  		actual := p.TestConfig.TestContext.GetOutput()
   170  		assert.Equal(t, expected, actual)
   171  	})
   172  
   173  	t.Run("yaml", func(t *testing.T) {
   174  		ctx := context.Background()
   175  		p := NewTestPorter(t)
   176  		defer p.Close()
   177  
   178  		opts := ShowPluginOptions{Name: "plugin1"}
   179  		opts.Format = printer.FormatYaml
   180  		err := p.ShowPlugin(ctx, opts)
   181  		require.NoError(t, err, "ShowPlugin failed")
   182  
   183  		expected := `name: plugin1
   184  versioninfo:
   185    version: v1.0
   186    commit: abc123
   187    author: Porter Authors
   188  implementations:
   189    - type: storage
   190      name: blob
   191    - type: storage
   192      name: mongo
   193  `
   194  		actual := p.TestConfig.TestContext.GetOutput()
   195  		assert.Equal(t, expected, actual)
   196  	})
   197  
   198  	t.Run("json", func(t *testing.T) {
   199  		ctx := context.Background()
   200  
   201  		p := NewTestPorter(t)
   202  		defer p.Close()
   203  
   204  		opts := ShowPluginOptions{Name: "plugin1"}
   205  		opts.Format = printer.FormatJson
   206  		err := p.ShowPlugin(ctx, opts)
   207  		require.NoError(t, err, "ShowPlugin failed")
   208  
   209  		expected := `{
   210    "name": "plugin1",
   211    "version": "v1.0",
   212    "commit": "abc123",
   213    "author": "Porter Authors",
   214    "implementations": [
   215      {
   216        "type": "storage",
   217        "implementation": "blob"
   218      },
   219      {
   220        "type": "storage",
   221        "implementation": "mongo"
   222      }
   223    ]
   224  }
   225  `
   226  		actual := p.TestConfig.TestContext.GetOutput()
   227  		assert.Equal(t, expected, actual)
   228  	})
   229  }
   230  
   231  func TestPorter_InstallPlugin(t *testing.T) {
   232  	defaultFeedURL := pkgmgmt.DefaultPackageMirror + "/plugins/atom.xml"
   233  	type expectedResults struct {
   234  		output  string
   235  		feedURL string
   236  		mirror  string
   237  	}
   238  	testcases := []struct {
   239  		name     string
   240  		args     []string
   241  		config   plugins.InstallOptions
   242  		expected expectedResults
   243  	}{
   244  		{
   245  			name:   "json file",
   246  			config: plugins.InstallOptions{File: "plugins.json"},
   247  			expected: expectedResults{
   248  				output:  "installed plugin1 plugin v1.0 (abc123)\ninstalled plugin2 plugin v1.0 (abc123)\n",
   249  				feedURL: defaultFeedURL,
   250  				mirror:  pkgmgmt.DefaultPackageMirror,
   251  			},
   252  		},
   253  		{
   254  			name:   "yaml file",
   255  			config: plugins.InstallOptions{File: "plugins.yaml"},
   256  			expected: expectedResults{
   257  				output:  "installed plugin1 plugin v1.0 (abc123)\ninstalled plugin2 plugin v1.0 (abc123)\n",
   258  				feedURL: defaultFeedURL,
   259  				mirror:  pkgmgmt.DefaultPackageMirror,
   260  			},
   261  		},
   262  		{
   263  			name:   "with feed url default",
   264  			config: plugins.InstallOptions{File: "plugins.yaml", InstallOptions: pkgmgmt.InstallOptions{FeedURL: "https://example-feed-url.com/"}},
   265  			expected: expectedResults{
   266  				output:  "installed plugin1 plugin v1.0 (abc123)\ninstalled plugin2 plugin v1.0 (abc123)\n",
   267  				feedURL: "https://example-feed-url.com/",
   268  				mirror:  pkgmgmt.DefaultPackageMirror,
   269  			},
   270  		},
   271  		{
   272  			name:   "with mirror default",
   273  			config: plugins.InstallOptions{File: "plugins.json", InstallOptions: pkgmgmt.InstallOptions{PackageDownloadOptions: pkgmgmt.PackageDownloadOptions{Mirror: "https://example-mirror.com/"}}},
   274  			expected: expectedResults{
   275  				output:  "installed plugin1 plugin v1.0 (abc123)\ninstalled plugin2 plugin v1.0 (abc123)\n",
   276  				feedURL: "https://example-mirror.com/plugins/atom.xml",
   277  				mirror:  "https://example-mirror.com/",
   278  			},
   279  		},
   280  		{
   281  			name: "through arg", args: []string{"plugin1"},
   282  			config: plugins.InstallOptions{InstallOptions: pkgmgmt.InstallOptions{URL: "https://example.com/"}},
   283  			expected: expectedResults{
   284  				output: "installed plugin1 plugin v1.0 (abc123)\n",
   285  				mirror: pkgmgmt.DefaultPackageMirror,
   286  			},
   287  		},
   288  	}
   289  
   290  	for _, tc := range testcases {
   291  		t.Run(tc.name, func(t *testing.T) {
   292  			p := NewTestPorter(t)
   293  			defer p.Close()
   294  
   295  			if tc.config.File != "" {
   296  				p.TestConfig.TestContext.AddTestFile(fmt.Sprintf("testdata/%s", tc.config.File), fmt.Sprintf("/%s", tc.config.File))
   297  			}
   298  			err := tc.config.Validate(tc.args, p.Context)
   299  			require.NoError(t, err, "Validate failed")
   300  
   301  			pp := p.Plugins.(*client.TestPackageManager)
   302  			pp.InstallAssertions = append(pp.InstallAssertions, func(installOpt pkgmgmt.InstallOptions) error {
   303  				assert.Equal(t, tc.expected.feedURL, installOpt.FeedURL)
   304  				assert.Equal(t, tc.expected.mirror, installOpt.Mirror)
   305  
   306  				fmt.Fprint(p.Err, installOpt)
   307  				return nil
   308  			})
   309  			err = p.InstallPlugin(context.Background(), tc.config)
   310  			require.NoError(t, err, "InstallPlugin failed")
   311  
   312  			gotOutput := p.TestConfig.TestContext.GetOutput()
   313  			assert.NotEmpty(t, gotOutput)
   314  			assert.Contains(t, tc.expected.output, gotOutput)
   315  		})
   316  	}
   317  }
   318  
   319  func TestPorter_InstallPluginsSchema(t *testing.T) {
   320  	p := NewTestPorter(t)
   321  	schema, err := os.ReadFile(filepath.Join(p.RepoRoot, "pkg/schema/plugins.schema.json"))
   322  	require.NoError(t, err, "failed to read plugins.schema.json file")
   323  	testcases := []struct {
   324  		name    string
   325  		path    string
   326  		wantErr string
   327  	}{
   328  		{
   329  			name:    "valid",
   330  			path:    "testdata/plugins.json",
   331  			wantErr: "",
   332  		},
   333  		{
   334  			name:    "invalid",
   335  			path:    "testdata/invalid-plugins.json",
   336  			wantErr: "(root): Additional property invalid-field is not allowed\nplugins.plugin1: Additional property random-field is not allowed\n",
   337  		},
   338  	}
   339  
   340  	for _, tc := range testcases {
   341  		t.Run(tc.name, func(t *testing.T) {
   342  			// Load manifest as a go dump
   343  			testManifest, err := os.ReadFile(tc.path)
   344  			require.NoError(t, err, "failed to read %s", tc.path)
   345  
   346  			m := make(map[string]interface{})
   347  			err = yaml.Unmarshal(testManifest, &m)
   348  			require.NoError(t, err, "failed to unmarshal %s", tc.path)
   349  
   350  			// Load the manifest schema returned from `porter schema`
   351  			manifestLoader := gojsonschema.NewGoLoader(m)
   352  			schemaLoader := gojsonschema.NewBytesLoader(schema)
   353  
   354  			// Validate the manifest against the schema
   355  			fails, err := gojsonschema.Validate(schemaLoader, manifestLoader)
   356  			require.NoError(t, err)
   357  
   358  			if tc.wantErr == "" {
   359  				assert.Empty(t, fails.Errors(), "expected %s to validate against the plugins schema", tc.path)
   360  				// Print any validation errors returned
   361  				for _, err := range fails.Errors() {
   362  					t.Logf("%s", err)
   363  				}
   364  			} else {
   365  				var allFails strings.Builder
   366  				for _, err := range fails.Errors() {
   367  					allFails.WriteString(err.String())
   368  					allFails.WriteString("\n")
   369  				}
   370  				tests.RequireOutputContains(t, tc.wantErr, allFails.String())
   371  			}
   372  		})
   373  	}
   374  }
   375  
   376  func TestPorter_UninstallPlugin(t *testing.T) {
   377  	ctx := context.Background()
   378  	p := NewTestPorter(t)
   379  	defer p.Close()
   380  
   381  	opts := pkgmgmt.UninstallOptions{}
   382  	err := opts.Validate([]string{"plugin1"})
   383  	require.NoError(t, err, "Validate failed")
   384  
   385  	err = p.UninstallPlugin(ctx, opts)
   386  	require.NoError(t, err, "UninstallPlugin failed")
   387  
   388  	wantOutput := "Uninstalled plugin1 plugin"
   389  	gotoutput := p.TestConfig.TestContext.GetOutput()
   390  	assert.Contains(t, wantOutput, gotoutput)
   391  }