istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/request/command_test.go (about)

     1  // Copyright Istio 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  //     http://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 request
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"net/http"
    21  	"net/http/httptest"
    22  	"net/url"
    23  	"sync"
    24  	"testing"
    25  
    26  	"istio.io/istio/pkg/ptr"
    27  	"istio.io/istio/tests/util"
    28  )
    29  
    30  type pilotStubHandler struct {
    31  	sync.Mutex
    32  	States []pilotStubState
    33  }
    34  
    35  type pilotStubState struct {
    36  	wantMethod string
    37  	wantPath   string
    38  	wantBody   []byte
    39  	StatusCode int
    40  	Response   string
    41  }
    42  
    43  func (p *pilotStubHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    44  	p.Lock()
    45  	if r.Method == p.States[0].wantMethod {
    46  		if r.URL.Path == p.States[0].wantPath {
    47  			defer r.Body.Close()
    48  			body, _ := io.ReadAll(r.Body)
    49  			if err := util.Compare(body, p.States[0].wantBody); err == nil {
    50  				w.WriteHeader(p.States[0].StatusCode)
    51  				w.Write([]byte(p.States[0].Response))
    52  			} else {
    53  				w.WriteHeader(http.StatusBadRequest)
    54  				w.Write([]byte(fmt.Sprintf("wanted body %q got %q", string(p.States[0].wantBody), string(body))))
    55  			}
    56  		} else {
    57  			w.WriteHeader(http.StatusBadRequest)
    58  			w.Write([]byte(fmt.Sprintf("wanted path %q got %q", p.States[0].wantPath, r.URL.Path)))
    59  		}
    60  	} else {
    61  		w.WriteHeader(http.StatusBadRequest)
    62  		w.Write([]byte(fmt.Sprintf("wanted method %q got %q", p.States[0].wantMethod, r.Method)))
    63  	}
    64  
    65  	p.States[0] = ptr.Empty[pilotStubState]()
    66  	p.States = p.States[1:]
    67  	p.Unlock()
    68  }
    69  
    70  func Test_command_do(t *testing.T) {
    71  	tests := []struct {
    72  		name              string
    73  		method            string
    74  		path              string
    75  		body              string
    76  		pilotStates       []pilotStubState
    77  		pilotNotReachable bool
    78  		wantError         bool
    79  	}{
    80  		{
    81  			name:   "makes a request using passed method, url and body",
    82  			method: "POST",
    83  			path:   "/want/path",
    84  			body:   "body",
    85  			pilotStates: []pilotStubState{
    86  				{StatusCode: 200, Response: "fine", wantMethod: "POST", wantPath: "/want/path", wantBody: []byte("body")},
    87  			},
    88  		},
    89  		{
    90  			name:   "adds / prefix to path if required",
    91  			method: "POST",
    92  			path:   "want/path",
    93  			body:   "body",
    94  			pilotStates: []pilotStubState{
    95  				{StatusCode: 200, Response: "fine", wantMethod: "POST", wantPath: "/want/path", wantBody: []byte("body")},
    96  			},
    97  		},
    98  		{
    99  			name:   "handles empty string body in args",
   100  			method: "GET",
   101  			path:   "/want/path",
   102  			body:   "",
   103  			pilotStates: []pilotStubState{
   104  				{StatusCode: 200, Response: "fine", wantMethod: "GET", wantPath: "/want/path", wantBody: nil},
   105  			},
   106  		},
   107  		{
   108  			name:   "doesn't error on 404",
   109  			method: "GET",
   110  			path:   "/want/path",
   111  			body:   "",
   112  			pilotStates: []pilotStubState{
   113  				{StatusCode: 404, Response: "not-found", wantMethod: "GET", wantPath: "/want/path", wantBody: nil},
   114  			},
   115  		},
   116  		{
   117  			name:              "errors if Pilot is unreachable",
   118  			method:            "GET",
   119  			path:              "/want/path",
   120  			pilotNotReachable: true,
   121  			wantError:         true,
   122  		},
   123  		{
   124  			name:   "errors if Pilot responds with a non success status",
   125  			method: "GET",
   126  			path:   "/not/wanted/path",
   127  			body:   "",
   128  			pilotStates: []pilotStubState{
   129  				{StatusCode: 200, Response: "fine", wantMethod: "GET", wantPath: "/want/path", wantBody: nil},
   130  			},
   131  			wantError: true,
   132  		},
   133  	}
   134  	for _, tt := range tests {
   135  		t.Run(tt.name, func(t *testing.T) {
   136  			pilotStub := httptest.NewServer(
   137  				&pilotStubHandler{States: tt.pilotStates},
   138  			)
   139  			stubURL, _ := url.Parse(pilotStub.URL)
   140  			if tt.pilotNotReachable {
   141  				stubURL, _ = url.Parse("http://notpilot")
   142  			}
   143  			c := &Command{
   144  				Address: stubURL.Host,
   145  				Client:  &http.Client{},
   146  			}
   147  			err := c.Do(tt.method, tt.path, tt.body)
   148  			if (err == nil) && tt.wantError {
   149  				t.Errorf("Expected an error but received none")
   150  			} else if (err != nil) && !tt.wantError {
   151  				t.Errorf("Unexpected err: %v", err)
   152  			}
   153  		})
   154  	}
   155  }