1 package handler
2
3 import (
4 json "encoding/json"
5 "fmt"
6 "io"
7 "mime"
8 "net/http"
9 "net/http/httptest"
10 "os"
11 "reflect"
12 "testing"
13
14 "k8s.io/kube-openapi/pkg/cached"
15 "k8s.io/kube-openapi/pkg/validation/spec"
16 )
17
18 var returnedSwagger = []byte(`{
19 "swagger": "2.0",
20 "info": {
21 "title": "Kubernetes",
22 "version": "v1.11.0"
23 }}`)
24
25 func TestRegisterOpenAPIVersionedService(t *testing.T) {
26 var s spec.Swagger
27 err := s.UnmarshalJSON(returnedSwagger)
28 if err != nil {
29 t.Errorf("Unexpected error in unmarshalling SwaggerJSON: %v", err)
30 }
31
32 returnedJSON := normalizeSwaggerOrDie(returnedSwagger)
33 var decodedJSON map[string]interface{}
34 if err := json.Unmarshal(returnedJSON, &decodedJSON); err != nil {
35 t.Fatal(err)
36 }
37 returnedPb, err := ToProtoBinary(returnedJSON)
38 if err != nil {
39 t.Errorf("Unexpected error in preparing returnedPb: %v", err)
40 }
41
42 mux := http.NewServeMux()
43 o := NewOpenAPIService(&s)
44 o.RegisterOpenAPIVersionedService("/openapi/v2", mux)
45 server := httptest.NewServer(mux)
46 defer server.Close()
47 client := server.Client()
48
49 tcs := []struct {
50 acceptHeader string
51 respStatus int
52 responseContentTypeHeader string
53 respBody []byte
54 }{
55 {"", 200, "application/json", returnedJSON},
56 {"*/*", 200, "application/json", returnedJSON},
57 {"application/*", 200, "application/json", returnedJSON},
58 {"application/json", 200, "application/json", returnedJSON},
59 {"test/test", 406, "", []byte{}},
60 {"application/test", 406, "", []byte{}},
61 {"application/test, */*", 200, "application/json", returnedJSON},
62 {"application/test, application/json", 200, "application/json", returnedJSON},
63 {"application/com.github.proto-openapi.spec.v2.v1.0+protobuf", 200, "application/com.github.proto-openapi.spec.v2.v1.0+protobuf", returnedPb},
64 {"application/json, application/com.github.proto-openapi.spec.v2.v1.0+protobuf", 200, "application/json", returnedJSON},
65 {"application/com.github.proto-openapi.spec.v2.v1.0+protobuf, application/json", 200, "application/com.github.proto-openapi.spec.v2.v1.0+protobuf", returnedPb},
66 {"application/com.github.proto-openapi.spec.v2.v1.0+protobuf; q=0.5, application/json", 200, "application/json", returnedJSON},
67 {"application/com.github.proto-openapi.spec.v2@v1.0+protobuf", 200, "application/com.github.proto-openapi.spec.v2.v1.0+protobuf", returnedPb},
68 {"application/json, application/com.github.proto-openapi.spec.v2@v1.0+protobuf", 200, "application/json", returnedJSON},
69 {"application/com.github.proto-openapi.spec.v2@v1.0+protobuf, application/json", 200, "application/com.github.proto-openapi.spec.v2.v1.0+protobuf", returnedPb},
70 {"application/com.github.proto-openapi.spec.v2@v1.0+protobuf; q=0.5, application/json", 200, "application/json", returnedJSON},
71 }
72
73 for _, tc := range tcs {
74 req, err := http.NewRequest("GET", server.URL+"/openapi/v2", nil)
75 if err != nil {
76 t.Errorf("Accept: %v: Unexpected error in creating new request: %v", tc.acceptHeader, err)
77 }
78
79 req.Header.Add("Accept", tc.acceptHeader)
80 resp, err := client.Do(req)
81 if err != nil {
82 t.Errorf("Accept: %v: Unexpected error in serving HTTP request: %v", tc.acceptHeader, err)
83 }
84
85 if resp.StatusCode != tc.respStatus {
86 t.Errorf("Accept: %v: Unexpected response status code, want: %v, got: %v", tc.acceptHeader, tc.respStatus, resp.StatusCode)
87 }
88 if tc.respStatus != 200 {
89 continue
90 }
91
92 responseContentType := resp.Header.Get("Content-Type")
93 if responseContentType != tc.responseContentTypeHeader {
94 t.Errorf("Accept: %v: Unexpected content type in response, want: %v, got: %v", tc.acceptHeader, tc.responseContentTypeHeader, responseContentType)
95 }
96
97 _, _, err = mime.ParseMediaType(responseContentType)
98 if err != nil {
99 t.Errorf("Unexpected error in parsing response content type: %v, err: %v", responseContentType, err)
100 }
101
102 defer resp.Body.Close()
103 body, err := io.ReadAll(resp.Body)
104 if err != nil {
105 t.Errorf("Accept: %v: Unexpected error in reading response body: %v", tc.acceptHeader, err)
106 }
107 if !reflect.DeepEqual(body, tc.respBody) {
108 t.Errorf("Accept: %v: Response body mismatches, \nwant: %s, \ngot: %s", tc.acceptHeader, string(tc.respBody), string(body))
109 }
110 }
111 }
112
113 var updatedSwagger = []byte(`{
114 "swagger": "2.0",
115 "info": {
116 "title": "Kubernetes",
117 "version": "v1.12.0"
118 }}`)
119
120 func getJSONBodyOrDie(server *httptest.Server) []byte {
121 return getBodyOrDie(server, "application/json")
122 }
123
124 func getProtoBodyOrDie(server *httptest.Server) []byte {
125 return getBodyOrDie(server, "application/com.github.proto-openapi.spec.v2.v1.0+protobuf")
126 }
127
128 func getBodyOrDie(server *httptest.Server, acceptHeader string) []byte {
129 req, err := http.NewRequest("GET", server.URL+"/openapi/v2", nil)
130 if err != nil {
131 panic(fmt.Errorf("Unexpected error in creating new request: %v", err))
132 }
133
134 req.Header.Add("Accept", acceptHeader)
135 resp, err := server.Client().Do(req)
136 if err != nil {
137 panic(fmt.Errorf("Unexpected error in serving HTTP request: %v", err))
138 }
139
140 defer resp.Body.Close()
141 body, err := io.ReadAll(resp.Body)
142 if err != nil {
143 panic(fmt.Errorf("Unexpected error in reading response body: %v", err))
144 }
145 return body
146 }
147
148 func normalizeSwaggerOrDie(j []byte) []byte {
149 var s spec.Swagger
150 err := s.UnmarshalJSON(j)
151 if err != nil {
152 panic(fmt.Errorf("Unexpected error in unmarshalling SwaggerJSON: %v", err))
153 }
154 rj, err := json.Marshal(s)
155 if err != nil {
156 panic(fmt.Errorf("Unexpected error in preparing returnedJSON: %v", err))
157 }
158 return rj
159 }
160
161 func TestUpdateSpecLazy(t *testing.T) {
162 returnedJSON := normalizeSwaggerOrDie(returnedSwagger)
163 var s spec.Swagger
164 err := s.UnmarshalJSON(returnedJSON)
165 if err != nil {
166 t.Errorf("Unexpected error in unmarshalling SwaggerJSON: %v", err)
167 }
168
169 mux := http.NewServeMux()
170 o := NewOpenAPIService(&s)
171 o.RegisterOpenAPIVersionedService("/openapi/v2", mux)
172 server := httptest.NewServer(mux)
173 defer server.Close()
174
175 body := string(getJSONBodyOrDie(server))
176 if body != string(returnedJSON) {
177 t.Errorf("Unexpected swagger received, got %q, expected %q", body, string(returnedSwagger))
178 }
179
180 o.UpdateSpecLazy(cached.Func(func() (*spec.Swagger, string, error) {
181 var s spec.Swagger
182 err := s.UnmarshalJSON(updatedSwagger)
183 if err != nil {
184 t.Errorf("Unexpected error in unmarshalling SwaggerJSON: %v", err)
185 }
186 return &s, "SOMEHASH", nil
187 }))
188
189 updatedJSON := normalizeSwaggerOrDie(updatedSwagger)
190 body = string(getJSONBodyOrDie(server))
191
192 if body != string(updatedJSON) {
193 t.Errorf("Unexpected swagger received, got %q, expected %q", body, string(updatedJSON))
194 }
195 }
196
197 func TestToProtoBinary(t *testing.T) {
198 bs, err := os.ReadFile("../../test/integration/testdata/aggregator/openapi.json")
199 if err != nil {
200 t.Fatal(err)
201 }
202 if _, err := ToProtoBinary(bs); err != nil {
203 t.Fatal()
204 }
205
206 }
207
208 func TestConcurrentReadStaleCache(t *testing.T) {
209
210 concurrency := 5
211
212 var s spec.Swagger
213 err := s.UnmarshalJSON(returnedSwagger)
214 if err != nil {
215 t.Errorf("Unexpected error in unmarshalling SwaggerJSON: %v", err)
216 }
217
218 mux := http.NewServeMux()
219 o := NewOpenAPIService(&s)
220 o.RegisterOpenAPIVersionedService("/openapi/v2", mux)
221 server := httptest.NewServer(mux)
222 defer server.Close()
223
224 returnedJSON := normalizeSwaggerOrDie(returnedSwagger)
225 returnedPb, err := ToProtoBinary(returnedJSON)
226 if err != nil {
227 t.Errorf("Unexpected error in preparing returnedPb: %v", err)
228 }
229
230 jsonResultsChan := make(chan []byte)
231 protoResultsChan := make(chan []byte)
232 updateSpecChan := make(chan struct{})
233 for i := 0; i < concurrency; i++ {
234 go func() {
235 sc := s
236 o.UpdateSpec(&sc)
237 updateSpecChan <- struct{}{}
238 }()
239 go func() { jsonResultsChan <- getJSONBodyOrDie(server) }()
240 go func() { protoResultsChan <- getProtoBodyOrDie(server) }()
241 }
242 for i := 0; i < concurrency; i++ {
243 r := <-jsonResultsChan
244 if !reflect.DeepEqual(r, returnedJSON) {
245 t.Errorf("Returned and expected JSON do not match: got %v, want %v", string(r), string(returnedJSON))
246 }
247 }
248 for i := 0; i < concurrency; i++ {
249 r := <-protoResultsChan
250 if !reflect.DeepEqual(r, returnedPb) {
251 t.Errorf("Returned and expected pb do not match: got %v, want %v", r, returnedPb)
252 }
253 }
254 for i := 0; i < concurrency; i++ {
255 <-updateSpecChan
256 }
257 }
258
View as plain text