Merge pull request #116972 from apelisse/update-openapi-file-client
openapi: Make file client more easy to re-use
This commit is contained in:
		@@ -119,7 +119,7 @@ func TestFallbackQueryParamVerifier_PrimaryNoFallback(t *testing.T) {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	root := openapi3.NewRoot(cached.NewClient(openapitest.NewFileClient(t)))
 | 
						root := openapi3.NewRoot(cached.NewClient(openapitest.NewEmbeddedFileClient()))
 | 
				
			||||||
	for tn, tc := range tests {
 | 
						for tn, tc := range tests {
 | 
				
			||||||
		t.Run(tn, func(t *testing.T) {
 | 
							t.Run(tn, func(t *testing.T) {
 | 
				
			||||||
			primary := createFakeV3Verifier(tc.crds, root, tc.queryParam)
 | 
								primary := createFakeV3Verifier(tc.crds, root, tc.queryParam)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -116,7 +116,7 @@ func TestV3SupportsQueryParamBatchV1(t *testing.T) {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	root := openapi3.NewRoot(cached.NewClient(openapitest.NewFileClient(t)))
 | 
						root := openapi3.NewRoot(cached.NewClient(openapitest.NewEmbeddedFileClient()))
 | 
				
			||||||
	for tn, tc := range tests {
 | 
						for tn, tc := range tests {
 | 
				
			||||||
		t.Run(tn, func(t *testing.T) {
 | 
							t.Run(tn, func(t *testing.T) {
 | 
				
			||||||
			verifier := &queryParamVerifierV3{
 | 
								verifier := &queryParamVerifierV3{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,33 +19,35 @@ package openapitest
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"embed"
 | 
						"embed"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"path/filepath"
 | 
						"io/fs"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"sync"
 | 
					 | 
				
			||||||
	"testing"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"k8s.io/client-go/openapi"
 | 
						"k8s.io/client-go/openapi"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//go:embed testdata/*_openapi.json
 | 
					//go:embed testdata/*_openapi.json
 | 
				
			||||||
var f embed.FS
 | 
					var embedded embed.FS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewFileClient returns a test client implementing the openapi.Client
 | 
					// NewFileClient returns a test client implementing the openapi.Client
 | 
				
			||||||
// interface, which serves a subset of hard-coded GroupVersion
 | 
					// interface, which serves Open API V3 specifications files from the
 | 
				
			||||||
// Open API V3 specifications files. The subset of specifications is
 | 
					// given path, as prepared in `api/openapi-spec/v3`.
 | 
				
			||||||
// located in the "testdata" subdirectory.
 | 
					func NewFileClient(path string) openapi.Client {
 | 
				
			||||||
func NewFileClient(t *testing.T) openapi.Client {
 | 
						return &fileClient{f: os.DirFS(path)}
 | 
				
			||||||
	if t == nil {
 | 
					 | 
				
			||||||
		panic("non-nil testing.T required; this package is only for use in tests")
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
	return &fileClient{t: t}
 | 
					
 | 
				
			||||||
 | 
					// NewEmbeddedFileClient returns a test client that uses the embedded
 | 
				
			||||||
 | 
					// `testdata` openapi files.
 | 
				
			||||||
 | 
					func NewEmbeddedFileClient() openapi.Client {
 | 
				
			||||||
 | 
						f, err := fs.Sub(embedded, "testdata")
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &fileClient{f: f}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type fileClient struct {
 | 
					type fileClient struct {
 | 
				
			||||||
	t     *testing.T
 | 
						f fs.FS
 | 
				
			||||||
	init  sync.Once
 | 
					 | 
				
			||||||
	paths map[string]openapi.GroupVersion
 | 
					 | 
				
			||||||
	err   error
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// fileClient implements the openapi.Client interface.
 | 
					// fileClient implements the openapi.Client interface.
 | 
				
			||||||
@@ -60,29 +62,23 @@ var _ openapi.Client = &fileClient{}
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
// The file contents are read only once. All files must parse correctly
 | 
					// The file contents are read only once. All files must parse correctly
 | 
				
			||||||
// into an api path, or an error is returned.
 | 
					// into an api path, or an error is returned.
 | 
				
			||||||
func (t *fileClient) Paths() (map[string]openapi.GroupVersion, error) {
 | 
					func (f *fileClient) Paths() (map[string]openapi.GroupVersion, error) {
 | 
				
			||||||
	t.init.Do(func() {
 | 
						paths := map[string]openapi.GroupVersion{}
 | 
				
			||||||
		t.paths = map[string]openapi.GroupVersion{}
 | 
						entries, err := fs.ReadDir(f.f, ".")
 | 
				
			||||||
		entries, err := f.ReadDir("testdata")
 | 
					 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
			t.err = err
 | 
							return nil, err
 | 
				
			||||||
			t.t.Error(err)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for _, e := range entries {
 | 
						for _, e := range entries {
 | 
				
			||||||
		// this reverses the transformation done in hack/update-openapi-spec.sh
 | 
							// this reverses the transformation done in hack/update-openapi-spec.sh
 | 
				
			||||||
		path := strings.ReplaceAll(strings.TrimSuffix(e.Name(), "_openapi.json"), "__", "/")
 | 
							path := strings.ReplaceAll(strings.TrimSuffix(e.Name(), "_openapi.json"), "__", "/")
 | 
				
			||||||
			t.paths[path] = &fileGroupVersion{t: t.t, filename: filepath.Join("testdata", e.Name())}
 | 
							paths[path] = &fileGroupVersion{f: f.f, filename: e.Name()}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	})
 | 
						return paths, nil
 | 
				
			||||||
	return t.paths, t.err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type fileGroupVersion struct {
 | 
					type fileGroupVersion struct {
 | 
				
			||||||
	t        *testing.T
 | 
						f        fs.FS
 | 
				
			||||||
	init     sync.Once
 | 
					 | 
				
			||||||
	filename string
 | 
						filename string
 | 
				
			||||||
	data     []byte
 | 
					 | 
				
			||||||
	err      error
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// fileGroupVersion implements the openapi.GroupVersion interface.
 | 
					// fileGroupVersion implements the openapi.GroupVersion interface.
 | 
				
			||||||
@@ -91,17 +87,10 @@ var _ openapi.GroupVersion = &fileGroupVersion{}
 | 
				
			|||||||
// Schema returns the OpenAPI V3 specification for the GroupVersion as
 | 
					// Schema returns the OpenAPI V3 specification for the GroupVersion as
 | 
				
			||||||
// unstructured bytes, or an error if the contentType is not
 | 
					// unstructured bytes, or an error if the contentType is not
 | 
				
			||||||
// "application/json" or there is an error reading the spec file. The
 | 
					// "application/json" or there is an error reading the spec file. The
 | 
				
			||||||
// file is read only once. The embedded file is located in the "testdata"
 | 
					// file is read only once.
 | 
				
			||||||
// subdirectory.
 | 
					func (f *fileGroupVersion) Schema(contentType string) ([]byte, error) {
 | 
				
			||||||
func (t *fileGroupVersion) Schema(contentType string) ([]byte, error) {
 | 
					 | 
				
			||||||
	if contentType != "application/json" {
 | 
						if contentType != "application/json" {
 | 
				
			||||||
		return nil, errors.New("openapitest only supports 'application/json' contentType")
 | 
							return nil, errors.New("openapitest only supports 'application/json' contentType")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	t.init.Do(func() {
 | 
						return fs.ReadFile(f.f, f.filename)
 | 
				
			||||||
		t.data, t.err = f.ReadFile(t.filename)
 | 
					 | 
				
			||||||
		if t.err != nil {
 | 
					 | 
				
			||||||
			t.t.Error(t.err)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
	return t.data, t.err
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,16 +14,61 @@ See the License for the specific language governing permissions and
 | 
				
			|||||||
limitations under the License.
 | 
					limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package openapitest
 | 
					package openapitest_test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"k8s.io/client-go/openapi/openapitest"
 | 
				
			||||||
	"k8s.io/kube-openapi/pkg/spec3"
 | 
						"k8s.io/kube-openapi/pkg/spec3"
 | 
				
			||||||
	kjson "sigs.k8s.io/json"
 | 
						kjson "sigs.k8s.io/json"
 | 
				
			||||||
	"testing"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestOpenAPITest(t *testing.T) {
 | 
					func TestOpenAPIEmbeddedTest(t *testing.T) {
 | 
				
			||||||
	client := NewFileClient(t)
 | 
						client := openapitest.NewEmbeddedFileClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// make sure we get paths
 | 
				
			||||||
 | 
						paths, err := client.Paths()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("error fetching paths: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(paths) == 0 {
 | 
				
			||||||
 | 
							t.Error("empty paths")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// spot check specific paths
 | 
				
			||||||
 | 
						expectedPaths := []string{
 | 
				
			||||||
 | 
							"api/v1",
 | 
				
			||||||
 | 
							"apis/apps/v1",
 | 
				
			||||||
 | 
							"apis/batch/v1",
 | 
				
			||||||
 | 
							"apis/networking.k8s.io/v1alpha1",
 | 
				
			||||||
 | 
							"apis/discovery.k8s.io/v1",
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, p := range expectedPaths {
 | 
				
			||||||
 | 
							if _, ok := paths[p]; !ok {
 | 
				
			||||||
 | 
								t.Fatalf("expected %s", p)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// make sure all paths can load
 | 
				
			||||||
 | 
						for path, gv := range paths {
 | 
				
			||||||
 | 
							data, err := gv.Schema("application/json")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("error reading schema for %v: %v", path, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							o := &spec3.OpenAPI{}
 | 
				
			||||||
 | 
							stricterrs, err := kjson.UnmarshalStrict(data, o)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								t.Fatalf("error unmarshaling schema for %v: %v", path, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(stricterrs) > 0 {
 | 
				
			||||||
 | 
								t.Fatalf("strict errors unmarshaling schema for %v: %v", path, stricterrs)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestOpenAPITest(t *testing.T) {
 | 
				
			||||||
 | 
						client := openapitest.NewFileClient("testdata")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// make sure we get paths
 | 
						// make sure we get paths
 | 
				
			||||||
	paths, err := client.Paths()
 | 
						paths, err := client.Paths()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -150,7 +150,7 @@ func TestOpenAPIV3Root_GVSpec(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for _, test := range tests {
 | 
						for _, test := range tests {
 | 
				
			||||||
		t.Run(test.name, func(t *testing.T) {
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
			client := openapitest.NewFileClient(t)
 | 
								client := openapitest.NewEmbeddedFileClient()
 | 
				
			||||||
			root := NewRoot(client)
 | 
								root := NewRoot(client)
 | 
				
			||||||
			gvSpec, err := root.GVSpec(test.gv)
 | 
								gvSpec, err := root.GVSpec(test.gv)
 | 
				
			||||||
			if test.err != nil {
 | 
								if test.err != nil {
 | 
				
			||||||
@@ -209,8 +209,7 @@ func TestOpenAPIV3Root_GVSpecAsMap(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for _, test := range tests {
 | 
						for _, test := range tests {
 | 
				
			||||||
		t.Run(test.name, func(t *testing.T) {
 | 
							t.Run(test.name, func(t *testing.T) {
 | 
				
			||||||
			client := openapitest.NewFileClient(t)
 | 
								root := NewRoot(openapitest.NewEmbeddedFileClient())
 | 
				
			||||||
			root := NewRoot(client)
 | 
					 | 
				
			||||||
			gvSpecAsMap, err := root.GVSpecAsMap(test.gv)
 | 
								gvSpecAsMap, err := root.GVSpecAsMap(test.gv)
 | 
				
			||||||
			if test.err != nil {
 | 
								if test.err != nil {
 | 
				
			||||||
				assert.True(t, reflect.DeepEqual(test.err, err))
 | 
									assert.True(t, reflect.DeepEqual(test.err, err))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,7 +74,7 @@ func TestExplainErrors(t *testing.T) {
 | 
				
			|||||||
	require.ErrorContains(t, err, "failed to parse openapi schema")
 | 
						require.ErrorContains(t, err, "failed to parse openapi schema")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Validate error when render template is not recognized.
 | 
						// Validate error when render template is not recognized.
 | 
				
			||||||
	client := openapitest.NewFileClient(t)
 | 
						client := openapitest.NewEmbeddedFileClient()
 | 
				
			||||||
	err = PrintModelDescription(nil, &buf, client, apiGroupsGVR, false, "unknown-format")
 | 
						err = PrintModelDescription(nil, &buf, client, apiGroupsGVR, false, "unknown-format")
 | 
				
			||||||
	require.ErrorContains(t, err, "unrecognized format: unknown-format")
 | 
						require.ErrorContains(t, err, "unrecognized format: unknown-format")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -84,7 +84,7 @@ func TestExplainErrors(t *testing.T) {
 | 
				
			|||||||
func TestExplainOpenAPIClient(t *testing.T) {
 | 
					func TestExplainOpenAPIClient(t *testing.T) {
 | 
				
			||||||
	var buf bytes.Buffer
 | 
						var buf bytes.Buffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fileClient := openapitest.NewFileClient(t)
 | 
						fileClient := openapitest.NewEmbeddedFileClient()
 | 
				
			||||||
	paths, err := fileClient.Paths()
 | 
						paths, err := fileClient.Paths()
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
	gv, found := paths[apiGroupsPath]
 | 
						gv, found := paths[apiGroupsPath]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -99,7 +99,7 @@ func TestGeneratorContext(t *testing.T) {
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
//	apis/apps/v1
 | 
					//	apis/apps/v1
 | 
				
			||||||
func bytesForGV(t *testing.T, gvPath string) []byte {
 | 
					func bytesForGV(t *testing.T, gvPath string) []byte {
 | 
				
			||||||
	fakeClient := openapitest.NewFileClient(t)
 | 
						fakeClient := openapitest.NewEmbeddedFileClient()
 | 
				
			||||||
	paths, err := fakeClient.Paths()
 | 
						paths, err := fakeClient.Paths()
 | 
				
			||||||
	require.NoError(t, err)
 | 
						require.NoError(t, err)
 | 
				
			||||||
	gv, found := paths[gvPath]
 | 
						gv, found := paths[gvPath]
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user