sigs.k8s.io/kubebuilder/v3@v3.14.0/pkg/cli/cli_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cli
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"strings"
    24  
    25  	. "github.com/onsi/ginkgo/v2"
    26  	. "github.com/onsi/gomega"
    27  	"github.com/spf13/afero"
    28  	"github.com/spf13/cobra"
    29  
    30  	"sigs.k8s.io/kubebuilder/v3/pkg/config"
    31  	cfgv2 "sigs.k8s.io/kubebuilder/v3/pkg/config/v2"
    32  	cfgv3 "sigs.k8s.io/kubebuilder/v3/pkg/config/v3"
    33  	"sigs.k8s.io/kubebuilder/v3/pkg/machinery"
    34  	"sigs.k8s.io/kubebuilder/v3/pkg/model/stage"
    35  	"sigs.k8s.io/kubebuilder/v3/pkg/plugin"
    36  	goPluginV3 "sigs.k8s.io/kubebuilder/v3/pkg/plugins/golang/v3"
    37  )
    38  
    39  func makeMockPluginsFor(projectVersion config.Version, pluginKeys ...string) []plugin.Plugin {
    40  	plugins := make([]plugin.Plugin, 0, len(pluginKeys))
    41  	for _, key := range pluginKeys {
    42  		n, v := plugin.SplitKey(key)
    43  		plugins = append(plugins, newMockPlugin(n, v, projectVersion))
    44  	}
    45  	return plugins
    46  }
    47  
    48  func makeMapFor(plugins ...plugin.Plugin) map[string]plugin.Plugin {
    49  	pluginMap := make(map[string]plugin.Plugin, len(plugins))
    50  	for _, p := range plugins {
    51  		pluginMap[plugin.KeyFor(p)] = p
    52  	}
    53  	return pluginMap
    54  }
    55  
    56  func setFlag(flag, value string) {
    57  	os.Args = append(os.Args, "subcommand", "--"+flag, value)
    58  }
    59  
    60  func setBoolFlag(flag string) {
    61  	os.Args = append(os.Args, "subcommand", "--"+flag)
    62  }
    63  
    64  // nolint:unparam
    65  func setProjectVersionFlag(value string) {
    66  	setFlag(projectVersionFlag, value)
    67  }
    68  
    69  func setPluginsFlag(value string) {
    70  	setFlag(pluginsFlag, value)
    71  }
    72  
    73  func hasSubCommand(cmd *cobra.Command, name string) bool {
    74  	for _, subcommand := range cmd.Commands() {
    75  		if subcommand.Name() == name {
    76  			return true
    77  		}
    78  	}
    79  	return false
    80  }
    81  
    82  var _ = Describe("CLI", func() {
    83  	var (
    84  		c              *CLI
    85  		projectVersion = config.Version{Number: 3}
    86  	)
    87  
    88  	BeforeEach(func() {
    89  		c = &CLI{
    90  			fs: machinery.Filesystem{FS: afero.NewMemMapFs()},
    91  		}
    92  	})
    93  
    94  	Context("buildCmd", func() {
    95  		var projectFile string
    96  
    97  		BeforeEach(func() {
    98  			projectFile = `domain: zeusville.com
    99  layout: go.kubebuilder.io/v3
   100  projectName: demo-zeus-operator
   101  repo: github.com/jmrodri/demo-zeus-operator
   102  resources:
   103  - crdVersion: v1
   104    group: test
   105    kind: Test
   106    version: v1
   107  version: 3-alpha
   108  plugins:
   109    manifests.sdk.operatorframework.io/v2: {}
   110  `
   111  			f, err := c.fs.FS.Create("PROJECT")
   112  			Expect(err).To(Not(HaveOccurred()))
   113  
   114  			_, err = f.WriteString(projectFile)
   115  			Expect(err).To(Not(HaveOccurred()))
   116  		})
   117  
   118  		When("reading a 3-alpha config", func() {
   119  			It("should succeed and set the projectVersion", func() {
   120  				err := c.buildCmd()
   121  				Expect(err).To(Not(HaveOccurred()))
   122  				Expect(c.projectVersion.Compare(
   123  					config.Version{
   124  						Number: 3,
   125  						Stage:  stage.Stable,
   126  					})).To(Equal(0))
   127  			})
   128  			It("should fail when stable is not registered ", func() {
   129  				// overwrite project file with fake 4-alpha
   130  				f, err := c.fs.FS.OpenFile("PROJECT", os.O_WRONLY, 0)
   131  				Expect(err).To(Not(HaveOccurred()))
   132  				_, err = f.WriteString(strings.ReplaceAll(projectFile, "3-alpha", "4-alpha"))
   133  				Expect(err).To(Not(HaveOccurred()))
   134  
   135  				// buildCmd should return an error
   136  				err = c.buildCmd()
   137  				Expect(err).To(HaveOccurred())
   138  			})
   139  		})
   140  	})
   141  
   142  	// TODO: test CLI.getInfoFromConfigFile using a mock filesystem
   143  
   144  	Context("getInfoFromConfig", func() {
   145  		When("not having layout field", func() {
   146  			It("should succeed", func() {
   147  				pluginChain := []string{"go.kubebuilder.io/v2"}
   148  
   149  				projectConfig := cfgv2.New()
   150  
   151  				Expect(c.getInfoFromConfig(projectConfig)).To(Succeed())
   152  				Expect(c.pluginKeys).To(Equal(pluginChain))
   153  				Expect(c.projectVersion.Compare(projectConfig.GetVersion())).To(Equal(0))
   154  			})
   155  		})
   156  
   157  		When("having a single plugin in the layout field", func() {
   158  			It("should succeed", func() {
   159  				pluginChain := []string{"go.kubebuilder.io/v2"}
   160  
   161  				projectConfig := cfgv3.New()
   162  				Expect(projectConfig.SetPluginChain(pluginChain)).To(Succeed())
   163  
   164  				Expect(c.getInfoFromConfig(projectConfig)).To(Succeed())
   165  				Expect(c.pluginKeys).To(Equal(pluginChain))
   166  				Expect(c.projectVersion.Compare(projectConfig.GetVersion())).To(Equal(0))
   167  			})
   168  		})
   169  
   170  		When("having multiple plugins in the layout field", func() {
   171  			It("should succeed", func() {
   172  				pluginChain := []string{"go.kubebuilder.io/v2", "declarative.kubebuilder.io/v1"}
   173  
   174  				projectConfig := cfgv3.New()
   175  				Expect(projectConfig.SetPluginChain(pluginChain)).To(Succeed())
   176  
   177  				Expect(c.getInfoFromConfig(projectConfig)).To(Succeed())
   178  				Expect(c.pluginKeys).To(Equal(pluginChain))
   179  				Expect(c.projectVersion.Compare(projectConfig.GetVersion())).To(Equal(0))
   180  			})
   181  		})
   182  
   183  		When("having invalid plugin keys in the layout field", func() {
   184  			It("should fail", func() {
   185  				pluginChain := []string{"_/v1"}
   186  
   187  				projectConfig := cfgv3.New()
   188  				Expect(projectConfig.SetPluginChain(pluginChain)).To(Succeed())
   189  
   190  				Expect(c.getInfoFromConfig(projectConfig)).NotTo(Succeed())
   191  			})
   192  		})
   193  	})
   194  
   195  	Context("getInfoFromFlags", func() {
   196  		// Save os.Args and restore it for every test
   197  		var args []string
   198  		BeforeEach(func() {
   199  			c.cmd = c.newRootCmd()
   200  
   201  			args = os.Args
   202  		})
   203  		AfterEach(func() {
   204  			os.Args = args
   205  		})
   206  
   207  		When("no flag is set", func() {
   208  			It("should succeed", func() {
   209  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   210  				Expect(c.pluginKeys).To(BeEmpty())
   211  				Expect(c.projectVersion.Compare(config.Version{})).To(Equal(0))
   212  			})
   213  		})
   214  
   215  		When(fmt.Sprintf("--%s flag is set", pluginsFlag), func() {
   216  			It("should succeed using one plugin key", func() {
   217  				pluginKeys := []string{"go/v1"}
   218  				setPluginsFlag(strings.Join(pluginKeys, ","))
   219  
   220  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   221  				Expect(c.pluginKeys).To(Equal(pluginKeys))
   222  				Expect(c.projectVersion.Compare(config.Version{})).To(Equal(0))
   223  			})
   224  
   225  			It("should succeed using more than one plugin key", func() {
   226  				pluginKeys := []string{"go/v1", "example/v2", "test/v1"}
   227  				setPluginsFlag(strings.Join(pluginKeys, ","))
   228  
   229  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   230  				Expect(c.pluginKeys).To(Equal(pluginKeys))
   231  				Expect(c.projectVersion.Compare(config.Version{})).To(Equal(0))
   232  			})
   233  
   234  			It("should succeed using more than one plugin key with spaces", func() {
   235  				pluginKeys := []string{"go/v1", "example/v2", "test/v1"}
   236  				setPluginsFlag(strings.Join(pluginKeys, ", "))
   237  
   238  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   239  				Expect(c.pluginKeys).To(Equal(pluginKeys))
   240  				Expect(c.projectVersion.Compare(config.Version{})).To(Equal(0))
   241  			})
   242  
   243  			It("should fail for an invalid plugin key", func() {
   244  				setPluginsFlag("_/v1")
   245  
   246  				Expect(c.getInfoFromFlags(false)).NotTo(Succeed())
   247  			})
   248  		})
   249  
   250  		When(fmt.Sprintf("--%s flag is set", projectVersionFlag), func() {
   251  			It("should succeed", func() {
   252  				setProjectVersionFlag(projectVersion.String())
   253  
   254  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   255  				Expect(c.pluginKeys).To(BeEmpty())
   256  				Expect(c.projectVersion.Compare(projectVersion)).To(Equal(0))
   257  			})
   258  
   259  			It("should fail for an invalid project version", func() {
   260  				setProjectVersionFlag("v_1")
   261  
   262  				Expect(c.getInfoFromFlags(false)).NotTo(Succeed())
   263  			})
   264  		})
   265  
   266  		When(fmt.Sprintf("--%s and --%s flags are set", pluginsFlag, projectVersionFlag), func() {
   267  			It("should succeed using one plugin key", func() {
   268  				pluginKeys := []string{"go/v1"}
   269  				setPluginsFlag(strings.Join(pluginKeys, ","))
   270  				setProjectVersionFlag(projectVersion.String())
   271  
   272  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   273  				Expect(c.pluginKeys).To(Equal(pluginKeys))
   274  				Expect(c.projectVersion.Compare(projectVersion)).To(Equal(0))
   275  			})
   276  
   277  			It("should succeed using more than one plugin key", func() {
   278  				pluginKeys := []string{"go/v1", "example/v2", "test/v1"}
   279  				setPluginsFlag(strings.Join(pluginKeys, ","))
   280  				setProjectVersionFlag(projectVersion.String())
   281  
   282  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   283  				Expect(c.pluginKeys).To(Equal(pluginKeys))
   284  				Expect(c.projectVersion.Compare(projectVersion)).To(Equal(0))
   285  			})
   286  
   287  			It("should succeed using more than one plugin key with spaces", func() {
   288  				pluginKeys := []string{"go/v1", "example/v2", "test/v1"}
   289  				setPluginsFlag(strings.Join(pluginKeys, ", "))
   290  				setProjectVersionFlag(projectVersion.String())
   291  
   292  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   293  				Expect(c.pluginKeys).To(Equal(pluginKeys))
   294  				Expect(c.projectVersion.Compare(projectVersion)).To(Equal(0))
   295  			})
   296  		})
   297  
   298  		When("additional flags are set", func() {
   299  			It("should succeed", func() {
   300  				setFlag("extra-flag", "extra-value")
   301  
   302  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   303  			})
   304  
   305  			// `--help` is not captured by the allowlist, so we need to special case it
   306  			It("should not fail for `--help`", func() {
   307  				setBoolFlag("help")
   308  
   309  				Expect(c.getInfoFromFlags(false)).To(Succeed())
   310  			})
   311  		})
   312  	})
   313  
   314  	Context("getInfoFromDefaults", func() {
   315  		pluginKeys := []string{"go.kubebuilder.io/v2"}
   316  
   317  		It("should be a no-op if already have plugin keys", func() {
   318  			c.pluginKeys = pluginKeys
   319  
   320  			c.getInfoFromDefaults()
   321  			Expect(c.pluginKeys).To(Equal(pluginKeys))
   322  			Expect(c.projectVersion.Compare(config.Version{})).To(Equal(0))
   323  		})
   324  
   325  		It("should succeed if default plugins for project version are set", func() {
   326  			c.projectVersion = projectVersion
   327  			c.defaultPlugins = map[config.Version][]string{projectVersion: pluginKeys}
   328  
   329  			c.getInfoFromDefaults()
   330  			Expect(c.pluginKeys).To(Equal(pluginKeys))
   331  			Expect(c.projectVersion.Compare(projectVersion)).To(Equal(0))
   332  		})
   333  
   334  		It("should succeed if default plugins for default project version are set", func() {
   335  			c.defaultPlugins = map[config.Version][]string{projectVersion: pluginKeys}
   336  			c.defaultProjectVersion = projectVersion
   337  
   338  			c.getInfoFromDefaults()
   339  			Expect(c.pluginKeys).To(Equal(pluginKeys))
   340  			Expect(c.projectVersion.Compare(projectVersion)).To(Equal(0))
   341  		})
   342  
   343  		It("should succeed if default plugins for only a single project version are set", func() {
   344  			c.defaultPlugins = map[config.Version][]string{projectVersion: pluginKeys}
   345  
   346  			c.getInfoFromDefaults()
   347  			Expect(c.pluginKeys).To(Equal(pluginKeys))
   348  			Expect(c.projectVersion.Compare(projectVersion)).To(Equal(0))
   349  		})
   350  	})
   351  
   352  	Context("resolvePlugins", func() {
   353  		pluginKeys := []string{
   354  			"foo.example.com/v1",
   355  			"bar.example.com/v1",
   356  			"baz.example.com/v1",
   357  			"foo.kubebuilder.io/v1",
   358  			"foo.kubebuilder.io/v2",
   359  			"bar.kubebuilder.io/v1",
   360  			"bar.kubebuilder.io/v2",
   361  		}
   362  
   363  		plugins := makeMockPluginsFor(projectVersion, pluginKeys...)
   364  		plugins = append(plugins,
   365  			newMockPlugin("invalid.kubebuilder.io", "v1"),
   366  			newMockPlugin("only1.kubebuilder.io", "v1",
   367  				config.Version{Number: 1}),
   368  			newMockPlugin("only2.kubebuilder.io", "v1",
   369  				config.Version{Number: 2}),
   370  			newMockPlugin("1and2.kubebuilder.io", "v1",
   371  				config.Version{Number: 1}, config.Version{Number: 2}),
   372  			newMockPlugin("2and3.kubebuilder.io", "v1",
   373  				config.Version{Number: 2}, config.Version{Number: 3}),
   374  			newMockPlugin("1-2and3.kubebuilder.io", "v1",
   375  				config.Version{Number: 1}, config.Version{Number: 2}, config.Version{Number: 3}),
   376  		)
   377  		pluginMap := makeMapFor(plugins...)
   378  
   379  		BeforeEach(func() {
   380  			c.plugins = pluginMap
   381  		})
   382  
   383  		DescribeTable("should resolve",
   384  			func(key, qualified string) {
   385  				c.pluginKeys = []string{key}
   386  				c.projectVersion = projectVersion
   387  
   388  				Expect(c.resolvePlugins()).To(Succeed())
   389  				Expect(len(c.resolvedPlugins)).To(Equal(1))
   390  				Expect(plugin.KeyFor(c.resolvedPlugins[0])).To(Equal(qualified))
   391  			},
   392  			Entry("fully qualified plugin", "foo.example.com/v1", "foo.example.com/v1"),
   393  			Entry("plugin without version", "foo.example.com", "foo.example.com/v1"),
   394  			Entry("shortname without version", "baz", "baz.example.com/v1"),
   395  			Entry("shortname with version", "foo/v2", "foo.kubebuilder.io/v2"),
   396  		)
   397  
   398  		DescribeTable("should not resolve",
   399  			func(key string) {
   400  				c.pluginKeys = []string{key}
   401  				c.projectVersion = projectVersion
   402  
   403  				Expect(c.resolvePlugins()).NotTo(Succeed())
   404  			},
   405  			Entry("for an ambiguous version", "foo.kubebuilder.io"),
   406  			Entry("for an ambiguous name", "foo/v1"),
   407  			Entry("for an ambiguous name and version", "foo"),
   408  			Entry("for a non-existent name", "blah"),
   409  			Entry("for a non-existent version", "foo.example.com/v2"),
   410  			Entry("for a non-existent version", "foo/v3"),
   411  			Entry("for a non-existent version", "foo.example.com/v3"),
   412  			Entry("for a plugin that doesn't support the project version", "invalid.kubebuilder.io/v1"),
   413  		)
   414  
   415  		It("should succeed if only one common project version is found", func() {
   416  			c.pluginKeys = []string{"1and2", "2and3"}
   417  
   418  			Expect(c.resolvePlugins()).To(Succeed())
   419  			Expect(c.projectVersion.Compare(config.Version{Number: 2})).To(Equal(0))
   420  		})
   421  
   422  		It("should fail if no common project version is found", func() {
   423  			c.pluginKeys = []string{"only1", "only2"}
   424  
   425  			Expect(c.resolvePlugins()).NotTo(Succeed())
   426  		})
   427  
   428  		It("should fail if more than one common project versions are found", func() {
   429  			c.pluginKeys = []string{"1and2", "1-2and3"}
   430  
   431  			Expect(c.resolvePlugins()).NotTo(Succeed())
   432  		})
   433  
   434  		It("should succeed if more than one common project versions are found and one is the default", func() {
   435  			c.pluginKeys = []string{"2and3", "1-2and3"}
   436  			c.defaultProjectVersion = projectVersion
   437  
   438  			Expect(c.resolvePlugins()).To(Succeed())
   439  			Expect(c.projectVersion.Compare(projectVersion)).To(Equal(0))
   440  		})
   441  	})
   442  
   443  	Context("New", func() {
   444  		var c *CLI
   445  		var err error
   446  
   447  		When("no option is provided", func() {
   448  			It("should create a valid CLI", func() {
   449  				_, err = New()
   450  				Expect(err).NotTo(HaveOccurred())
   451  			})
   452  		})
   453  
   454  		// NOTE: Options are extensively tested in their own tests.
   455  		//       The ones tested here ensure better coverage.
   456  
   457  		When("providing a version string", func() {
   458  			It("should create a valid CLI", func() {
   459  				const version = "version string"
   460  				c, err = New(
   461  					WithPlugins(&goPluginV3.Plugin{}),
   462  					WithDefaultPlugins(projectVersion, &goPluginV3.Plugin{}),
   463  					WithVersion(version),
   464  				)
   465  				Expect(err).NotTo(HaveOccurred())
   466  				Expect(hasSubCommand(c.cmd, "version")).To(BeTrue())
   467  
   468  				// Test the version command
   469  				c.cmd.SetArgs([]string{"version"})
   470  				// Overwrite stdout to read the output and reset it afterwards
   471  				r, w, _ := os.Pipe()
   472  				temp := os.Stdout
   473  				defer func() {
   474  					os.Stdout = temp
   475  				}()
   476  				os.Stdout = w
   477  				Expect(c.cmd.Execute()).Should(Succeed())
   478  
   479  				_ = w.Close()
   480  
   481  				Expect(err).NotTo(HaveOccurred())
   482  				printed, _ := io.ReadAll(r)
   483  				Expect(string(printed)).To(Equal(
   484  					fmt.Sprintf("%s\n", version)))
   485  
   486  			})
   487  		})
   488  
   489  		When("enabling completion", func() {
   490  			It("should create a valid CLI", func() {
   491  				c, err = New(
   492  					WithPlugins(&goPluginV3.Plugin{}),
   493  					WithDefaultPlugins(projectVersion, &goPluginV3.Plugin{}),
   494  					WithCompletion(),
   495  				)
   496  				Expect(err).NotTo(HaveOccurred())
   497  				Expect(hasSubCommand(c.cmd, "completion")).To(BeTrue())
   498  			})
   499  		})
   500  
   501  		When("providing an invalid option", func() {
   502  			It("should return an error", func() {
   503  				// An empty project version is not valid
   504  				_, err = New(WithDefaultProjectVersion(config.Version{}))
   505  				Expect(err).To(HaveOccurred())
   506  			})
   507  		})
   508  
   509  		When("being unable to resolve plugins", func() {
   510  			// Save os.Args and restore it for every test
   511  			var args []string
   512  			BeforeEach(func() { args = os.Args })
   513  			AfterEach(func() { os.Args = args })
   514  
   515  			It("should return a CLI that returns an error", func() {
   516  				setPluginsFlag("foo")
   517  
   518  				c, err = New()
   519  				Expect(err).NotTo(HaveOccurred())
   520  
   521  				// Overwrite stderr to read the output and reset it afterwards
   522  				_, w, _ := os.Pipe()
   523  				temp := os.Stderr
   524  				defer func() {
   525  					os.Stderr = temp
   526  					_ = w.Close()
   527  				}()
   528  				os.Stderr = w
   529  
   530  				Expect(c.Run()).NotTo(Succeed())
   531  			})
   532  		})
   533  
   534  		When("providing extra commands", func() {
   535  			It("should create a valid CLI for non-conflicting ones", func() {
   536  				extraCommand := &cobra.Command{Use: "extra"}
   537  				c, err = New(
   538  					WithPlugins(&goPluginV3.Plugin{}),
   539  					WithDefaultPlugins(projectVersion, &goPluginV3.Plugin{}),
   540  					WithExtraCommands(extraCommand),
   541  				)
   542  				Expect(err).NotTo(HaveOccurred())
   543  				Expect(hasSubCommand(c.cmd, extraCommand.Use)).To(BeTrue())
   544  			})
   545  
   546  			It("should return an error for conflicting ones", func() {
   547  				extraCommand := &cobra.Command{Use: "init"}
   548  				c, err = New(
   549  					WithPlugins(&goPluginV3.Plugin{}),
   550  					WithDefaultPlugins(projectVersion, &goPluginV3.Plugin{}),
   551  					WithExtraCommands(extraCommand),
   552  				)
   553  				Expect(err).To(HaveOccurred())
   554  			})
   555  		})
   556  
   557  		When("providing extra alpha commands", func() {
   558  			It("should create a valid CLI for non-conflicting ones", func() {
   559  				extraAlphaCommand := &cobra.Command{Use: "extra"}
   560  				c, err = New(
   561  					WithPlugins(&goPluginV3.Plugin{}),
   562  					WithDefaultPlugins(projectVersion, &goPluginV3.Plugin{}),
   563  					WithExtraAlphaCommands(extraAlphaCommand),
   564  				)
   565  				Expect(err).NotTo(HaveOccurred())
   566  				var alpha *cobra.Command
   567  				for _, subcmd := range c.cmd.Commands() {
   568  					if subcmd.Name() == alphaCommand {
   569  						alpha = subcmd
   570  						break
   571  					}
   572  				}
   573  				Expect(alpha).NotTo(BeNil())
   574  				Expect(hasSubCommand(alpha, extraAlphaCommand.Use)).To(BeTrue())
   575  			})
   576  
   577  			It("should return an error for conflicting ones", func() {
   578  				extraAlphaCommand := &cobra.Command{Use: "extra"}
   579  				_, err = New(
   580  					WithPlugins(&goPluginV3.Plugin{}),
   581  					WithDefaultPlugins(projectVersion, &goPluginV3.Plugin{}),
   582  					WithExtraAlphaCommands(extraAlphaCommand, extraAlphaCommand),
   583  				)
   584  				Expect(err).To(HaveOccurred())
   585  			})
   586  		})
   587  
   588  		When("providing deprecated plugins", func() {
   589  			It("should succeed and print the deprecation notice", func() {
   590  				const (
   591  					deprecationWarning = "DEPRECATED"
   592  				)
   593  				deprecatedPlugin := newMockDeprecatedPlugin("deprecated", "v1", deprecationWarning, projectVersion)
   594  
   595  				// Overwrite stderr to read the deprecation output and reset it afterwards
   596  				r, w, _ := os.Pipe()
   597  				temp := os.Stderr
   598  				defer func() {
   599  					os.Stderr = temp
   600  				}()
   601  				os.Stderr = w
   602  
   603  				c, err = New(
   604  					WithPlugins(deprecatedPlugin),
   605  					WithDefaultPlugins(projectVersion, deprecatedPlugin),
   606  					WithDefaultProjectVersion(projectVersion),
   607  				)
   608  
   609  				_ = w.Close()
   610  
   611  				Expect(err).NotTo(HaveOccurred())
   612  				printed, _ := io.ReadAll(r)
   613  				Expect(string(printed)).To(Equal(
   614  					fmt.Sprintf(noticeColor, fmt.Sprintf(deprecationFmt, deprecationWarning))))
   615  			})
   616  		})
   617  	})
   618  })