github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/api/http_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package api_test
     5  
     6  import (
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"reflect"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/httprequest"
    13  	jc "github.com/juju/testing/checkers"
    14  	gc "gopkg.in/check.v1"
    15  
    16  	"github.com/juju/juju/api"
    17  	"github.com/juju/juju/apiserver/params"
    18  	jujutesting "github.com/juju/juju/juju/testing"
    19  	"github.com/juju/juju/state"
    20  	"github.com/juju/juju/testing/factory"
    21  )
    22  
    23  type httpSuite struct {
    24  	jujutesting.JujuConnSuite
    25  
    26  	client *httprequest.Client
    27  }
    28  
    29  var _ = gc.Suite(&httpSuite{})
    30  
    31  func (s *httpSuite) SetUpTest(c *gc.C) {
    32  	s.JujuConnSuite.SetUpTest(c)
    33  
    34  	client, err := s.APIState.HTTPClient()
    35  	c.Assert(err, gc.IsNil)
    36  	s.client = client
    37  }
    38  
    39  var httpClientTests = []struct {
    40  	about           string
    41  	handler         http.HandlerFunc
    42  	expectResponse  interface{}
    43  	expectError     string
    44  	expectErrorCode string
    45  	expectErrorInfo *params.ErrorInfo
    46  }{{
    47  	about: "success",
    48  	handler: func(w http.ResponseWriter, req *http.Request) {
    49  		httprequest.WriteJSON(w, http.StatusOK, "hello, world")
    50  	},
    51  	expectResponse: newString("hello, world"),
    52  }, {
    53  	about: "unauthorized status without discharge-required error",
    54  	handler: func(w http.ResponseWriter, req *http.Request) {
    55  		httprequest.WriteJSON(w, http.StatusUnauthorized, params.Error{
    56  			Message: "something",
    57  		})
    58  	},
    59  	expectError: `GET http://.*/: something`,
    60  }, {
    61  	about:       "non-JSON error response",
    62  	handler:     http.NotFound,
    63  	expectError: `GET http://.*/: unexpected content type text/plain; want application/json; content: 404 page not found`,
    64  }, {
    65  	about: "bad error response",
    66  	handler: func(w http.ResponseWriter, req *http.Request) {
    67  		type badResponse struct {
    68  			Message map[string]int
    69  		}
    70  		httprequest.WriteJSON(w, http.StatusUnauthorized, badResponse{
    71  			Message: make(map[string]int),
    72  		})
    73  	},
    74  	expectError: `GET http://.*/: incompatible error response: json: cannot unmarshal object into Go value of type string`,
    75  }, {
    76  	about: "bad charms error response",
    77  	handler: func(w http.ResponseWriter, req *http.Request) {
    78  		type badResponse struct {
    79  			Error    string         `json:"error"`
    80  			CharmURL map[string]int `json:"charm-url"`
    81  		}
    82  		httprequest.WriteJSON(w, http.StatusUnauthorized, badResponse{
    83  			Error:    "something",
    84  			CharmURL: make(map[string]int),
    85  		})
    86  	},
    87  	expectError: `GET http://.*/: incompatible error response: json: cannot unmarshal object into Go value of type string`,
    88  }, {
    89  	about: "no message in ErrorResponse",
    90  	handler: func(w http.ResponseWriter, req *http.Request) {
    91  		httprequest.WriteJSON(w, http.StatusUnauthorized, params.ErrorResult{
    92  			Error: &params.Error{},
    93  		})
    94  	},
    95  	expectError: `GET http://.*/: error response with no message`,
    96  }, {
    97  	about: "no message in Error",
    98  	handler: func(w http.ResponseWriter, req *http.Request) {
    99  		httprequest.WriteJSON(w, http.StatusUnauthorized, params.Error{})
   100  	},
   101  	expectError: `GET http://.*/: error response with no message`,
   102  }, {
   103  	about: "charms error response",
   104  	handler: func(w http.ResponseWriter, req *http.Request) {
   105  		httprequest.WriteJSON(w, http.StatusBadRequest, params.CharmsResponse{
   106  			Error:     "some error",
   107  			ErrorCode: params.CodeBadRequest,
   108  			ErrorInfo: &params.ErrorInfo{
   109  				MacaroonPath: "foo",
   110  			},
   111  		})
   112  	},
   113  	expectError:     `.*some error$`,
   114  	expectErrorCode: params.CodeBadRequest,
   115  	expectErrorInfo: &params.ErrorInfo{
   116  		MacaroonPath: "foo",
   117  	},
   118  }, {
   119  	about: "discharge-required response with no error info",
   120  	handler: func(w http.ResponseWriter, req *http.Request) {
   121  		httprequest.WriteJSON(w, http.StatusUnauthorized, params.Error{
   122  			Message: "some error",
   123  			Code:    params.CodeDischargeRequired,
   124  		})
   125  	},
   126  	expectError:     `GET http://.*/: no error info found in discharge-required response error: some error`,
   127  	expectErrorCode: params.CodeDischargeRequired,
   128  }, {
   129  	about: "discharge-required response with no macaroon",
   130  	handler: func(w http.ResponseWriter, req *http.Request) {
   131  		httprequest.WriteJSON(w, http.StatusUnauthorized, params.Error{
   132  			Message: "some error",
   133  			Code:    params.CodeDischargeRequired,
   134  			Info:    &params.ErrorInfo{},
   135  		})
   136  	},
   137  	expectError: `GET http://.*/: no macaroon found in discharge-required response`,
   138  }}
   139  
   140  func (s *httpSuite) TestHTTPClient(c *gc.C) {
   141  	var handler http.HandlerFunc
   142  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   143  		handler(w, req)
   144  	}))
   145  	defer srv.Close()
   146  	s.client.BaseURL = srv.URL
   147  	for i, test := range httpClientTests {
   148  		c.Logf("test %d: %s", i, test.about)
   149  		handler = test.handler
   150  		var resp interface{}
   151  		if test.expectResponse != nil {
   152  			resp = reflect.New(reflect.TypeOf(test.expectResponse).Elem()).Interface()
   153  		}
   154  		err := s.client.Get("/", resp)
   155  		if test.expectError != "" {
   156  			c.Check(err, gc.ErrorMatches, test.expectError)
   157  			c.Check(params.ErrCode(err), gc.Equals, test.expectErrorCode)
   158  			if err, ok := errors.Cause(err).(*params.Error); ok {
   159  				c.Check(err.Info, jc.DeepEquals, test.expectErrorInfo)
   160  			} else if test.expectErrorInfo != nil {
   161  				c.Fatalf("no error info found in error")
   162  			}
   163  			continue
   164  		}
   165  		c.Check(err, gc.IsNil)
   166  		c.Check(resp, jc.DeepEquals, test.expectResponse)
   167  	}
   168  }
   169  
   170  func (s *httpSuite) TestControllerMachineAuthForHostedModel(c *gc.C) {
   171  	// Create a controller machine & hosted model.
   172  	const nonce = "gary"
   173  	m, password := s.Factory.MakeMachineReturningPassword(c, &factory.MachineParams{
   174  		Jobs:  []state.MachineJob{state.JobManageModel},
   175  		Nonce: nonce,
   176  	})
   177  	hostedState := s.Factory.MakeModel(c, nil)
   178  	defer hostedState.Close()
   179  
   180  	// Connect to the hosted model using the credentials of the
   181  	// controller machine.
   182  	apiInfo := s.APIInfo(c)
   183  	apiInfo.Tag = m.Tag()
   184  	apiInfo.Password = password
   185  	apiInfo.ModelTag = hostedState.ModelTag()
   186  	apiInfo.Nonce = nonce
   187  	conn, err := api.Open(apiInfo, api.DialOpts{})
   188  	c.Assert(err, jc.ErrorIsNil)
   189  	httpClient, err := conn.HTTPClient()
   190  	c.Assert(err, jc.ErrorIsNil)
   191  
   192  	// Test with a dummy HTTP server returns the auth related headers used.
   193  	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
   194  		username, password, ok := req.BasicAuth()
   195  		if ok {
   196  			httprequest.WriteJSON(w, http.StatusOK, map[string]string{
   197  				"username": username,
   198  				"password": password,
   199  				"nonce":    req.Header.Get(params.MachineNonceHeader),
   200  			})
   201  		} else {
   202  			httprequest.WriteJSON(w, http.StatusUnauthorized, params.Error{
   203  				Message: "no auth header",
   204  			})
   205  		}
   206  	}))
   207  	defer srv.Close()
   208  	httpClient.BaseURL = srv.URL
   209  	var out map[string]string
   210  	c.Assert(httpClient.Get("/", &out), jc.ErrorIsNil)
   211  	c.Assert(out, gc.DeepEquals, map[string]string{
   212  		"username": m.Tag().String(),
   213  		"password": password,
   214  		"nonce":    nonce,
   215  	})
   216  }
   217  
   218  // Note: the fact that the code works against the actual API server is
   219  // well tested by some of the other API tests.
   220  // This suite focuses on less reachable paths by changing
   221  // the BaseURL of the httprequest.Client so that
   222  // we can use our own custom servers.
   223  
   224  func newString(s string) *string {
   225  	return &s
   226  }