kubectl/pkg/cmd/apiresources/apiresources_test.go

421 lines
12 KiB
Go

/*
Copyright 2022 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiresources
import (
"encoding/json"
"strings"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/require"
apiequality "k8s.io/apimachinery/pkg/api/equality"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericiooptions"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
"sigs.k8s.io/yaml"
)
func TestAPIResourcesComplete(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
cmd := NewCmdAPIResources(tf, genericiooptions.NewTestIOStreamsDiscard())
parentCmd := &cobra.Command{Use: "kubectl"}
parentCmd.AddCommand(cmd)
o := NewAPIResourceOptions(genericiooptions.NewTestIOStreamsDiscard())
err := o.Complete(tf, cmd, []string{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
err = o.Complete(tf, cmd, []string{"foo"})
if err == nil {
t.Fatalf("An error was expected but not returned")
}
expectedError := `unexpected arguments: [foo]
See 'kubectl api-resources -h' for help and examples`
if err.Error() != expectedError {
t.Fatalf("Unexpected error: %v\n expected: %v", err, expectedError)
}
*o.PrintFlags.OutputFormat = "foo"
err = o.Complete(tf, cmd, []string{})
if err == nil {
t.Fatalf("An error was expected but not returned")
}
expectedError = `unable to match a printer suitable for the output format "foo", allowed formats are:`
if !strings.HasPrefix(err.Error(), expectedError) {
t.Fatalf("Unexpected error: %v\n expected: %v", err, expectedError)
}
}
func TestAPIResourcesValidate(t *testing.T) {
testCases := []struct {
name string
optionSetupFn func(o *APIResourceOptions)
expectedError string
}{
{
name: "no errors",
optionSetupFn: func(o *APIResourceOptions) {},
expectedError: "",
},
{
name: "invalid sort by",
optionSetupFn: func(o *APIResourceOptions) {
o.SortBy = "foo"
},
expectedError: "--sort-by accepts only name or kind",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(tt *testing.T) {
o := NewAPIResourceOptions(genericiooptions.NewTestIOStreamsDiscard())
tc.optionSetupFn(o)
err := o.Validate()
if tc.expectedError == "" {
if err != nil {
tt.Fatalf("Unexpected error: %v", err)
}
} else {
if err == nil {
tt.Fatalf("An error was expected but not returned")
}
if err.Error() != tc.expectedError {
tt.Fatalf("Unexpected error: %v, expected: %v", err, tc.expectedError)
}
}
})
}
}
func TestAPIResourcesRun(t *testing.T) {
dc := cmdtesting.NewFakeCachedDiscoveryClient()
dc.PreferredResources = []*v1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []v1.APIResource{
{
Name: "foos",
Namespaced: false,
Kind: "Foo",
Verbs: []string{"get", "list"},
ShortNames: []string{"f", "fo"},
Categories: []string{"some-category"},
},
{
Name: "bars",
Namespaced: true,
Kind: "Bar",
Verbs: []string{"get", "list", "create"},
ShortNames: []string{},
Categories: []string{},
},
},
},
{
GroupVersion: "somegroup/v1",
APIResources: []v1.APIResource{
{
Name: "bazzes",
Namespaced: true,
Kind: "Baz",
Verbs: []string{"get", "list", "create", "delete"},
ShortNames: []string{"b"},
Categories: []string{"some-category", "another-category"},
},
{
Name: "NoVerbs",
Namespaced: true,
Kind: "NoVerbs",
Verbs: []string{},
ShortNames: []string{"b"},
Categories: []string{},
},
},
},
{
GroupVersion: "someothergroup/v1",
APIResources: []v1.APIResource{},
},
}
tf := cmdtesting.NewTestFactory().WithDiscoveryClient(dc)
defer tf.Cleanup()
testCases := []struct {
name string
commandSetupFn func(cmd *cobra.Command)
expectedOutput string
expectedInvalidations int
}{
{
name: "defaults",
commandSetupFn: func(cmd *cobra.Command) {},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
bars v1 true Bar
foos f,fo v1 false Foo
bazzes b somegroup/v1 true Baz
`,
expectedInvalidations: 1,
},
{
name: "no headers",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("no-headers", "true")
},
expectedOutput: `bars v1 true Bar
foos f,fo v1 false Foo
bazzes b somegroup/v1 true Baz
`,
expectedInvalidations: 1,
},
{
name: "specific api group",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("api-group", "somegroup")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
bazzes b somegroup/v1 true Baz
`,
expectedInvalidations: 1,
},
{
name: "output wide",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("output", "wide")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND VERBS CATEGORIES
bars v1 true Bar get,list,create
foos f,fo v1 false Foo get,list some-category
bazzes b somegroup/v1 true Baz get,list,create,delete some-category,another-category
`,
expectedInvalidations: 1,
},
{
name: "output name",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("output", "name")
},
expectedOutput: `bars
foos
bazzes.somegroup
`,
expectedInvalidations: 1,
},
{
name: "namespaced",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("namespaced", "true")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
bars v1 true Bar
bazzes b somegroup/v1 true Baz
`,
expectedInvalidations: 1,
},
{
name: "single verb",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("verbs", "create")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
bars v1 true Bar
bazzes b somegroup/v1 true Baz
`,
expectedInvalidations: 1,
},
{
name: "multiple verbs",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("verbs", "create,delete")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
bazzes b somegroup/v1 true Baz
`,
expectedInvalidations: 1,
},
{
name: "single category",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("categories", "some-category")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
foos f,fo v1 false Foo
bazzes b somegroup/v1 true Baz
`,
expectedInvalidations: 1,
},
{
name: "multiple categories",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("categories", "some-category,another-category")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
bazzes b somegroup/v1 true Baz
`,
expectedInvalidations: 1,
},
{
name: "sort by name",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("sort-by", "name")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
bars v1 true Bar
bazzes b somegroup/v1 true Baz
foos f,fo v1 false Foo
`,
expectedInvalidations: 1,
},
{
name: "sort by kind",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("sort-by", "kind")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
bars v1 true Bar
bazzes b somegroup/v1 true Baz
foos f,fo v1 false Foo
`,
expectedInvalidations: 1,
},
{
name: "cached",
commandSetupFn: func(cmd *cobra.Command) {
cmd.Flags().Set("cached", "true")
},
expectedOutput: `NAME SHORTNAMES APIVERSION NAMESPACED KIND
bars v1 true Bar
foos f,fo v1 false Foo
bazzes b somegroup/v1 true Baz
`,
expectedInvalidations: 0,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(tt *testing.T) {
dc.Invalidations = 0
ioStreams, _, out, errOut := genericiooptions.NewTestIOStreams()
cmd := NewCmdAPIResources(tf, ioStreams)
tc.commandSetupFn(cmd)
cmd.Run(cmd, []string{})
if errOut.Len() > 0 {
t.Fatalf("unexpected error output: %s", errOut.String())
}
if out.String() != tc.expectedOutput {
tt.Fatalf("unexpected output: %s\nexpected: %s", out.String(), tc.expectedOutput)
}
if dc.Invalidations != tc.expectedInvalidations {
tt.Fatalf("unexpected invalidations: %d, expected: %d", dc.Invalidations, tc.expectedInvalidations)
}
})
}
}
// TestAPIResourcesRunJsonYaml is doing same thing as TestAPIResourcesRun but for JSON and YAML outputs
// A separate test function is created because we are using apieqaulity.Semantic.DeepEqual
// to check equality between input and output
func TestAPIResourcesRunJsonYaml(t *testing.T) {
dc := cmdtesting.NewFakeCachedDiscoveryClient()
tf := cmdtesting.NewTestFactory().WithDiscoveryClient(dc)
defer tf.Cleanup()
testCases := []struct {
name string
expectedInvalidations int
preferredResources []*v1.APIResourceList
}{
{
name: "one",
preferredResources: []*v1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []v1.APIResource{
{
Name: "foos",
Namespaced: false,
Kind: "Foo",
Verbs: []string{"get", "list"},
ShortNames: []string{"f", "fo"},
Categories: []string{"some-category"},
},
},
},
},
},
{
name: "two",
preferredResources: []*v1.APIResourceList{
{
GroupVersion: "somegroup/v1",
APIResources: []v1.APIResource{
{
Name: "bazzes",
Namespaced: true,
Kind: "Baz",
Verbs: []string{"get", "list", "create", "delete"},
ShortNames: []string{"b"},
Categories: []string{"some-category", "another-category"},
},
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(tt *testing.T) {
dc.PreferredResources = tc.preferredResources
ioStreams, _, out, errOut := genericiooptions.NewTestIOStreams()
for _, v := range []string{"json", "yaml"} {
cmd := NewCmdAPIResources(tf, ioStreams)
err := cmd.Flags().Set("output", v)
require.NoError(tt, err)
cmd.Run(cmd, []string{})
if errOut.Len() > 0 {
t.Fatalf("unexpected error output: %s", errOut.String())
}
apiResourceList := v1.APIResourceList{}
switch v {
case "json":
err = json.Unmarshal(out.Bytes(), &apiResourceList)
case "yaml":
err = yaml.Unmarshal(out.Bytes(), &apiResourceList)
}
require.NoError(tt, err)
// this will undo custom value we add in RunAPIResources in the lines:
// resource.Group = gv.Group
// resource.Version = gv.Version
apiResourceList.GroupVersion = apiResourceList.APIResources[0].Group + "/" + apiResourceList.APIResources[0].Version
apiResourceList.APIResources[0].Version = ""
apiResourceList.APIResources[0].Group = ""
if !apiequality.Semantic.DeepEqual(tc.preferredResources[0].APIResources[0], apiResourceList.APIResources[0]) {
tt.Fatalf("expected output: [%v]\n, but got [%v]", tc.preferredResources[0].APIResources[0], apiResourceList.APIResources[0])
}
}
})
}
}