mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +00:00
add explain tests for openapiv3
This commit is contained in:
parent
8249a827bd
commit
9597abd089
@ -14,24 +14,31 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package explain
|
||||
package explain_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
sptest "k8s.io/apimachinery/pkg/util/strategicpatch/testing"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/discovery"
|
||||
openapiclient "k8s.io/client-go/openapi"
|
||||
"k8s.io/client-go/rest"
|
||||
clienttestutil "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubectl/pkg/cmd/explain"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
)
|
||||
|
||||
var (
|
||||
fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "testdata", "openapi", "swagger.json")}
|
||||
testDataPath = filepath.Join("..", "..", "..", "testdata")
|
||||
fakeSchema = sptest.Fake{Path: filepath.Join(testDataPath, "openapi", "swagger.json")}
|
||||
FakeOpenAPISchema = testOpenAPISchema{
|
||||
OpenAPISchemaFn: func() (openapi.Resources, error) {
|
||||
s, err := fakeSchema.OpenAPISchema()
|
||||
@ -51,8 +58,8 @@ func TestExplainInvalidArgs(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
|
||||
opts := NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd := NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||
opts := explain.NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd := explain.NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||
err := opts.Complete(tf, cmd, []string{})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@ -78,8 +85,8 @@ func TestExplainNotExistResource(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
|
||||
opts := NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd := NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||
opts := explain.NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd := explain.NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||
err := opts.Complete(tf, cmd, []string{"foo"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
@ -96,30 +103,106 @@ func TestExplainNotExistResource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExplainNotExistVersion(t *testing.T) {
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
type explainTestCase struct {
|
||||
Name string
|
||||
Args []string
|
||||
Flags map[string]string
|
||||
ExpectPattern []string
|
||||
ExpectErrorPattern string
|
||||
|
||||
opts := NewExplainOptions("kubectl", genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd := NewCmdExplain("kubectl", tf, genericclioptions.NewTestIOStreamsDiscard())
|
||||
err := opts.Complete(tf, cmd, []string{"pods"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
opts.APIVersion = "v99"
|
||||
|
||||
err = opts.Validate()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
err = opts.Run()
|
||||
if err.Error() != "couldn't find resource for \"/v99, Kind=Pod\"" {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
// Custom OpenAPI V3 client to use for the test. If nil, a default one will
|
||||
// be provided
|
||||
OpenAPIV3SchemaFn func() (openapiclient.Client, error)
|
||||
}
|
||||
|
||||
func TestExplain(t *testing.T) {
|
||||
var explainV2Cases = []explainTestCase{
|
||||
{
|
||||
Name: "Basic",
|
||||
Args: []string{"pods"},
|
||||
ExpectPattern: []string{`\s*KIND:[\t ]*Pod\s*`},
|
||||
},
|
||||
{
|
||||
Name: "Recursive",
|
||||
Args: []string{"pods"},
|
||||
Flags: map[string]string{"recursive": "true"},
|
||||
ExpectPattern: []string{`\s*KIND:[\t ]*Pod\s*`},
|
||||
},
|
||||
{
|
||||
Name: "DefaultAPIVersion",
|
||||
Args: []string{"horizontalpodautoscalers"},
|
||||
Flags: map[string]string{"api-version": "autoscaling/v1"},
|
||||
ExpectPattern: []string{`\s*VERSION:[\t ]*(v1|autoscaling/v1)\s*`},
|
||||
},
|
||||
{
|
||||
Name: "NonExistingAPIVersion",
|
||||
Args: []string{"pods"},
|
||||
Flags: map[string]string{"api-version": "v99"},
|
||||
ExpectErrorPattern: `couldn't find resource for \"/v99, (Kind=Pod|Resource=pods)\"`,
|
||||
},
|
||||
{
|
||||
Name: "NonExistingResource",
|
||||
Args: []string{"foo"},
|
||||
ExpectErrorPattern: `the server doesn't have a resource type "foo"`,
|
||||
},
|
||||
}
|
||||
|
||||
func TestExplainOpenAPIV2(t *testing.T) {
|
||||
runExplainTestCases(t, explainV2Cases)
|
||||
}
|
||||
|
||||
func TestExplainOpenAPIV3(t *testing.T) {
|
||||
|
||||
fallbackV3SchemaFn := func() (openapiclient.Client, error) {
|
||||
fakeDiscoveryClient := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: "https://not.a.real.site:65543/"})
|
||||
return fakeDiscoveryClient.OpenAPIV3(), nil
|
||||
}
|
||||
// Returns a client that causes fallback to v2 implementation
|
||||
cases := []explainTestCase{
|
||||
{
|
||||
// No --output, but OpenAPIV3 enabled should fall back to v2 if
|
||||
// v2 is not available. Shows this by making openapiv3 client
|
||||
// point to a bad URL. So the fact the proper data renders is
|
||||
// indication v2 was used instead.
|
||||
Name: "Fallback",
|
||||
Args: []string{"pods"},
|
||||
ExpectPattern: []string{`\s*KIND:[\t ]*Pod\s*`},
|
||||
OpenAPIV3SchemaFn: fallbackV3SchemaFn,
|
||||
},
|
||||
{
|
||||
Name: "NonDefaultAPIVersion",
|
||||
Args: []string{"horizontalpodautoscalers"},
|
||||
Flags: map[string]string{"api-version": "autoscaling/v2"},
|
||||
ExpectPattern: []string{`\s*VERSION:[\t ]*(v2|autoscaling/v2)\s*`},
|
||||
},
|
||||
{
|
||||
// Show that explicitly specifying --output plaintext-openapiv2 causes
|
||||
// old implementation to be used even though OpenAPIV3 is enabled
|
||||
Name: "OutputPlaintextV2",
|
||||
Args: []string{"pods"},
|
||||
Flags: map[string]string{"output": "plaintext-openapiv2"},
|
||||
ExpectPattern: []string{`\s*KIND:[\t ]*Pod\s*`},
|
||||
OpenAPIV3SchemaFn: fallbackV3SchemaFn,
|
||||
},
|
||||
}
|
||||
cases = append(cases, explainV2Cases...)
|
||||
|
||||
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.ExplainOpenapiV3}, t, func(t *testing.T) {
|
||||
runExplainTestCases(t, cases)
|
||||
})
|
||||
}
|
||||
|
||||
func runExplainTestCases(t *testing.T, cases []explainTestCase) {
|
||||
fakeServer, err := clienttestutil.NewFakeOpenAPIV3Server(filepath.Join(testDataPath, "openapi", "v3"))
|
||||
if err != nil {
|
||||
t.Fatalf("error starting fake openapi server: %v", err.Error())
|
||||
}
|
||||
defer fakeServer.HttpServer.Close()
|
||||
|
||||
openapiV3SchemaFn := func() (openapiclient.Client, error) {
|
||||
fakeDiscoveryClient := discovery.NewDiscoveryClientForConfigOrDie(&rest.Config{Host: fakeServer.HttpServer.URL})
|
||||
return fakeDiscoveryClient.OpenAPIV3(), nil
|
||||
}
|
||||
|
||||
tf := cmdtesting.NewTestFactory()
|
||||
defer tf.Cleanup()
|
||||
|
||||
@ -127,29 +210,72 @@ func TestExplain(t *testing.T) {
|
||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||
|
||||
ioStreams, _, buf, _ := genericclioptions.NewTestIOStreams()
|
||||
cmd := NewCmdExplain("kubectl", tf, ioStreams)
|
||||
cmd.Run(cmd, []string{"pods"})
|
||||
if !strings.Contains(buf.String(), "KIND: Pod") {
|
||||
t.Fatalf("expected output should include pod kind")
|
||||
}
|
||||
|
||||
cmd.Flags().Set("recursive", "true")
|
||||
cmd.Run(cmd, []string{"pods"})
|
||||
if !strings.Contains(buf.String(), "KIND: Pod") ||
|
||||
!strings.Contains(buf.String(), "annotations\t<map[string]string>") {
|
||||
t.Fatalf("expected output should include pod kind")
|
||||
}
|
||||
type catchFatal error
|
||||
|
||||
cmd.Flags().Set("api-version", "batch/v1")
|
||||
cmd.Run(cmd, []string{"cronjobs"})
|
||||
if !strings.Contains(buf.String(), "VERSION: batch/v1") {
|
||||
t.Fatalf("expected output should include pod batch/v1")
|
||||
}
|
||||
for _, tcase := range cases {
|
||||
|
||||
cmd.Flags().Set("api-version", "batch/v1beta1")
|
||||
cmd.Run(cmd, []string{"cronjobs"})
|
||||
if !strings.Contains(buf.String(), "VERSION: batch/v1beta1") {
|
||||
t.Fatalf("expected output should include pod batch/v1beta1")
|
||||
t.Run(tcase.Name, func(t *testing.T) {
|
||||
|
||||
// Catch os.Exit calls for tests which expect them
|
||||
// and replace them with panics that we catch in each test
|
||||
// to check if it is expected.
|
||||
cmdutil.BehaviorOnFatal(func(str string, code int) {
|
||||
panic(catchFatal(errors.New(str)))
|
||||
})
|
||||
defer cmdutil.DefaultBehaviorOnFatal()
|
||||
|
||||
var err error
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
// Catch panic and check at end of test if it is
|
||||
// expected.
|
||||
if panicErr := recover(); panicErr != nil {
|
||||
if e := panicErr.(catchFatal); e != nil {
|
||||
err = e
|
||||
} else {
|
||||
panic(panicErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if tcase.OpenAPIV3SchemaFn != nil {
|
||||
tf.OpenAPIV3ClientFunc = tcase.OpenAPIV3SchemaFn
|
||||
} else {
|
||||
tf.OpenAPIV3ClientFunc = openapiV3SchemaFn
|
||||
}
|
||||
|
||||
cmd := explain.NewCmdExplain("kubectl", tf, ioStreams)
|
||||
for k, v := range tcase.Flags {
|
||||
if err := cmd.Flags().Set(k, v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
cmd.Run(cmd, tcase.Args)
|
||||
}()
|
||||
|
||||
for _, rexp := range tcase.ExpectPattern {
|
||||
if matched, err := regexp.MatchString(rexp, buf.String()); err != nil || !matched {
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else {
|
||||
t.Errorf("expected output to match regex:\n\t%s\ninstead got:\n\t%s", rexp, buf.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if matched, regexErr := regexp.MatchString(tcase.ExpectErrorPattern, err.Error()); len(tcase.ExpectErrorPattern) == 0 || regexErr != nil || !matched {
|
||||
t.Fatalf("unexpected error: %s did not match regex %s (%v)", err.Error(),
|
||||
tcase.ExpectErrorPattern, regexErr)
|
||||
}
|
||||
} else if len(tcase.ExpectErrorPattern) > 0 {
|
||||
t.Fatalf("did not trigger expected error: %s in output:\n%s", tcase.ExpectErrorPattern, buf.String())
|
||||
}
|
||||
})
|
||||
|
||||
buf.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,11 +287,11 @@ func TestAlphaEnablement(t *testing.T) {
|
||||
f := cmdtesting.NewTestFactory()
|
||||
defer f.Cleanup()
|
||||
|
||||
cmd := NewCmdExplain("kubectl", f, genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd := explain.NewCmdExplain("kubectl", f, genericclioptions.NewTestIOStreamsDiscard())
|
||||
require.Nil(t, cmd.Flags().Lookup(flag), "flag %q should not be registered without the %q feature enabled", flag, feature)
|
||||
|
||||
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{feature}, t, func(t *testing.T) {
|
||||
cmd := NewCmdExplain("kubectl", f, genericclioptions.NewTestIOStreamsDiscard())
|
||||
cmd := explain.NewCmdExplain("kubectl", f, genericclioptions.NewTestIOStreamsDiscard())
|
||||
require.NotNil(t, cmd.Flags().Lookup(flag), "flag %q should be registered with the %q feature enabled", flag, feature)
|
||||
})
|
||||
}
|
||||
|
28772
staging/src/k8s.io/kubectl/testdata/openapi/v3/api/v1.json
vendored
Normal file
28772
staging/src/k8s.io/kubectl/testdata/openapi/v3/api/v1.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/autoscaling/v1.json
vendored
Normal file
1
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/autoscaling/v1.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/autoscaling/v2.json
vendored
Normal file
1
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/autoscaling/v2.json
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user