1 package jsonrpc_test
2
3 import (
4 "context"
5 "encoding/json"
6 "errors"
7 "io"
8 "io/ioutil"
9 "net/http"
10 "net/http/httptest"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/go-kit/kit/endpoint"
16 "github.com/go-kit/kit/transport/http/jsonrpc"
17 )
18
19 func addBody() io.Reader {
20 return body(`{"jsonrpc": "2.0", "method": "add", "params": [3, 2], "id": 1}`)
21 }
22
23 func body(in string) io.Reader {
24 return strings.NewReader(in)
25 }
26
27 func unmarshalResponse(body []byte) (resp jsonrpc.Response, err error) {
28 err = json.Unmarshal(body, &resp)
29 return
30 }
31
32 func expectErrorCode(t *testing.T, want int, body []byte) {
33 t.Helper()
34
35 r, err := unmarshalResponse(body)
36 if err != nil {
37 t.Fatalf("Can't decode response: %v (%s)", err, body)
38 }
39 if r.Error == nil {
40 t.Fatalf("Expected error on response. Got none: %s", body)
41 }
42 if have := r.Error.Code; want != have {
43 t.Fatalf("Unexpected error code. Want %d, have %d: %s", want, have, body)
44 }
45 }
46
47 func expectValidRequestID(t *testing.T, want int, body []byte) {
48 t.Helper()
49
50 r, err := unmarshalResponse(body)
51 if err != nil {
52 t.Fatalf("Can't decode response: %v (%s)", err, body)
53 }
54 have, err := r.ID.Int()
55 if err != nil {
56 t.Fatalf("Can't get requestID in response. err=%s, body=%s", err, body)
57 }
58 if want != have {
59 t.Fatalf("Request ID: want %d, have %d (%s)", want, have, body)
60 }
61 }
62
63 func expectNilRequestID(t *testing.T, body []byte) {
64 t.Helper()
65
66 r, err := unmarshalResponse(body)
67 if err != nil {
68 t.Fatalf("Can't decode response: %v (%s)", err, body)
69 }
70 if r.ID != nil {
71 t.Fatalf("Request ID: want nil, have %v", r.ID)
72 }
73 }
74
75 func nopDecoder(context.Context, json.RawMessage) (interface{}, error) { return struct{}{}, nil }
76 func nopEncoder(context.Context, interface{}) (json.RawMessage, error) { return []byte("[]"), nil }
77
78 type mockLogger struct {
79 Called bool
80 LastArgs []interface{}
81 }
82
83 func (l *mockLogger) Log(keyvals ...interface{}) error {
84 l.Called = true
85 l.LastArgs = append(l.LastArgs, keyvals)
86 return nil
87 }
88
89 func TestServerBadDecode(t *testing.T) {
90 ecm := jsonrpc.EndpointCodecMap{
91 "add": jsonrpc.EndpointCodec{
92 Endpoint: endpoint.Nop,
93 Decode: func(context.Context, json.RawMessage) (interface{}, error) { return struct{}{}, errors.New("oof") },
94 Encode: nopEncoder,
95 },
96 }
97 logger := mockLogger{}
98 handler := jsonrpc.NewServer(ecm, jsonrpc.ServerErrorLogger(&logger))
99 server := httptest.NewServer(handler)
100 defer server.Close()
101 resp, _ := http.Post(server.URL, "application/json", addBody())
102 buf, _ := ioutil.ReadAll(resp.Body)
103 if want, have := http.StatusOK, resp.StatusCode; want != have {
104 t.Errorf("want %d, have %d: %s", want, have, buf)
105 }
106 expectErrorCode(t, jsonrpc.InternalError, buf)
107 if !logger.Called {
108 t.Fatal("Expected logger to be called with error. Wasn't.")
109 }
110 }
111
112 func TestServerBadEndpoint(t *testing.T) {
113 ecm := jsonrpc.EndpointCodecMap{
114 "add": jsonrpc.EndpointCodec{
115 Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errors.New("oof") },
116 Decode: nopDecoder,
117 Encode: nopEncoder,
118 },
119 }
120 handler := jsonrpc.NewServer(ecm)
121 server := httptest.NewServer(handler)
122 defer server.Close()
123 resp, _ := http.Post(server.URL, "application/json", addBody())
124 if want, have := http.StatusOK, resp.StatusCode; want != have {
125 t.Errorf("want %d, have %d", want, have)
126 }
127 buf, _ := ioutil.ReadAll(resp.Body)
128 expectErrorCode(t, jsonrpc.InternalError, buf)
129 expectValidRequestID(t, 1, buf)
130 }
131
132 func TestServerBadEncode(t *testing.T) {
133 ecm := jsonrpc.EndpointCodecMap{
134 "add": jsonrpc.EndpointCodec{
135 Endpoint: endpoint.Nop,
136 Decode: nopDecoder,
137 Encode: func(context.Context, interface{}) (json.RawMessage, error) { return []byte{}, errors.New("oof") },
138 },
139 }
140 handler := jsonrpc.NewServer(ecm)
141 server := httptest.NewServer(handler)
142 defer server.Close()
143 resp, _ := http.Post(server.URL, "application/json", addBody())
144 if want, have := http.StatusOK, resp.StatusCode; want != have {
145 t.Errorf("want %d, have %d", want, have)
146 }
147 buf, _ := ioutil.ReadAll(resp.Body)
148 expectErrorCode(t, jsonrpc.InternalError, buf)
149 expectValidRequestID(t, 1, buf)
150 }
151
152 func TestServerErrorEncoder(t *testing.T) {
153 errTeapot := errors.New("teapot")
154 code := func(err error) int {
155 if errors.Is(err, errTeapot) {
156 return http.StatusTeapot
157 }
158 return http.StatusInternalServerError
159 }
160 ecm := jsonrpc.EndpointCodecMap{
161 "add": jsonrpc.EndpointCodec{
162 Endpoint: func(context.Context, interface{}) (interface{}, error) { return struct{}{}, errTeapot },
163 Decode: nopDecoder,
164 Encode: nopEncoder,
165 },
166 }
167 handler := jsonrpc.NewServer(
168 ecm,
169 jsonrpc.ServerErrorEncoder(func(_ context.Context, err error, w http.ResponseWriter) { w.WriteHeader(code(err)) }),
170 )
171 server := httptest.NewServer(handler)
172 defer server.Close()
173 resp, _ := http.Post(server.URL, "application/json", addBody())
174 if want, have := http.StatusTeapot, resp.StatusCode; want != have {
175 t.Errorf("want %d, have %d", want, have)
176 }
177 }
178
179 func TestCanRejectNonPostRequest(t *testing.T) {
180 ecm := jsonrpc.EndpointCodecMap{}
181 handler := jsonrpc.NewServer(ecm)
182 server := httptest.NewServer(handler)
183 defer server.Close()
184 resp, _ := http.Get(server.URL)
185 if want, have := http.StatusMethodNotAllowed, resp.StatusCode; want != have {
186 t.Errorf("want %d, have %d", want, have)
187 }
188 }
189
190 func TestCanRejectInvalidJSON(t *testing.T) {
191 ecm := jsonrpc.EndpointCodecMap{}
192 handler := jsonrpc.NewServer(ecm)
193 server := httptest.NewServer(handler)
194 defer server.Close()
195 resp, _ := http.Post(server.URL, "application/json", body("clearlynotjson"))
196 if want, have := http.StatusOK, resp.StatusCode; want != have {
197 t.Errorf("want %d, have %d", want, have)
198 }
199 buf, _ := ioutil.ReadAll(resp.Body)
200 expectErrorCode(t, jsonrpc.ParseError, buf)
201 expectNilRequestID(t, buf)
202 }
203
204 func TestServerUnregisteredMethod(t *testing.T) {
205 ecm := jsonrpc.EndpointCodecMap{}
206 handler := jsonrpc.NewServer(ecm)
207 server := httptest.NewServer(handler)
208 defer server.Close()
209 resp, _ := http.Post(server.URL, "application/json", addBody())
210 if want, have := http.StatusOK, resp.StatusCode; want != have {
211 t.Errorf("want %d, have %d", want, have)
212 }
213 buf, _ := ioutil.ReadAll(resp.Body)
214 expectErrorCode(t, jsonrpc.MethodNotFoundError, buf)
215 }
216
217 func TestServerHappyPath(t *testing.T) {
218 step, response := testServer(t)
219 step()
220 resp := <-response
221 defer resp.Body.Close()
222 buf, _ := ioutil.ReadAll(resp.Body)
223 if want, have := http.StatusOK, resp.StatusCode; want != have {
224 t.Errorf("want %d, have %d (%s)", want, have, buf)
225 }
226 r, err := unmarshalResponse(buf)
227 if err != nil {
228 t.Fatalf("Can't decode response. err=%s, body=%s", err, buf)
229 }
230 if r.JSONRPC != jsonrpc.Version {
231 t.Fatalf("JSONRPC Version: want=%s, got=%s", jsonrpc.Version, r.JSONRPC)
232 }
233 if r.Error != nil {
234 t.Fatalf("Unxpected error on response: %s", buf)
235 }
236 }
237
238 func TestMultipleServerBeforeCodec(t *testing.T) {
239 var done = make(chan struct{})
240 ecm := jsonrpc.EndpointCodecMap{
241 "add": jsonrpc.EndpointCodec{
242 Endpoint: endpoint.Nop,
243 Decode: nopDecoder,
244 Encode: nopEncoder,
245 },
246 }
247 handler := jsonrpc.NewServer(
248 ecm,
249 jsonrpc.ServerBeforeCodec(func(ctx context.Context, r *http.Request, req jsonrpc.Request) context.Context {
250 ctx = context.WithValue(ctx, "one", 1)
251
252 return ctx
253 }),
254 jsonrpc.ServerBeforeCodec(func(ctx context.Context, r *http.Request, req jsonrpc.Request) context.Context {
255 if _, ok := ctx.Value("one").(int); !ok {
256 t.Error("Value was not set properly when multiple ServerBeforeCodecs are used")
257 }
258
259 close(done)
260 return ctx
261 }),
262 )
263 server := httptest.NewServer(handler)
264 defer server.Close()
265 http.Post(server.URL, "application/json", addBody())
266
267 select {
268 case <-done:
269 case <-time.After(time.Second):
270 t.Fatal("timeout waiting for finalizer")
271 }
272 }
273
274 func TestMultipleServerBefore(t *testing.T) {
275 var done = make(chan struct{})
276 ecm := jsonrpc.EndpointCodecMap{
277 "add": jsonrpc.EndpointCodec{
278 Endpoint: endpoint.Nop,
279 Decode: nopDecoder,
280 Encode: nopEncoder,
281 },
282 }
283 handler := jsonrpc.NewServer(
284 ecm,
285 jsonrpc.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
286 ctx = context.WithValue(ctx, "one", 1)
287
288 return ctx
289 }),
290 jsonrpc.ServerBefore(func(ctx context.Context, r *http.Request) context.Context {
291 if _, ok := ctx.Value("one").(int); !ok {
292 t.Error("Value was not set properly when multiple ServerBefores are used")
293 }
294
295 close(done)
296 return ctx
297 }),
298 )
299 server := httptest.NewServer(handler)
300 defer server.Close()
301 http.Post(server.URL, "application/json", addBody())
302
303 select {
304 case <-done:
305 case <-time.After(time.Second):
306 t.Fatal("timeout waiting for finalizer")
307 }
308 }
309
310 func TestMultipleServerAfter(t *testing.T) {
311 var done = make(chan struct{})
312 ecm := jsonrpc.EndpointCodecMap{
313 "add": jsonrpc.EndpointCodec{
314 Endpoint: endpoint.Nop,
315 Decode: nopDecoder,
316 Encode: nopEncoder,
317 },
318 }
319 handler := jsonrpc.NewServer(
320 ecm,
321 jsonrpc.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context {
322 ctx = context.WithValue(ctx, "one", 1)
323
324 return ctx
325 }),
326 jsonrpc.ServerAfter(func(ctx context.Context, w http.ResponseWriter) context.Context {
327 if _, ok := ctx.Value("one").(int); !ok {
328 t.Error("Value was not set properly when multiple ServerAfters are used")
329 }
330
331 close(done)
332 return ctx
333 }),
334 )
335 server := httptest.NewServer(handler)
336 defer server.Close()
337 http.Post(server.URL, "application/json", addBody())
338
339 select {
340 case <-done:
341 case <-time.After(time.Second):
342 t.Fatal("timeout waiting for finalizer")
343 }
344 }
345
346 func TestCanFinalize(t *testing.T) {
347 var done = make(chan struct{})
348 var finalizerCalled bool
349 ecm := jsonrpc.EndpointCodecMap{
350 "add": jsonrpc.EndpointCodec{
351 Endpoint: endpoint.Nop,
352 Decode: nopDecoder,
353 Encode: nopEncoder,
354 },
355 }
356 handler := jsonrpc.NewServer(
357 ecm,
358 jsonrpc.ServerFinalizer(func(ctx context.Context, code int, req *http.Request) {
359 finalizerCalled = true
360 close(done)
361 }),
362 )
363 server := httptest.NewServer(handler)
364 defer server.Close()
365 http.Post(server.URL, "application/json", addBody())
366
367 select {
368 case <-done:
369 case <-time.After(time.Second):
370 t.Fatal("timeout waiting for finalizer")
371 }
372
373 if !finalizerCalled {
374 t.Fatal("Finalizer was not called.")
375 }
376 }
377
378 func testServer(t *testing.T) (step func(), resp <-chan *http.Response) {
379 var (
380 stepch = make(chan bool)
381 endpoint = func(ctx context.Context, request interface{}) (response interface{}, err error) {
382 <-stepch
383 return struct{}{}, nil
384 }
385 response = make(chan *http.Response)
386 ecm = jsonrpc.EndpointCodecMap{
387 "add": jsonrpc.EndpointCodec{
388 Endpoint: endpoint,
389 Decode: nopDecoder,
390 Encode: nopEncoder,
391 },
392 }
393 handler = jsonrpc.NewServer(ecm)
394 )
395 go func() {
396 server := httptest.NewServer(handler)
397 defer server.Close()
398 rb := strings.NewReader(`{"jsonrpc": "2.0", "method": "add", "params": [3, 2], "id": 1}`)
399 resp, err := http.Post(server.URL, "application/json", rb)
400 if err != nil {
401 t.Error(err)
402 return
403 }
404 response <- resp
405 }()
406 return func() { stepch <- true }, response
407 }
408
View as plain text