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  }