mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-19 08:40:42 +00:00
Make client.Request more testable, break coupling with RESTClient
Moves polling to a function provided by the RESTClient, not innate to Request. Moves doRequest from RESTClient to Request for clarity.
This commit is contained in:
parent
1da5c444e8
commit
eac933eb44
@ -603,6 +603,7 @@ type Status struct {
|
||||
type StatusDetails struct {
|
||||
// The ID attribute of the resource associated with the status StatusReason
|
||||
// (when there is a single ID which can be described).
|
||||
// TODO: replace with Name
|
||||
ID string `json:"id,omitempty" yaml:"id,omitempty"`
|
||||
// The kind attribute of the resource associated with the status StatusReason.
|
||||
// On some operations may differ from the requested resource Kind.
|
||||
|
@ -28,32 +28,65 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// specialParams lists parameters that are handled specially and which users of Request
|
||||
// are therefore not allowed to set manually.
|
||||
var specialParams = util.NewStringSet("sync", "timeout")
|
||||
|
||||
// PollFunc is called when a server operation returns 202 accepted. The name of the
|
||||
// operation is extracted from the response and passed to this function. Return a
|
||||
// request to retrieve the result of the operation, or false for the second argument
|
||||
// if polling should end.
|
||||
type PollFunc func(name string) (*Request, bool)
|
||||
|
||||
// HTTPClient is an interface for testing a request object.
|
||||
type HTTPClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// Request allows for building up a request to a server in a chained fashion.
|
||||
// Any errors are stored until the end of your call, so you only have to
|
||||
// check once.
|
||||
type Request struct {
|
||||
c *RESTClient
|
||||
err error
|
||||
verb string
|
||||
path string
|
||||
body io.Reader
|
||||
params map[string]string
|
||||
selector labels.Selector
|
||||
timeout time.Duration
|
||||
sync bool
|
||||
pollPeriod time.Duration
|
||||
// required
|
||||
client HTTPClient
|
||||
verb string
|
||||
baseURL *url.URL
|
||||
codec runtime.Codec
|
||||
|
||||
// optional, will be invoked if the server returns a 202 to decide
|
||||
// whether to poll.
|
||||
poller PollFunc
|
||||
|
||||
// accessible via method setters
|
||||
path string
|
||||
params map[string]string
|
||||
selector labels.Selector
|
||||
sync bool
|
||||
timeout time.Duration
|
||||
|
||||
// output
|
||||
err error
|
||||
body io.Reader
|
||||
}
|
||||
|
||||
// NewRequest creates a new request with the core attributes.
|
||||
func NewRequest(client HTTPClient, verb string, baseURL *url.URL, codec runtime.Codec) *Request {
|
||||
return &Request{
|
||||
client: client,
|
||||
verb: verb,
|
||||
baseURL: baseURL,
|
||||
codec: codec,
|
||||
|
||||
path: baseURL.Path,
|
||||
}
|
||||
}
|
||||
|
||||
// Path appends an item to the request path. You must call Path at least once.
|
||||
@ -135,6 +168,9 @@ func (r *Request) setParam(paramName, value string) *Request {
|
||||
r.err = fmt.Errorf("must set %v through the corresponding function, not directly.", paramName)
|
||||
return r
|
||||
}
|
||||
if r.params == nil {
|
||||
r.params = make(map[string]string)
|
||||
}
|
||||
r.params[paramName] = value
|
||||
return r
|
||||
}
|
||||
@ -172,7 +208,7 @@ func (r *Request) Body(obj interface{}) *Request {
|
||||
case io.Reader:
|
||||
r.body = t
|
||||
case runtime.Object:
|
||||
data, err := r.c.Codec.Encode(t)
|
||||
data, err := r.codec.Encode(t)
|
||||
if err != nil {
|
||||
r.err = err
|
||||
return r
|
||||
@ -184,21 +220,23 @@ func (r *Request) Body(obj interface{}) *Request {
|
||||
return r
|
||||
}
|
||||
|
||||
// PollPeriod sets the poll period.
|
||||
// If the server sends back a "working" status message, then repeatedly poll the server
|
||||
// to see if the operation has completed yet, waiting 'd' between each poll.
|
||||
// If you want to handle the "working" status yourself (it'll be delivered as StatusErr),
|
||||
// set d to 0 to turn off this behavior.
|
||||
func (r *Request) PollPeriod(d time.Duration) *Request {
|
||||
// NoPoll indicates a server "working" response should be returned as an error
|
||||
func (r *Request) NoPoll() *Request {
|
||||
return r.Poller(nil)
|
||||
}
|
||||
|
||||
// Poller indicates this request should use the specify poll function to determine whether
|
||||
// a server "working" response should be retried.
|
||||
func (r *Request) Poller(poller PollFunc) *Request {
|
||||
if r.err != nil {
|
||||
return r
|
||||
}
|
||||
r.pollPeriod = d
|
||||
r.poller = poller
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Request) finalURL() string {
|
||||
finalURL := *r.c.baseURL
|
||||
finalURL := *r.baseURL
|
||||
finalURL.Path = r.path
|
||||
query := url.Values{}
|
||||
for key, value := range r.params {
|
||||
@ -227,18 +265,18 @@ func (r *Request) Watch() (watch.Interface, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := r.c.Client
|
||||
client := r.client
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
response, err := client.Do(req)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Got status: %v", response.StatusCode)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Got status: %v", resp.StatusCode)
|
||||
}
|
||||
return watch.NewStreamWatcher(watchjson.NewDecoder(response.Body, r.c.Codec)), nil
|
||||
return watch.NewStreamWatcher(watchjson.NewDecoder(resp.Body, r.codec)), nil
|
||||
}
|
||||
|
||||
// Stream formats and executes the request, and offers streaming of the response.
|
||||
@ -251,51 +289,106 @@ func (r *Request) Stream() (io.ReadCloser, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := r.c.Client
|
||||
client := r.client
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
response, err := client.Do(req)
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Body, nil
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// Do formats and executes the request. Returns the API object received, or an error.
|
||||
// Do formats and executes the request. Returns a Result object for easy response
|
||||
// processing. Handles polling the server in the event a continuation was sent.
|
||||
func (r *Request) Do() Result {
|
||||
client := r.client
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
|
||||
for {
|
||||
if r.err != nil {
|
||||
return Result{err: r.err}
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(r.verb, r.finalURL(), r.body)
|
||||
if err != nil {
|
||||
return Result{err: err}
|
||||
}
|
||||
respBody, created, err := r.c.doRequest(req)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
if s, ok := err.(APIStatus); ok {
|
||||
status := s.Status()
|
||||
if status.Status == api.StatusWorking && r.pollPeriod != 0 {
|
||||
if status.Details != nil {
|
||||
id := status.Details.ID
|
||||
if len(id) > 0 {
|
||||
glog.Infof("Waiting for completion of /operations/%s", id)
|
||||
time.Sleep(r.pollPeriod)
|
||||
// Make a poll request
|
||||
pollOp := r.c.PollFor(id).PollPeriod(r.pollPeriod)
|
||||
// Could also say "return r.Do()" but this way doesn't grow the callstack.
|
||||
r = pollOp
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Result{err: err}
|
||||
}
|
||||
return Result{respBody, created, err, r.c.Codec}
|
||||
|
||||
respBody, created, err := r.transformResponse(resp, req)
|
||||
if poll, ok := r.shouldPoll(err); ok {
|
||||
r = poll
|
||||
continue
|
||||
}
|
||||
|
||||
return Result{respBody, created, err, r.codec}
|
||||
}
|
||||
}
|
||||
|
||||
// shouldPoll checks the server error for an incomplete operation
|
||||
// and if found returns a request that would check the response.
|
||||
// If no polling is necessary or possible, it will return false.
|
||||
func (r *Request) shouldPoll(err error) (*Request, bool) {
|
||||
if err == nil || r.poller == nil {
|
||||
return nil, false
|
||||
}
|
||||
apistatus, ok := err.(APIStatus)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
status := apistatus.Status()
|
||||
if status.Status != api.StatusWorking {
|
||||
return nil, false
|
||||
}
|
||||
if status.Details == nil || len(status.Details.ID) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
return r.poller(status.Details.ID)
|
||||
}
|
||||
|
||||
// transformResponse converts an API response into a structured API object.
|
||||
func (r *Request) transformResponse(resp *http.Response, req *http.Request) ([]byte, bool, error) {
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Did the server give us a status response?
|
||||
isStatusResponse := false
|
||||
var status api.Status
|
||||
if err := r.codec.DecodeInto(body, &status); err == nil && status.Status != "" {
|
||||
isStatusResponse = true
|
||||
}
|
||||
|
||||
switch {
|
||||
case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent:
|
||||
if !isStatusResponse {
|
||||
return nil, false, fmt.Errorf("request [%#v] failed (%d) %s: %s", req, resp.StatusCode, resp.Status, string(body))
|
||||
}
|
||||
return nil, false, errors.FromObject(&status)
|
||||
}
|
||||
|
||||
// If the server gave us a status back, look at what it was.
|
||||
if isStatusResponse && status.Status != api.StatusSuccess {
|
||||
// "Working" requests need to be handled specially.
|
||||
// "Failed" requests are clearly just an error and it makes sense to return them as such.
|
||||
return nil, false, errors.FromObject(&status)
|
||||
}
|
||||
|
||||
created := resp.StatusCode == http.StatusCreated
|
||||
return body, created, err
|
||||
}
|
||||
|
||||
// Result contains the result of calling Request.Do().
|
||||
type Result struct {
|
||||
body []byte
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -29,6 +30,7 @@ import (
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta2"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
@ -38,6 +40,41 @@ import (
|
||||
watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
|
||||
)
|
||||
|
||||
func TestTransformResponse(t *testing.T) {
|
||||
invalid := []byte("aaaaa")
|
||||
uri, _ := url.Parse("http://localhost")
|
||||
testCases := []struct {
|
||||
Response *http.Response
|
||||
Data []byte
|
||||
Created bool
|
||||
Error bool
|
||||
}{
|
||||
{Response: &http.Response{StatusCode: 200}, Data: []byte{}},
|
||||
{Response: &http.Response{StatusCode: 201}, Data: []byte{}, Created: true},
|
||||
{Response: &http.Response{StatusCode: 199}, Error: true},
|
||||
{Response: &http.Response{StatusCode: 500}, Error: true},
|
||||
{Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
|
||||
{Response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid))}, Data: invalid},
|
||||
}
|
||||
for i, test := range testCases {
|
||||
r := NewRequest(nil, "", uri, testapi.Codec())
|
||||
if test.Response.Body == nil {
|
||||
test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{}))
|
||||
}
|
||||
response, created, err := r.transformResponse(test.Response, &http.Request{})
|
||||
hasErr := err != nil
|
||||
if hasErr != test.Error {
|
||||
t.Errorf("%d: unexpected error: %f %v", i, test.Error, err)
|
||||
}
|
||||
if !(test.Data == nil && response == nil) && !reflect.DeepEqual(test.Data, response) {
|
||||
t.Errorf("%d: unexpected response: %#v %#v", i, test.Data, response)
|
||||
}
|
||||
if test.Created != created {
|
||||
t.Errorf("%d: expected created %f, got %f", i, test.Created, created)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRequestNewWay(t *testing.T) {
|
||||
reqBody := "request body"
|
||||
expectedObj := &api.Service{Port: 12345}
|
||||
@ -48,6 +85,7 @@ func TestDoRequestNewWay(t *testing.T) {
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
defer testServer.Close()
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta2", Username: "user", Password: "pass"})
|
||||
obj, err := c.Verb("POST").
|
||||
Path("foo/bar").
|
||||
@ -351,15 +389,18 @@ func TestBody(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetPollPeriod(t *testing.T) {
|
||||
func TestSetPoller(t *testing.T) {
|
||||
c := NewOrDie(&Config{})
|
||||
r := c.Get()
|
||||
if r.pollPeriod == 0 {
|
||||
if c.PollPeriod == 0 {
|
||||
t.Errorf("polling should be on by default")
|
||||
}
|
||||
r.PollPeriod(time.Hour)
|
||||
if r.pollPeriod != time.Hour {
|
||||
t.Errorf("'PollPeriod' doesn't work")
|
||||
if r.poller == nil {
|
||||
t.Errorf("polling should be on by default")
|
||||
}
|
||||
r.NoPoll()
|
||||
if r.poller != nil {
|
||||
t.Errorf("'NoPoll' doesn't work")
|
||||
}
|
||||
}
|
||||
|
||||
@ -374,6 +415,16 @@ func TestPolling(t *testing.T) {
|
||||
|
||||
callNumber := 0
|
||||
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if callNumber == 0 {
|
||||
if r.URL.Path != "/api/v1beta1/" {
|
||||
t.Fatalf("unexpected request URL path %s", r.URL.Path)
|
||||
}
|
||||
} else {
|
||||
if r.URL.Path != "/api/v1beta1/operations/1234" {
|
||||
t.Fatalf("unexpected request URL path %s", r.URL.Path)
|
||||
}
|
||||
}
|
||||
t.Logf("About to write %d", callNumber)
|
||||
data, err := v1beta1.Codec.Encode(objects[callNumber])
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected encode error")
|
||||
@ -383,11 +434,11 @@ func TestPolling(t *testing.T) {
|
||||
}))
|
||||
|
||||
c := NewOrDie(&Config{Host: testServer.URL, Version: "v1beta1", Username: "user", Password: "pass"})
|
||||
|
||||
c.PollPeriod = 1 * time.Millisecond
|
||||
trials := []func(){
|
||||
func() {
|
||||
// Check that we do indeed poll when asked to.
|
||||
obj, err := c.Get().PollPeriod(5 * time.Millisecond).Do().Get()
|
||||
obj, err := c.Get().Do().Get()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v %#v", err, err)
|
||||
return
|
||||
@ -402,7 +453,7 @@ func TestPolling(t *testing.T) {
|
||||
},
|
||||
func() {
|
||||
// Check that we don't poll when asked not to.
|
||||
obj, err := c.Get().PollPeriod(0).Do().Get()
|
||||
obj, err := c.Get().NoPoll().Do().Get()
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected non error: %v", obj)
|
||||
return
|
||||
|
@ -17,16 +17,13 @@ limitations under the License.
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// RESTClient imposes common Kubernetes API conventions on a set of resource paths.
|
||||
@ -45,7 +42,11 @@ type RESTClient struct {
|
||||
|
||||
// Set specific behavior of the client. If not set http.DefaultClient will be
|
||||
// used.
|
||||
Client *http.Client
|
||||
Client HTTPClient
|
||||
|
||||
// Set the poll behavior of this client. If not set the DefaultPoll method will
|
||||
// be called.
|
||||
Poller PollFunc
|
||||
|
||||
Sync bool
|
||||
PollPeriod time.Duration
|
||||
@ -68,56 +69,13 @@ func NewRESTClient(baseURL *url.URL, c runtime.Codec) *RESTClient {
|
||||
Codec: c,
|
||||
|
||||
// Make asynchronous requests by default
|
||||
// TODO: flip me to the default
|
||||
Sync: false,
|
||||
|
||||
// Poll frequently when asynchronous requests are provided
|
||||
PollPeriod: time.Second * 2,
|
||||
}
|
||||
}
|
||||
|
||||
// doRequest executes a request against a server
|
||||
func (c *RESTClient) doRequest(request *http.Request) ([]byte, bool, error) {
|
||||
client := c.Client
|
||||
if client == nil {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return body, false, err
|
||||
}
|
||||
|
||||
// Did the server give us a status response?
|
||||
isStatusResponse := false
|
||||
var status api.Status
|
||||
if err := c.Codec.DecodeInto(body, &status); err == nil && status.Status != "" {
|
||||
isStatusResponse = true
|
||||
}
|
||||
|
||||
switch {
|
||||
case response.StatusCode < http.StatusOK || response.StatusCode > http.StatusPartialContent:
|
||||
if !isStatusResponse {
|
||||
return nil, false, fmt.Errorf("request [%#v] failed (%d) %s: %s", request, response.StatusCode, response.Status, string(body))
|
||||
}
|
||||
return nil, false, errors.FromObject(&status)
|
||||
}
|
||||
|
||||
// If the server gave us a status back, look at what it was.
|
||||
if isStatusResponse && status.Status != api.StatusSuccess {
|
||||
// "Working" requests need to be handled specially.
|
||||
// "Failed" requests are clearly just an error and it makes sense to return them as such.
|
||||
return nil, false, errors.FromObject(&status)
|
||||
}
|
||||
|
||||
created := response.StatusCode == http.StatusCreated
|
||||
return body, created, err
|
||||
}
|
||||
|
||||
// Verb begins a request with a verb (GET, POST, PUT, DELETE).
|
||||
//
|
||||
// Example usage of RESTClient's request building interface:
|
||||
@ -136,15 +94,11 @@ func (c *RESTClient) Verb(verb string) *Request {
|
||||
// if c.Client != nil {
|
||||
// timeout = c.Client.Timeout
|
||||
// }
|
||||
return &Request{
|
||||
verb: verb,
|
||||
c: c,
|
||||
path: c.baseURL.Path,
|
||||
sync: c.Sync,
|
||||
timeout: c.Timeout,
|
||||
params: map[string]string{},
|
||||
pollPeriod: c.PollPeriod,
|
||||
poller := c.Poller
|
||||
if poller == nil {
|
||||
poller = c.DefaultPoll
|
||||
}
|
||||
return NewRequest(c.Client, verb, c.baseURL, c.Codec).Poller(poller).Sync(c.Sync).Timeout(c.Timeout)
|
||||
}
|
||||
|
||||
// Post begins a POST request. Short for c.Verb("POST").
|
||||
@ -168,6 +122,16 @@ func (c *RESTClient) Delete() *Request {
|
||||
}
|
||||
|
||||
// PollFor makes a request to do a single poll of the completion of the given operation.
|
||||
func (c *RESTClient) PollFor(operationID string) *Request {
|
||||
return c.Get().Path("operations").Path(operationID).Sync(false).PollPeriod(0)
|
||||
func (c *RESTClient) Operation(name string) *Request {
|
||||
return c.Get().Path("operations").Path(name).Sync(false).NoPoll()
|
||||
}
|
||||
|
||||
func (c *RESTClient) DefaultPoll(name string) (*Request, bool) {
|
||||
if c.PollPeriod == 0 {
|
||||
return nil, false
|
||||
}
|
||||
glog.Infof("Waiting for completion of operation %s", name)
|
||||
time.Sleep(c.PollPeriod)
|
||||
// Make a poll request
|
||||
return c.Operation(name).Poller(c.DefaultPoll), true
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ package client
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@ -100,36 +99,6 @@ func TestValidatesHostParameter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRequest(t *testing.T) {
|
||||
invalid := "aaaaa"
|
||||
uri, _ := url.Parse("http://localhost")
|
||||
testClients := []testClient{
|
||||
{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 200}},
|
||||
{Request: testRequest{Method: "GET", Path: "good"}, Response: Response{StatusCode: 201}, Created: true},
|
||||
{Request: testRequest{Method: "GET", Path: "bad%ZZ"}, Error: true},
|
||||
{Request: testRequest{Method: "GET", Path: "error"}, Response: Response{StatusCode: 500}, Error: true},
|
||||
{Request: testRequest{Method: "POST", Path: "faildecode"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
|
||||
{Request: testRequest{Method: "GET", Path: "failread"}, Response: Response{StatusCode: 200, RawBody: &invalid}},
|
||||
{Client: &Client{&RESTClient{baseURL: uri, Codec: testapi.Codec()}}, Request: testRequest{Method: "GET", Path: "nocertificate"}, Error: true},
|
||||
}
|
||||
for _, c := range testClients {
|
||||
client := c.Setup()
|
||||
prefix := *client.baseURL
|
||||
prefix.Path += c.Request.Path
|
||||
request := &http.Request{
|
||||
Method: c.Request.Method,
|
||||
Header: make(http.Header),
|
||||
URL: &prefix,
|
||||
}
|
||||
response, created, err := client.doRequest(request)
|
||||
if c.Created != created {
|
||||
t.Errorf("expected created %f, got %f", c.Created, created)
|
||||
}
|
||||
//t.Logf("dorequest: %#v\n%#v\n%v", request.URL, response, err)
|
||||
c.ValidateRaw(t, response, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDoRequestBearer(t *testing.T) {
|
||||
status := &api.Status{Status: api.StatusWorking}
|
||||
expectedBody, _ := latest.Codec.Encode(status)
|
||||
@ -139,12 +108,15 @@ func TestDoRequestBearer(t *testing.T) {
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
request, _ := http.NewRequest("GET", testServer.URL, nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, BearerToken: "test"})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
c.doRequest(request)
|
||||
err = c.Get().Do().Error()
|
||||
if err == nil {
|
||||
t.Fatalf("unexpected non-error: %v", err)
|
||||
}
|
||||
if fakeHandler.RequestReceived.Header.Get("Authorization") != "Bearer test" {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
@ -159,18 +131,17 @@ func TestDoRequestAccepted(t *testing.T) {
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "test"})
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "test", Version: testapi.Version()})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, _, err := c.doRequest(request)
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
body, err := c.Get().Path("test").Do().Raw()
|
||||
if err == nil {
|
||||
t.Fatalf("Unexpected non-error")
|
||||
}
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", fakeHandler.RequestReceived)
|
||||
}
|
||||
se, ok := err.(APIStatus)
|
||||
if !ok {
|
||||
t.Fatalf("Unexpected kind of error: %#v", err)
|
||||
@ -179,9 +150,9 @@ func TestDoRequestAccepted(t *testing.T) {
|
||||
t.Errorf("Unexpected status: %#v %#v", se.Status(), status)
|
||||
}
|
||||
if body != nil {
|
||||
t.Errorf("Expected nil body, but saw: '%s'", body)
|
||||
t.Errorf("Expected nil body, but saw: '%s'", string(body))
|
||||
}
|
||||
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
|
||||
fakeHandler.ValidateRequest(t, "/"+testapi.Version()+"/test", "GET", nil)
|
||||
}
|
||||
|
||||
func TestDoRequestAcceptedSuccess(t *testing.T) {
|
||||
@ -193,17 +164,16 @@ func TestDoRequestAcceptedSuccess(t *testing.T) {
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass"})
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass", Version: testapi.Version()})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, _, err := c.doRequest(request)
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", *request)
|
||||
}
|
||||
body, err := c.Get().Path("test").Do().Raw()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if fakeHandler.RequestReceived.Header["Authorization"] == nil {
|
||||
t.Errorf("Request is missing authorization header: %#v", fakeHandler.RequestReceived)
|
||||
}
|
||||
statusOut, err := latest.Codec.Decode(body)
|
||||
if err != nil {
|
||||
@ -212,7 +182,7 @@ func TestDoRequestAcceptedSuccess(t *testing.T) {
|
||||
if !reflect.DeepEqual(status, statusOut) {
|
||||
t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, statusOut)
|
||||
}
|
||||
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
|
||||
fakeHandler.ValidateRequest(t, "/"+testapi.Version()+"/test", "GET", nil)
|
||||
}
|
||||
|
||||
func TestDoRequestFailed(t *testing.T) {
|
||||
@ -224,12 +194,11 @@ func TestDoRequestFailed(t *testing.T) {
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, _, err := c.doRequest(request)
|
||||
body, err := c.Get().Do().Raw()
|
||||
if err == nil || body != nil {
|
||||
t.Errorf("unexpected non-error: %#v", body)
|
||||
}
|
||||
@ -252,12 +221,12 @@ func TestDoRequestCreated(t *testing.T) {
|
||||
T: t,
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
request, _ := http.NewRequest("GET", testServer.URL+"/foo/bar", nil)
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass"})
|
||||
c, err := RESTClientFor(&Config{Host: testServer.URL, Username: "user", Password: "pass", Version: testapi.Version()})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
body, created, err := c.doRequest(request)
|
||||
created := false
|
||||
body, err := c.Get().Path("test").Do().WasCreated(&created).Raw()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %#v", err)
|
||||
}
|
||||
@ -271,5 +240,5 @@ func TestDoRequestCreated(t *testing.T) {
|
||||
if !reflect.DeepEqual(status, statusOut) {
|
||||
t.Errorf("Unexpected mis-match. Expected %#v. Saw %#v", status, statusOut)
|
||||
}
|
||||
fakeHandler.ValidateRequest(t, "/foo/bar", "GET", nil)
|
||||
fakeHandler.ValidateRequest(t, "/"+testapi.Version()+"/test", "GET", nil)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user