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 |