webmachine 1.2.2 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/CHANGELOG.md +57 -0
- data/Gemfile +20 -15
- data/README.md +89 -91
- data/RELEASING.md +21 -0
- data/Rakefile +5 -21
- data/documentation/adapters.md +41 -0
- data/documentation/authentication-and-authorization.md +37 -0
- data/documentation/configurator.md +19 -0
- data/documentation/error-handling.md +86 -0
- data/documentation/examples.md +224 -0
- data/documentation/how-it-works.md +76 -0
- data/documentation/routes.md +112 -0
- data/documentation/validation.md +159 -0
- data/documentation/versioning-apis.md +74 -0
- data/documentation/visual-debugger.md +38 -0
- data/examples/application.rb +2 -2
- data/examples/debugger.rb +1 -1
- data/lib/webmachine.rb +3 -1
- data/lib/webmachine/adapter.rb +7 -13
- data/lib/webmachine/adapters.rb +1 -2
- data/lib/webmachine/adapters/httpkit.rb +74 -0
- data/lib/webmachine/adapters/lazy_request_body.rb +1 -2
- data/lib/webmachine/adapters/rack.rb +70 -25
- data/lib/webmachine/adapters/rack_mapped.rb +42 -0
- data/lib/webmachine/adapters/reel.rb +22 -23
- data/lib/webmachine/adapters/webrick.rb +16 -16
- data/lib/webmachine/application.rb +2 -2
- data/lib/webmachine/chunked_body.rb +3 -4
- data/lib/webmachine/configuration.rb +1 -1
- data/lib/webmachine/constants.rb +75 -0
- data/lib/webmachine/decision/conneg.rb +12 -10
- data/lib/webmachine/decision/flow.rb +42 -32
- data/lib/webmachine/decision/fsm.rb +14 -21
- data/lib/webmachine/decision/helpers.rb +10 -38
- data/lib/webmachine/dispatcher.rb +13 -10
- data/lib/webmachine/dispatcher/route.rb +45 -9
- data/lib/webmachine/errors.rb +9 -3
- data/lib/webmachine/events.rb +2 -2
- data/lib/webmachine/header_negotiation.rb +25 -0
- data/lib/webmachine/headers.rb +8 -3
- data/lib/webmachine/locale/en.yml +7 -5
- data/lib/webmachine/media_type.rb +10 -8
- data/lib/webmachine/request.rb +67 -26
- data/lib/webmachine/rescueable_exception.rb +62 -0
- data/lib/webmachine/resource.rb +1 -1
- data/lib/webmachine/resource/callbacks.rb +11 -9
- data/lib/webmachine/response.rb +3 -5
- data/lib/webmachine/spec/IO_response.body +1 -0
- data/lib/webmachine/spec/adapter_lint.rb +83 -37
- data/lib/webmachine/spec/test_resource.rb +15 -4
- data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
- data/lib/webmachine/streaming/io_encoder.rb +7 -1
- data/lib/webmachine/trace.rb +1 -0
- data/lib/webmachine/trace/fsm.rb +20 -10
- data/lib/webmachine/trace/resource_proxy.rb +2 -0
- data/lib/webmachine/translation.rb +2 -1
- data/lib/webmachine/version.rb +3 -3
- data/memory_test.rb +37 -0
- data/spec/spec_helper.rb +17 -9
- data/spec/webmachine/adapter_spec.rb +14 -15
- data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
- data/spec/webmachine/adapters/rack_mapped_spec.rb +71 -0
- data/spec/webmachine/adapters/rack_spec.rb +32 -6
- data/spec/webmachine/adapters/reel_spec.rb +16 -12
- data/spec/webmachine/adapters/webrick_spec.rb +2 -2
- data/spec/webmachine/application_spec.rb +18 -17
- data/spec/webmachine/chunked_body_spec.rb +3 -3
- data/spec/webmachine/configuration_spec.rb +5 -5
- data/spec/webmachine/cookie_spec.rb +13 -13
- data/spec/webmachine/decision/conneg_spec.rb +49 -43
- data/spec/webmachine/decision/falsey_spec.rb +4 -4
- data/spec/webmachine/decision/flow_spec.rb +195 -145
- data/spec/webmachine/decision/fsm_spec.rb +81 -19
- data/spec/webmachine/decision/helpers_spec.rb +20 -20
- data/spec/webmachine/dispatcher/rfc3986_percent_decode_spec.rb +22 -0
- data/spec/webmachine/dispatcher/route_spec.rb +114 -32
- data/spec/webmachine/dispatcher_spec.rb +49 -24
- data/spec/webmachine/errors_spec.rb +1 -1
- data/spec/webmachine/etags_spec.rb +19 -19
- data/spec/webmachine/events_spec.rb +6 -6
- data/spec/webmachine/headers_spec.rb +14 -14
- data/spec/webmachine/media_type_spec.rb +36 -36
- data/spec/webmachine/request_spec.rb +70 -39
- data/spec/webmachine/rescueable_exception_spec.rb +15 -0
- data/spec/webmachine/resource/authentication_spec.rb +6 -6
- data/spec/webmachine/response_spec.rb +18 -12
- data/spec/webmachine/trace/fsm_spec.rb +8 -8
- data/spec/webmachine/trace/resource_proxy_spec.rb +9 -9
- data/spec/webmachine/trace/trace_store_spec.rb +5 -5
- data/spec/webmachine/trace_spec.rb +3 -3
- data/webmachine.gemspec +2 -6
- metadata +78 -228
- data/lib/webmachine/adapters/hatetepe.rb +0 -108
- data/lib/webmachine/adapters/mongrel.rb +0 -127
- data/lib/webmachine/dispatcher/not_found_resource.rb +0 -5
- data/lib/webmachine/fiber18.rb +0 -88
- data/spec/webmachine/adapters/hatetepe_spec.rb +0 -60
- data/spec/webmachine/adapters/mongrel_spec.rb +0 -16
@@ -5,64 +5,126 @@ describe Webmachine::Decision::FSM do
|
|
5
5
|
|
6
6
|
subject { described_class.new(resource, request, response) }
|
7
7
|
|
8
|
+
let(:run_with_exception) do
|
9
|
+
begin
|
10
|
+
subject.run
|
11
|
+
rescue Exception
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
8
15
|
describe 'handling of exceptions from decision methods' do
|
9
|
-
let(:
|
16
|
+
let(:UNRESCUABLE_exceptions) do
|
17
|
+
Webmachine::RescuableException::UNRESCUABLE
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "rescueable exceptions" do
|
21
|
+
it 'does rescue Exception' do
|
22
|
+
allow(subject).to receive(Webmachine::Decision::Flow::START) { raise(Exception) }
|
23
|
+
expect(resource).to receive(:handle_exception).with instance_of(Exception)
|
24
|
+
expect { subject.run }.to_not raise_error
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'does rescue a failed require' do
|
28
|
+
allow(subject).to receive(Webmachine::Decision::Flow::START) { require 'laterequire' }
|
29
|
+
expect(resource).to receive(:handle_exception).with instance_of(LoadError)
|
30
|
+
expect { subject.run }.to_not raise_error
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "UNRESCUABLE exceptions" do
|
35
|
+
shared_examples "UNRESCUABLE" do |e|
|
36
|
+
specify "#{e} is not rescued" do
|
37
|
+
allow(subject).to receive(Webmachine::Decision::Flow::START) {raise(e)}
|
38
|
+
expect(resource).to_not receive(:handle_exception).with instance_of(e)
|
39
|
+
expect { subject.run }.to raise_error(e)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
eary = Webmachine::RescuableException::UNRESCUABLE_DEFAULTS - [
|
43
|
+
Webmachine::MalformedRequest, # Webmachine rescues by default, so it won't re-raise.
|
44
|
+
SignalException # Requires raise in form 'raise SignalException, "SIGSOMESIGNAL"'.
|
45
|
+
# Haven't found a good no-op signal to use here.
|
46
|
+
]
|
47
|
+
eary.each{|e| include_examples "UNRESCUABLE", e}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'handling of errors from decision methods' do
|
52
|
+
let(:error) { RuntimeError.new }
|
10
53
|
|
11
54
|
before do
|
12
|
-
subject.
|
55
|
+
allow(subject).to receive(Webmachine::Decision::Flow::START) { raise error }
|
13
56
|
end
|
14
57
|
|
15
58
|
it 'calls resource.handle_exception' do
|
16
|
-
resource.
|
59
|
+
expect(resource).to receive(:handle_exception).with(error)
|
17
60
|
subject.run
|
18
61
|
end
|
19
62
|
|
20
63
|
it 'calls resource.finish_request' do
|
21
|
-
resource.
|
64
|
+
expect(resource).to receive(:finish_request)
|
22
65
|
subject.run
|
23
66
|
end
|
24
67
|
end
|
25
68
|
|
26
|
-
describe 'handling of
|
27
|
-
let(:
|
69
|
+
describe 'handling of errors from resource.handle_exception' do
|
70
|
+
let(:error) { RuntimeError.new('an error message') }
|
28
71
|
|
29
72
|
before do
|
30
|
-
subject.
|
31
|
-
resource.
|
73
|
+
allow(subject).to receive(Webmachine::Decision::Flow::START) { raise }
|
74
|
+
allow(resource).to receive(:handle_exception) { raise error }
|
32
75
|
end
|
33
76
|
|
34
77
|
it 'does not call resource.handle_exception again' do
|
35
|
-
resource.
|
78
|
+
expect(resource).to receive(:handle_exception).once { raise }
|
36
79
|
subject.run
|
37
80
|
end
|
38
81
|
|
39
82
|
it 'does not call resource.finish_request' do
|
40
|
-
resource.
|
83
|
+
expect(resource).not_to receive(:finish_request)
|
41
84
|
subject.run
|
42
85
|
end
|
43
86
|
|
44
87
|
it 'renders an error' do
|
45
|
-
Webmachine.
|
46
|
-
|
47
|
-
with(500, request, response, { :message =>
|
88
|
+
expect(Webmachine).
|
89
|
+
to receive(:render_error).
|
90
|
+
with(500, request, response, { :message => error.message })
|
48
91
|
subject.run
|
49
92
|
end
|
50
93
|
end
|
51
94
|
|
52
95
|
describe 'handling of exceptions from resource.finish_request' do
|
53
|
-
let(:exception) { Exception.new }
|
96
|
+
let(:exception) { Class.new(Exception).new }
|
97
|
+
|
98
|
+
before do
|
99
|
+
Webmachine::RescuableException.remove(exception)
|
100
|
+
allow(resource).to receive(:finish_request) { raise exception }
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'does not call resource.handle_exception' do
|
104
|
+
expect(resource).to_not receive(:handle_exception)
|
105
|
+
run_with_exception
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'does not call resource.finish_request again' do
|
109
|
+
expect(resource).to receive(:finish_request).once
|
110
|
+
run_with_exception
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe 'handling of errors from resource.finish_request' do
|
115
|
+
let(:error) { RuntimeError.new }
|
54
116
|
|
55
117
|
before do
|
56
|
-
resource.
|
118
|
+
allow(resource).to receive(:finish_request) { raise error }
|
57
119
|
end
|
58
120
|
|
59
121
|
it 'calls resource.handle_exception' do
|
60
|
-
resource.
|
122
|
+
expect(resource).to receive(:handle_exception).with(error)
|
61
123
|
subject.run
|
62
124
|
end
|
63
125
|
|
64
126
|
it 'does not call resource.finish_request again' do
|
65
|
-
resource.
|
127
|
+
expect(resource).to receive(:finish_request).once { raise }
|
66
128
|
subject.run
|
67
129
|
end
|
68
130
|
end
|
@@ -84,7 +146,7 @@ describe Webmachine::Decision::FSM do
|
|
84
146
|
|
85
147
|
subject.run
|
86
148
|
|
87
|
-
resource_class.current_response_code.
|
149
|
+
expect(resource_class.current_response_code).to be(201)
|
88
150
|
end
|
89
151
|
|
90
152
|
it 'respects a response code set by resource.finish_request' do
|
@@ -96,6 +158,6 @@ describe Webmachine::Decision::FSM do
|
|
96
158
|
|
97
159
|
subject.run
|
98
160
|
|
99
|
-
response.code.
|
161
|
+
expect(response.code).to be(451)
|
100
162
|
end
|
101
163
|
end
|
@@ -29,20 +29,20 @@ describe Webmachine::Decision::Helpers do
|
|
29
29
|
end
|
30
30
|
|
31
31
|
it "should return 415 when no types are accepted" do
|
32
|
-
subject.accept_helper.
|
32
|
+
expect(subject.accept_helper).to eq 415
|
33
33
|
end
|
34
34
|
|
35
35
|
it "should return 415 when the posted type is not acceptable" do
|
36
36
|
resource.accepted = %W{application/json}
|
37
37
|
headers['Content-Type'] = "text/xml"
|
38
|
-
subject.accept_helper.
|
38
|
+
expect(subject.accept_helper).to eq 415
|
39
39
|
end
|
40
40
|
|
41
41
|
it "should call the method for the first acceptable type, taking into account params" do
|
42
42
|
resource.accepted = ["application/json;v=3", ["application/json", :other]]
|
43
|
-
resource.
|
43
|
+
expect(resource).to receive(:other).and_return(true)
|
44
44
|
headers['Content-Type'] = 'application/json;v=2'
|
45
|
-
subject.accept_helper.
|
45
|
+
expect(subject.accept_helper).to be(true)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -52,7 +52,7 @@ describe Webmachine::Decision::Helpers do
|
|
52
52
|
response.headers['Content-Length'] = '0'
|
53
53
|
response.body = nil
|
54
54
|
subject.send :respond, code
|
55
|
-
response.headers.
|
55
|
+
expect(response.headers).to_not include 'Content-Length'
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -65,7 +65,7 @@ describe Webmachine::Decision::Helpers do
|
|
65
65
|
response.code = code
|
66
66
|
response.body = nil
|
67
67
|
subject.send :respond, code
|
68
|
-
response.headers['Content-Length'].
|
68
|
+
expect(response.headers['Content-Length']).to eq '0'
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
@@ -76,7 +76,7 @@ describe Webmachine::Decision::Helpers do
|
|
76
76
|
response.headers['Transfer-Encoding'] = 'chunked'
|
77
77
|
response.body = []
|
78
78
|
subject.send :respond, code
|
79
|
-
response.headers.
|
79
|
+
expect(response.headers).to_not include 'Content-Length'
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
@@ -89,24 +89,24 @@ describe Webmachine::Decision::Helpers do
|
|
89
89
|
|
90
90
|
it "does not modify the response body" do
|
91
91
|
subject.encode_body
|
92
|
-
|
92
|
+
expect(response.body).to be_instance_of(String)
|
93
93
|
end
|
94
94
|
|
95
95
|
it "sets the Content-Length header in the response" do
|
96
96
|
subject.encode_body
|
97
|
-
response.headers['Content-Length'].
|
97
|
+
expect(response.headers['Content-Length']).to eq response.body.bytesize.to_s
|
98
98
|
end
|
99
99
|
end
|
100
100
|
|
101
101
|
shared_examples_for "a non-String body" do
|
102
102
|
it "does not set the Content-Length header in the response" do
|
103
103
|
subject.encode_body
|
104
|
-
response.headers.
|
104
|
+
expect(response.headers).to_not have_key('Content-Length')
|
105
105
|
end
|
106
106
|
|
107
107
|
it "sets the Transfer-Encoding response header to chunked" do
|
108
108
|
subject.encode_body
|
109
|
-
response.headers['Transfer-Encoding'].
|
109
|
+
expect(response.headers['Transfer-Encoding']).to eq 'chunked'
|
110
110
|
end
|
111
111
|
end
|
112
112
|
|
@@ -115,7 +115,7 @@ describe Webmachine::Decision::Helpers do
|
|
115
115
|
|
116
116
|
it "wraps the response body in an EnumerableEncoder" do
|
117
117
|
subject.encode_body
|
118
|
-
Webmachine::Streaming::EnumerableEncoder
|
118
|
+
expect(response.body).to be_instance_of(Webmachine::Streaming::EnumerableEncoder)
|
119
119
|
end
|
120
120
|
|
121
121
|
it_should_behave_like "a non-String body"
|
@@ -126,7 +126,7 @@ describe Webmachine::Decision::Helpers do
|
|
126
126
|
|
127
127
|
it "wraps the response body in a CallableEncoder" do
|
128
128
|
subject.encode_body
|
129
|
-
Webmachine::Streaming::CallableEncoder
|
129
|
+
expect(response.body).to be_instance_of(Webmachine::Streaming::CallableEncoder)
|
130
130
|
end
|
131
131
|
|
132
132
|
it_should_behave_like "a non-String body"
|
@@ -137,7 +137,7 @@ describe Webmachine::Decision::Helpers do
|
|
137
137
|
|
138
138
|
it "wraps the response body in a FiberEncoder" do
|
139
139
|
subject.encode_body
|
140
|
-
Webmachine::Streaming::FiberEncoder
|
140
|
+
expect(response.body).to be_instance_of(Webmachine::Streaming::FiberEncoder)
|
141
141
|
end
|
142
142
|
|
143
143
|
it_should_behave_like "a non-String body"
|
@@ -148,22 +148,22 @@ describe Webmachine::Decision::Helpers do
|
|
148
148
|
|
149
149
|
it "wraps the response body in an IOEncoder" do
|
150
150
|
subject.encode_body
|
151
|
-
Webmachine::Streaming::IOEncoder
|
151
|
+
expect(response.body).to be_instance_of(Webmachine::Streaming::IOEncoder)
|
152
152
|
end
|
153
153
|
|
154
154
|
it "sets the Content-Length header to the size of the file" do
|
155
155
|
subject.encode_body
|
156
|
-
response.headers['Content-Length'].
|
156
|
+
expect(response.headers['Content-Length']).to eq File.stat('spec/spec_helper.rb').size.to_s
|
157
157
|
end
|
158
158
|
|
159
159
|
it "progressively yields file contents for each enumeration" do
|
160
160
|
subject.encode_body
|
161
161
|
body_size = 0
|
162
162
|
response.body.each do |chunk|
|
163
|
-
chunk.
|
163
|
+
expect(chunk).to be_instance_of(String)
|
164
164
|
body_size += chunk.length
|
165
165
|
end
|
166
|
-
body_size.
|
166
|
+
expect(body_size).to eq File.stat('spec/spec_helper.rb').size
|
167
167
|
end
|
168
168
|
|
169
169
|
context "when the resource provides a non-identity encoding that the client accepts" do
|
@@ -188,12 +188,12 @@ describe Webmachine::Decision::Helpers do
|
|
188
188
|
|
189
189
|
it "wraps the response body in an IOEncoder" do
|
190
190
|
subject.encode_body
|
191
|
-
Webmachine::Streaming::IOEncoder
|
191
|
+
expect(response.body).to be_instance_of(Webmachine::Streaming::IOEncoder)
|
192
192
|
end
|
193
193
|
|
194
194
|
it "sets the Content-Length header to the size of the string" do
|
195
195
|
subject.encode_body
|
196
|
-
response.headers['Content-Length'].
|
196
|
+
expect(response.headers['Content-Length']).to eq response.body.size.to_s
|
197
197
|
end
|
198
198
|
|
199
199
|
context "when the resource provides a non-identity encoding that the client accepts" do
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Webmachine::Dispatcher::Route do
|
4
|
+
describe '#rfc3986_percent_decode' do
|
5
|
+
def call_subject(value)
|
6
|
+
Webmachine::Dispatcher::Route.rfc3986_percent_decode(value)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'does not change un-encoded strings' do
|
10
|
+
expect(call_subject('this is a normal string, I think')).to eq 'this is a normal string, I think'
|
11
|
+
expect(call_subject('')).to eq ''
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'decodes percent encoded sequences' do
|
15
|
+
expect(call_subject('/tenants/esckimo+test%20%65')).to eq '/tenants/esckimo+test e'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'leaves incorrectly encoded sequences as is' do
|
19
|
+
expect(call_subject('/tenants/esckimo+test%2%65')).to eq '/tenants/esckimo+test%2e'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,43 +1,74 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
Webmachine::Dispatcher::Route.class_eval do
|
4
|
+
def warn(*msgs); end # silence warnings for tests
|
5
|
+
end
|
6
|
+
|
3
7
|
describe Webmachine::Dispatcher::Route do
|
4
8
|
let(:method) { "GET" }
|
5
9
|
let(:uri) { URI.parse("http://localhost:8080/") }
|
6
|
-
let(:
|
10
|
+
let(:routing_tokens) { nil }
|
11
|
+
let(:request){ Webmachine::Request.new(method, uri, Webmachine::Headers.new, "", routing_tokens) }
|
7
12
|
let(:resource){ Class.new(Webmachine::Resource) }
|
8
13
|
|
14
|
+
describe '#apply' do
|
15
|
+
let(:route) {
|
16
|
+
Webmachine::Dispatcher::Route.new ['hello', :string], resource, {}
|
17
|
+
}
|
18
|
+
|
19
|
+
describe 'a path_info fragment' do
|
20
|
+
let(:uri) { URI.parse("http://localhost:8080/hello/planet%20earth%20++") }
|
21
|
+
|
22
|
+
it 'should decode the value' do
|
23
|
+
route.apply(request)
|
24
|
+
expect(request.path_info).to eq({:string => 'planet earth ++'})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
9
29
|
matcher :match_route do |*expected|
|
10
30
|
route = Webmachine::Dispatcher::Route.new(expected[0], Class.new(Webmachine::Resource), expected[1] || {})
|
11
31
|
match do |actual|
|
12
|
-
|
13
|
-
|
32
|
+
uri = URI.parse("http://localhost:8080")
|
33
|
+
uri.path = actual
|
34
|
+
req = Webmachine::Request.new("GET", uri, Webmachine::Headers.new, "", routing_tokens)
|
35
|
+
route.match?(req)
|
14
36
|
end
|
15
37
|
|
16
|
-
|
38
|
+
failure_message do |_|
|
17
39
|
"expected route #{expected[0].inspect} to match path #{request.uri.path}"
|
18
40
|
end
|
19
|
-
|
41
|
+
failure_message_when_negated do |_|
|
20
42
|
"expected route #{expected[0].inspect} not to match path #{request.uri.path}"
|
21
43
|
end
|
22
44
|
end
|
23
45
|
|
46
|
+
it "warns about the deprecated string splat when initializing" do
|
47
|
+
[["*"],["foo", "*"],["foo", :bar, "*"]].each do |path|
|
48
|
+
route = described_class.allocate
|
49
|
+
expect(route).to receive(:warn)
|
50
|
+
route.send :initialize, path, resource, {}
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
24
54
|
context "matching a request" do
|
25
55
|
context "on the root path" do
|
26
56
|
subject { "/" }
|
27
|
-
it {
|
28
|
-
it {
|
29
|
-
it {
|
30
|
-
it {
|
57
|
+
it { is_expected.to match_route([]) }
|
58
|
+
it { is_expected.to match_route ['*'] }
|
59
|
+
it { is_expected.to match_route [:*] }
|
60
|
+
it { is_expected.not_to match_route %w{foo} }
|
61
|
+
it { is_expected.not_to match_route [:id] }
|
31
62
|
end
|
32
63
|
|
33
64
|
context "on a deep path" do
|
34
65
|
subject { "/foo/bar/baz" }
|
35
|
-
it {
|
36
|
-
it {
|
37
|
-
it {
|
38
|
-
it {
|
39
|
-
it {
|
40
|
-
it {
|
66
|
+
it { is_expected.to match_route %w{foo bar baz} }
|
67
|
+
it { is_expected.to match_route ['foo', :id, "baz"] }
|
68
|
+
it { is_expected.to match_route ['foo', :*] }
|
69
|
+
it { is_expected.to match_route [:id, :*] }
|
70
|
+
it { is_expected.not_to match_route [] }
|
71
|
+
it { is_expected.not_to match_route ['bar', :*] }
|
41
72
|
end
|
42
73
|
|
43
74
|
context "with a guard on the request method" do
|
@@ -53,17 +84,17 @@ describe Webmachine::Dispatcher::Route do
|
|
53
84
|
|
54
85
|
context "when guard passes" do
|
55
86
|
let(:method){ "POST" }
|
56
|
-
it {
|
87
|
+
it { is_expected.to be_match(request) }
|
57
88
|
|
58
89
|
context "but the path match fails" do
|
59
90
|
let(:uri){ URI.parse("http://localhost:8080/other") }
|
60
|
-
it {
|
91
|
+
it { is_expected.not_to be_match(request) }
|
61
92
|
end
|
62
93
|
end
|
63
94
|
|
64
95
|
context "when guard fails" do
|
65
96
|
let(:method) { "GET" }
|
66
|
-
it {
|
97
|
+
it { is_expected.not_to be_match(request) }
|
67
98
|
end
|
68
99
|
|
69
100
|
context "when the guard responds to #call" do
|
@@ -85,15 +116,27 @@ describe Webmachine::Dispatcher::Route do
|
|
85
116
|
|
86
117
|
context "when the guard passes" do
|
87
118
|
let(:method){ "POST" }
|
88
|
-
it {
|
119
|
+
it { is_expected.to be_match(request) }
|
89
120
|
end
|
90
121
|
|
91
122
|
context "when the guard fails" do
|
92
123
|
# let(:method){ "GET" }
|
93
|
-
it {
|
124
|
+
it { is_expected.not_to be_match(request) }
|
94
125
|
end
|
95
126
|
end
|
96
127
|
end
|
128
|
+
|
129
|
+
context "with a request with explicitly specified routing tokens" do
|
130
|
+
subject { "/some/route/foo/bar" }
|
131
|
+
let(:routing_tokens) { ["foo", "bar"] }
|
132
|
+
it { is_expected.to match_route(["foo", "bar"]) }
|
133
|
+
it { is_expected.to match_route(["foo", :id]) }
|
134
|
+
it { is_expected.to match_route ['*'] }
|
135
|
+
it { is_expected.to match_route [:*] }
|
136
|
+
it { is_expected.not_to match_route(["some", "route", "foo", "bar"]) }
|
137
|
+
it { is_expected.not_to match_route %w{foo} }
|
138
|
+
it { is_expected.not_to match_route [:id] }
|
139
|
+
end
|
97
140
|
end
|
98
141
|
|
99
142
|
context "applying bindings" do
|
@@ -102,63 +145,102 @@ describe Webmachine::Dispatcher::Route do
|
|
102
145
|
before { subject.apply(request) }
|
103
146
|
|
104
147
|
it "should assign the dispatched path to the empty string" do
|
105
|
-
request.disp_path.
|
148
|
+
expect(request.disp_path).to eq("")
|
106
149
|
end
|
107
150
|
|
108
151
|
it "should assign empty bindings" do
|
109
|
-
request.path_info.
|
152
|
+
expect(request.path_info).to eq({})
|
110
153
|
end
|
111
154
|
|
112
155
|
it "should assign empty path tokens" do
|
113
|
-
request.path_tokens.
|
156
|
+
expect(request.path_tokens).to eq([])
|
114
157
|
end
|
115
158
|
|
116
159
|
context "with extra user-defined bindings" do
|
117
160
|
subject { described_class.new([], resource, "bar" => "baz") }
|
118
161
|
|
119
162
|
it "should assign the user-defined bindings" do
|
120
|
-
request.path_info.
|
163
|
+
expect(request.path_info).to eq({"bar" => "baz"})
|
121
164
|
end
|
122
165
|
end
|
123
166
|
|
124
167
|
context "with a splat" do
|
168
|
+
subject { described_class.new([:*], resource) }
|
169
|
+
|
170
|
+
it "should assign empty path tokens" do
|
171
|
+
expect(request.path_tokens).to eq([])
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context "with a deprecated splat string" do
|
125
176
|
subject { described_class.new(['*'], resource) }
|
126
177
|
|
127
178
|
it "should assign empty path tokens" do
|
128
|
-
request.path_tokens.
|
179
|
+
expect(request.path_tokens).to eq([])
|
129
180
|
end
|
130
181
|
end
|
131
182
|
end
|
132
|
-
|
133
183
|
context "on a deep path" do
|
134
184
|
subject { described_class.new(%w{foo bar baz}, resource) }
|
135
|
-
|
185
|
+
let(:uri) { URI.parse("http://localhost:8080/foo/bar/baz") }
|
186
|
+
before { subject.apply(request) }
|
136
187
|
|
137
188
|
it "should assign the dispatched path as the path past the initial slash" do
|
138
|
-
request.disp_path.
|
189
|
+
expect(request.disp_path).to eq("foo/bar/baz")
|
139
190
|
end
|
140
191
|
|
141
192
|
it "should assign empty bindings" do
|
142
|
-
request.path_info.
|
193
|
+
expect(request.path_info).to eq({})
|
143
194
|
end
|
144
195
|
|
145
196
|
it "should assign empty path tokens" do
|
146
|
-
request.path_tokens.
|
197
|
+
expect(request.path_tokens).to eq([])
|
147
198
|
end
|
148
199
|
|
149
200
|
context "with path variables" do
|
150
201
|
subject { described_class.new(['foo', :id, 'baz'], resource) }
|
151
202
|
|
152
203
|
it "should assign the path variables in the bindings" do
|
153
|
-
request.path_info.
|
204
|
+
expect(request.path_info).to eq({:id => "bar"})
|
205
|
+
end
|
206
|
+
end
|
207
|
+
context "with regex" do
|
208
|
+
subject { described_class.new([/foo/, /(.*)/, 'baz'], resource) }
|
209
|
+
|
210
|
+
it "should assign the captures path variables" do
|
211
|
+
expect(request.path_info).to eq({:captures => ["bar"]})
|
212
|
+
end
|
213
|
+
end
|
214
|
+
context "with multi-capture regex" do
|
215
|
+
subject { described_class.new([/foo/, /(.*)/, /baz\.(.*)/], resource) }
|
216
|
+
let(:uri) { URI.parse("http://localhost:8080/foo/bar/baz.json") }
|
217
|
+
|
218
|
+
it "should assign the captures path variables" do
|
219
|
+
expect(request.path_info).to eq({:captures => ["bar", "json"]})
|
220
|
+
end
|
221
|
+
end
|
222
|
+
context "with named capture regex" do
|
223
|
+
subject { described_class.new(['foo', :bar, /(?<baz>[^.]+)\.(?<format>.*)/], resource) }
|
224
|
+
let(:uri) { URI.parse("http://localhost:8080/foo/bar/baz.json") }
|
225
|
+
|
226
|
+
it "should assign the captures path variables" do
|
227
|
+
expect(request.path_info).to eq({bar: 'bar', baz: 'baz', format: "json"})
|
154
228
|
end
|
155
229
|
end
|
156
230
|
|
157
231
|
context "with a splat" do
|
232
|
+
subject { described_class.new(['foo', :*], resource) }
|
233
|
+
|
234
|
+
it "should capture the path tokens matched by the splat" do
|
235
|
+
expect(request.path_tokens).to eq(%w{ bar baz })
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
context "with a deprecated splat string" do
|
158
240
|
subject { described_class.new(%w{foo *}, resource) }
|
159
241
|
|
160
242
|
it "should capture the path tokens matched by the splat" do
|
161
|
-
request.path_tokens.
|
243
|
+
expect(request.path_tokens).to eq(%w{ bar baz })
|
162
244
|
end
|
163
245
|
end
|
164
246
|
end
|