diff --git a/openapi/openapitest/fileclient.go b/openapi/openapitest/fileclient.go index 90004d74..d53f63a1 100644 --- a/openapi/openapitest/fileclient.go +++ b/openapi/openapitest/fileclient.go @@ -19,33 +19,35 @@ package openapitest import ( "embed" "errors" - "path/filepath" + "io/fs" + "os" "strings" - "sync" - "testing" "k8s.io/client-go/openapi" ) //go:embed testdata/*_openapi.json -var f embed.FS +var embedded embed.FS // NewFileClient returns a test client implementing the openapi.Client -// interface, which serves a subset of hard-coded GroupVersion -// Open API V3 specifications files. The subset of specifications is -// located in the "testdata" subdirectory. -func NewFileClient(t *testing.T) openapi.Client { - if t == nil { - panic("non-nil testing.T required; this package is only for use in tests") +// interface, which serves Open API V3 specifications files from the +// given path, as prepared in `api/openapi-spec/v3`. +func NewFileClient(path string) openapi.Client { + return &fileClient{f: os.DirFS(path)} +} + +// 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{t: t} + return &fileClient{f: f} } type fileClient struct { - t *testing.T - init sync.Once - paths map[string]openapi.GroupVersion - err error + f fs.FS } // 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 // into an api path, or an error is returned. -func (t *fileClient) Paths() (map[string]openapi.GroupVersion, error) { - t.init.Do(func() { - t.paths = map[string]openapi.GroupVersion{} - entries, err := f.ReadDir("testdata") - if err != nil { - t.err = err - t.t.Error(err) - } - for _, e := range entries { - // this reverses the transformation done in hack/update-openapi-spec.sh - path := strings.ReplaceAll(strings.TrimSuffix(e.Name(), "_openapi.json"), "__", "/") - t.paths[path] = &fileGroupVersion{t: t.t, filename: filepath.Join("testdata", e.Name())} - } - }) - return t.paths, t.err +func (f *fileClient) Paths() (map[string]openapi.GroupVersion, error) { + paths := map[string]openapi.GroupVersion{} + entries, err := fs.ReadDir(f.f, ".") + if err != nil { + return nil, err + } + for _, e := range entries { + // this reverses the transformation done in hack/update-openapi-spec.sh + path := strings.ReplaceAll(strings.TrimSuffix(e.Name(), "_openapi.json"), "__", "/") + paths[path] = &fileGroupVersion{f: f.f, filename: e.Name()} + } + return paths, nil } type fileGroupVersion struct { - t *testing.T - init sync.Once + f fs.FS filename string - data []byte - err error } // fileGroupVersion implements the openapi.GroupVersion interface. @@ -91,17 +87,10 @@ var _ openapi.GroupVersion = &fileGroupVersion{} // Schema returns the OpenAPI V3 specification for the GroupVersion as // unstructured bytes, or an error if the contentType is not // "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" -// subdirectory. -func (t *fileGroupVersion) Schema(contentType string) ([]byte, error) { +// file is read only once. +func (f *fileGroupVersion) Schema(contentType string) ([]byte, error) { if contentType != "application/json" { return nil, errors.New("openapitest only supports 'application/json' contentType") } - t.init.Do(func() { - t.data, t.err = f.ReadFile(t.filename) - if t.err != nil { - t.t.Error(t.err) - } - }) - return t.data, t.err + return fs.ReadFile(f.f, f.filename) } diff --git a/openapi/openapitest/fileclient_test.go b/openapi/openapitest/fileclient_test.go index 78483ed2..702520ac 100644 --- a/openapi/openapitest/fileclient_test.go +++ b/openapi/openapitest/fileclient_test.go @@ -14,16 +14,61 @@ See the License for the specific language governing permissions and limitations under the License. */ -package openapitest +package openapitest_test import ( + "testing" + + "k8s.io/client-go/openapi/openapitest" "k8s.io/kube-openapi/pkg/spec3" kjson "sigs.k8s.io/json" - "testing" ) -func TestOpenAPITest(t *testing.T) { - client := NewFileClient(t) +func TestOpenAPIEmbeddedTest(t *testing.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 paths, err := client.Paths() diff --git a/openapi3/root_test.go b/openapi3/root_test.go index 004674e9..dc433b71 100644 --- a/openapi3/root_test.go +++ b/openapi3/root_test.go @@ -150,7 +150,7 @@ func TestOpenAPIV3Root_GVSpec(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - client := openapitest.NewFileClient(t) + client := openapitest.NewEmbeddedFileClient() root := NewRoot(client) gvSpec, err := root.GVSpec(test.gv) if test.err != nil { @@ -209,8 +209,7 @@ func TestOpenAPIV3Root_GVSpecAsMap(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - client := openapitest.NewFileClient(t) - root := NewRoot(client) + root := NewRoot(openapitest.NewEmbeddedFileClient()) gvSpecAsMap, err := root.GVSpecAsMap(test.gv) if test.err != nil { assert.True(t, reflect.DeepEqual(test.err, err))