github.com/SaurabhDubey-Groww/go-cloud@v0.0.0-20221124105541-b26c29285fd8/runtimevar/httpvar/httpvar_test.go (about) 1 // Copyright 2019 The Go Cloud Development Kit Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // https://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package httpvar 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "net/http" 22 "net/http/httptest" 23 "net/url" 24 "os" 25 "strconv" 26 "strings" 27 "testing" 28 "time" 29 30 "github.com/google/go-cmp/cmp" 31 "gocloud.dev/internal/gcerr" 32 "gocloud.dev/runtimevar" 33 "gocloud.dev/runtimevar/driver" 34 "gocloud.dev/runtimevar/drivertest" 35 ) 36 37 type harness struct { 38 mockServer *mockServer 39 } 40 41 func (h *harness) MakeWatcher(ctx context.Context, name string, decoder *runtimevar.Decoder) (driver.Watcher, error) { 42 endpointURL, err := url.Parse(h.mockServer.baseURL + "/" + name) 43 if err != nil { 44 return nil, err 45 } 46 return newWatcher(http.DefaultClient, endpointURL, decoder, nil), nil 47 } 48 49 func (h *harness) CreateVariable(ctx context.Context, name string, val []byte) error { 50 h.mockServer.SetResponse(name, string(val)) 51 return nil 52 } 53 54 func (h *harness) UpdateVariable(ctx context.Context, name string, val []byte) error { 55 h.mockServer.SetResponse(name, string(val)) 56 return nil 57 } 58 59 func (h *harness) DeleteVariable(ctx context.Context, name string) error { 60 h.mockServer.DeleteResponse(name) 61 return nil 62 } 63 64 func (h *harness) Close() { 65 h.mockServer.close() 66 } 67 68 func (h *harness) Mutable() bool { 69 return true 70 } 71 72 func newHarness(t *testing.T) (drivertest.Harness, error) { 73 return &harness{ 74 mockServer: newMockServer(), 75 }, nil 76 } 77 78 func TestConformance(t *testing.T) { 79 drivertest.RunConformanceTests(t, newHarness, []drivertest.AsTest{verifyAs{}}) 80 } 81 82 type verifyAs struct{} 83 84 func (verifyAs) Name() string { 85 return "verify As" 86 } 87 88 func (verifyAs) SnapshotCheck(s *runtimevar.Snapshot) error { 89 var resp *http.Response 90 if !s.As(&resp) { 91 return errors.New("Snapshot.As failed") 92 } 93 94 s2 := state{raw: nil} 95 if s2.As(nil) { 96 return errors.New("Snapshot.As was expected to fail") 97 } 98 return nil 99 } 100 101 func (verifyAs) ErrorCheck(v *runtimevar.Variable, err error) error { 102 var e RequestError 103 if !v.ErrorAs(err, &e) { 104 return errors.New("ErrorAs expected to succeed with *httpvar.RequestError") 105 } 106 if !strings.Contains(e.Error(), strconv.Itoa(e.Response.StatusCode)) { 107 return errors.New("should contain url and status code") 108 } 109 110 var e2 url.Error 111 urlError := &url.Error{URL: "http://example.com", Op: "GET", Err: errors.New("example error")} 112 if !v.ErrorAs(urlError, &e2) { 113 return errors.New("ErrorAs expected to succeed with *url.Error") 114 } 115 116 var e3 RequestError 117 if v.ErrorAs(errors.New("example error"), &e3) { 118 return errors.New("ErrorAs was expected to fail") 119 } 120 return nil 121 } 122 123 // httpvar-specific tests. 124 125 func TestOpenVariable(t *testing.T) { 126 tests := []struct { 127 URL string 128 WantErr bool 129 }{ 130 {"http://example.com/config", false}, 131 {"%gh&%ij", true}, 132 } 133 134 for _, test := range tests { 135 v, err := OpenVariable(http.DefaultClient, test.URL, runtimevar.StringDecoder, nil) 136 if (err != nil) != test.WantErr { 137 t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr) 138 } 139 if v != nil { 140 v.Close() 141 } 142 } 143 } 144 145 func TestEquivalentError(t *testing.T) { 146 notFoundErr := newRequestError(&http.Response{StatusCode: http.StatusNotFound}) 147 badGatewayErr := newRequestError(&http.Response{StatusCode: http.StatusBadGateway}) 148 tests := []struct { 149 Err1, Err2 error 150 Want bool 151 }{ 152 {Err1: errors.New("error one"), Err2: errors.New("error one"), Want: true}, 153 {Err1: errors.New("error one"), Err2: errors.New("error two"), Want: false}, 154 {Err1: errors.New("error one"), Err2: notFoundErr, Want: false}, 155 {Err1: notFoundErr, Err2: notFoundErr, Want: true}, 156 {Err1: notFoundErr, Err2: badGatewayErr, Want: false}, 157 } 158 159 for _, test := range tests { 160 got := equivalentError(test.Err1, test.Err2) 161 if got != test.Want { 162 t.Errorf("%v vs %v: got %v want %v", test.Err1, test.Err2, got, test.Want) 163 } 164 } 165 } 166 167 func TestWatcher_ErrorCode(t *testing.T) { 168 tests := []struct { 169 Err *RequestError 170 GCErr gcerr.ErrorCode 171 }{ 172 {Err: newRequestError(&http.Response{StatusCode: http.StatusBadRequest}), GCErr: gcerr.InvalidArgument}, 173 {Err: newRequestError(&http.Response{StatusCode: http.StatusNotFound}), GCErr: gcerr.NotFound}, 174 {Err: newRequestError(&http.Response{StatusCode: http.StatusUnauthorized}), GCErr: gcerr.PermissionDenied}, 175 {Err: newRequestError(&http.Response{StatusCode: http.StatusGatewayTimeout}), GCErr: gcerr.DeadlineExceeded}, 176 {Err: newRequestError(&http.Response{StatusCode: http.StatusRequestTimeout}), GCErr: gcerr.DeadlineExceeded}, 177 {Err: newRequestError(&http.Response{StatusCode: http.StatusInternalServerError}), GCErr: gcerr.Internal}, 178 {Err: newRequestError(&http.Response{StatusCode: http.StatusServiceUnavailable}), GCErr: gcerr.Internal}, 179 {Err: newRequestError(&http.Response{StatusCode: http.StatusBadGateway}), GCErr: gcerr.Internal}, 180 } 181 182 endpointURL, err := url.Parse("http://example.com") 183 if err != nil { 184 t.Fatal(err) 185 } 186 187 watcher := newWatcher(http.DefaultClient, endpointURL, runtimevar.StringDecoder, nil) 188 defer watcher.Close() 189 for _, test := range tests { 190 actualGCErr := watcher.ErrorCode(test.Err) 191 if test.GCErr != actualGCErr { 192 t.Errorf("expected gcerr.ErrorCode to be %d, got %d", test.GCErr, actualGCErr) 193 } 194 } 195 } 196 197 func TestWatcher_WatchVariable(t *testing.T) { 198 t.Run("client returns an error", func(t *testing.T) { 199 endpointURL, err := url.Parse("http://example.com") 200 if err != nil { 201 t.Fatal(err) 202 } 203 204 // In order to force httpClient.Get to return an error, we pass custom *http.Client 205 // with every short timeout, so that request will timed out and return an error. 206 httpClient := &http.Client{ 207 Timeout: time.Duration(1 * time.Millisecond), 208 } 209 watcher := newWatcher(httpClient, endpointURL, runtimevar.StringDecoder, nil) 210 defer watcher.Close() 211 state, _ := watcher.WatchVariable(context.Background(), &state{}) 212 213 val, err := state.Value() 214 if err == nil { 215 t.Errorf("expected error got nil") 216 } 217 if val != nil { 218 t.Errorf("expected state value to be nil, got %v", val) 219 } 220 }) 221 } 222 223 func TestWithAuth(t *testing.T) { 224 const ( 225 authUser = "test_user" 226 authPwd = "test_pwd" 227 value = "hello world" 228 ) 229 h, err := newHarness(t) 230 if err != nil { 231 t.Fatal(err) 232 } 233 defer h.Close() 234 mockServer := h.(*harness).mockServer 235 testURL := mockServer.baseURL + "/string-var?decoder=string" 236 mockServer.authUser = authUser 237 mockServer.authPwd = authPwd 238 239 ctx := context.Background() 240 if err := h.CreateVariable(ctx, "string-var", []byte(value)); err != nil { 241 t.Fatal(err) 242 } 243 244 tests := []struct { 245 AuthUser string 246 AuthPwd string 247 WantErr bool 248 }{ 249 // No auth provided, fails. 250 {"", "", true}, 251 // Invalid user, fails. 252 {"wronguser", authPwd, true}, 253 // Invalid password, fails. 254 {authUser, "wrongpassword", true}, 255 // Auth good, works. 256 {authUser, authPwd, false}, 257 } 258 259 for _, test := range tests { 260 name := fmt.Sprintf("user=%s,pwd=%s", test.AuthUser, test.AuthPwd) 261 t.Run(name, func(t *testing.T) { 262 os.Setenv("HTTPVAR_AUTH_USERNAME", test.AuthUser) 263 os.Setenv("HTTPVAR_AUTH_PASSWORD", test.AuthPwd) 264 265 v, err := runtimevar.OpenVariable(ctx, testURL) 266 if err != nil { 267 t.Fatalf("failed OpenVariable: %v", err) 268 } 269 defer v.Close() 270 snapshot, err := v.Watch(ctx) 271 if (err != nil) != test.WantErr { 272 t.Errorf("got Watch error %v, want error %v", err, test.WantErr) 273 } 274 if err != nil { 275 return 276 } 277 if !cmp.Equal(snapshot.Value, value) { 278 t.Errorf("got snapshot value\n%v\n want\n%v", snapshot.Value, value) 279 } 280 }) 281 } 282 } 283 284 func TestOpenVariableURL(t *testing.T) { 285 h, err := newHarness(t) 286 if err != nil { 287 t.Fatal(err) 288 } 289 defer h.Close() 290 baseURL := h.(*harness).mockServer.baseURL 291 292 ctx := context.Background() 293 if err := h.CreateVariable(ctx, "string-var", []byte("hello world")); err != nil { 294 t.Fatal(err) 295 } 296 if err := h.CreateVariable(ctx, "json-var", []byte(`{"Foo": "Bar"}`)); err != nil { 297 t.Fatal(err) 298 } 299 300 tests := []struct { 301 URL string 302 WantErr bool 303 WantWatchErr bool 304 Want interface{} 305 }{ 306 // Nonexistentvar does not exist, so we get an error from Watch. 307 {baseURL + "/nonexistentvar", false, true, nil}, 308 // Invalid decoder arg. 309 {baseURL + "/string-var?decoder=notadecoder", true, false, nil}, 310 // Working example with string decoder. 311 {baseURL + "/string-var?decoder=string", false, false, "hello world"}, 312 // Working example with default decoder. 313 {baseURL + "/string-var", false, false, []byte("hello world")}, 314 // Working example with JSON decoder. 315 {baseURL + "/json-var?decoder=jsonmap", false, false, &map[string]interface{}{"Foo": "Bar"}}, 316 // Setting wait. 317 {baseURL + "/string-var?decoder=string&wait=1m", false, false, "hello world"}, 318 // Invalid wait. 319 {baseURL + "/string-var?decoder=string&wait=xx", true, false, nil}, 320 } 321 322 for _, test := range tests { 323 t.Run(test.URL, func(t *testing.T) { 324 v, err := runtimevar.OpenVariable(ctx, test.URL) 325 if (err != nil) != test.WantErr { 326 t.Errorf("%s: got error %v, want error %v", test.URL, err, test.WantErr) 327 } 328 if err != nil { 329 return 330 } 331 defer v.Close() 332 snapshot, err := v.Watch(ctx) 333 if (err != nil) != test.WantWatchErr { 334 t.Errorf("%s: got Watch error %v, want error %v", test.URL, err, test.WantWatchErr) 335 } 336 if err != nil { 337 return 338 } 339 if !cmp.Equal(snapshot.Value, test.Want) { 340 t.Errorf("%s: got snapshot value\n%v\n want\n%v", test.URL, snapshot.Value, test.Want) 341 } 342 }) 343 } 344 } 345 346 type mockServer struct { 347 baseURL string 348 close func() 349 responses map[string]interface{} 350 authUser string 351 authPwd string 352 } 353 354 func (m *mockServer) SetResponse(name string, response interface{}) { 355 m.responses[name] = response 356 } 357 358 func (m *mockServer) DeleteResponse(name string) { 359 delete(m.responses, name) 360 } 361 362 func newMockServer() *mockServer { 363 mock := &mockServer{responses: map[string]interface{}{}} 364 365 mux := http.NewServeMux() 366 mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 367 if mock.authUser != "" { 368 user, pwd, ok := r.BasicAuth() 369 if !ok || user != mock.authUser || pwd != mock.authPwd { 370 w.WriteHeader(http.StatusUnauthorized) 371 return 372 } 373 } 374 resp := mock.responses[strings.TrimPrefix(r.URL.String(), "/")] 375 if resp == nil { 376 w.WriteHeader(http.StatusNotFound) 377 return 378 } 379 fmt.Fprint(w, resp) 380 }) 381 382 server := httptest.NewServer(mux) 383 mock.baseURL = server.URL 384 mock.close = server.Close 385 return mock 386 }