sr-jimson 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'sr/jimson'
3
+ require 'simplecov'
4
+
5
+ SPEC_URL = 'http://localhost:8999'
6
+
7
+ SimpleCov.start 'rails' if ENV['COVERAGE']
@@ -0,0 +1,191 @@
1
+ require 'spec_helper'
2
+
3
+ module Sr::Jimson
4
+ describe Client do
5
+ BOILERPLATE = {'jsonrpc' => '2.0', 'id' => 1}
6
+
7
+ before(:each) do
8
+ @resp_mock = double('http_response')
9
+ ClientHelper.stub(:make_id).and_return(1)
10
+ end
11
+
12
+ after(:each) do
13
+ end
14
+
15
+ describe "hidden methods" do
16
+ it "should reveal inspect" do
17
+ Client.new(SPEC_URL).inspect.should match /Sr::Jimson::Client/
18
+ end
19
+
20
+ it "should reveal to_s" do
21
+ Client.new(SPEC_URL).to_s.should match /Sr::Jimson::Client/
22
+ end
23
+ end
24
+
25
+ describe "#[]" do
26
+ before(:each) do
27
+ @client = Client.new(SPEC_URL)
28
+ end
29
+
30
+ context "when using a symbol to specify a namespace" do
31
+ it "sends the method prefixed with the namespace in the request" do
32
+ expected = MultiJson.encode({
33
+ 'jsonrpc' => '2.0',
34
+ 'method' => 'foo.sum',
35
+ 'params' => [1,2,3],
36
+ 'id' => 1
37
+ })
38
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
39
+ RestClient.should_receive(:post).with(SPEC_URL, expected, {:content_type => 'application/json'}).and_return(@resp_mock)
40
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
41
+ @client[:foo].sum(1, 2, 3).should == 42
42
+ end
43
+
44
+ context "when the namespace is nested" do
45
+ it "sends the method prefixed with the full namespace in the request" do
46
+ expected = MultiJson.encode({
47
+ 'jsonrpc' => '2.0',
48
+ 'method' => 'foo.bar.sum',
49
+ 'params' => [1,2,3],
50
+ 'id' => 1
51
+ })
52
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
53
+ RestClient.should_receive(:post).with(SPEC_URL, expected, {:content_type => 'application/json'}).and_return(@resp_mock)
54
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
55
+ @client[:foo][:bar].sum(1, 2, 3).should == 42
56
+ end
57
+ end
58
+ end
59
+
60
+ context "when sending positional arguments" do
61
+ it "sends a request with the correct method and args" do
62
+ expected = MultiJson.encode({
63
+ 'jsonrpc' => '2.0',
64
+ 'method' => 'foo',
65
+ 'params' => [1,2,3],
66
+ 'id' => 1
67
+ })
68
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
69
+ RestClient.should_receive(:post).with(SPEC_URL, expected, {:content_type => 'application/json'}).and_return(@resp_mock)
70
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
71
+ @client['foo', 1, 2, 3].should == 42
72
+ end
73
+
74
+ context "when one of the args is an array" do
75
+ it "sends a request with the correct method and args" do
76
+ expected = MultiJson.encode({
77
+ 'jsonrpc' => '2.0',
78
+ 'method' => 'foo',
79
+ 'params' => [[1,2],3],
80
+ 'id' => 1
81
+ })
82
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
83
+ RestClient.should_receive(:post).with(SPEC_URL, expected, {:content_type => 'application/json'}).and_return(@resp_mock)
84
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
85
+ @client['foo', [1, 2], 3].should == 42
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ describe "sending a single request" do
92
+ context "when using positional parameters" do
93
+ before(:each) do
94
+ @expected = MultiJson.encode({
95
+ 'jsonrpc' => '2.0',
96
+ 'method' => 'foo',
97
+ 'params' => [1,2,3],
98
+ 'id' => 1
99
+ })
100
+ end
101
+ it "sends a valid JSON-RPC request and returns the result" do
102
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
103
+ RestClient.should_receive(:post).with(SPEC_URL, @expected, {:content_type => 'application/json'}).and_return(@resp_mock)
104
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
105
+ client = Client.new(SPEC_URL)
106
+ client.foo(1,2,3).should == 42
107
+ end
108
+
109
+ it "sends a valid JSON-RPC request with custom options" do
110
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
111
+ RestClient.should_receive(:post).with(SPEC_URL, @expected, {:content_type => 'application/json', :timeout => 10000}).and_return(@resp_mock)
112
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
113
+ client = Client.new(SPEC_URL, :timeout => 10000)
114
+ client.foo(1,2,3).should == 42
115
+ end
116
+ end
117
+
118
+ context "when one of the parameters is an array" do
119
+ it "sends a correct JSON-RPC request (array is preserved) and returns the result" do
120
+ expected = MultiJson.encode({
121
+ 'jsonrpc' => '2.0',
122
+ 'method' => 'foo',
123
+ 'params' => [[1,2],3],
124
+ 'id' => 1
125
+ })
126
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
127
+ RestClient.should_receive(:post).with(SPEC_URL, expected, {:content_type => 'application/json'}).and_return(@resp_mock)
128
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
129
+ client = Client.new(SPEC_URL)
130
+ client.foo([1,2],3).should == 42
131
+ end
132
+ end
133
+ end
134
+
135
+ describe "sending a batch request" do
136
+ it "sends a valid JSON-RPC batch request and puts the results in the response objects" do
137
+ batch = MultiJson.encode([
138
+ {"jsonrpc" => "2.0", "method" => "sum", "params" => [1,2,4], "id" => "1"},
139
+ {"jsonrpc" => "2.0", "method" => "subtract", "params" => [42,23], "id" => "2"},
140
+ {"jsonrpc" => "2.0", "method" => "foo_get", "params" => [{"name" => "myself"}], "id" => "5"},
141
+ {"jsonrpc" => "2.0", "method" => "get_data", "id" => "9"}
142
+ ])
143
+
144
+ response = MultiJson.encode([
145
+ {"jsonrpc" => "2.0", "result" => 7, "id" => "1"},
146
+ {"jsonrpc" => "2.0", "result" => 19, "id" => "2"},
147
+ {"jsonrpc" => "2.0", "error" => {"code" => -32601, "message" => "Method not found."}, "id" => "5"},
148
+ {"jsonrpc" => "2.0", "result" => ["hello", 5], "id" => "9"}
149
+ ])
150
+
151
+ ClientHelper.stub(:make_id).and_return('1', '2', '5', '9')
152
+ RestClient.should_receive(:post).with(SPEC_URL, batch, {:content_type => 'application/json'}).and_return(@resp_mock)
153
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
154
+ client = Client.new(SPEC_URL)
155
+
156
+ sum = subtract = foo = data = nil
157
+ Sr::Jimson::Client.batch(client) do |batch|
158
+ sum = batch.sum(1,2,4)
159
+ subtract = batch.subtract(42,23)
160
+ foo = batch.foo_get('name' => 'myself')
161
+ data = batch.get_data
162
+ end
163
+
164
+ sum.succeeded?.should be_true
165
+ sum.is_error?.should be_false
166
+ sum.result.should == 7
167
+
168
+ subtract.result.should == 19
169
+
170
+ foo.is_error?.should be_true
171
+ foo.succeeded?.should be_false
172
+ foo.error['code'].should == -32601
173
+
174
+ data.result.should == ['hello', 5]
175
+ end
176
+ end
177
+
178
+ describe "error handling" do
179
+ context "when an error occurs in the Sr::Jimson::Client code" do
180
+ it "tags the raised exception with Sr::Jimson::Client::Error" do
181
+ client_helper = ClientHelper.new(SPEC_URL)
182
+ ClientHelper.stub(:new).and_return(client_helper)
183
+ client = Client.new(SPEC_URL)
184
+ client_helper.stub(:send_single_request).and_raise "intentional error"
185
+ lambda { client.foo }.should raise_error(Sr::Jimson::Client::Error)
186
+ end
187
+ end
188
+ end
189
+
190
+ end
191
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ module Sr::Jimson
4
+ describe Handler do
5
+
6
+ class FooHandler
7
+ extend Sr::Jimson::Handler
8
+
9
+ jimson_expose :to_s, :bye
10
+
11
+ jimson_exclude :hi, :bye
12
+
13
+ def hi
14
+ 'hi'
15
+ end
16
+
17
+ def bye
18
+ 'bye'
19
+ end
20
+
21
+ def to_s
22
+ 'foo'
23
+ end
24
+
25
+ def so_exposed
26
+ "I'm so exposed!"
27
+ end
28
+ end
29
+
30
+ let(:foo) { FooHandler.new }
31
+
32
+ describe "#jimson_expose" do
33
+ it "exposes a method even if it was defined on Object" do
34
+ foo.class.jimson_exposed_methods.should include('to_s')
35
+ end
36
+ end
37
+
38
+ describe "#jimson_exclude" do
39
+ context "when a method was not explicitly exposed" do
40
+ it "excludes the method" do
41
+ foo.class.jimson_exposed_methods.should_not include('hi')
42
+ end
43
+ end
44
+ context "when a method was explicitly exposed" do
45
+ it "does not exclude the method" do
46
+ foo.class.jimson_exposed_methods.should include('bye')
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "#jimson_exposed_methods" do
52
+ it "doesn't include methods defined on Object" do
53
+ foo.class.jimson_exposed_methods.should_not include('object_id')
54
+ end
55
+ it "includes methods defined on the extending class but not on Object" do
56
+ foo.class.jimson_exposed_methods.should include('so_exposed')
57
+ end
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+
3
+ module Sr::Jimson
4
+ describe Router do
5
+
6
+ let(:router) { Router.new }
7
+
8
+ class RouterFooHandler
9
+ extend Sr::Jimson::Handler
10
+
11
+ def hi
12
+ 'hi'
13
+ end
14
+ end
15
+
16
+ class RouterBarHandler
17
+ extend Sr::Jimson::Handler
18
+
19
+ def bye
20
+ 'bye'
21
+ end
22
+ end
23
+
24
+ class RouterBazHandler
25
+ extend Sr::Jimson::Handler
26
+
27
+ def meh
28
+ 'mehkayla'
29
+ end
30
+ end
31
+
32
+
33
+ describe '#draw' do
34
+ context 'when given non-nested namespaces' do
35
+ it 'takes a block with a DSL to set the root and namespaces' do
36
+ router.draw do
37
+ root RouterFooHandler
38
+ namespace 'ns', RouterBarHandler
39
+ end
40
+
41
+ router.handler_for_method('hi').should be_a(RouterFooHandler)
42
+ router.handler_for_method('ns.hi').should be_a(RouterBarHandler)
43
+ end
44
+ end
45
+
46
+ context 'when given nested namespaces' do
47
+ it 'takes a block with a DSL to set the root and namespaces' do
48
+ router.draw do
49
+ root RouterFooHandler
50
+ namespace 'ns1' do
51
+ root RouterBazHandler
52
+ namespace 'ns2', RouterBarHandler
53
+ end
54
+ end
55
+
56
+ router.handler_for_method('hi').should be_a(RouterFooHandler)
57
+ router.handler_for_method('ns1.hi').should be_a(RouterBazHandler)
58
+ router.handler_for_method('ns1.ns2.hi').should be_a(RouterBarHandler)
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '#jimson_methods' do
64
+ it 'returns an array of namespaced method names from all registered handlers' do
65
+ router.draw do
66
+ root RouterFooHandler
67
+ namespace 'foo', RouterBarHandler
68
+ end
69
+
70
+ router.jimson_methods.sort.should == ['hi', 'foo.bye'].sort
71
+ end
72
+ end
73
+
74
+ end
75
+ end
@@ -0,0 +1,466 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+
4
+ module Sr::Jimson
5
+ describe Server do
6
+ include Rack::Test::Methods
7
+
8
+ class TestHandler
9
+ extend Sr::Jimson::Handler
10
+
11
+ def subtract(a, b = nil)
12
+ if a.is_a?(Hash)
13
+ return a['minuend'] - a['subtrahend']
14
+ else
15
+ return a - b
16
+ end
17
+ end
18
+
19
+ def sum(a,b,c)
20
+ a + b + c
21
+ end
22
+
23
+ def car(array)
24
+ array.first
25
+ end
26
+
27
+ def notify_hello(*args)
28
+ # notification, doesn't do anything
29
+ end
30
+
31
+ def update(*args)
32
+ # notification, doesn't do anything
33
+ end
34
+
35
+ def get_data
36
+ ['hello', 5]
37
+ end
38
+
39
+ def ugly_method
40
+ raise RuntimeError
41
+ end
42
+ end
43
+
44
+ class OtherHandler
45
+ extend Sr::Jimson::Handler
46
+
47
+ def multiply(a,b)
48
+ a * b
49
+ end
50
+ end
51
+
52
+ INVALID_RESPONSE_EXPECTATION = {
53
+ 'jsonrpc' => '2.0',
54
+ 'error' => {
55
+ 'code' => -32600,
56
+ 'message' => 'The JSON sent is not a valid Request object.'
57
+ },
58
+ 'id' => nil
59
+ }
60
+ let(:router) do
61
+ router = Router.new.draw do
62
+ root TestHandler.new
63
+ namespace 'other', OtherHandler.new
64
+ end
65
+ end
66
+
67
+ let(:app) do
68
+ Server.new(router, :environment => "production")
69
+ end
70
+
71
+ def post_json(hash)
72
+ post '/', MultiJson.encode(hash), {'Content-Type' => 'application/json'}
73
+ end
74
+
75
+ before(:each) do
76
+ @url = SPEC_URL
77
+ end
78
+
79
+ it "exposes the given options" do
80
+ app.opts.should == { :environment => "production" }
81
+ end
82
+
83
+ describe "receiving a request with positional parameters" do
84
+ context "when no errors occur" do
85
+ it "returns a response with 'result'" do
86
+ req = {
87
+ 'jsonrpc' => '2.0',
88
+ 'method' => 'subtract',
89
+ 'params' => [24, 20],
90
+ 'id' => 1
91
+ }
92
+ post_json(req)
93
+
94
+ last_response.should be_ok
95
+ resp = MultiJson.decode(last_response.body)
96
+ resp.should == {
97
+ 'jsonrpc' => '2.0',
98
+ 'result' => 4,
99
+ 'id' => 1
100
+ }
101
+ end
102
+
103
+ it "handles an array in the parameters" do
104
+ req = {
105
+ 'jsonrpc' => '2.0',
106
+ 'method' => 'car',
107
+ 'params' => [['a', 'b']],
108
+ 'id' => 1
109
+ }
110
+ post_json(req)
111
+
112
+ last_response.should be_ok
113
+ resp = MultiJson.decode(last_response.body)
114
+ resp.should == {
115
+ 'jsonrpc' => '2.0',
116
+ 'result' => 'a',
117
+ 'id' => 1
118
+ }
119
+ end
120
+
121
+ it "handles bignums" do
122
+ req = {
123
+ 'jsonrpc' => '2.0',
124
+ 'method' => 'subtract',
125
+ 'params' => [24, 20],
126
+ 'id' => 123456789_123456789_123456789
127
+ }
128
+ post_json(req)
129
+
130
+ last_response.should be_ok
131
+ resp = MultiJson.decode(last_response.body)
132
+ resp.should == {
133
+ 'jsonrpc' => '2.0',
134
+ 'result' => 4,
135
+ 'id' => 123456789_123456789_123456789
136
+ }
137
+ end
138
+ end
139
+ end
140
+
141
+ describe "receiving a request with named parameters" do
142
+ context "when no errors occur" do
143
+ it "returns a response with 'result'" do
144
+ req = {
145
+ 'jsonrpc' => '2.0',
146
+ 'method' => 'subtract',
147
+ 'params' => {'subtrahend'=> 20, 'minuend' => 24},
148
+ 'id' => 1
149
+ }
150
+ post_json(req)
151
+
152
+ last_response.should be_ok
153
+ resp = MultiJson.decode(last_response.body)
154
+ resp.should == {
155
+ 'jsonrpc' => '2.0',
156
+ 'result' => 4,
157
+ 'id' => 1
158
+ }
159
+ end
160
+ end
161
+ end
162
+
163
+ describe "receiving a notification" do
164
+ context "when no errors occur" do
165
+ it "returns no response" do
166
+ req = {
167
+ 'jsonrpc' => '2.0',
168
+ 'method' => 'update',
169
+ 'params' => [1,2,3,4,5]
170
+ }
171
+ post_json(req)
172
+ last_response.body.should be_empty
173
+ end
174
+ end
175
+ end
176
+
177
+ describe "receiving a call for a non-existent method" do
178
+ it "returns an error response" do
179
+ req = {
180
+ 'jsonrpc' => '2.0',
181
+ 'method' => 'foobar',
182
+ 'id' => 1
183
+ }
184
+ post_json(req)
185
+
186
+ resp = MultiJson.decode(last_response.body)
187
+ resp.should == {
188
+ 'jsonrpc' => '2.0',
189
+ 'error' => {
190
+ 'code' => -32601,
191
+ 'message' => "Method 'foobar' not found."
192
+ },
193
+ 'id' => 1
194
+ }
195
+ end
196
+ end
197
+
198
+ describe "receiving a call for a method which exists but is not exposed" do
199
+ it "returns an error response" do
200
+ req = {
201
+ 'jsonrpc' => '2.0',
202
+ 'method' => 'object_id',
203
+ 'id' => 1
204
+ }
205
+ post_json(req)
206
+
207
+ resp = MultiJson.decode(last_response.body)
208
+ resp.should == {
209
+ 'jsonrpc' => '2.0',
210
+ 'error' => {
211
+ 'code' => -32601,
212
+ 'message' => "Method 'object_id' not found."
213
+ },
214
+ 'id' => 1
215
+ }
216
+ end
217
+ end
218
+
219
+ describe "receiving a call with the wrong number of params" do
220
+ it "returns an error response" do
221
+ req = {
222
+ 'jsonrpc' => '2.0',
223
+ 'method' => 'subtract',
224
+ 'params' => [1,2,3],
225
+ 'id' => 1
226
+ }
227
+ post_json(req)
228
+
229
+ resp = MultiJson.decode(last_response.body)
230
+ resp.should == {
231
+ 'jsonrpc' => '2.0',
232
+ 'error' => {
233
+ 'code' => -32602,
234
+ 'message' => 'Invalid method parameter(s).'
235
+ },
236
+ 'id' => 1
237
+ }
238
+ end
239
+ end
240
+
241
+ describe "receiving a call for ugly method" do
242
+ context "by default" do
243
+ it "returns only global error without stack trace" do
244
+ req = {
245
+ 'jsonrpc' => '2.0',
246
+ 'method' => 'ugly_method',
247
+ 'id' => 1
248
+ }
249
+ post_json(req)
250
+
251
+ resp = MultiJson.decode(last_response.body)
252
+ resp.should == {
253
+ 'jsonrpc' => '2.0',
254
+ 'error' => {
255
+ 'code' => -32099,
256
+ 'message' => 'Server application error'
257
+ },
258
+ 'id' => 1
259
+ }
260
+ end
261
+ end
262
+
263
+ context "with 'show_errors' enabled" do
264
+ it "returns an error name and first line of the stack trace" do
265
+ req = {
266
+ 'jsonrpc' => '2.0',
267
+ 'method' => 'ugly_method',
268
+ 'id' => 1
269
+ }
270
+
271
+ app = Server.new(router, :environment => "production", :show_errors => true)
272
+
273
+ # have to make a new Rack::Test browser since this server is different than the normal one
274
+ browser = Rack::Test::Session.new(Rack::MockSession.new(app))
275
+ browser.post '/', MultiJson.encode(req), {'Content-Type' => 'application/json'}
276
+
277
+ resp = MultiJson.decode(browser.last_response.body)
278
+ resp.should == {
279
+ 'jsonrpc' => '2.0',
280
+ 'error' => {
281
+ 'code' => -32099,
282
+ 'message' => "Server application error: RuntimeError at #{__FILE__}:40:in `ugly_method'"
283
+ },
284
+ 'id' => 1
285
+ }
286
+ end
287
+ end
288
+ end
289
+
290
+ describe "receiving invalid JSON" do
291
+ it "returns an error response" do
292
+ req = MultiJson.encode({
293
+ 'jsonrpc' => '2.0',
294
+ 'method' => 'foobar',
295
+ 'id' => 1
296
+ })
297
+ req += '}' # make the json invalid
298
+ post '/', req, {'Content-type' => 'application/json'}
299
+
300
+ resp = MultiJson.decode(last_response.body)
301
+ resp.should == {
302
+ 'jsonrpc' => '2.0',
303
+ 'error' => {
304
+ 'code' => -32700,
305
+ 'message' => 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.'
306
+ },
307
+ 'id' => nil
308
+ }
309
+ end
310
+ end
311
+
312
+ describe "receiving an invalid request" do
313
+ context "when the request is not a batch" do
314
+ it "returns an error response" do
315
+ req = {
316
+ 'jsonrpc' => '2.0',
317
+ 'method' => 1 # method as int is invalid
318
+ }
319
+ post_json(req)
320
+ resp = MultiJson.decode(last_response.body)
321
+ resp.should == INVALID_RESPONSE_EXPECTATION
322
+ end
323
+ end
324
+
325
+ context "when the request is an empty batch" do
326
+ it "returns an error response" do
327
+ req = []
328
+ post_json(req)
329
+ resp = MultiJson.decode(last_response.body)
330
+ resp.should == INVALID_RESPONSE_EXPECTATION
331
+ end
332
+ end
333
+
334
+ context "when the request is an invalid batch" do
335
+ it "returns an error response" do
336
+ req = [1,2]
337
+ post_json(req)
338
+ resp = MultiJson.decode(last_response.body)
339
+ resp.should == [INVALID_RESPONSE_EXPECTATION, INVALID_RESPONSE_EXPECTATION]
340
+ end
341
+ end
342
+ end
343
+
344
+ describe "receiving a valid batch request" do
345
+ context "when not all requests are notifications" do
346
+ it "returns an array of responses" do
347
+ reqs = [
348
+ {'jsonrpc' => '2.0', 'method' => 'sum', 'params' => [1,2,4], 'id' => '1'},
349
+ {'jsonrpc' => '2.0', 'method' => 'notify_hello', 'params' => [7]},
350
+ {'jsonrpc' => '2.0', 'method' => 'subtract', 'params' => [42,23], 'id' => '2'},
351
+ {'foo' => 'boo'},
352
+ {'jsonrpc' => '2.0', 'method' => 'foo.get', 'params' => {'name' => 'myself'}, 'id' => '5'},
353
+ {'jsonrpc' => '2.0', 'method' => 'get_data', 'id' => '9'}
354
+ ]
355
+ post_json(reqs)
356
+ resp = MultiJson.decode(last_response.body)
357
+ resp.should == [
358
+ {'jsonrpc' => '2.0', 'result' => 7, 'id' => '1'},
359
+ {'jsonrpc' => '2.0', 'result' => 19, 'id' => '2'},
360
+ {'jsonrpc' => '2.0', 'error' => {'code' => -32600, 'message' => 'The JSON sent is not a valid Request object.'}, 'id' => nil},
361
+ {'jsonrpc' => '2.0', 'error' => {'code' => -32601, 'message' => "Method 'foo.get' not found."}, 'id' => '5'},
362
+ {'jsonrpc' => '2.0', 'result' => ['hello', 5], 'id' => '9'}
363
+ ]
364
+ end
365
+ end
366
+
367
+ context "when all the requests are notifications" do
368
+ it "returns no response" do
369
+ req = [
370
+ {
371
+ 'jsonrpc' => '2.0',
372
+ 'method' => 'update',
373
+ 'params' => [1,2,3,4,5]
374
+ },
375
+ {
376
+ 'jsonrpc' => '2.0',
377
+ 'method' => 'update',
378
+ 'params' => [1,2,3,4,5]
379
+ }
380
+ ]
381
+ post_json(req)
382
+ last_response.body.should be_empty
383
+ end
384
+ end
385
+ end
386
+
387
+ describe "receiving a 'system.' request" do
388
+ context "when the request is 'isAlive'" do
389
+ it "returns response 'true'" do
390
+ req = {
391
+ 'jsonrpc' => '2.0',
392
+ 'method' => 'system.isAlive',
393
+ 'params' => [],
394
+ 'id' => 1
395
+ }
396
+ post_json(req)
397
+
398
+ last_response.should be_ok
399
+ resp = MultiJson.decode(last_response.body)
400
+ resp.should == {
401
+ 'jsonrpc' => '2.0',
402
+ 'result' => true,
403
+ 'id' => 1
404
+ }
405
+ end
406
+ end
407
+ context "when the request is 'system.listMethods'" do
408
+ it "returns response with all jimson_exposed_methods on the handler(s) as strings" do
409
+ req = {
410
+ 'jsonrpc' => '2.0',
411
+ 'method' => 'system.listMethods',
412
+ 'params' => [],
413
+ 'id' => 1
414
+ }
415
+ post_json(req)
416
+
417
+ last_response.should be_ok
418
+ resp = MultiJson.decode(last_response.body)
419
+ resp['jsonrpc'].should == '2.0'
420
+ resp['id'].should == 1
421
+ expected = ['get_data', 'notify_hello', 'subtract', 'sum', 'car', 'ugly_method', 'update', 'system.isAlive', 'system.listMethods', 'other.multiply']
422
+ (resp['result'] - expected).should == []
423
+ end
424
+ end
425
+ end
426
+
427
+ describe ".with_routes" do
428
+ it "creates a server with a router by passing the block to Router#draw" do
429
+ app = Server.with_routes do
430
+ root TestHandler.new
431
+ namespace 'foo', OtherHandler.new
432
+ end
433
+
434
+ # have to make a new Rack::Test browser since this server is different than the normal one
435
+ browser = Rack::Test::Session.new(Rack::MockSession.new(app))
436
+
437
+ req = {
438
+ 'jsonrpc' => '2.0',
439
+ 'method' => 'foo.multiply',
440
+ 'params' => [2, 3],
441
+ 'id' => 1
442
+ }
443
+ browser.post '/', MultiJson.encode(req), {'Content-Type' => 'application/json'}
444
+
445
+ browser.last_response.should be_ok
446
+ resp = MultiJson.decode(browser.last_response.body)
447
+ resp.should == {
448
+ 'jsonrpc' => '2.0',
449
+ 'result' => 6,
450
+ 'id' => 1
451
+ }
452
+ end
453
+
454
+ context "when opts are given" do
455
+ it "passes the opts to the new server" do
456
+ app = Server.with_routes(:show_errors => true) do
457
+ root TestHandler.new
458
+ namespace 'foo', OtherHandler.new
459
+ end
460
+
461
+ app.show_errors.should be_true
462
+ end
463
+ end
464
+ end
465
+ end
466
+ end