restfulness 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +342 -0
- data/Rakefile +1 -0
- data/example/Gemfile +5 -0
- data/example/README.md +42 -0
- data/example/app.rb +42 -0
- data/example/config.ru +5 -0
- data/lib/restfulness.rb +36 -0
- data/lib/restfulness/application.rb +83 -0
- data/lib/restfulness/dispatcher.rb +14 -0
- data/lib/restfulness/dispatchers/rack.rb +91 -0
- data/lib/restfulness/exceptions.rb +17 -0
- data/lib/restfulness/log_formatters/quiet_formatter.rb +7 -0
- data/lib/restfulness/log_formatters/verbose_formatter.rb +16 -0
- data/lib/restfulness/path.rb +46 -0
- data/lib/restfulness/request.rb +81 -0
- data/lib/restfulness/resource.rb +111 -0
- data/lib/restfulness/response.rb +57 -0
- data/lib/restfulness/route.rb +48 -0
- data/lib/restfulness/router.rb +27 -0
- data/lib/restfulness/statuses.rb +71 -0
- data/lib/restfulness/version.rb +3 -0
- data/restfulness.gemspec +29 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/unit/application_spec.rb +91 -0
- data/spec/unit/dispatcher_spec.rb +14 -0
- data/spec/unit/dispatchers/rack_spec.rb +34 -0
- data/spec/unit/exceptions_spec.rb +21 -0
- data/spec/unit/path_spec.rb +91 -0
- data/spec/unit/request_spec.rb +129 -0
- data/spec/unit/resource_spec.rb +245 -0
- data/spec/unit/response_spec.rb +65 -0
- data/spec/unit/route_spec.rb +160 -0
- data/spec/unit/router_spec.rb +103 -0
- metadata +189 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Restfulness::Dispatchers::Rack do
|
5
|
+
|
6
|
+
let :klass do
|
7
|
+
Restfulness::Dispatchers::Rack
|
8
|
+
end
|
9
|
+
|
10
|
+
let :app do
|
11
|
+
Class.new(Restfulness::Application).new
|
12
|
+
end
|
13
|
+
|
14
|
+
let :obj do
|
15
|
+
klass.new(app)
|
16
|
+
end
|
17
|
+
|
18
|
+
let :env do
|
19
|
+
{
|
20
|
+
'REQUEST_METHOD' => 'GET',
|
21
|
+
'SCRIPT_NAME' => 'projects',
|
22
|
+
'PATH_INFO' => 'projects',
|
23
|
+
'QUERY_STRING' => '',
|
24
|
+
'SERVER_NAME' => 'localhost',
|
25
|
+
'SERVER_PORT' => '3000',
|
26
|
+
'HTTP_CONTENT_TYPE' => 'application/json'
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#" do
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Restfulness::HTTPException do
|
4
|
+
|
5
|
+
describe "#initialize" do
|
6
|
+
it "should assign variables" do
|
7
|
+
obj = Restfulness::HTTPException.new(200, "payload", :message => 'foo', :headers => {})
|
8
|
+
obj.code.should eql(200)
|
9
|
+
obj.payload.should eql("payload")
|
10
|
+
obj.message.should eql('foo')
|
11
|
+
obj.headers.should eql({})
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should use status code for message if none provided" do
|
15
|
+
obj = Restfulness::HTTPException.new(200, "payload")
|
16
|
+
obj.message.should eql('OK')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Restfulness::Path do
|
4
|
+
|
5
|
+
class PathResource < Restfulness::Resource
|
6
|
+
end
|
7
|
+
|
8
|
+
let :klass do
|
9
|
+
Restfulness::Path
|
10
|
+
end
|
11
|
+
let :route_class do
|
12
|
+
Restfulness::Route
|
13
|
+
end
|
14
|
+
let :resource_class do
|
15
|
+
PathResource
|
16
|
+
end
|
17
|
+
let :simple_route do
|
18
|
+
route_class.new('project', resource_class)
|
19
|
+
end
|
20
|
+
let :complex_route do
|
21
|
+
route_class.new('project', :project_id, 'status', resource_class)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "#initialize" do
|
25
|
+
|
26
|
+
it "should assign route" do
|
27
|
+
obj = klass.new(simple_route, '/project')
|
28
|
+
obj.route.should eql(simple_route)
|
29
|
+
end
|
30
|
+
|
31
|
+
context "simple paths" do
|
32
|
+
it "should prepare basic path" do
|
33
|
+
obj = klass.new(simple_route, '/project')
|
34
|
+
obj.components.should eql(['project'])
|
35
|
+
obj.params[:id].should be_nil
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should prepare irregular path components" do
|
39
|
+
obj = klass.new(simple_route, '/project/')
|
40
|
+
obj.components.should eql(['project'])
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should include id" do
|
44
|
+
obj = klass.new(simple_route, '/project/12345')
|
45
|
+
obj.components.should eql(['project', '12345'])
|
46
|
+
obj.params[:id].should eql('12345')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
context "complex paths" do
|
52
|
+
it "should prepare path" do
|
53
|
+
obj = klass.new(complex_route, '/project/12345/status')
|
54
|
+
obj.components.should eql(['project', '12345', 'status'])
|
55
|
+
obj.params[:project_id].should eql('12345')
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should handle end id" do
|
59
|
+
obj = klass.new(complex_route, '/project/12345/status/23456')
|
60
|
+
obj.components.should eql(['project', '12345', 'status', '23456'])
|
61
|
+
obj.params[:project_id].should eql('12345')
|
62
|
+
obj.params[:id].should eql('23456')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#to_s" do
|
68
|
+
it "should provide simple string" do
|
69
|
+
obj = klass.new(complex_route, '/project/12345/status/23456')
|
70
|
+
obj.to_s.should eql('/project/12345/status/23456')
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#[]" do
|
75
|
+
let :obj do
|
76
|
+
obj = klass.new(complex_route, '/project/12345/status/23456')
|
77
|
+
end
|
78
|
+
it "should grant access to components by index" do
|
79
|
+
obj[0].should eql('project')
|
80
|
+
obj[1].should eql('12345')
|
81
|
+
obj[2].should eql('status')
|
82
|
+
obj[3].should eql('23456')
|
83
|
+
obj[4].should be_nil
|
84
|
+
end
|
85
|
+
it "should grant access to path parameters by symbol" do
|
86
|
+
obj[:project_id].should eql('12345')
|
87
|
+
obj[:id].should eql('23456')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Restfulness::Request do
|
4
|
+
|
5
|
+
let :app do
|
6
|
+
Class.new(Restfulness::Application) do
|
7
|
+
routes do
|
8
|
+
# nothing
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
let :klass do
|
14
|
+
Restfulness::Request
|
15
|
+
end
|
16
|
+
|
17
|
+
let :obj do
|
18
|
+
klass.new(app)
|
19
|
+
end
|
20
|
+
|
21
|
+
class RequestResource < Restfulness::Resource
|
22
|
+
end
|
23
|
+
let :resource do
|
24
|
+
RequestResource
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
describe "#initialize" do
|
29
|
+
it "should prepare basic objects" do
|
30
|
+
obj.action.should be_nil
|
31
|
+
obj.headers.should eql({})
|
32
|
+
obj.body.should be_nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#uri=" do
|
37
|
+
it "should convert URL into URI" do
|
38
|
+
obj.uri = "https://example.com/project/12345"
|
39
|
+
obj.uri.path.should eql("/project/12345")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#path" do
|
44
|
+
it "should be nil if there is no route" do
|
45
|
+
obj.stub(:route).and_return(nil)
|
46
|
+
obj.path.should be_nil
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with route" do
|
50
|
+
let :path do
|
51
|
+
obj.uri = "https://example.com/project/12345"
|
52
|
+
route = Restfulness::Route.new('project', resource)
|
53
|
+
obj.stub(:route).and_return(route)
|
54
|
+
obj.path
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should build path" do
|
58
|
+
path.to_s.should eql('/project/12345')
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should re-use same path object" do
|
62
|
+
path.object_id.should eql(obj.path.object_id)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#route" do
|
68
|
+
it "should ask the router for a route" do
|
69
|
+
obj.uri = "https://example.com/project/12345"
|
70
|
+
route = double(:Route)
|
71
|
+
app.router.should_receive(:route_for).with(obj.uri.path).and_return(route)
|
72
|
+
obj.route.should eql(route)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#query" do
|
77
|
+
it "should parse uri with query" do
|
78
|
+
obj.uri = "https://example.com/project/12345?foo=bar&test=23"
|
79
|
+
obj.query.should be_a(HashWithIndifferentAccess)
|
80
|
+
obj.query[:foo].should eql('bar')
|
81
|
+
obj.query[:test].to_i.should eql(23)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should handle uri with empty query" do
|
85
|
+
obj.uri = "https://example.com/project/12345"
|
86
|
+
obj.query.should be_empty
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should handle complex query items" do
|
90
|
+
obj.uri = "https://example.com/project/12345?foo[]=bar&foo[]=bar2&hash[a]=b&hash[b]=c"
|
91
|
+
obj.query[:foo].should eql(['bar', 'bar2'])
|
92
|
+
obj.query[:hash].should be_a(HashWithIndifferentAccess)
|
93
|
+
obj.query[:hash][:a].should eql('b')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#params" do
|
98
|
+
it "should not return anything for empty body" do
|
99
|
+
obj.stub(:body).and_return(nil)
|
100
|
+
obj.params.should be_nil
|
101
|
+
end
|
102
|
+
it "should raise 406 error if no content type" do
|
103
|
+
obj.headers[:content_type] = nil
|
104
|
+
obj.body = "{\"foo\":\"bar\"}"
|
105
|
+
expect {
|
106
|
+
obj.params
|
107
|
+
}.to raise_error(Restfulness::HTTPException, "Not Acceptable")
|
108
|
+
end
|
109
|
+
it "should decode a JSON body" do
|
110
|
+
obj.headers[:content_type] = "application/json"
|
111
|
+
obj.body = "{\"foo\":\"bar\"}"
|
112
|
+
obj.params['foo'].should eql('bar')
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe "method helpers" do
|
117
|
+
it "should respond to method questions" do
|
118
|
+
[:get?, :post?, :put?, :delete?, :head?, :options?].each do |q|
|
119
|
+
obj.should respond_to(q)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
it "should suggest type of method" do
|
123
|
+
obj.action = :get
|
124
|
+
obj.get?.should be_true
|
125
|
+
obj.post?.should be_false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Restfulness::Resource do
|
4
|
+
|
5
|
+
class GetResource < Restfulness::Resource
|
6
|
+
def get
|
7
|
+
'result'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class GetPostResource < Restfulness::Resource
|
12
|
+
def get
|
13
|
+
'result'
|
14
|
+
end
|
15
|
+
def post
|
16
|
+
'post'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
let :app do
|
21
|
+
Class.new(Restfulness::Application) do
|
22
|
+
routes do
|
23
|
+
# empty
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
let :request do
|
28
|
+
Restfulness::Request.new(app)
|
29
|
+
end
|
30
|
+
let :response do
|
31
|
+
Restfulness::Response.new(request)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#initialize" do
|
35
|
+
let :resource do
|
36
|
+
GetResource
|
37
|
+
end
|
38
|
+
it "should assign request and response" do
|
39
|
+
obj = resource.new(request, response)
|
40
|
+
obj.request.should eql(request)
|
41
|
+
obj.response.should eql(response)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#options" do
|
46
|
+
let :resource do
|
47
|
+
GetPostResource
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should return list of supported methods" do
|
51
|
+
obj = resource.new(request, response)
|
52
|
+
obj.options.should be_nil
|
53
|
+
response.headers['Allow'].should eql('GET, POST, OPTIONS')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#call" do
|
58
|
+
let :resource do
|
59
|
+
GetResource
|
60
|
+
end
|
61
|
+
it "should perform action" do
|
62
|
+
request.action = :get
|
63
|
+
obj = resource.new(request, response)
|
64
|
+
obj.should_receive(:get).and_return('res')
|
65
|
+
obj.call.should eql('res')
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#method_allowed?" do
|
71
|
+
let :resource do
|
72
|
+
GetPostResource
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should be true on valid method" do
|
76
|
+
request.action = :get
|
77
|
+
obj = resource.new(request, response)
|
78
|
+
obj.method_allowed?.should be_true
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should be false on invalid method" do
|
82
|
+
request.action = :put
|
83
|
+
obj = resource.new(request, response)
|
84
|
+
obj.method_allowed?.should be_false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "basic callback responses" do
|
89
|
+
let :resource do
|
90
|
+
GetPostResource
|
91
|
+
end
|
92
|
+
|
93
|
+
let :obj do
|
94
|
+
request.action = :get
|
95
|
+
obj = resource.new(request, response)
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should all be true for questions" do
|
99
|
+
obj.exists?.should be_true
|
100
|
+
obj.authorized?.should be_true
|
101
|
+
obj.allowed?.should be_true
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should be nil for values" do
|
105
|
+
obj.last_modified.should be_nil
|
106
|
+
obj.etag.should be_nil
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe "#check_callbacks" do
|
111
|
+
let :resource do
|
112
|
+
Class.new(GetPostResource) do
|
113
|
+
def head; nil; end
|
114
|
+
def put; nil; end
|
115
|
+
def delete; nil; end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
let :obj do
|
120
|
+
request.action = :get
|
121
|
+
obj = resource.new(request, response)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should all be good by default" do
|
125
|
+
expect {
|
126
|
+
obj.check_callbacks
|
127
|
+
}.to_not raise_error
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should raise error on invalid method" do
|
131
|
+
obj.stub(:method_allowed?).and_return(false)
|
132
|
+
expect {
|
133
|
+
obj.check_callbacks
|
134
|
+
}.to raise_error(Restfulness::HTTPException, "Method Not Allowed")
|
135
|
+
end
|
136
|
+
|
137
|
+
[:head, :get, :put, :delete].each do |action|
|
138
|
+
it "should raise error when not exists for #{action.to_s.upcase}" do
|
139
|
+
request.action = action
|
140
|
+
obj.stub(:exists?).and_return(false)
|
141
|
+
expect {
|
142
|
+
obj.check_callbacks
|
143
|
+
}.to raise_error(Restfulness::HTTPException, "Resource Not Found")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
[:post].each do |action|
|
148
|
+
it "should not check exists? for #{action.to_s.upcase}" do
|
149
|
+
obj.request.action = action
|
150
|
+
obj.should_not_receive(:exists?)
|
151
|
+
obj.check_callbacks
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should raise error when not authorized" do
|
156
|
+
obj.stub(:authorized?).and_return(false)
|
157
|
+
expect {
|
158
|
+
obj.check_callbacks
|
159
|
+
}.to raise_error(Restfulness::HTTPException, "Unauthorized")
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should raise error when not allowed" do
|
163
|
+
obj.stub(:allowed?).and_return(false)
|
164
|
+
expect {
|
165
|
+
obj.check_callbacks
|
166
|
+
}.to raise_error(Restfulness::HTTPException, "Forbidden")
|
167
|
+
end
|
168
|
+
|
169
|
+
describe "with etag" do
|
170
|
+
it "should raise error when equal" do
|
171
|
+
obj.stub(:etag).and_return('sometag')
|
172
|
+
request.headers[:if_none_match] = 'sometag'
|
173
|
+
expect {
|
174
|
+
obj.check_callbacks
|
175
|
+
}.to raise_error(Restfulness::HTTPException, "Not Modified")
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should continue if not equal" do
|
179
|
+
obj.stub(:etag).and_return('sometag')
|
180
|
+
request.headers[:if_none_match] = 'someoldtag'
|
181
|
+
expect {
|
182
|
+
obj.check_callbacks
|
183
|
+
}.to_not raise_error
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should not be called unless action is :get or :head" do
|
187
|
+
obj.should_not_receive(:etag)
|
188
|
+
request.headers[:if_none_match] = 'sometag'
|
189
|
+
[:post, :put, :delete].each do |action|
|
190
|
+
request.action = action
|
191
|
+
obj.check_callbacks
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe "with if modified" do
|
197
|
+
it "should raise error when equal" do
|
198
|
+
time = Time.now
|
199
|
+
obj.stub(:last_modified).and_return(time)
|
200
|
+
request.headers[:if_modified_since] = time.to_s
|
201
|
+
expect {
|
202
|
+
obj.check_callbacks
|
203
|
+
}.to raise_error(Restfulness::HTTPException, "Not Modified")
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should continue if not equal" do
|
207
|
+
time = Time.now
|
208
|
+
obj.stub(:last_modified).and_return(time)
|
209
|
+
request.headers[:if_modified_since] = (time - 60).to_s
|
210
|
+
expect {
|
211
|
+
obj.check_callbacks
|
212
|
+
}.to_not raise_error
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should not be called unless action is :get or :head" do
|
216
|
+
obj.should_not_receive(:last_modified)
|
217
|
+
request.headers[:if_modified_since] = 'somedate'
|
218
|
+
[:post, :put, :delete].each do |action|
|
219
|
+
request.action = action
|
220
|
+
obj.check_callbacks
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe "#error" do
|
228
|
+
|
229
|
+
class Get418Resource < Restfulness::Resource
|
230
|
+
def get
|
231
|
+
error(418, {})
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should raise a new exception" do
|
236
|
+
klass = Get418Resource
|
237
|
+
obj = klass.new(request, response)
|
238
|
+
expect {
|
239
|
+
obj.get
|
240
|
+
}.to raise_error(Restfulness::HTTPException, "I'm A Teapot")
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|