webmachine 1.6.0 → 2.0.0.beta
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +2 -5
- data/documentation/adapters.md +2 -11
- data/examples/debugger.rb +5 -3
- data/examples/logging.rb +2 -2
- data/examples/webrick.rb +2 -2
- data/lib/webmachine/adapter.rb +0 -3
- data/lib/webmachine/adapters/lazy_request_body.rb +1 -1
- data/lib/webmachine/adapters/rack.rb +38 -34
- data/lib/webmachine/adapters/rack_mapped.rb +1 -2
- data/lib/webmachine/adapters/webrick.rb +11 -12
- data/lib/webmachine/adapters.rb +0 -2
- data/lib/webmachine/application.rb +2 -2
- data/lib/webmachine/chunked_body.rb +1 -1
- data/lib/webmachine/configuration.rb +1 -1
- data/lib/webmachine/constants.rb +12 -12
- data/lib/webmachine/cookie.rb +20 -18
- data/lib/webmachine/decision/conneg.rb +24 -24
- data/lib/webmachine/decision/falsey.rb +0 -1
- data/lib/webmachine/decision/flow.rb +19 -20
- data/lib/webmachine/decision/fsm.rb +4 -4
- data/lib/webmachine/decision/helpers.rb +21 -21
- data/lib/webmachine/dispatcher/route.rb +28 -28
- data/lib/webmachine/dispatcher.rb +3 -2
- data/lib/webmachine/errors.rb +7 -8
- data/lib/webmachine/etags.rb +2 -1
- data/lib/webmachine/header_negotiation.rb +5 -6
- data/lib/webmachine/headers.rb +5 -5
- data/lib/webmachine/media_type.rb +5 -5
- data/lib/webmachine/quoted_string.rb +3 -3
- data/lib/webmachine/request.rb +7 -10
- data/lib/webmachine/rescueable_exception.rb +3 -3
- data/lib/webmachine/resource/authentication.rb +3 -4
- data/lib/webmachine/resource/callbacks.rb +3 -3
- data/lib/webmachine/resource/encodings.rb +3 -9
- data/lib/webmachine/resource.rb +1 -1
- data/lib/webmachine/response.rb +7 -9
- data/lib/webmachine/spec/adapter_lint.rb +67 -69
- data/lib/webmachine/spec/test_resource.rb +22 -22
- data/lib/webmachine/streaming/encoder.rb +3 -2
- data/lib/webmachine/streaming/io_encoder.rb +4 -3
- data/lib/webmachine/trace/fsm.rb +25 -18
- data/lib/webmachine/trace/resource_proxy.rb +10 -9
- data/lib/webmachine/trace/static/http-headers-status-v3.png +0 -0
- data/lib/webmachine/trace/trace_resource.rb +22 -24
- data/lib/webmachine/trace.rb +7 -6
- data/lib/webmachine/translation.rb +3 -3
- data/lib/webmachine/version.rb +1 -1
- metadata +52 -86
- data/.gitignore +0 -31
- data/Gemfile +0 -46
- data/Guardfile +0 -11
- data/RELEASING.md +0 -21
- data/Rakefile +0 -44
- data/lib/webmachine/adapters/httpkit.rb +0 -74
- data/lib/webmachine/adapters/reel.rb +0 -113
- data/memory_test.rb +0 -37
- data/spec/spec_helper.rb +0 -56
- data/spec/webmachine/adapter_spec.rb +0 -39
- data/spec/webmachine/adapters/httpkit_spec.rb +0 -10
- data/spec/webmachine/adapters/rack_mapped_spec.rb +0 -71
- data/spec/webmachine/adapters/rack_spec.rb +0 -62
- data/spec/webmachine/adapters/reel_spec.rb +0 -76
- data/spec/webmachine/adapters/webrick_spec.rb +0 -12
- data/spec/webmachine/application_spec.rb +0 -74
- data/spec/webmachine/chunked_body_spec.rb +0 -30
- data/spec/webmachine/configuration_spec.rb +0 -27
- data/spec/webmachine/cookie_spec.rb +0 -99
- data/spec/webmachine/decision/conneg_spec.rb +0 -166
- data/spec/webmachine/decision/falsey_spec.rb +0 -8
- data/spec/webmachine/decision/flow_spec.rb +0 -1148
- data/spec/webmachine/decision/fsm_spec.rb +0 -163
- data/spec/webmachine/decision/helpers_spec.rb +0 -216
- data/spec/webmachine/dispatcher/rfc3986_percent_decode_spec.rb +0 -22
- data/spec/webmachine/dispatcher/route_spec.rb +0 -248
- data/spec/webmachine/dispatcher_spec.rb +0 -104
- data/spec/webmachine/errors_spec.rb +0 -13
- data/spec/webmachine/etags_spec.rb +0 -75
- data/spec/webmachine/events_spec.rb +0 -58
- data/spec/webmachine/headers_spec.rb +0 -99
- data/spec/webmachine/media_type_spec.rb +0 -85
- data/spec/webmachine/request_spec.rb +0 -273
- data/spec/webmachine/rescueable_exception_spec.rb +0 -15
- data/spec/webmachine/resource/authentication_spec.rb +0 -68
- data/spec/webmachine/response_spec.rb +0 -51
- data/spec/webmachine/trace/fsm_spec.rb +0 -37
- data/spec/webmachine/trace/resource_proxy_spec.rb +0 -34
- data/spec/webmachine/trace/trace_store_spec.rb +0 -29
- data/spec/webmachine/trace_spec.rb +0 -17
- data/webmachine.gemspec +0 -25
@@ -1,163 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Webmachine::Decision::FSM do
|
4
|
-
include_context 'default resource'
|
5
|
-
|
6
|
-
subject { described_class.new(resource, request, response) }
|
7
|
-
|
8
|
-
let(:run_with_exception) do
|
9
|
-
begin
|
10
|
-
subject.run
|
11
|
-
rescue Exception
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
describe 'handling of exceptions from decision methods' do
|
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 }
|
53
|
-
|
54
|
-
before do
|
55
|
-
allow(subject).to receive(Webmachine::Decision::Flow::START) { raise error }
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'calls resource.handle_exception' do
|
59
|
-
expect(resource).to receive(:handle_exception).with(error)
|
60
|
-
subject.run
|
61
|
-
end
|
62
|
-
|
63
|
-
it 'calls resource.finish_request' do
|
64
|
-
expect(resource).to receive(:finish_request)
|
65
|
-
subject.run
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
describe 'handling of errors from resource.handle_exception' do
|
70
|
-
let(:error) { RuntimeError.new('an error message') }
|
71
|
-
|
72
|
-
before do
|
73
|
-
allow(subject).to receive(Webmachine::Decision::Flow::START) { raise }
|
74
|
-
allow(resource).to receive(:handle_exception) { raise error }
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'does not call resource.handle_exception again' do
|
78
|
-
expect(resource).to receive(:handle_exception).once { raise }
|
79
|
-
subject.run
|
80
|
-
end
|
81
|
-
|
82
|
-
it 'does not call resource.finish_request' do
|
83
|
-
expect(resource).not_to receive(:finish_request)
|
84
|
-
subject.run
|
85
|
-
end
|
86
|
-
|
87
|
-
it 'renders an error' do
|
88
|
-
expect(Webmachine).
|
89
|
-
to receive(:render_error).
|
90
|
-
with(500, request, response, { :message => error.message })
|
91
|
-
subject.run
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
describe 'handling of exceptions from resource.finish_request' do
|
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 }
|
116
|
-
|
117
|
-
before do
|
118
|
-
allow(resource).to receive(:finish_request) { raise error }
|
119
|
-
end
|
120
|
-
|
121
|
-
it 'calls resource.handle_exception' do
|
122
|
-
expect(resource).to receive(:handle_exception).with(error)
|
123
|
-
subject.run
|
124
|
-
end
|
125
|
-
|
126
|
-
it 'does not call resource.finish_request again' do
|
127
|
-
expect(resource).to receive(:finish_request).once { raise }
|
128
|
-
subject.run
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
it "sets the response code before calling finish_request" do
|
133
|
-
resource_class.class_eval do
|
134
|
-
class << self
|
135
|
-
attr_accessor :current_response_code
|
136
|
-
end
|
137
|
-
|
138
|
-
def to_html
|
139
|
-
201
|
140
|
-
end
|
141
|
-
|
142
|
-
def finish_request
|
143
|
-
self.class.current_response_code = response.code
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
subject.run
|
148
|
-
|
149
|
-
expect(resource_class.current_response_code).to be(201)
|
150
|
-
end
|
151
|
-
|
152
|
-
it 'respects a response code set by resource.finish_request' do
|
153
|
-
resource_class.class_eval do
|
154
|
-
def finish_request
|
155
|
-
response.code = 451
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
subject.run
|
160
|
-
|
161
|
-
expect(response.code).to be(451)
|
162
|
-
end
|
163
|
-
end
|
@@ -1,216 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Webmachine::Decision::Helpers do
|
4
|
-
include_context "default resource"
|
5
|
-
subject { Webmachine::Decision::FSM.new(resource, request, response) }
|
6
|
-
|
7
|
-
def resource_with(&block)
|
8
|
-
klass = Class.new(Webmachine::Resource) do
|
9
|
-
def to_html; "test resource"; end
|
10
|
-
end
|
11
|
-
klass.module_eval(&block) if block_given?
|
12
|
-
klass.new(request, response)
|
13
|
-
end
|
14
|
-
|
15
|
-
let(:resource) { resource_with }
|
16
|
-
|
17
|
-
describe "accepting request bodies" do
|
18
|
-
let(:resource) do
|
19
|
-
resource_with do
|
20
|
-
def initialize
|
21
|
-
@accepted, @result = [], true
|
22
|
-
end
|
23
|
-
attr_accessor :accepted, :result
|
24
|
-
def content_types_accepted
|
25
|
-
(accepted || []).map {|t| Array === t ? t : [t, :accept_doc] }
|
26
|
-
end
|
27
|
-
def accept_doc; result; end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it "should return 415 when no types are accepted" do
|
32
|
-
expect(subject.accept_helper).to eq 415
|
33
|
-
end
|
34
|
-
|
35
|
-
it "should return 415 when the posted type is not acceptable" do
|
36
|
-
resource.accepted = %W{application/json}
|
37
|
-
headers['Content-Type'] = "text/xml"
|
38
|
-
expect(subject.accept_helper).to eq 415
|
39
|
-
end
|
40
|
-
|
41
|
-
it "should call the method for the first acceptable type, taking into account params" do
|
42
|
-
resource.accepted = ["application/json;v=3", ["application/json", :other]]
|
43
|
-
expect(resource).to receive(:other).and_return(true)
|
44
|
-
headers['Content-Type'] = 'application/json;v=2'
|
45
|
-
expect(subject.accept_helper).to be(true)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
context "setting the Content-Length header when responding" do
|
50
|
-
[204, 205, 304].each do |code|
|
51
|
-
it "removes the header for entity-less response code #{code}" do
|
52
|
-
response.headers['Content-Length'] = '0'
|
53
|
-
response.body = nil
|
54
|
-
subject.send :respond, code
|
55
|
-
expect(response.headers).to_not include 'Content-Length'
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
(200..599).each do |code|
|
60
|
-
# 204, 205 and 304 have no bodies, 404 is set to a default
|
61
|
-
# non-zero response by Webmachine
|
62
|
-
next if [204, 205, 304, 404].include? code
|
63
|
-
|
64
|
-
it "adds the header for response code #{code} that should include an entity but has an empty body" do
|
65
|
-
response.code = code
|
66
|
-
response.body = nil
|
67
|
-
subject.send :respond, code
|
68
|
-
expect(response.headers['Content-Length']).to eq '0'
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
(200..599).each do |code|
|
73
|
-
next if [204, 205, 304].include? code
|
74
|
-
|
75
|
-
it "does not add the header when Transfer-Encoding is set on code #{code}" do
|
76
|
-
response.headers['Transfer-Encoding'] = 'chunked'
|
77
|
-
response.body = []
|
78
|
-
subject.send :respond, code
|
79
|
-
expect(response.headers).to_not include 'Content-Length'
|
80
|
-
end
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
describe "#encode_body" do
|
85
|
-
before { subject.run }
|
86
|
-
|
87
|
-
context "with a String body" do
|
88
|
-
before { response.body = '<body></body>' }
|
89
|
-
|
90
|
-
it "does not modify the response body" do
|
91
|
-
subject.encode_body
|
92
|
-
expect(response.body).to be_instance_of(String)
|
93
|
-
end
|
94
|
-
|
95
|
-
it "sets the Content-Length header in the response" do
|
96
|
-
subject.encode_body
|
97
|
-
expect(response.headers['Content-Length']).to eq response.body.bytesize.to_s
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
shared_examples_for "a non-String body" do
|
102
|
-
it "does not set the Content-Length header in the response" do
|
103
|
-
subject.encode_body
|
104
|
-
expect(response.headers).to_not have_key('Content-Length')
|
105
|
-
end
|
106
|
-
|
107
|
-
it "sets the Transfer-Encoding response header to chunked" do
|
108
|
-
subject.encode_body
|
109
|
-
expect(response.headers['Transfer-Encoding']).to eq 'chunked'
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
context "with an Enumerable body" do
|
114
|
-
before { response.body = ['one', 'two'] }
|
115
|
-
|
116
|
-
it "wraps the response body in an EnumerableEncoder" do
|
117
|
-
subject.encode_body
|
118
|
-
expect(response.body).to be_instance_of(Webmachine::Streaming::EnumerableEncoder)
|
119
|
-
end
|
120
|
-
|
121
|
-
it_should_behave_like "a non-String body"
|
122
|
-
end
|
123
|
-
|
124
|
-
context "with a callable body" do
|
125
|
-
before { response.body = Proc.new { 'proc' } }
|
126
|
-
|
127
|
-
it "wraps the response body in a CallableEncoder" do
|
128
|
-
subject.encode_body
|
129
|
-
expect(response.body).to be_instance_of(Webmachine::Streaming::CallableEncoder)
|
130
|
-
end
|
131
|
-
|
132
|
-
it_should_behave_like "a non-String body"
|
133
|
-
end
|
134
|
-
|
135
|
-
context "with a Fiber body" do
|
136
|
-
before { response.body = Fiber.new { Fiber.yield "foo" } }
|
137
|
-
|
138
|
-
it "wraps the response body in a FiberEncoder" do
|
139
|
-
subject.encode_body
|
140
|
-
expect(response.body).to be_instance_of(Webmachine::Streaming::FiberEncoder)
|
141
|
-
end
|
142
|
-
|
143
|
-
it_should_behave_like "a non-String body"
|
144
|
-
end
|
145
|
-
|
146
|
-
context "with a File body" do
|
147
|
-
before { response.body = File.open("spec/spec_helper.rb", "r") }
|
148
|
-
|
149
|
-
it "wraps the response body in an IOEncoder" do
|
150
|
-
subject.encode_body
|
151
|
-
expect(response.body).to be_instance_of(Webmachine::Streaming::IOEncoder)
|
152
|
-
end
|
153
|
-
|
154
|
-
it "sets the Content-Length header to the size of the file" do
|
155
|
-
subject.encode_body
|
156
|
-
expect(response.headers['Content-Length']).to eq File.stat('spec/spec_helper.rb').size.to_s
|
157
|
-
end
|
158
|
-
|
159
|
-
it "progressively yields file contents for each enumeration" do
|
160
|
-
subject.encode_body
|
161
|
-
body_size = 0
|
162
|
-
response.body.each do |chunk|
|
163
|
-
expect(chunk).to be_instance_of(String)
|
164
|
-
body_size += chunk.length
|
165
|
-
end
|
166
|
-
expect(body_size).to eq File.stat('spec/spec_helper.rb').size
|
167
|
-
end
|
168
|
-
|
169
|
-
context "when the resource provides a non-identity encoding that the client accepts" do
|
170
|
-
let(:resource) do
|
171
|
-
resource_with do
|
172
|
-
def encodings_provided
|
173
|
-
{ "deflate" => :encode_deflate, "identity" => :encode_identity }
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
let(:headers) do
|
179
|
-
Webmachine::Headers.new({"Accept-Encoding" => "deflate, identity"})
|
180
|
-
end
|
181
|
-
|
182
|
-
it_should_behave_like "a non-String body"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
context "with a StringIO body" do
|
187
|
-
before { response.body = StringIO.new("A VERY LONG STRING, NOT") }
|
188
|
-
|
189
|
-
it "wraps the response body in an IOEncoder" do
|
190
|
-
subject.encode_body
|
191
|
-
expect(response.body).to be_instance_of(Webmachine::Streaming::IOEncoder)
|
192
|
-
end
|
193
|
-
|
194
|
-
it "sets the Content-Length header to the size of the string" do
|
195
|
-
subject.encode_body
|
196
|
-
expect(response.headers['Content-Length']).to eq response.body.size.to_s
|
197
|
-
end
|
198
|
-
|
199
|
-
context "when the resource provides a non-identity encoding that the client accepts" do
|
200
|
-
let(:resource) do
|
201
|
-
resource_with do
|
202
|
-
def encodings_provided
|
203
|
-
{ "deflate" => :encode_deflate, "identity" => :encode_identity }
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
|
-
let(:headers) do
|
209
|
-
Webmachine::Headers.new({"Accept-Encoding" => "deflate, identity"})
|
210
|
-
end
|
211
|
-
|
212
|
-
it_should_behave_like "a non-String body"
|
213
|
-
end
|
214
|
-
end
|
215
|
-
end
|
216
|
-
end
|
@@ -1,22 +0,0 @@
|
|
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,248 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
Webmachine::Dispatcher::Route.class_eval do
|
4
|
-
def warn(*msgs); end # silence warnings for tests
|
5
|
-
end
|
6
|
-
|
7
|
-
describe Webmachine::Dispatcher::Route do
|
8
|
-
let(:method) { "GET" }
|
9
|
-
let(:uri) { URI.parse("http://localhost:8080/") }
|
10
|
-
let(:routing_tokens) { nil }
|
11
|
-
let(:request){ Webmachine::Request.new(method, uri, Webmachine::Headers.new, "", routing_tokens) }
|
12
|
-
let(:resource){ Class.new(Webmachine::Resource) }
|
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
|
-
|
29
|
-
matcher :match_route do |*expected|
|
30
|
-
route = Webmachine::Dispatcher::Route.new(expected[0], Class.new(Webmachine::Resource), expected[1] || {})
|
31
|
-
match do |actual|
|
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)
|
36
|
-
end
|
37
|
-
|
38
|
-
failure_message do |_|
|
39
|
-
"expected route #{expected[0].inspect} to match path #{request.uri.path}"
|
40
|
-
end
|
41
|
-
failure_message_when_negated do |_|
|
42
|
-
"expected route #{expected[0].inspect} not to match path #{request.uri.path}"
|
43
|
-
end
|
44
|
-
end
|
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
|
-
|
54
|
-
context "matching a request" do
|
55
|
-
context "on the root path" do
|
56
|
-
subject { "/" }
|
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] }
|
62
|
-
end
|
63
|
-
|
64
|
-
context "on a deep path" do
|
65
|
-
subject { "/foo/bar/baz" }
|
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', :*] }
|
72
|
-
end
|
73
|
-
|
74
|
-
context "with a guard on the request method" do
|
75
|
-
let(:uri){ URI.parse("http://localhost:8080/notes") }
|
76
|
-
let(:route) do
|
77
|
-
described_class.new(
|
78
|
-
["notes"],
|
79
|
-
lambda { |request| request.method == "POST" },
|
80
|
-
resource
|
81
|
-
)
|
82
|
-
end
|
83
|
-
subject { route }
|
84
|
-
|
85
|
-
context "when guard passes" do
|
86
|
-
let(:method){ "POST" }
|
87
|
-
it { is_expected.to be_match(request) }
|
88
|
-
|
89
|
-
context "but the path match fails" do
|
90
|
-
let(:uri){ URI.parse("http://localhost:8080/other") }
|
91
|
-
it { is_expected.not_to be_match(request) }
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
context "when guard fails" do
|
96
|
-
let(:method) { "GET" }
|
97
|
-
it { is_expected.not_to be_match(request) }
|
98
|
-
end
|
99
|
-
|
100
|
-
context "when the guard responds to #call" do
|
101
|
-
let(:guard_class) do
|
102
|
-
Class.new do
|
103
|
-
def initialize(method)
|
104
|
-
@method = method
|
105
|
-
end
|
106
|
-
|
107
|
-
def call(request)
|
108
|
-
request.method == @method
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
|
-
let(:route) do
|
114
|
-
described_class.new(["notes"], guard_class.new("POST"), resource)
|
115
|
-
end
|
116
|
-
|
117
|
-
context "when the guard passes" do
|
118
|
-
let(:method){ "POST" }
|
119
|
-
it { is_expected.to be_match(request) }
|
120
|
-
end
|
121
|
-
|
122
|
-
context "when the guard fails" do
|
123
|
-
# let(:method){ "GET" }
|
124
|
-
it { is_expected.not_to be_match(request) }
|
125
|
-
end
|
126
|
-
end
|
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
|
140
|
-
end
|
141
|
-
|
142
|
-
context "applying bindings" do
|
143
|
-
context "on the root path" do
|
144
|
-
subject { described_class.new([], resource) }
|
145
|
-
before { subject.apply(request) }
|
146
|
-
|
147
|
-
it "should assign the dispatched path to the empty string" do
|
148
|
-
expect(request.disp_path).to eq("")
|
149
|
-
end
|
150
|
-
|
151
|
-
it "should assign empty bindings" do
|
152
|
-
expect(request.path_info).to eq({})
|
153
|
-
end
|
154
|
-
|
155
|
-
it "should assign empty path tokens" do
|
156
|
-
expect(request.path_tokens).to eq([])
|
157
|
-
end
|
158
|
-
|
159
|
-
context "with extra user-defined bindings" do
|
160
|
-
subject { described_class.new([], resource, "bar" => "baz") }
|
161
|
-
|
162
|
-
it "should assign the user-defined bindings" do
|
163
|
-
expect(request.path_info).to eq({"bar" => "baz"})
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
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
|
176
|
-
subject { described_class.new(['*'], resource) }
|
177
|
-
|
178
|
-
it "should assign empty path tokens" do
|
179
|
-
expect(request.path_tokens).to eq([])
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
context "on a deep path" do
|
184
|
-
subject { described_class.new(%w{foo bar baz}, resource) }
|
185
|
-
let(:uri) { URI.parse("http://localhost:8080/foo/bar/baz") }
|
186
|
-
before { subject.apply(request) }
|
187
|
-
|
188
|
-
it "should assign the dispatched path as the path past the initial slash" do
|
189
|
-
expect(request.disp_path).to eq("foo/bar/baz")
|
190
|
-
end
|
191
|
-
|
192
|
-
it "should assign empty bindings" do
|
193
|
-
expect(request.path_info).to eq({})
|
194
|
-
end
|
195
|
-
|
196
|
-
it "should assign empty path tokens" do
|
197
|
-
expect(request.path_tokens).to eq([])
|
198
|
-
end
|
199
|
-
|
200
|
-
context "with path variables" do
|
201
|
-
subject { described_class.new(['foo', :id, 'baz'], resource) }
|
202
|
-
|
203
|
-
it "should assign the path variables in the bindings" do
|
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"})
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
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
|
240
|
-
subject { described_class.new(%w{foo *}, resource) }
|
241
|
-
|
242
|
-
it "should capture the path tokens matched by the splat" do
|
243
|
-
expect(request.path_tokens).to eq(%w{ bar baz })
|
244
|
-
end
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
248
|
-
end
|