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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.rdoc +92 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +17 -0
- data/README.md +29 -0
- data/Rakefile +21 -0
- data/VERSION +1 -0
- data/lib/sr/jimson.rb +10 -0
- data/lib/sr/jimson/blankslate.rb +132 -0
- data/lib/sr/jimson/client.rb +180 -0
- data/lib/sr/jimson/client/error.rb +23 -0
- data/lib/sr/jimson/handler.rb +25 -0
- data/lib/sr/jimson/request.rb +25 -0
- data/lib/sr/jimson/response.rb +30 -0
- data/lib/sr/jimson/router.rb +25 -0
- data/lib/sr/jimson/router/map.rb +75 -0
- data/lib/sr/jimson/server.rb +224 -0
- data/lib/sr/jimson/server/error.rb +66 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/sr/jimson/client_spec.rb +191 -0
- data/spec/sr/jimson/handler_spec.rb +61 -0
- data/spec/sr/jimson/router_spec.rb +75 -0
- data/spec/sr/jimson/server_spec.rb +466 -0
- data/spec/sr/jimson_spec.rb +7 -0
- data/sr-jimson.gemspec +31 -0
- metadata +206 -0
data/spec/spec_helper.rb
ADDED
@@ -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
|