jimson-reloaded 0.12.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,3 @@
1
+ module Jimson
2
+ VERSION = '0.12.0'
3
+ end
@@ -0,0 +1,239 @@
1
+ require 'spec_helper'
2
+
3
+ module 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 /Jimson::Client/
18
+ end
19
+
20
+ it "should reveal to_s" do
21
+ Client.new(SPEC_URL).to_s.should match /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::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {: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::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {: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::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {: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::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {: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::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: @expected, headers: {: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::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: @expected, headers: {: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
+
117
+ it "sends a valid JSON-RPC request with custom content_type" do
118
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
119
+ RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: @expected, headers: {:content_type => 'application/json-rpc', :timeout => 10000}).and_return(@resp_mock)
120
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
121
+ client = Client.new(SPEC_URL, :timeout => 10000, :content_type => 'application/json-rpc')
122
+ client.foo(1,2,3).should == 42
123
+ end
124
+
125
+ it "adds RestClient::Request parameters if given" do
126
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
127
+ RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: @expected, headers: {:content_type => 'application/json-rpc', :timeout => 10000}, ssl_ca_file: 'myca.pem').and_return(@resp_mock)
128
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
129
+ client = Client.new(SPEC_URL, {:timeout => 10000, :content_type => 'application/json-rpc'}, '', {ssl_ca_file: 'myca.pem'})
130
+ client.foo(1,2,3).should == 42
131
+ end
132
+ end
133
+
134
+ context "when one of the parameters is an array" do
135
+ it "sends a correct JSON-RPC request (array is preserved) and returns the result" do
136
+ expected = MultiJson.encode({
137
+ 'jsonrpc' => '2.0',
138
+ 'method' => 'foo',
139
+ 'params' => [[1,2],3],
140
+ 'id' => 1
141
+ })
142
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
143
+ RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
144
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
145
+ client = Client.new(SPEC_URL)
146
+ client.foo([1,2],3).should == 42
147
+ end
148
+ end
149
+
150
+ context "when one of the parameters is a hash" do
151
+ it "sends a correct JSON-RPC request (array is preserved with hash) and returns the results" do
152
+ expected = MultiJson.encode({
153
+ 'jsonrpc' => '2.0',
154
+ 'method' => 'foo',
155
+ 'params' => [1,{'bar' => 'baz'},3],
156
+ 'id' => 1
157
+ })
158
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
159
+ RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
160
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
161
+ client = Client.new(SPEC_URL)
162
+ client.foo(1, {'bar' => 'baz'}, 3).should == 42
163
+ end
164
+ end
165
+
166
+ context "when using named parameters" do
167
+ it "sends a correct JSON-RPC request (named parameters are preserved) and returns the result" do
168
+ expected = MultiJson.encode({
169
+ 'jsonrpc' => '2.0',
170
+ 'method' => 'foo',
171
+ 'params' => {'bar' => 'baz'},
172
+ 'id' => 1
173
+ })
174
+ response = MultiJson.encode(BOILERPLATE.merge({'result' => 42}))
175
+ RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: expected, headers: {:content_type => 'application/json'}).and_return(@resp_mock)
176
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
177
+ client = Client.new(SPEC_URL)
178
+ client.foo({'bar' => 'baz'}).should == 42
179
+ end
180
+ end
181
+ end
182
+
183
+ describe "sending a batch request" do
184
+ it "sends a valid JSON-RPC batch request and puts the results in the response objects" do
185
+ batch = MultiJson.encode([
186
+ {"jsonrpc" => "2.0", "method" => "sum", "params" => [1,2,4], "id" => "1"},
187
+ {"jsonrpc" => "2.0", "method" => "subtract", "params" => [42,23], "id" => "2"},
188
+ {"jsonrpc" => "2.0", "method" => "foo_get", "params" => [{"name" => "myself"}], "id" => "5"},
189
+ {"jsonrpc" => "2.0", "method" => "get_data", "id" => "9"}
190
+ ])
191
+
192
+ response = MultiJson.encode([
193
+ {"jsonrpc" => "2.0", "result" => 7, "id" => "1"},
194
+ {"jsonrpc" => "2.0", "result" => 19, "id" => "2"},
195
+ {"jsonrpc" => "2.0", "error" => {"code" => -32601, "message" => "Method not found."}, "id" => "5"},
196
+ {"jsonrpc" => "2.0", "result" => ["hello", 5], "id" => "9"}
197
+ ])
198
+
199
+ ClientHelper.stub(:make_id).and_return('1', '2', '5', '9')
200
+ RestClient::Request.should_receive(:execute).with(method: :post, url: SPEC_URL, payload: batch, headers: {:content_type => 'application/json'}, ssl_ca_file: 'myca.pem').and_return(@resp_mock)
201
+ @resp_mock.should_receive(:body).at_least(:once).and_return(response)
202
+ client = Client.new(SPEC_URL, {}, '', {ssl_ca_file: 'myca.pem'})
203
+
204
+ sum = subtract = foo = data = nil
205
+ Jimson::Client.batch(client) do |batch|
206
+ sum = batch.sum(1,2,4)
207
+ subtract = batch.subtract(42,23)
208
+ foo = batch.foo_get('name' => 'myself')
209
+ data = batch.get_data
210
+ end
211
+
212
+ sum.succeeded?.should be true
213
+ sum.is_error?.should be false
214
+ sum.result.should == 7
215
+
216
+ subtract.result.should == 19
217
+
218
+ foo.is_error?.should be true
219
+ foo.succeeded?.should be false
220
+ foo.error['code'].should == -32601
221
+
222
+ data.result.should == ['hello', 5]
223
+ end
224
+ end
225
+
226
+ describe "error handling" do
227
+ context "when an error occurs in the Jimson::Client code" do
228
+ it "tags the raised exception with Jimson::Client::Error" do
229
+ client_helper = ClientHelper.new(SPEC_URL)
230
+ ClientHelper.stub(:new).and_return(client_helper)
231
+ client = Client.new(SPEC_URL)
232
+ client_helper.stub(:send_single_request).and_raise "intentional error"
233
+ lambda { client.foo }.should raise_error(Jimson::Client::Error)
234
+ end
235
+ end
236
+ end
237
+
238
+ end
239
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ module Jimson
4
+ describe Handler do
5
+
6
+ class FooHandler
7
+ extend 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
62
+
@@ -0,0 +1,97 @@
1
+ require 'spec_helper'
2
+
3
+ module Jimson
4
+ describe Router do
5
+
6
+ let(:router) { Router.new(opts) }
7
+ let(:opts) { {} }
8
+
9
+ class RouterFooHandler
10
+ extend Jimson::Handler
11
+
12
+ def hi
13
+ 'hi'
14
+ end
15
+ end
16
+
17
+ class RouterBarHandler
18
+ extend Jimson::Handler
19
+
20
+ def bye
21
+ 'bye'
22
+ end
23
+ end
24
+
25
+ class RouterBazHandler
26
+ extend Jimson::Handler
27
+
28
+ def meh
29
+ 'mehkayla'
30
+ end
31
+ end
32
+
33
+
34
+ describe '#draw' do
35
+ context 'when given non-nested namespaces' do
36
+ it 'takes a block with a DSL to set the root and namespaces' do
37
+ router.draw do
38
+ root RouterFooHandler
39
+ namespace 'ns', RouterBarHandler
40
+ end
41
+
42
+ router.handler_for_method('hi').should be_a(RouterFooHandler)
43
+ router.handler_for_method('ns.hi').should be_a(RouterBarHandler)
44
+ end
45
+ end
46
+
47
+ context 'when given nested namespaces' do
48
+ before {
49
+ router.draw do
50
+ root RouterFooHandler
51
+ namespace 'ns1' do
52
+ root RouterBazHandler
53
+ namespace 'ns2', RouterBarHandler
54
+ end
55
+ end
56
+ }
57
+ context 'default ns_sep' do
58
+ it 'takes a block with a DSL to set the root and namespaces' do
59
+ router.handler_for_method('hi').should be_a(RouterFooHandler)
60
+ router.handler_for_method('ns1.hi').should be_a(RouterBazHandler)
61
+ router.handler_for_method('ns1.ns2.hi').should be_a(RouterBarHandler)
62
+ end
63
+ end
64
+ context 'custom ns_sep' do
65
+ let(:opts) { {ns_sep: '::'} }
66
+ it 'takes a block with a DSL to set the root and namespaces' do
67
+ router.handler_for_method('hi').should be_a(RouterFooHandler)
68
+ router.handler_for_method('ns1::hi').should be_a(RouterBazHandler)
69
+ router.handler_for_method('ns1::ns2::hi').should be_a(RouterBarHandler)
70
+ end
71
+ end
72
+
73
+ end
74
+ end
75
+
76
+ describe '#jimson_methods' do
77
+ before {
78
+ router.draw do
79
+ root RouterFooHandler
80
+ namespace 'foo', RouterBarHandler
81
+ end
82
+ }
83
+ context 'default ns_sep' do
84
+ it 'returns an array of namespaced method names from all registered handlers' do
85
+ router.jimson_methods.sort.should == ['hi', 'foo.bye'].sort
86
+ end
87
+ end
88
+ context 'custom ns_sep' do
89
+ let(:opts) { {ns_sep: '::'} }
90
+ it 'returns an array of namespaced method names from all registered handlers' do
91
+ router.jimson_methods.sort.should == ['hi', 'foo::bye'].sort
92
+ end
93
+ end
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,466 @@
1
+ require 'spec_helper'
2
+ require 'rack/test'
3
+
4
+ module Jimson
5
+ describe Server do
6
+ include Rack::Test::Methods
7
+
8
+ class TestHandler
9
+ extend 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 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 integer" 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