| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package epfrontend | |
| 6 | |
| 7 import ( | |
| 8 "bytes" | |
| 9 "encoding/json" | |
| 10 "errors" | |
| 11 "fmt" | |
| 12 "net/http" | |
| 13 "net/http/httptest" | |
| 14 "net/url" | |
| 15 "testing" | |
| 16 | |
| 17 "github.com/GoogleCloudPlatform/go-endpoints/endpoints" | |
| 18 . "github.com/smartystreets/goconvey/convey" | |
| 19 "golang.org/x/net/context" | |
| 20 ) | |
| 21 | |
| 22 type serviceBackendStub struct { | |
| 23 body bytes.Buffer | |
| 24 callback func(w http.ResponseWriter) | |
| 25 } | |
| 26 | |
| 27 func (s *serviceBackendStub) ServeHTTP(w http.ResponseWriter, req *http.Request)
{ | |
| 28 defer req.Body.Close() | |
| 29 _, err := s.body.ReadFrom(req.Body) | |
| 30 if err != nil { | |
| 31 panic(err) | |
| 32 } | |
| 33 | |
| 34 if s.callback != nil { | |
| 35 s.callback(w) | |
| 36 } | |
| 37 } | |
| 38 | |
| 39 type requestNormalizer struct { | |
| 40 http.Handler | |
| 41 scheme string | |
| 42 host string | |
| 43 } | |
| 44 | |
| 45 func (s *requestNormalizer) ServeHTTP(w http.ResponseWriter, req *http.Request)
{ | |
| 46 u, err := url.ParseRequestURI(req.RequestURI) | |
| 47 if err != nil { | |
| 48 panic(err) | |
| 49 } | |
| 50 | |
| 51 u.Scheme = s.scheme | |
| 52 u.Host = s.host | |
| 53 req.URL = u | |
| 54 req.RequestURI = u.Path | |
| 55 req.Host = u.Host | |
| 56 s.Handler.ServeHTTP(w, req) | |
| 57 } | |
| 58 | |
| 59 type ServiceTestService struct { | |
| 60 } | |
| 61 | |
| 62 type ServiceTestPathReq struct { | |
| 63 Name string `endpoints:"required"` | |
| 64 Count int64 `endpoints:"required"` | |
| 65 } | |
| 66 | |
| 67 type ServiceTestResponse struct { | |
| 68 Count int64 `json:"count"` | |
| 69 } | |
| 70 | |
| 71 func (s *ServiceTestService) PathReq(c context.Context, req *ServiceTestPathReq)
(*ServiceTestResponse, error) { | |
| 72 return nil, endpoints.UnauthorizedError | |
| 73 } | |
| 74 | |
| 75 type ServiceTestQueryReq struct { | |
| 76 Name string `endpoints:"required"` | |
| 77 Count int64 `json:"count,string"` | |
| 78 | |
| 79 S string | |
| 80 F float32 | |
| 81 B bool | |
| 82 B2 bool | |
| 83 I int32 | |
| 84 } | |
| 85 | |
| 86 func (s *ServiceTestService) QueryReq(c context.Context, req *ServiceTestQueryRe
q) error { | |
| 87 return endpoints.UnauthorizedError | |
| 88 } | |
| 89 | |
| 90 type ServiceTestPostReq struct { | |
| 91 S string | |
| 92 T string | |
| 93 } | |
| 94 | |
| 95 func (s *ServiceTestService) Post(c context.Context, req *ServiceTestPostReq) er
ror { | |
| 96 return endpoints.UnauthorizedError | |
| 97 } | |
| 98 | |
| 99 func readErrorResponse(resp *http.Response) *outerError { | |
| 100 defer resp.Body.Close() | |
| 101 | |
| 102 e := outerError{} | |
| 103 if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { | |
| 104 return nil | |
| 105 } | |
| 106 return &e | |
| 107 } | |
| 108 | |
| 109 func TestService(t *testing.T) { | |
| 110 Convey(`A testing service`, t, func() { | |
| 111 be := serviceBackendStub{} | |
| 112 fe := New("", &be) | |
| 113 | |
| 114 ts := httptest.NewServer(&requestNormalizer{ | |
| 115 Handler: fe, | |
| 116 scheme: "http", | |
| 117 host: "example.com", | |
| 118 }) | |
| 119 defer ts.Close() | |
| 120 | |
| 121 c := http.Client{} | |
| 122 | |
| 123 Convey(`Requests to non-root URLs are rejected.`, func() { | |
| 124 resp, err := c.Get(fmt.Sprintf("%s%s", ts.URL, "/ohai")) | |
| 125 So(err, ShouldBeNil) | |
| 126 So(resp.StatusCode, ShouldEqual, http.StatusNotFound) | |
| 127 }) | |
| 128 | |
| 129 Convey(`With a registered test service (query parameters)`, func
() { | |
| 130 // Create an endpoints backend because we need the API d
escriptors that it | |
| 131 // creates from our services. | |
| 132 epbe := endpoints.NewServer("") | |
| 133 svc, err := epbe.RegisterService(&ServiceTestService{},
"test", "v1", "Test Service", true) | |
| 134 So(err, ShouldBeNil) | |
| 135 So(svc, ShouldNotBeNil) | |
| 136 | |
| 137 m := svc.MethodByName("QueryReq") | |
| 138 m.Info().HTTPMethod = "GET" | |
| 139 m.Info().Path = "queryreq/{Name}" | |
| 140 | |
| 141 So(fe.RegisterService(svc), ShouldBeNil) | |
| 142 | |
| 143 Convey(`Will not register the service again.`, func() { | |
| 144 So(fe.RegisterService(svc), ShouldNotBeNil) | |
| 145 }) | |
| 146 | |
| 147 Convey(`Exposes a redirecting "explorer" API.`, func() { | |
| 148 redirected := false | |
| 149 c.CheckRedirect = func(req *http.Request, via []
*http.Request) error { | |
| 150 redirected = true | |
| 151 return errors.New("testing, no redirect"
) | |
| 152 } | |
| 153 | |
| 154 c.Get(fmt.Sprintf("%s%s", ts.URL, "/_ah/api/expl
orer")) | |
| 155 So(redirected, ShouldBeTrue) | |
| 156 }) | |
| 157 | |
| 158 Convey(`POST requests to directory endpoint return http.
StatusMethodNotAllowed.`, func() { | |
| 159 resp, err := c.Post(fmt.Sprintf("%s%s", ts.URL,
"/_ah/api/discovery/v1/apis"), "", nil) | |
| 160 So(err, ShouldBeNil) | |
| 161 So(resp.StatusCode, ShouldEqual, http.StatusMeth
odNotAllowed) | |
| 162 }) | |
| 163 | |
| 164 Convey(`Exposes a directory REST endpoint.`, func() { | |
| 165 exp := directoryList{} | |
| 166 loadJSONTestCase(&exp, "service", "test", "direc
tory") | |
| 167 | |
| 168 act := directoryList{} | |
| 169 resp, err := c.Get(fmt.Sprintf("%s%s", ts.URL, "
/_ah/api/discovery/v1/apis")) | |
| 170 So(err, ShouldBeNil) | |
| 171 defer resp.Body.Close() | |
| 172 So(json.NewDecoder(resp.Body).Decode(&act), Shou
ldBeNil) | |
| 173 | |
| 174 So(act, ShouldResemble, exp) | |
| 175 }) | |
| 176 | |
| 177 Convey(`Exposes a service REST endpoint.`, func() { | |
| 178 exp := restDescription{} | |
| 179 loadJSONTestCase(&exp, "service", "test", "servi
ce") | |
| 180 | |
| 181 act := restDescription{} | |
| 182 resp, err := c.Get(fmt.Sprintf("%s%s", ts.URL, "
/_ah/api/discovery/v1/apis/test/v1/rest")) | |
| 183 So(err, ShouldBeNil) | |
| 184 defer resp.Body.Close() | |
| 185 So(json.NewDecoder(resp.Body).Decode(&act), Shou
ldBeNil) | |
| 186 | |
| 187 So(act, ShouldResemble, exp) | |
| 188 }) | |
| 189 | |
| 190 Convey(`Can access the "pathreq" endpoint with path elem
ents.`, func() { | |
| 191 _, err := c.Get(fmt.Sprintf("%s%s", ts.URL, "/_a
h/api/test/v1/pathreq/testname/12345")) | |
| 192 So(err, ShouldBeNil) | |
| 193 | |
| 194 So(be.body.String(), ShouldEqual, `{"Count":"123
45","Name":"testname"}`+"\n") | |
| 195 }) | |
| 196 | |
| 197 Convey(`Will return an error if an invalid "pathreq" pat
h parameter is supplied.`, func() { | |
| 198 resp, err := c.Get(fmt.Sprintf("%s%s", ts.URL, "
/_ah/api/test/v1/pathreq/testname/pi")) | |
| 199 So(err, ShouldBeNil) | |
| 200 | |
| 201 e := readErrorResponse(resp) | |
| 202 So(e, ShouldNotBeNil) | |
| 203 So(e.Error, ShouldNotBeNil) | |
| 204 So(e.Error.Code, ShouldEqual, http.StatusBadRequ
est) | |
| 205 So(e.Error.Message, ShouldEqual, "unspecified er
ror") | |
| 206 }) | |
| 207 | |
| 208 Convey(`Can access the "queryreq" endpoint with query pa
rameters.`, func() { | |
| 209 _, err := c.Get(fmt.Sprintf("%s%s", ts.URL, | |
| 210 "/_ah/api/test/v1/queryreq/testname?coun
t=12345&S=foo&F=3.14&B=true&B2=false&I=1337")) | |
| 211 So(err, ShouldBeNil) | |
| 212 | |
| 213 So(be.body.String(), ShouldEqual, | |
| 214 `{"B":true,"B2":false,"F":3.14,"I":1337,
"Name":"testname","S":"foo","count":"12345"}`+"\n") | |
| 215 }) | |
| 216 | |
| 217 Convey(`Will return an error if an invalid "queryreq" qu
ery parameter is supplied.`, func() { | |
| 218 resp, err := c.Get(fmt.Sprintf("%s%s", ts.URL, "
/_ah/api/test/v1/queryreq/testname?count=pi")) | |
| 219 So(err, ShouldBeNil) | |
| 220 | |
| 221 e := readErrorResponse(resp) | |
| 222 So(e, ShouldNotBeNil) | |
| 223 So(e.Error, ShouldNotBeNil) | |
| 224 So(e.Error.Code, ShouldEqual, http.StatusBadRequ
est) | |
| 225 So(e.Error.Message, ShouldEqual, "unspecified er
ror") | |
| 226 }) | |
| 227 | |
| 228 Convey(`Will augment POST data with query parameters.`,
func() { | |
| 229 _, err := c.Post(fmt.Sprintf("%s%s", ts.URL, "/_
ah/api/test/v1/post?T=bar"), | |
| 230 "application/json", bytes.NewBufferStrin
g(`{"S":"foo"}`)) | |
| 231 So(err, ShouldBeNil) | |
| 232 | |
| 233 So(be.body.String(), ShouldEqual, `{"S":"foo","T
":"bar"}`+"\n") | |
| 234 }) | |
| 235 | |
| 236 Convey(`Will return an error if an invalid endpoint is r
equested.`, func() { | |
| 237 resp, err := c.Post(fmt.Sprintf("%s%s", ts.URL,
"/_ah/api/test/v1/does.not.exist"), "", nil) | |
| 238 So(err, ShouldBeNil) | |
| 239 | |
| 240 e := readErrorResponse(resp) | |
| 241 So(e, ShouldNotBeNil) | |
| 242 So(e.Error, ShouldNotBeNil) | |
| 243 So(e.Error.Code, ShouldEqual, http.StatusNotFoun
d) | |
| 244 So(e.Error.Message, ShouldEqual, "unspecified er
ror") | |
| 245 }) | |
| 246 | |
| 247 Convey(`Will catch backend panic() and wrap it with an e
rror.`, func() { | |
| 248 didPanic := false | |
| 249 be.callback = func(http.ResponseWriter) { | |
| 250 didPanic = true | |
| 251 panic("test panic") | |
| 252 } | |
| 253 | |
| 254 resp, err := c.Post(fmt.Sprintf("%s%s", ts.URL,
"/_ah/api/test/v1/post"), "", nil) | |
| 255 So(err, ShouldBeNil) | |
| 256 | |
| 257 So(didPanic, ShouldBeTrue) | |
| 258 | |
| 259 e := readErrorResponse(resp) | |
| 260 So(e, ShouldNotBeNil) | |
| 261 So(e.Error, ShouldNotBeNil) | |
| 262 So(e.Error.Code, ShouldEqual, http.StatusService
Unavailable) | |
| 263 }) | |
| 264 | |
| 265 Convey(`Will catch and forward backend JSON errors.`, fu
nc() { | |
| 266 be.callback = func(w http.ResponseWriter) { | |
| 267 w.WriteHeader(http.StatusNotFound) | |
| 268 w.Write([]byte(`{"error_message": "test
message"}`)) | |
| 269 } | |
| 270 | |
| 271 resp, err := c.Post(fmt.Sprintf("%s%s", ts.URL,
"/_ah/api/test/v1/post"), "", nil) | |
| 272 So(err, ShouldBeNil) | |
| 273 | |
| 274 e := readErrorResponse(resp) | |
| 275 So(e, ShouldNotBeNil) | |
| 276 So(e.Error, ShouldNotBeNil) | |
| 277 So(e.Error.Code, ShouldEqual, http.StatusNotFoun
d) | |
| 278 So(e.Error.Message, ShouldEqual, "test message") | |
| 279 }) | |
| 280 | |
| 281 Convey(`Will catch non-JSON backend errors and wrap with
generic error.`, func() { | |
| 282 be.callback = func(w http.ResponseWriter) { | |
| 283 w.WriteHeader(http.StatusNotFound) | |
| 284 w.Write([]byte("$$NOT JSON$$")) | |
| 285 } | |
| 286 | |
| 287 resp, err := c.Post(fmt.Sprintf("%s%s", ts.URL,
"/_ah/api/test/v1/post"), "", nil) | |
| 288 So(err, ShouldBeNil) | |
| 289 | |
| 290 e := readErrorResponse(resp) | |
| 291 So(e, ShouldNotBeNil) | |
| 292 So(e.Error, ShouldNotBeNil) | |
| 293 So(e.Error.Code, ShouldEqual, http.StatusNotFoun
d) | |
| 294 So(e.Error.Message, ShouldEqual, "unspecified er
ror") | |
| 295 So(len(e.Error.Errors), ShouldEqual, 1) | |
| 296 So(e.Error.Errors[0].Message, ShouldContainSubst
ring, "Failed to decode error JSON") | |
| 297 }) | |
| 298 }) | |
| 299 }) | |
| 300 } | |
| OLD | NEW |