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.
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