http_router 0.0.1
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.
- data/README.rdoc +60 -0
- data/Rakefile +32 -0
- data/VERSION +1 -0
- data/lib/http_router/glob.rb +18 -0
- data/lib/http_router/node.rb +107 -0
- data/lib/http_router/path.rb +38 -0
- data/lib/http_router/response.rb +20 -0
- data/lib/http_router/root.rb +103 -0
- data/lib/http_router/route.rb +102 -0
- data/lib/http_router/sinatra.rb +149 -0
- data/lib/http_router/variable.rb +26 -0
- data/lib/http_router.rb +250 -0
- data/lib/rack/uri_escape.rb +38 -0
- data/spec/generate_spec.rb +54 -0
- data/spec/rack/dispatch_spec.rb +113 -0
- data/spec/rack/generate_spec.rb +27 -0
- data/spec/rack/route_spec.rb +70 -0
- data/spec/recognize_spec.rb +176 -0
- data/spec/sinatra/recognize_spec.rb +140 -0
- data/spec/spec.opts +7 -0
- data/spec/spec_helper.rb +24 -0
- metadata +88 -0
data/lib/http_router.rb
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
require 'rack'
|
3
|
+
require 'rack/uri_escape'
|
4
|
+
|
5
|
+
class HttpRouter
|
6
|
+
autoload :Node, 'http_router/node'
|
7
|
+
autoload :Root, 'http_router/root'
|
8
|
+
autoload :Variable, 'http_router/variable'
|
9
|
+
autoload :Glob, 'http_router/glob'
|
10
|
+
autoload :Route, 'http_router/route'
|
11
|
+
autoload :Response, 'http_router/response'
|
12
|
+
autoload :Path, 'http_router/path'
|
13
|
+
|
14
|
+
UngeneratableRouteException = Class.new(RuntimeError)
|
15
|
+
MissingParameterException = Class.new(RuntimeError)
|
16
|
+
TooManyParametersException = Class.new(RuntimeError)
|
17
|
+
RoutingError = Struct.new(:status, :headers)
|
18
|
+
|
19
|
+
attr_reader :routes
|
20
|
+
|
21
|
+
def initialize(options = nil)
|
22
|
+
reset!
|
23
|
+
@default_app = options && options[:default_app] || proc{|env| ::Rack::Response.new("Not Found", 404).finish }
|
24
|
+
@ignore_trailing_slash = options && options.key?(:ignore_trailing_slash) ? options[:ignore_trailing_slash] : true
|
25
|
+
@redirect_trailing_slash = options && options.key?(:redirect_trailing_slash) ? options[:redirect_trailing_slash] : false
|
26
|
+
end
|
27
|
+
|
28
|
+
def ignore_trailing_slash?
|
29
|
+
@ignore_trailing_slash
|
30
|
+
end
|
31
|
+
|
32
|
+
def redirect_trailing_slash?
|
33
|
+
@redirect_trailing_slash
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset!
|
37
|
+
@root = Root.new(self)
|
38
|
+
@routes = {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def default(app)
|
42
|
+
@default_app = app
|
43
|
+
end
|
44
|
+
|
45
|
+
def split(path, with_delimiter = false)
|
46
|
+
path.slice!(0) if path[0] == ?/
|
47
|
+
with_delimiter ? path.split('(/)') : path.split('/')
|
48
|
+
end
|
49
|
+
|
50
|
+
def add(path, options = nil)
|
51
|
+
path = path.dup
|
52
|
+
partially_match = extract_partial_match(path)
|
53
|
+
trailing_slash_ignore = extract_trailing_slash(path)
|
54
|
+
paths = compile(path, options)
|
55
|
+
|
56
|
+
route = Route.new(self, options && options[:default_values])
|
57
|
+
route.trailing_slash_ignore = trailing_slash_ignore
|
58
|
+
route.partially_match = partially_match
|
59
|
+
paths.each_with_index do |path, i|
|
60
|
+
current_node = @root
|
61
|
+
path.parts.each { |part| current_node = current_node.add(part) }
|
62
|
+
working_set = current_node.add_request_methods(options)
|
63
|
+
working_set.each do |current_node|
|
64
|
+
current_node.value = path
|
65
|
+
path.route = route
|
66
|
+
route.paths << current_node.value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
route
|
70
|
+
end
|
71
|
+
|
72
|
+
def get(path, options = {})
|
73
|
+
options[:conditions] ||= {}
|
74
|
+
options[:conditions][:request_method] = ['HEAD', 'GET'] #TODO, this should be able to take an array
|
75
|
+
add(path, options)
|
76
|
+
end
|
77
|
+
|
78
|
+
def post(path, options = {})
|
79
|
+
options[:conditions] ||= {}
|
80
|
+
options[:conditions][:request_method] = 'POST'
|
81
|
+
add(path, options)
|
82
|
+
end
|
83
|
+
|
84
|
+
def put(path, options = {})
|
85
|
+
options[:conditions] ||= {}
|
86
|
+
options[:conditions][:request_method] = 'PUT'
|
87
|
+
add(path, options)
|
88
|
+
end
|
89
|
+
|
90
|
+
def delete(path, options = {})
|
91
|
+
options[:conditions] ||= {}
|
92
|
+
options[:conditions][:request_method] = 'DELETE'
|
93
|
+
add(path, options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def only_get(path, options = {})
|
97
|
+
options[:conditions] ||= {}
|
98
|
+
options[:conditions][:request_method] = "GET"
|
99
|
+
add(path, options)
|
100
|
+
end
|
101
|
+
|
102
|
+
def extract_partial_match(path)
|
103
|
+
if path[-1] == ?*
|
104
|
+
path.slice!(-1)
|
105
|
+
true
|
106
|
+
else
|
107
|
+
false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def extract_trailing_slash(path)
|
112
|
+
if path[-2, 2] == '/?'
|
113
|
+
path.slice!(-2, 2)
|
114
|
+
true
|
115
|
+
else
|
116
|
+
false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def extract_extension(path)
|
121
|
+
if match = path.match(/^(.*)(\.:([a-zA-Z_]+))$/)
|
122
|
+
path.replace(match[1])
|
123
|
+
Variable.new(self, match[3].to_sym)
|
124
|
+
elsif match = path.match(/^(.*)(\.([a-zA-Z_]+))$/)
|
125
|
+
path.replace(match[1])
|
126
|
+
match[3]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def compile(path, options)
|
131
|
+
start_index = 0
|
132
|
+
end_index = 1
|
133
|
+
|
134
|
+
paths = [""]
|
135
|
+
chars = path.split('')
|
136
|
+
|
137
|
+
chars.each do |c|
|
138
|
+
case c
|
139
|
+
when '('
|
140
|
+
# over current working set, double paths
|
141
|
+
(start_index...end_index).each do |path_index|
|
142
|
+
paths << paths[path_index].dup
|
143
|
+
end
|
144
|
+
start_index = end_index
|
145
|
+
end_index = paths.size
|
146
|
+
when ')'
|
147
|
+
start_index -= end_index - start_index
|
148
|
+
else
|
149
|
+
(start_index...end_index).each do |path_index|
|
150
|
+
paths[path_index] << c
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
variables = {}
|
156
|
+
paths.map do |path|
|
157
|
+
original_path = path.dup
|
158
|
+
extension = extract_extension(path)
|
159
|
+
new_path = split(path).map do |part|
|
160
|
+
case part[0]
|
161
|
+
when ?:
|
162
|
+
v_name = part[1, part.size].to_sym
|
163
|
+
variables[v_name] ||= Variable.new(self, v_name, options && options[:matches_with] && options && options[:matches_with][v_name])
|
164
|
+
when ?*
|
165
|
+
v_name = part[1, part.size].to_sym
|
166
|
+
variables[v_name] ||= Glob.new(self, v_name, options && options[:matches_with] && options && options[:matches_with][v_name])
|
167
|
+
else
|
168
|
+
part_segments = part.split(/(:[a-zA-Z_]+)/)
|
169
|
+
if part_segments.size > 1
|
170
|
+
index = 0
|
171
|
+
part_segments.map do |seg|
|
172
|
+
new_seg = if seg[0] == ?:
|
173
|
+
next_index = index + 1
|
174
|
+
scan_regex = if next_index == part_segments.size
|
175
|
+
/^[^\/]+/
|
176
|
+
else
|
177
|
+
/^.*?(?=#{Regexp.quote(part_segments[next_index])})/
|
178
|
+
end
|
179
|
+
v_name = seg[1, seg.size].to_sym
|
180
|
+
variables[v_name] ||= Variable.new(self, v_name, scan_regex)
|
181
|
+
else
|
182
|
+
/^#{Regexp.quote(seg)}/
|
183
|
+
end
|
184
|
+
index += 1
|
185
|
+
new_seg
|
186
|
+
end
|
187
|
+
else
|
188
|
+
part
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
new_path.flatten!
|
193
|
+
Path.new(original_path, new_path, extension)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def call(env)
|
198
|
+
request = Rack::Request.new(env)
|
199
|
+
if redirect_trailing_slash? && (request.head? || request.get?) && request.path_info[-1] == ?/
|
200
|
+
response = Rack::Response.new
|
201
|
+
response.redirect(request.path_info[0, request.path_info.size - 1], 302)
|
202
|
+
response.finish
|
203
|
+
else
|
204
|
+
response = recognize(request)
|
205
|
+
env['router'] = self
|
206
|
+
if response.is_a?(RoutingError)
|
207
|
+
[response.status, response.headers, []]
|
208
|
+
elsif response && response.route.dest && response.route.dest.respond_to?(:call)
|
209
|
+
process_params(env, response)
|
210
|
+
consume_path!(request, response) if response.partial_match?
|
211
|
+
#if response.rest
|
212
|
+
# request.env["SCRIPT_NAME"] += request.env["PATH_INFO"][0, -response.rest.size]
|
213
|
+
# request.env["PATH_INFO"] = response.rest || ''
|
214
|
+
#end
|
215
|
+
response.route.dest.call(env)
|
216
|
+
else
|
217
|
+
@default_app.call(env)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def consume_path!(request, response)
|
223
|
+
request.env["SCRIPT_NAME"] = (request.env["SCRIPT_NAME"] + response.matched_path)
|
224
|
+
request.env["PATH_INFO"] = response.remaining_path || ""
|
225
|
+
end
|
226
|
+
|
227
|
+
def process_params(env, response)
|
228
|
+
if env.key?('router.params')
|
229
|
+
env['router.params'].merge!(response.route.default_values) if response.route.default_values
|
230
|
+
env['router.params'].merge!(response.params_as_hash)
|
231
|
+
else
|
232
|
+
env['router.params'] = response.route.default_values ? response.route.default_values.merge(response.params_as_hash) : response.params_as_hash
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def recognize(env)
|
237
|
+
response = @root.find(env.is_a?(Hash) ? Rack::Request.new(env) : env)
|
238
|
+
end
|
239
|
+
|
240
|
+
def url(route, *args)
|
241
|
+
case route
|
242
|
+
when Symbol
|
243
|
+
url(@routes[route], *args)
|
244
|
+
when nil
|
245
|
+
raise UngeneratableRouteException.new
|
246
|
+
else
|
247
|
+
route.url(*args)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
unless Rack::Utils.respond_to?(:uri_escape)
|
2
|
+
module Rack
|
3
|
+
module Utils
|
4
|
+
def uri_escape(s)
|
5
|
+
s.to_s.gsub(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
|
6
|
+
'%'<<$1.unpack('H2'*$1.size).join('%').upcase
|
7
|
+
}
|
8
|
+
end
|
9
|
+
module_function :uri_escape
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
unless Rack::Utils.respond_to?(:uri_escape!)
|
15
|
+
module Rack
|
16
|
+
module Utils
|
17
|
+
def uri_escape!(s)
|
18
|
+
s.to_s.gsub!(/([^:\/?\[\]\-_~\.!\$&'\(\)\*\+,;=@a-zA-Z0-9]+)/n) {
|
19
|
+
'%'<<$1.unpack('H2'*$1.size).join('%').upcase
|
20
|
+
}
|
21
|
+
end
|
22
|
+
module_function :uri_escape!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
unless Rack::Utils.respond_to?(:uri_unescape)
|
28
|
+
module Rack
|
29
|
+
module Utils
|
30
|
+
def uri_unescape(s)
|
31
|
+
gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
32
|
+
[$1.delete('%')].pack('H*')
|
33
|
+
}
|
34
|
+
end
|
35
|
+
module_function :uri_unescape
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
describe "HttpRouter#generate" do
|
2
|
+
before(:each) do
|
3
|
+
@router = HttpRouter.new
|
4
|
+
end
|
5
|
+
|
6
|
+
context("static paths") do
|
7
|
+
['/', '/test', '/test/time', '/one/more/what', '/test.html'].each do |path|
|
8
|
+
it "should generate #{path.inspect}" do
|
9
|
+
route = @router.add(path)
|
10
|
+
@router.url(route).should == path
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context("dynamic paths") do
|
16
|
+
it "should generate from a hash" do
|
17
|
+
@router.add("/:var").name(:test)
|
18
|
+
@router.url(:test, :var => 'test').should == '/test'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should generate from an array" do
|
22
|
+
@router.add("/:var").name(:test)
|
23
|
+
@router.url(:test, 'test').should == '/test'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should generate with a format" do
|
27
|
+
@router.add("/test.:format").name(:test)
|
28
|
+
@router.url(:test, 'html').should == '/test.html'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should generate with a format as a hash" do
|
32
|
+
@router.add("/test.:format").name(:test)
|
33
|
+
@router.url(:test, :format => 'html').should == '/test.html'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should generate with an optional format" do
|
37
|
+
@router.add("/test(.:format)").name(:test)
|
38
|
+
@router.url(:test, 'html').should == '/test.html'
|
39
|
+
@router.url(:test).should == '/test'
|
40
|
+
end
|
41
|
+
|
42
|
+
context "with optional parts" do
|
43
|
+
it "should generate both" do
|
44
|
+
@router.add("/:var1(/:var2)").name(:test)
|
45
|
+
@router.url(:test, 'var').should == '/var'
|
46
|
+
@router.url(:test, 'var', 'fooz').should == '/var/fooz'
|
47
|
+
@router.url(:test, :var1 => 'var').should == '/var'
|
48
|
+
@router.url(:test, :var1 => 'var', :var2 => 'fooz').should == '/var/fooz'
|
49
|
+
proc{@router.url(:test, :var2 => 'fooz').should == '/var/fooz'}.should raise_error(HttpRouter::UngeneratableRouteException)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
route_set = HttpRouter.new
|
2
|
+
route_set.extend(CallWithMockRequestMixin)
|
3
|
+
|
4
|
+
describe "Usher (for rack) route dispatching with redirect_on_trailing_delimiters" do
|
5
|
+
before(:each) do
|
6
|
+
@route_set = HttpRouter.new(:redirect_trailing_slash => true)
|
7
|
+
@route_set.extend(CallWithMockRequestMixin)
|
8
|
+
@app = MockApp.new("Hello World!")
|
9
|
+
@route_set.add('/sample').to(@app)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should dispatch a request" do
|
13
|
+
response = @route_set.call_with_mock_request('/sample/')
|
14
|
+
response.headers["Location"].should == "/sample"
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "Usher (for rack) route dispatching" do
|
20
|
+
before(:each) do
|
21
|
+
route_set.reset!
|
22
|
+
@app = MockApp.new("Hello World!")
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "HTTP GET" do
|
26
|
+
before(:each) do
|
27
|
+
route_set.reset!
|
28
|
+
route_set.add('/sample', :conditions => {:request_method => 'GET'}).to(@app)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should dispatch a request" do
|
32
|
+
response = route_set.call_with_mock_request
|
33
|
+
response.body.should eql("Hello World!")
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should write router.params" do
|
37
|
+
response = route_set.call_with_mock_request
|
38
|
+
@app.env["router.params"].should == {}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "HTTP POST" do
|
43
|
+
before(:each) do
|
44
|
+
route_set.reset!
|
45
|
+
route_set.add('/sample', :conditions => {:request_method => 'POST'}).to(@app)
|
46
|
+
route_set.add('/sample').to(MockApp.new("You shouldn't get here if you are using POST"))
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should dispatch a POST request" do
|
50
|
+
response = route_set.call_with_mock_request('/sample', 'POST')
|
51
|
+
response.body.should eql("Hello World!")
|
52
|
+
end
|
53
|
+
|
54
|
+
it "shouldn't dispatch a GET request" do
|
55
|
+
response = route_set.call_with_mock_request('/sample', 'GET')
|
56
|
+
response.body.should eql("You shouldn't get here if you are using POST")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should write router.params" do
|
60
|
+
response = route_set.call_with_mock_request("/sample", 'POST')
|
61
|
+
@app.env["router.params"].should == {}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should returns HTTP 405 if the method mis-matches" do
|
66
|
+
route_set.reset!
|
67
|
+
route_set.add('/sample', :conditions => {:request_method => 'POST'}).to(@app)
|
68
|
+
route_set.add('/sample', :conditions => {:request_method => 'PUT'}).to(@app)
|
69
|
+
response = route_set.call_with_mock_request('/sample', 'GET')
|
70
|
+
response.status.should eql(405)
|
71
|
+
response['Allow'].should == 'POST, PUT'
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should returns HTTP 404 if route doesn't exist" do
|
75
|
+
response = route_set.call_with_mock_request("/not-existing-url")
|
76
|
+
response.status.should eql(404)
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "shortcuts" do
|
80
|
+
describe "get" do
|
81
|
+
before(:each) do
|
82
|
+
route_set.reset!
|
83
|
+
route_set.get('/sample').to(@app)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should dispatch a GET request" do
|
87
|
+
response = route_set.call_with_mock_request("/sample", "GET")
|
88
|
+
response.body.should eql("Hello World!")
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should dispatch a HEAD request" do
|
92
|
+
response = route_set.call_with_mock_request("/sample", "HEAD")
|
93
|
+
response.body.should eql("Hello World!")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "non rack app destinations" do
|
99
|
+
it "should route to a default application when using a hash" do
|
100
|
+
$captures = []
|
101
|
+
@default_app = lambda do |e|
|
102
|
+
$captures << :default
|
103
|
+
Rack::Response.new("Default").finish
|
104
|
+
end
|
105
|
+
@router = HttpRouter.new
|
106
|
+
@router.default(@default_app)
|
107
|
+
@router.add("/default").to(:action => "default")
|
108
|
+
response = @router.call(Rack::MockRequest.env_for("/default"))
|
109
|
+
$captures.should == [:default]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
route_set = HttpRouter.new
|
2
|
+
route_set.extend(CallWithMockRequestMixin)
|
3
|
+
|
4
|
+
describe "Usher (for rack) route generation" do
|
5
|
+
before(:each) do
|
6
|
+
route_set.reset!
|
7
|
+
@app = MockApp.new("Hello World!")
|
8
|
+
route_set.add("/fixed").name(:fixed)
|
9
|
+
route_set.add("/named/simple/:named_simple_var").name(:simple)
|
10
|
+
route_set.add("/named/optional(/:named_optional_var)").name(:optional)
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "named routes" do
|
14
|
+
it "should generate a fixed path" do
|
15
|
+
route_set.url(:fixed).should == "/fixed"
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should generate a named path route" do
|
19
|
+
route_set.url(:simple, :named_simple_var => "the_var").should == "/named/simple/the_var"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should generate a named route with options" do
|
23
|
+
route_set.url(:optional).should == "/named/optional"
|
24
|
+
route_set.url(:optional, :named_optional_var => "the_var").should == "/named/optional/the_var"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
describe "Rack interface extensions for Usher::Route" do
|
2
|
+
before(:each) do
|
3
|
+
@route_set = HttpRouter.new
|
4
|
+
@app = MockApp.new("Hello World!")
|
5
|
+
@env = Rack::MockRequest.env_for("/index.html")
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "basic functinality" do
|
9
|
+
it "should set redirect headers" do
|
10
|
+
@route_set.get("/index.html").redirect("/")
|
11
|
+
raw_response = @route_set.call(@env)
|
12
|
+
response = Rack::MockResponse.new(*raw_response)
|
13
|
+
response.should be_redirect
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should redirect '/index.html' to '/'" do
|
17
|
+
@route_set.get("/index.html").redirect("/")
|
18
|
+
status, headers, body = @route_set.call(@env)
|
19
|
+
headers["Location"].should eql("/")
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should redirect '/:id.html' to '/:id'" do
|
23
|
+
@route_set.get("/:id.html").redirect('/#{params[:id]}')
|
24
|
+
@env = Rack::MockRequest.env_for("/123.html")
|
25
|
+
status, headers, body = @route_set.call(@env)
|
26
|
+
headers["Location"].should eql("/123")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "static file serving" do
|
31
|
+
it "should serve from a static directory" do
|
32
|
+
@route_set.get("/static").serves_static_from(File.dirname(__FILE__))
|
33
|
+
@env = Rack::MockRequest.env_for("/static/#{File.basename(__FILE__)}")
|
34
|
+
status, headers, body = @route_set.call(@env)
|
35
|
+
body.path.should == File.join(File.dirname(__FILE__), File.basename(__FILE__))
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should serve a specific file" do
|
39
|
+
@route_set.get("/static-file").serves_static_from(__FILE__)
|
40
|
+
@env = Rack::MockRequest.env_for("/static-file")
|
41
|
+
status, headers, body = @route_set.call(@env)
|
42
|
+
body.path.should == __FILE__
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "chaining" do
|
47
|
+
it "should be chainable" do
|
48
|
+
@route_set.get("/index.html").redirect("/").name(:root)
|
49
|
+
url = @route_set.url(:root)
|
50
|
+
url.should eql("/index.html")
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should not influence actual invoking" do
|
54
|
+
@route_set.get("/index.html").redirect("/").name(:root)
|
55
|
+
@route_set.call(@env)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "custom status" do
|
60
|
+
it "should enable to set custom HTTP status" do
|
61
|
+
@route_set.get("/index.html").redirect("/", 303)
|
62
|
+
status, headers, body = @route_set.call(@env)
|
63
|
+
status.should eql(303)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should raise an exception if given HTTP code isn't a redirection" do
|
67
|
+
lambda { @route_set.get("/index.html").redirect("/", 200) }.should raise_error(ArgumentError)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|