Skip to content

Commit 4c3fa0f

Browse files
author
Alexander Zielenski
committed
add mock openapi server for testing
1 parent ac6404e commit 4c3fa0f

File tree

3 files changed

+182
-37
lines changed

3 files changed

+182
-37
lines changed

pkg/util/proto/openapi_test.go

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -266,25 +266,20 @@ var _ = Describe("Path", func() {
266266
})
267267

268268
var _ = Describe("Reading apps/v1/Deployment from v3.0.0 openAPIData", func() {
269-
var models proto.Models
270-
s, schemaErr := fakeSchemaV300.OpenAPIV3Schema("apps/v1")
271-
models, modelsErr := proto.NewOpenAPIV3Data(s)
269+
var deployment *proto.Kind
270+
BeforeSuite(func() {
271+
var models proto.Models
272+
s, schemaErr := fakeSchemaV300.OpenAPIV3Schema("apps/v1")
273+
models, modelsErr := proto.NewOpenAPIV3Data(s)
272274

273-
It("should load the schema", func() {
274275
Expect(schemaErr).To(BeNil())
275276
Expect(modelsErr).To(BeNil())
276-
})
277277

278-
model := "io.k8s.api.apps.v1.Deployment"
279-
schema := models.LookupModel(model)
280-
deployment := schema.(*proto.Kind)
281-
282-
It("should lookup the Schema by its model name", func() {
278+
model := "io.k8s.api.apps.v1.Deployment"
279+
schema := models.LookupModel(model)
283280
Expect(schema).ToNot(BeNil())
284-
})
285281

286-
It("should be a Kind", func() {
287-
deployment := schema.(*proto.Kind)
282+
deployment = schema.(*proto.Kind)
288283
Expect(deployment).ToNot(BeNil())
289284
})
290285

@@ -319,13 +314,12 @@ var _ = Describe("Reading apps/v1/Deployment from v3.0.0 openAPIData", func() {
319314

320315
Describe("status", func() {
321316
var status *proto.Kind
322-
key := deployment.Fields["status"].(proto.Reference)
323-
status = key.SubSchema().(*proto.Kind)
324-
325-
It("status key of type Reference", func() {
317+
BeforeSuite(func() {
326318
Expect(deployment.Fields).To(HaveKey("status"))
319+
key := deployment.Fields["status"].(proto.Reference)
327320
Expect(key).ToNot(BeNil())
328321
Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1.DeploymentStatus"))
322+
status = key.SubSchema().(*proto.Kind)
329323
Expect(status).ToNot(BeNil())
330324
})
331325

@@ -351,13 +345,13 @@ var _ = Describe("Reading apps/v1/Deployment from v3.0.0 openAPIData", func() {
351345
})
352346

353347
Describe("spec subschema", func() {
354-
key, _ := deployment.Fields["spec"].(proto.Reference)
355-
spec := key.SubSchema().(*proto.Kind)
356-
357-
It("should have a spec key of type Reference", func() {
348+
var spec *proto.Kind
349+
BeforeSuite(func() {
358350
Expect(deployment.Fields).To(HaveKey("spec"))
351+
key, _ := deployment.Fields["spec"].(proto.Reference)
359352
Expect(key).ToNot(BeNil())
360353
Expect(key.Reference()).To(Equal("io.k8s.api.apps.v1.DeploymentSpec"))
354+
spec = key.SubSchema().(*proto.Kind)
361355
Expect(spec).ToNot(BeNil())
362356
})
363357

pkg/util/proto/testing/openapi.go

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,25 @@ limitations under the License.
1717
package testing
1818

1919
import (
20+
"io/fs"
2021
"io/ioutil"
2122
"os"
2223
"path/filepath"
2324
"sync"
2425

2526
openapi_v2 "github.com/google/gnostic/openapiv2"
2627
openapi_v3 "github.com/google/gnostic/openapiv3"
28+
"k8s.io/kube-openapi/pkg/handler3"
2729
)
2830

2931
// Fake opens and returns a openapi swagger from a file Path. It will
3032
// parse only once and then return the same copy everytime.
3133
type Fake struct {
3234
Path string
3335

34-
once sync.Once
35-
document *openapi_v2.Document
36-
documentV3 *openapi_v3.Document
37-
err error
36+
once sync.Once
37+
document *openapi_v2.Document
38+
err error
3839
}
3940

4041
// OpenAPISchema returns the openapi document and a potential error.
@@ -55,21 +56,33 @@ func (f *Fake) OpenAPISchema() (*openapi_v2.Document, error) {
5556
return f.document, f.err
5657
}
5758

58-
func (f *Fake) OpenAPIV3Schema(groupVersion string) (*openapi_v3.Document, error) {
59-
f.once.Do(func() {
60-
_, err := os.Stat(f.Path)
61-
if err != nil {
62-
f.err = err
63-
return
59+
func (f *Fake) OpenAPIV3Discovery() (*handler3.OpenAPIV3Discovery, error) {
60+
// Read directory to determine groups
61+
res := &handler3.OpenAPIV3Discovery{}
62+
filepath.WalkDir(f.Path, func(path string, d fs.DirEntry, err error) error {
63+
if filepath.Ext(path) != "json" {
64+
return nil
6465
}
65-
spec, err := ioutil.ReadFile(filepath.Join(f.Path, groupVersion+".json"))
66-
if err != nil {
67-
f.err = err
68-
return
66+
67+
res.Paths[path] = handler3.OpenAPIV3DiscoveryGroupVersion{
68+
URL: "/openapi/v3/" + path,
6969
}
70-
f.documentV3, f.err = openapi_v3.ParseDocument(spec)
70+
return nil
7171
})
72-
return f.documentV3, f.err
72+
73+
return res, nil
74+
}
75+
76+
func (f *Fake) OpenAPIV3Schema(groupVersion string) (*openapi_v3.Document, error) {
77+
_, err := os.Stat(f.Path)
78+
if err != nil {
79+
return nil, err
80+
}
81+
spec, err := ioutil.ReadFile(filepath.Join(f.Path, groupVersion+".json"))
82+
if err != nil {
83+
return nil, err
84+
}
85+
return openapi_v3.ParseDocument(spec)
7386
}
7487

7588
type Empty struct{}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package testing
18+
19+
import (
20+
"encoding/json"
21+
"io/fs"
22+
"io/ioutil"
23+
"net/http"
24+
"net/http/httptest"
25+
"os"
26+
"path/filepath"
27+
"strings"
28+
29+
openapi_v3 "github.com/google/gnostic/openapiv3"
30+
"k8s.io/kube-openapi/pkg/handler3"
31+
"k8s.io/kube-openapi/pkg/spec3"
32+
)
33+
34+
// Creates a mock OpenAPIV3 server as it would be on a standing kubernetes
35+
// API server.
36+
//
37+
// specsPath - Give a path to some test data organized so that each GroupVersion
38+
// has its own OpenAPI V3 JSON file.
39+
// i.e. apps/v1beta1 is stored in <specsPath>/apps/v1beta1.json
40+
func NewFakeOpenAPIV3Server(specsPath string) (*httptest.Server, map[string]*openapi_v3.Document, error) {
41+
mux := &testMux{}
42+
server := httptest.NewServer(mux)
43+
44+
openAPIVersionedService, err := handler3.NewOpenAPIService(nil)
45+
if err != nil {
46+
return nil, nil, err
47+
}
48+
49+
err = openAPIVersionedService.RegisterOpenAPIV3VersionedService("/openapi/v3", mux)
50+
if err != nil {
51+
return nil, nil, err
52+
}
53+
54+
grouped := make(map[string][]byte)
55+
var testV3Specs = make(map[string]*openapi_v3.Document)
56+
57+
addSpec := func(path string) {
58+
file, err := os.Open(path)
59+
if err != nil {
60+
panic(err)
61+
}
62+
63+
defer file.Close()
64+
vals, err := ioutil.ReadAll(file)
65+
if err != nil {
66+
panic(err)
67+
}
68+
69+
rel, err := filepath.Rel(specsPath, path)
70+
if err == nil {
71+
grouped[rel[:(len(rel)-len(filepath.Ext(rel)))]] = vals
72+
}
73+
}
74+
75+
filepath.WalkDir(specsPath, func(path string, d fs.DirEntry, err error) error {
76+
if filepath.Ext(path) != ".json" || d.IsDir() {
77+
return nil
78+
}
79+
80+
addSpec(path)
81+
return nil
82+
})
83+
84+
for gv, jsonSpec := range grouped {
85+
spec := &spec3.OpenAPI{}
86+
err = json.Unmarshal(jsonSpec, spec)
87+
if err != nil {
88+
return nil, nil, err
89+
}
90+
gnosticSpec, err := openapi_v3.ParseDocument(jsonSpec)
91+
if err != nil {
92+
return nil, nil, err
93+
}
94+
testV3Specs[gv] = gnosticSpec
95+
openAPIVersionedService.UpdateGroupVersion(gv, spec)
96+
}
97+
98+
return server, testV3Specs, nil
99+
}
100+
101+
////////////////////////////////////////////////////////////////////////////////
102+
// Tiny Test HTTP Mux
103+
////////////////////////////////////////////////////////////////////////////////
104+
105+
type testMux struct {
106+
prefixMap map[string]http.Handler
107+
pathMap map[string]http.Handler
108+
}
109+
110+
func (t *testMux) Handle(path string, handler http.Handler) {
111+
if t.pathMap == nil {
112+
t.pathMap = make(map[string]http.Handler)
113+
}
114+
t.pathMap[path] = handler
115+
}
116+
117+
func (t *testMux) HandlePrefix(path string, handler http.Handler) {
118+
if t.prefixMap == nil {
119+
t.prefixMap = make(map[string]http.Handler)
120+
}
121+
t.prefixMap[path] = handler
122+
}
123+
124+
func (t *testMux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
125+
if handler, ok := t.pathMap[req.URL.Path]; ok {
126+
handler.ServeHTTP(w, req)
127+
return
128+
}
129+
130+
for k, v := range t.prefixMap {
131+
if strings.HasPrefix(req.URL.Path, k) {
132+
v.ServeHTTP(w, req)
133+
return
134+
}
135+
}
136+
137+
w.WriteHeader(http.StatusNotFound)
138+
}

0 commit comments

Comments
 (0)