webmachine 1.2.2 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/CHANGELOG.md +57 -0
  4. data/Gemfile +20 -15
  5. data/README.md +89 -91
  6. data/RELEASING.md +21 -0
  7. data/Rakefile +5 -21
  8. data/documentation/adapters.md +41 -0
  9. data/documentation/authentication-and-authorization.md +37 -0
  10. data/documentation/configurator.md +19 -0
  11. data/documentation/error-handling.md +86 -0
  12. data/documentation/examples.md +224 -0
  13. data/documentation/how-it-works.md +76 -0
  14. data/documentation/routes.md +112 -0
  15. data/documentation/validation.md +159 -0
  16. data/documentation/versioning-apis.md +74 -0
  17. data/documentation/visual-debugger.md +38 -0
  18. data/examples/application.rb +2 -2
  19. data/examples/debugger.rb +1 -1
  20. data/lib/webmachine.rb +3 -1
  21. data/lib/webmachine/adapter.rb +7 -13
  22. data/lib/webmachine/adapters.rb +1 -2
  23. data/lib/webmachine/adapters/httpkit.rb +74 -0
  24. data/lib/webmachine/adapters/lazy_request_body.rb +1 -2
  25. data/lib/webmachine/adapters/rack.rb +70 -25
  26. data/lib/webmachine/adapters/rack_mapped.rb +42 -0
  27. data/lib/webmachine/adapters/reel.rb +22 -23
  28. data/lib/webmachine/adapters/webrick.rb +16 -16
  29. data/lib/webmachine/application.rb +2 -2
  30. data/lib/webmachine/chunked_body.rb +3 -4
  31. data/lib/webmachine/configuration.rb +1 -1
  32. data/lib/webmachine/constants.rb +75 -0
  33. data/lib/webmachine/decision/conneg.rb +12 -10
  34. data/lib/webmachine/decision/flow.rb +42 -32
  35. data/lib/webmachine/decision/fsm.rb +14 -21
  36. data/lib/webmachine/decision/helpers.rb +10 -38
  37. data/lib/webmachine/dispatcher.rb +13 -10
  38. data/lib/webmachine/dispatcher/route.rb +45 -9
  39. data/lib/webmachine/errors.rb +9 -3
  40. data/lib/webmachine/events.rb +2 -2
  41. data/lib/webmachine/header_negotiation.rb +25 -0
  42. data/lib/webmachine/headers.rb +8 -3
  43. data/lib/webmachine/locale/en.yml +7 -5
  44. data/lib/webmachine/media_type.rb +10 -8
  45. data/lib/webmachine/request.rb +67 -26
  46. data/lib/webmachine/rescueable_exception.rb +62 -0
  47. data/lib/webmachine/resource.rb +1 -1
  48. data/lib/webmachine/resource/callbacks.rb +11 -9
  49. data/lib/webmachine/response.rb +3 -5
  50. data/lib/webmachine/spec/IO_response.body +1 -0
  51. data/lib/webmachine/spec/adapter_lint.rb +83 -37
  52. data/lib/webmachine/spec/test_resource.rb +15 -4
  53. data/lib/webmachine/streaming/fiber_encoder.rb +1 -5
  54. data/lib/webmachine/streaming/io_encoder.rb +7 -1
  55. data/lib/webmachine/trace.rb +1 -0
  56. data/lib/webmachine/trace/fsm.rb +20 -10
  57. data/lib/webmachine/trace/resource_proxy.rb +2 -0
  58. data/lib/webmachine/translation.rb +2 -1
  59. data/lib/webmachine/version.rb +3 -3
  60. data/memory_test.rb +37 -0
  61. data/spec/spec_helper.rb +17 -9
  62. data/spec/webmachine/adapter_spec.rb +14 -15
  63. data/spec/webmachine/adapters/httpkit_spec.rb +10 -0
  64. data/spec/webmachine/adapters/rack_mapped_spec.rb +71 -0
  65. data/spec/webmachine/adapters/rack_spec.rb +32 -6
  66. data/spec/webmachine/adapters/reel_spec.rb +16 -12
  67. data/spec/webmachine/adapters/webrick_spec.rb +2 -2
  68. data/spec/webmachine/application_spec.rb +18 -17
  69. data/spec/webmachine/chunked_body_spec.rb +3 -3
  70. data/spec/webmachine/configuration_spec.rb +5 -5
  71. data/spec/webmachine/cookie_spec.rb +13 -13
  72. data/spec/webmachine/decision/conneg_spec.rb +49 -43
  73. data/spec/webmachine/decision/falsey_spec.rb +4 -4
  74. data/spec/webmachine/decision/flow_spec.rb +195 -145
  75. data/spec/webmachine/decision/fsm_spec.rb +81 -19
  76. data/spec/webmachine/decision/helpers_spec.rb +20 -20
  77. data/spec/webmachine/dispatcher/rfc3986_percent_decode_spec.rb +22 -0
  78. data/spec/webmachine/dispatcher/route_spec.rb +114 -32
  79. data/spec/webmachine/dispatcher_spec.rb +49 -24
  80. data/spec/webmachine/errors_spec.rb +1 -1
  81. data/spec/webmachine/etags_spec.rb +19 -19
  82. data/spec/webmachine/events_spec.rb +6 -6
  83. data/spec/webmachine/headers_spec.rb +14 -14
  84. data/spec/webmachine/media_type_spec.rb +36 -36
  85. data/spec/webmachine/request_spec.rb +70 -39
  86. data/spec/webmachine/rescueable_exception_spec.rb +15 -0
  87. data/spec/webmachine/resource/authentication_spec.rb +6 -6
  88. data/spec/webmachine/response_spec.rb +18 -12
  89. data/spec/webmachine/trace/fsm_spec.rb +8 -8
  90. data/spec/webmachine/trace/resource_proxy_spec.rb +9 -9
  91. data/spec/webmachine/trace/trace_store_spec.rb +5 -5
  92. data/spec/webmachine/trace_spec.rb +3 -3
  93. data/webmachine.gemspec +2 -6
  94. metadata +78 -228
  95. data/lib/webmachine/adapters/hatetepe.rb +0 -108
  96. data/lib/webmachine/adapters/mongrel.rb +0 -127
  97. data/lib/webmachine/dispatcher/not_found_resource.rb +0 -5
  98. data/lib/webmachine/fiber18.rb +0 -88
  99. data/spec/webmachine/adapters/hatetepe_spec.rb +0 -60
  100. 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(:exception) { Exception.new }
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.stub(Webmachine::Decision::Flow::START) { raise exception }
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.should_receive(:handle_exception).with(exception)
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.should_receive(:finish_request)
64
+ expect(resource).to receive(:finish_request)
22
65
  subject.run
23
66
  end
24
67
  end
25
68
 
26
- describe 'handling of exceptions from resource.handle_exception' do
27
- let(:exception) { Exception.new('an error message') }
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.stub(Webmachine::Decision::Flow::START) { raise }
31
- resource.stub(:handle_exception) { raise exception }
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.should_receive(:handle_exception).once { raise }
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.should_not_receive(:finish_request)
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
- should_receive(:render_error).
47
- with(500, request, response, { :message => exception.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.stub(:finish_request) { raise exception }
118
+ allow(resource).to receive(:finish_request) { raise error }
57
119
  end
58
120
 
59
121
  it 'calls resource.handle_exception' do
60
- resource.should_receive(:handle_exception).with(exception)
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.should_receive(:finish_request).once { raise }
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.should be(201)
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.should be(451)
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.should == 415
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.should == 415
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.should_receive(:other).and_return(true)
43
+ expect(resource).to receive(:other).and_return(true)
44
44
  headers['Content-Type'] = 'application/json;v=2'
45
- subject.accept_helper.should be_true
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.should_not include 'Content-Length'
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'].should == '0'
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.should_not include 'Content-Length'
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
- String.should === response.body
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'].should == response.body.bytesize.to_s
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.should_not have_key('Content-Length')
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'].should == 'chunked'
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.should === response.body
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.should === response.body
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.should === response.body
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.should === response.body
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'].should == File.stat('spec/spec_helper.rb').size.to_s
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.should be_a(String)
163
+ expect(chunk).to be_instance_of(String)
164
164
  body_size += chunk.length
165
165
  end
166
- body_size.should == File.stat('spec/spec_helper.rb').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.should === response.body
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'].should == response.body.size.to_s
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(:request){ Webmachine::Request.new(method, uri, Webmachine::Headers.new, "") }
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
- request.uri.path = actual if String === actual
13
- route.match?(request)
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
- failure_message_for_should do |_|
38
+ failure_message do |_|
17
39
  "expected route #{expected[0].inspect} to match path #{request.uri.path}"
18
40
  end
19
- failure_message_for_should_not do |_|
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 { should match_route([]) }
28
- it { should match_route ['*'] }
29
- it { should_not match_route %w{foo} }
30
- it { should_not match_route [:id] }
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 { should match_route %w{foo bar baz} }
36
- it { should match_route ['foo', :id, "baz"] }
37
- it { should match_route %w{foo *} }
38
- it { should match_route [:id, '*'] }
39
- it { should_not match_route [] }
40
- it { should_not match_route %w{bar *} }
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 { should be_match(request) }
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 { should_not be_match(request) }
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 { should_not be_match(request) }
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 { should be_match(request) }
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 { should_not be_match(request) }
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.should == ""
148
+ expect(request.disp_path).to eq("")
106
149
  end
107
150
 
108
151
  it "should assign empty bindings" do
109
- request.path_info.should == {}
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.should == []
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.should == {"bar" => "baz"}
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.should == []
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
- before { request.uri.path = "/foo/bar/baz"; subject.apply(request) }
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.should == "foo/bar/baz"
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.should == {}
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.should == []
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.should == {:id => "bar"}
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.should == %w{ bar baz }
243
+ expect(request.path_tokens).to eq(%w{ bar baz })
162
244
  end
163
245
  end
164
246
  end