k8s.io/kube-openapi@v0.0.0-20240826222958-65a50c78dec5/pkg/handler/handler_test.go (about) 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 // TODO: add some kind of roundtrip test here 206 } 207 208 func TestConcurrentReadStaleCache(t *testing.T) { 209 // Number of requests sent in parallel 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 }