Add unit tests for api-resources and api-versions commands

Kubernetes-commit: 13e0120bf0662c4530df2badf1f8f73426cf9e51
This commit is contained in:
Brian Pursley 2022-05-14 11:06:49 -04:00 committed by Kubernetes Publisher
parent ca3557e2c6
commit 96f0c72304
3 changed files with 437 additions and 6 deletions

View File

@ -0,0 +1,299 @@
/*
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 (
"testing"
"github.com/spf13/cobra"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
)
func TestAPIResourcesComplete(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
cmd := NewCmdAPIResources(tf, genericclioptions.NewTestIOStreamsDiscard())
parentCmd := &cobra.Command{Use: "kubectl"}
parentCmd.AddCommand(cmd)
o := NewAPIResourceOptions(genericclioptions.NewTestIOStreamsDiscard())
err := o.Complete(cmd, []string{})
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
err = o.Complete(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)
}
}
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 output",
optionSetupFn: func(o *APIResourceOptions) {
o.Output = "foo"
},
expectedError: "--output foo is not available",
},
{
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(genericclioptions.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"},
},
{
Name: "bars",
Namespaced: true,
Kind: "Bar",
Verbs: []string{"get", "list", "create"},
ShortNames: []string{},
},
},
},
{
GroupVersion: "somegroup/v1",
APIResources: []v1.APIResource{
{
Name: "bazzes",
Namespaced: true,
Kind: "Baz",
Verbs: []string{"get", "list", "create", "delete"},
ShortNames: []string{"b"},
},
{
Name: "NoVerbs",
Namespaced: true,
Kind: "NoVerbs",
Verbs: []string{},
ShortNames: []string{"b"},
},
},
},
{
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
bars v1 true Bar [get list create]
foos f,fo v1 false Foo [get list]
bazzes b somegroup/v1 true Baz [get list create delete]
`,
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: "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 := genericclioptions.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)
}
})
}
}

View File

@ -0,0 +1,101 @@
/*
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 (
"github.com/spf13/cobra"
"testing"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
)
func TestAPIVersionsComplete(t *testing.T) {
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
cmd := NewCmdAPIVersions(tf, genericclioptions.NewTestIOStreamsDiscard())
parentCmd := &cobra.Command{Use: "kubectl"}
parentCmd.AddCommand(cmd)
o := NewAPIVersionsOptions(genericclioptions.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-versions -h' for help and examples`
if err.Error() != expectedError {
t.Fatalf("Unexpected error: %v\n expected: %v", err, expectedError)
}
}
func TestAPIVersionsRun(t *testing.T) {
dc := cmdtesting.NewFakeCachedDiscoveryClient()
dc.Groups = []*v1.APIGroup{
{
Name: "",
Versions: []v1.GroupVersionForDiscovery{
{GroupVersion: "v1"},
},
},
{
Name: "foo",
Versions: []v1.GroupVersionForDiscovery{
{GroupVersion: "foo/v1beta1"},
{GroupVersion: "foo/v1"},
{GroupVersion: "foo/v2"},
},
},
{
Name: "bar",
Versions: []v1.GroupVersionForDiscovery{
{GroupVersion: "bar/v1"},
},
},
}
tf := cmdtesting.NewTestFactory().WithDiscoveryClient(dc)
defer tf.Cleanup()
ioStreams, _, out, errOut := genericclioptions.NewTestIOStreams()
cmd := NewCmdAPIVersions(tf, ioStreams)
cmd.Run(cmd, []string{})
if errOut.Len() > 0 {
t.Fatalf("unexpected error output: %s", errOut.String())
}
expectedOutput := `bar/v1
foo/v1
foo/v1beta1
foo/v2
v1
`
if out.String() != expectedOutput {
t.Fatalf("unexpected output: %s\nexpected: %s", out.String(), expectedOutput)
}
expectedInvalidations := 1
if dc.Invalidations != expectedInvalidations {
t.Fatalf("unexpected invalidations: %d, expected: %d", dc.Invalidations, expectedInvalidations)
}
}

View File

@ -364,19 +364,45 @@ func AddToScheme(scheme *runtime.Scheme) (meta.RESTMapper, runtime.Codec) {
return mapper, codec
}
type fakeCachedDiscoveryClient struct {
type FakeCachedDiscoveryClient struct {
discovery.DiscoveryInterface
Groups []*metav1.APIGroup
Resources []*metav1.APIResourceList
PreferredResources []*metav1.APIResourceList
Invalidations int
}
func (d *fakeCachedDiscoveryClient) Fresh() bool {
func NewFakeCachedDiscoveryClient() *FakeCachedDiscoveryClient {
return &FakeCachedDiscoveryClient{
Groups: []*metav1.APIGroup{},
Resources: []*metav1.APIResourceList{},
PreferredResources: []*metav1.APIResourceList{},
Invalidations: 0,
}
}
func (d *FakeCachedDiscoveryClient) Fresh() bool {
return true
}
func (d *fakeCachedDiscoveryClient) Invalidate() {
func (d *FakeCachedDiscoveryClient) Invalidate() {
d.Invalidations++
}
func (d *fakeCachedDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return []*metav1.APIGroup{}, []*metav1.APIResourceList{}, nil
func (d *FakeCachedDiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
return d.Groups, d.Resources, nil
}
func (d *FakeCachedDiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {
groupList := &metav1.APIGroupList{Groups: []metav1.APIGroup{}}
for _, g := range d.Groups {
groupList.Groups = append(groupList.Groups, *g)
}
return groupList, nil
}
func (d *FakeCachedDiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
return d.PreferredResources, nil
}
// TestFactory extends cmdutil.Factory
@ -447,6 +473,11 @@ func (f *TestFactory) WithClientConfig(clientConfig clientcmd.ClientConfig) *Tes
return f
}
func (f *TestFactory) WithDiscoveryClient(discoveryClient discovery.CachedDiscoveryInterface) *TestFactory {
f.kubeConfigFlags.WithDiscoveryClient(discoveryClient)
return f
}
// Cleanup cleans up TestFactory temp config file
func (f *TestFactory) Cleanup() {
if f.tempConfigFile == nil {
@ -618,7 +649,7 @@ func testRESTMapper() meta.RESTMapper {
},
}
fakeDs := &fakeCachedDiscoveryClient{}
fakeDs := NewFakeCachedDiscoveryClient()
expander := restmapper.NewShortcutExpander(mapper, fakeDs)
return expander
}