webmachine 1.2.2 → 1.6.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 +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
|