sinatra 0.9.1.1 → 0.9.2
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.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- data/CHANGES +81 -0
- data/LICENSE +1 -1
- data/README.rdoc +63 -23
- data/Rakefile +25 -23
- data/compat/haml_test.rb +7 -4
- data/compat/helper.rb +3 -0
- data/compat/sass_test.rb +10 -0
- data/lib/sinatra/base.rb +240 -148
- data/lib/sinatra/compat.rb +16 -0
- data/lib/sinatra/main.rb +2 -14
- data/lib/sinatra/showexceptions.rb +303 -0
- data/lib/sinatra/test.rb +2 -0
- data/sinatra.gemspec +13 -4
- data/test/base_test.rb +123 -93
- data/test/builder_test.rb +2 -1
- data/test/contest.rb +64 -0
- data/test/erb_test.rb +1 -1
- data/test/extensions_test.rb +17 -1
- data/test/filter_test.rb +1 -1
- data/test/haml_test.rb +25 -3
- data/test/helper.rb +43 -48
- data/test/helpers_test.rb +446 -440
- data/test/mapped_error_test.rb +138 -143
- data/test/middleware_test.rb +2 -2
- data/test/options_test.rb +241 -248
- data/test/render_backtrace_test.rb +145 -0
- data/test/request_test.rb +1 -1
- data/test/response_test.rb +2 -2
- data/test/result_test.rb +1 -1
- data/test/route_added_hook_test.rb +59 -0
- data/test/routing_test.rb +94 -22
- data/test/sass_test.rb +44 -1
- data/test/server_test.rb +5 -3
- data/test/sinatra_test.rb +1 -1
- data/test/static_test.rb +2 -2
- data/test/templates_test.rb +16 -4
- data/test/test_test.rb +15 -7
- data/test/views/error.builder +3 -0
- data/test/views/error.erb +3 -0
- data/test/views/error.haml +3 -0
- data/test/views/error.sass +2 -0
- metadata +32 -4
- data/test/reload_test.rb +0 -68
data/test/builder_test.rb
CHANGED
data/test/contest.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "test/unit"
|
2
|
+
|
3
|
+
# Test::Unit loads a default test if the suite is empty, and the only
|
4
|
+
# purpose of that test is to fail. As having empty contexts is a common
|
5
|
+
# practice, we decided to overwrite TestSuite#empty? in order to
|
6
|
+
# allow them. Having a failure when no tests have been defined seems
|
7
|
+
# counter-intuitive.
|
8
|
+
class Test::Unit::TestSuite
|
9
|
+
unless method_defined?(:empty?)
|
10
|
+
def empty?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# We added setup, test and context as class methods, and the instance
|
17
|
+
# method setup now iterates on the setup blocks. Note that all setup
|
18
|
+
# blocks must be defined with the block syntax. Adding a setup instance
|
19
|
+
# method defeats the purpose of this library.
|
20
|
+
class Test::Unit::TestCase
|
21
|
+
def self.setup(&block)
|
22
|
+
setup_blocks << block
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup
|
26
|
+
self.class.setup_blocks.each do |block|
|
27
|
+
instance_eval(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.context(name, &block)
|
32
|
+
subclass = Class.new(self.superclass)
|
33
|
+
subclass.setup_blocks.unshift(*setup_blocks)
|
34
|
+
subclass.class_eval(&block)
|
35
|
+
const_set(context_name(name), subclass)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.test(name, &block)
|
39
|
+
define_method(test_name(name), &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
class << self
|
43
|
+
alias_method :should, :test
|
44
|
+
alias_method :describe, :context
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def self.setup_blocks
|
50
|
+
@setup_blocks ||= []
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.context_name(name)
|
54
|
+
"Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.test_name(name)
|
58
|
+
"test_#{sanitize_name(name).gsub(/\s+/,'_')}".to_sym
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.sanitize_name(name)
|
62
|
+
name.gsub(/\W+/, ' ').strip
|
63
|
+
end
|
64
|
+
end
|
data/test/erb_test.rb
CHANGED
data/test/extensions_test.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/helper'
|
2
2
|
|
3
|
-
|
3
|
+
class ExtensionsTest < Test::Unit::TestCase
|
4
4
|
module FooExtensions
|
5
5
|
def foo
|
6
6
|
end
|
@@ -25,6 +25,12 @@ describe 'Registering extensions' do
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
module PainExtensions
|
29
|
+
def foo=(name); end
|
30
|
+
def bar?(name); end
|
31
|
+
def fizz!(name); end
|
32
|
+
end
|
33
|
+
|
28
34
|
it 'will add the methods to the DSL for the class in which you register them and its subclasses' do
|
29
35
|
Sinatra::Base.register FooExtensions
|
30
36
|
assert Sinatra::Base.respond_to?(:foo)
|
@@ -50,6 +56,16 @@ describe 'Registering extensions' do
|
|
50
56
|
map { |m| m.to_sym }.include?(:im_hiding_in_ur_foos)
|
51
57
|
end
|
52
58
|
|
59
|
+
it 'will handle special method names' do
|
60
|
+
Sinatra::Default.register PainExtensions
|
61
|
+
assert Sinatra::Delegator.private_instance_methods.
|
62
|
+
map { |m| m.to_sym }.include?(:foo=)
|
63
|
+
assert Sinatra::Delegator.private_instance_methods.
|
64
|
+
map { |m| m.to_sym }.include?(:bar?)
|
65
|
+
assert Sinatra::Delegator.private_instance_methods.
|
66
|
+
map { |m| m.to_sym }.include?(:fizz!)
|
67
|
+
end
|
68
|
+
|
53
69
|
it 'will not delegate methods on Base#register' do
|
54
70
|
Sinatra::Base.register QuuxExtensions
|
55
71
|
assert !Sinatra::Delegator.private_instance_methods.include?("quux")
|
data/test/filter_test.rb
CHANGED
data/test/haml_test.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/helper'
|
2
|
+
require 'haml'
|
2
3
|
|
3
|
-
|
4
|
+
class HAMLTest < Test::Unit::TestCase
|
4
5
|
def haml_app(&block)
|
5
6
|
mock_app {
|
6
7
|
set :views, File.dirname(__FILE__) + '/views'
|
@@ -47,9 +48,12 @@ describe "HAML Templates" do
|
|
47
48
|
end
|
48
49
|
|
49
50
|
it "passes HAML options to the Haml engine" do
|
50
|
-
|
51
|
-
|
51
|
+
mock_app {
|
52
|
+
get '/' do
|
53
|
+
haml "!!!\n%h1 Hello World", :format => :html5
|
54
|
+
end
|
52
55
|
}
|
56
|
+
get '/'
|
53
57
|
assert ok?
|
54
58
|
assert_equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n", body
|
55
59
|
end
|
@@ -65,4 +69,22 @@ describe "HAML Templates" do
|
|
65
69
|
assert ok?
|
66
70
|
assert_equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n", body
|
67
71
|
end
|
72
|
+
|
73
|
+
it "merges the default HAML options with the overrides and passes them to the Haml engine" do
|
74
|
+
mock_app {
|
75
|
+
set :haml, {:format => :html5, :attr_wrapper => '"'} # default HAML attr are <tag attr='single-quoted'>
|
76
|
+
get '/' do
|
77
|
+
haml "!!!\n%h1{:class => :header} Hello World"
|
78
|
+
end
|
79
|
+
get '/html4' do
|
80
|
+
haml "!!!\n%h1{:class => 'header'} Hello World", :format => :html4
|
81
|
+
end
|
82
|
+
}
|
83
|
+
get '/'
|
84
|
+
assert ok?
|
85
|
+
assert_equal "<!DOCTYPE html>\n<h1 class=\"header\">Hello World</h1>\n", body
|
86
|
+
get '/html4'
|
87
|
+
assert ok?
|
88
|
+
assert_match(/^<!DOCTYPE html PUBLIC (.*) HTML 4.01/, body)
|
89
|
+
end
|
68
90
|
end
|
data/test/helper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'rack'
|
3
5
|
rescue LoadError
|
@@ -5,19 +7,35 @@ rescue LoadError
|
|
5
7
|
require 'rack'
|
6
8
|
end
|
7
9
|
|
10
|
+
testdir = File.dirname(__FILE__)
|
11
|
+
$LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir)
|
12
|
+
|
8
13
|
libdir = File.dirname(File.dirname(__FILE__)) + '/lib'
|
9
14
|
$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
|
10
15
|
|
11
|
-
require '
|
12
|
-
require '
|
16
|
+
require 'contest'
|
17
|
+
require 'rack/test'
|
18
|
+
require 'sinatra/base'
|
13
19
|
|
14
20
|
class Sinatra::Base
|
15
21
|
# Allow assertions in request context
|
16
22
|
include Test::Unit::Assertions
|
17
23
|
end
|
18
24
|
|
25
|
+
Sinatra::Base.set :environment, :test
|
26
|
+
|
19
27
|
class Test::Unit::TestCase
|
20
|
-
include
|
28
|
+
include Rack::Test::Methods
|
29
|
+
|
30
|
+
class << self
|
31
|
+
alias_method :it, :test
|
32
|
+
end
|
33
|
+
|
34
|
+
alias_method :response, :last_response
|
35
|
+
|
36
|
+
setup do
|
37
|
+
Sinatra::Base.set :environment, :test
|
38
|
+
end
|
21
39
|
|
22
40
|
# Sets up a Sinatra::Base subclass defined with the block
|
23
41
|
# given. Used in setup or individual spec methods to establish
|
@@ -26,56 +44,33 @@ class Test::Unit::TestCase
|
|
26
44
|
@app = Sinatra.new(base, &block)
|
27
45
|
end
|
28
46
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
:logging => Proc.new { ! test? },
|
36
|
-
:methodoverride => true,
|
37
|
-
:static => true,
|
38
|
-
:run => Proc.new { ! test? }
|
39
|
-
)
|
47
|
+
def app
|
48
|
+
Rack::Lint.new(@app)
|
49
|
+
end
|
50
|
+
|
51
|
+
def body
|
52
|
+
response.body.to_s
|
40
53
|
end
|
41
|
-
end
|
42
54
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
return super unless (name = args.first.capitalize) && block
|
50
|
-
name = "#{name.gsub(/\W/, '')}Test"
|
51
|
-
Object.send :const_set, name, Class.new(Test::Unit::TestCase)
|
52
|
-
klass = Object.const_get(name)
|
53
|
-
klass.class_eval do
|
54
|
-
def self.it(name, &block)
|
55
|
-
define_method("test_#{name.gsub(/\W/,'_').downcase}", &block)
|
55
|
+
# Delegate other missing methods to response.
|
56
|
+
def method_missing(name, *args, &block)
|
57
|
+
if response && response.respond_to?(name)
|
58
|
+
response.send(name, *args, &block)
|
59
|
+
else
|
60
|
+
super
|
56
61
|
end
|
57
|
-
def self.xspecify(*args) end
|
58
|
-
def self.before(&block) define_method(:setup, &block) end
|
59
|
-
def self.after(&block) define_method(:teardown, &block) end
|
60
62
|
end
|
61
|
-
klass.class_eval &block
|
62
|
-
klass
|
63
|
-
end
|
64
63
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
restore_default_options
|
69
|
-
@base = Sinatra.new
|
70
|
-
@default = Class.new(Sinatra::Default)
|
64
|
+
# Also check response since we delegate there.
|
65
|
+
def respond_to?(symbol, include_private=false)
|
66
|
+
super || (response && response.respond_to?(symbol, include_private))
|
71
67
|
end
|
72
|
-
klass
|
73
|
-
end
|
74
68
|
|
75
|
-
# Do not output warnings for the duration of the block.
|
76
|
-
def silence_warnings
|
77
|
-
|
78
|
-
|
79
|
-
ensure
|
80
|
-
|
69
|
+
# Do not output warnings for the duration of the block.
|
70
|
+
def silence_warnings
|
71
|
+
$VERBOSE, v = nil, $VERBOSE
|
72
|
+
yield
|
73
|
+
ensure
|
74
|
+
$VERBOSE = v
|
75
|
+
end
|
81
76
|
end
|
data/test/helpers_test.rb
CHANGED
@@ -1,497 +1,503 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
get '/' do
|
7
|
-
status 207
|
8
|
-
nil
|
9
|
-
end
|
10
|
-
}
|
3
|
+
class HelpersTest < Test::Unit::TestCase
|
4
|
+
def test_default
|
5
|
+
assert true
|
11
6
|
end
|
12
7
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
get '/' do
|
23
|
-
body { 'Hello World' }
|
24
|
-
end
|
25
|
-
}
|
26
|
-
|
27
|
-
get '/'
|
28
|
-
assert_equal 'Hello World', body
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'takes a String, Array, or other object responding to #each' do
|
32
|
-
mock_app {
|
33
|
-
get '/' do
|
34
|
-
body 'Hello World'
|
35
|
-
end
|
36
|
-
}
|
37
|
-
|
38
|
-
get '/'
|
39
|
-
assert_equal 'Hello World', body
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
describe 'Helpers#redirect' do
|
44
|
-
it 'uses a 302 when only a path is given' do
|
45
|
-
mock_app {
|
46
|
-
get '/' do
|
47
|
-
redirect '/foo'
|
48
|
-
fail 'redirect should halt'
|
49
|
-
end
|
50
|
-
}
|
51
|
-
|
52
|
-
get '/'
|
53
|
-
assert_equal 302, status
|
54
|
-
assert_equal '', body
|
55
|
-
assert_equal '/foo', response['Location']
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'uses the code given when specified' do
|
59
|
-
mock_app {
|
60
|
-
get '/' do
|
61
|
-
redirect '/foo', 301
|
62
|
-
fail 'redirect should halt'
|
63
|
-
end
|
64
|
-
}
|
65
|
-
|
66
|
-
get '/'
|
67
|
-
assert_equal 301, status
|
68
|
-
assert_equal '', body
|
69
|
-
assert_equal '/foo', response['Location']
|
70
|
-
end
|
71
|
-
|
72
|
-
it 'redirects back to request.referer when passed back' do
|
73
|
-
mock_app {
|
74
|
-
get '/try_redirect' do
|
75
|
-
redirect back
|
76
|
-
end
|
77
|
-
}
|
78
|
-
|
79
|
-
request = Rack::MockRequest.new(@app)
|
80
|
-
response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo')
|
81
|
-
assert_equal 302, response.status
|
82
|
-
assert_equal '/foo', response['Location']
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
describe 'Helpers#error' do
|
88
|
-
it 'sets a status code and halts' do
|
89
|
-
mock_app {
|
90
|
-
get '/' do
|
91
|
-
error 501
|
92
|
-
fail 'error should halt'
|
93
|
-
end
|
94
|
-
}
|
95
|
-
|
96
|
-
get '/'
|
97
|
-
assert_equal 501, status
|
98
|
-
assert_equal '', body
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'takes an optional body' do
|
102
|
-
mock_app {
|
103
|
-
get '/' do
|
104
|
-
error 501, 'FAIL'
|
105
|
-
fail 'error should halt'
|
106
|
-
end
|
107
|
-
}
|
108
|
-
|
109
|
-
get '/'
|
110
|
-
assert_equal 501, status
|
111
|
-
assert_equal 'FAIL', body
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'uses a 500 status code when first argument is a body' do
|
115
|
-
mock_app {
|
116
|
-
get '/' do
|
117
|
-
error 'FAIL'
|
118
|
-
fail 'error should halt'
|
119
|
-
end
|
120
|
-
}
|
121
|
-
|
122
|
-
get '/'
|
123
|
-
assert_equal 500, status
|
124
|
-
assert_equal 'FAIL', body
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
describe 'Helpers#not_found' do
|
129
|
-
it 'halts with a 404 status' do
|
130
|
-
mock_app {
|
131
|
-
get '/' do
|
132
|
-
not_found
|
133
|
-
fail 'not_found should halt'
|
134
|
-
end
|
135
|
-
}
|
136
|
-
|
137
|
-
get '/'
|
138
|
-
assert_equal 404, status
|
139
|
-
assert_equal '', body
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
describe 'Helpers#headers' do
|
144
|
-
it 'sets headers on the response object when given a Hash' do
|
145
|
-
mock_app {
|
146
|
-
get '/' do
|
147
|
-
headers 'X-Foo' => 'bar', 'X-Baz' => 'bling'
|
148
|
-
'kthx'
|
149
|
-
end
|
150
|
-
}
|
151
|
-
|
152
|
-
get '/'
|
153
|
-
assert ok?
|
154
|
-
assert_equal 'bar', response['X-Foo']
|
155
|
-
assert_equal 'bling', response['X-Baz']
|
156
|
-
assert_equal 'kthx', body
|
157
|
-
end
|
158
|
-
|
159
|
-
it 'returns the response headers hash when no hash provided' do
|
160
|
-
mock_app {
|
161
|
-
get '/' do
|
162
|
-
headers['X-Foo'] = 'bar'
|
163
|
-
'kthx'
|
164
|
-
end
|
165
|
-
}
|
166
|
-
|
167
|
-
get '/'
|
168
|
-
assert ok?
|
169
|
-
assert_equal 'bar', response['X-Foo']
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
describe 'Helpers#session' do
|
174
|
-
it 'uses the existing rack.session' do
|
175
|
-
mock_app {
|
176
|
-
get '/' do
|
177
|
-
session[:foo]
|
178
|
-
end
|
179
|
-
}
|
180
|
-
|
181
|
-
get '/', :env => { 'rack.session' => { :foo => 'bar' } }
|
182
|
-
assert_equal 'bar', body
|
183
|
-
end
|
184
|
-
|
185
|
-
it 'creates a new session when none provided' do
|
186
|
-
mock_app {
|
187
|
-
get '/' do
|
188
|
-
assert session.empty?
|
189
|
-
session[:foo] = 'bar'
|
190
|
-
'Hi'
|
191
|
-
end
|
192
|
-
}
|
193
|
-
|
194
|
-
get '/'
|
195
|
-
assert_equal 'Hi', body
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
describe 'Helpers#media_type' do
|
200
|
-
include Sinatra::Helpers
|
201
|
-
|
202
|
-
it "looks up media types in Rack's MIME registry" do
|
203
|
-
Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
|
204
|
-
assert_equal 'application/foo', media_type('foo')
|
205
|
-
assert_equal 'application/foo', media_type('.foo')
|
206
|
-
assert_equal 'application/foo', media_type(:foo)
|
207
|
-
end
|
208
|
-
|
209
|
-
it 'returns nil when given nil' do
|
210
|
-
assert media_type(nil).nil?
|
211
|
-
end
|
212
|
-
|
213
|
-
it 'returns nil when media type not registered' do
|
214
|
-
assert media_type(:bizzle).nil?
|
215
|
-
end
|
8
|
+
describe 'status' do
|
9
|
+
setup do
|
10
|
+
mock_app {
|
11
|
+
get '/' do
|
12
|
+
status 207
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
}
|
16
|
+
end
|
216
17
|
|
217
|
-
|
218
|
-
|
18
|
+
it 'sets the response status code' do
|
19
|
+
get '/'
|
20
|
+
assert_equal 207, response.status
|
21
|
+
end
|
219
22
|
end
|
220
|
-
end
|
221
23
|
|
222
|
-
describe '
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
}
|
230
|
-
|
231
|
-
get '/'
|
232
|
-
assert_equal 'text/plain', response['Content-Type']
|
233
|
-
assert_equal 'Hello World', body
|
234
|
-
end
|
24
|
+
describe 'body' do
|
25
|
+
it 'takes a block for defered body generation' do
|
26
|
+
mock_app {
|
27
|
+
get '/' do
|
28
|
+
body { 'Hello World' }
|
29
|
+
end
|
30
|
+
}
|
235
31
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
content_type 'text/html', :charset => 'utf-8'
|
240
|
-
"<h1>Hello, World</h1>"
|
241
|
-
end
|
242
|
-
}
|
243
|
-
|
244
|
-
get '/'
|
245
|
-
assert ok?
|
246
|
-
assert_equal 'text/html;charset=utf-8', response['Content-Type']
|
247
|
-
assert_equal "<h1>Hello, World</h1>", body
|
248
|
-
end
|
32
|
+
get '/'
|
33
|
+
assert_equal 'Hello World', body
|
34
|
+
end
|
249
35
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
end
|
257
|
-
}
|
258
|
-
|
259
|
-
get '/foo.xml'
|
260
|
-
assert ok?
|
261
|
-
assert_equal 'application/foo', response['Content-Type']
|
262
|
-
assert_equal 'I AM FOO', body
|
263
|
-
end
|
36
|
+
it 'takes a String, Array, or other object responding to #each' do
|
37
|
+
mock_app {
|
38
|
+
get '/' do
|
39
|
+
body 'Hello World'
|
40
|
+
end
|
41
|
+
}
|
264
42
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
content_type :bizzle
|
269
|
-
"I AM FOO"
|
270
|
-
end
|
271
|
-
}
|
272
|
-
assert_raise(RuntimeError) { get '/foo.xml' }
|
43
|
+
get '/'
|
44
|
+
assert_equal 'Hello World', body
|
45
|
+
end
|
273
46
|
end
|
274
|
-
end
|
275
47
|
|
276
|
-
describe '
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
48
|
+
describe 'redirect' do
|
49
|
+
it 'uses a 302 when only a path is given' do
|
50
|
+
mock_app {
|
51
|
+
get '/' do
|
52
|
+
redirect '/foo'
|
53
|
+
fail 'redirect should halt'
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
get '/'
|
58
|
+
assert_equal 302, status
|
59
|
+
assert_equal '', body
|
60
|
+
assert_equal '/foo', response['Location']
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'uses the code given when specified' do
|
64
|
+
mock_app {
|
65
|
+
get '/' do
|
66
|
+
redirect '/foo', 301
|
67
|
+
fail 'redirect should halt'
|
68
|
+
end
|
69
|
+
}
|
70
|
+
|
71
|
+
get '/'
|
72
|
+
assert_equal 301, status
|
73
|
+
assert_equal '', body
|
74
|
+
assert_equal '/foo', response['Location']
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'redirects back to request.referer when passed back' do
|
78
|
+
mock_app {
|
79
|
+
get '/try_redirect' do
|
80
|
+
redirect back
|
81
|
+
end
|
82
|
+
}
|
281
83
|
|
282
|
-
|
283
|
-
|
284
|
-
|
84
|
+
request = Rack::MockRequest.new(@app)
|
85
|
+
response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo')
|
86
|
+
assert_equal 302, response.status
|
87
|
+
assert_equal '/foo', response['Location']
|
88
|
+
end
|
285
89
|
end
|
286
90
|
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
91
|
+
describe 'error' do
|
92
|
+
it 'sets a status code and halts' do
|
93
|
+
mock_app {
|
94
|
+
get '/' do
|
95
|
+
error 501
|
96
|
+
fail 'error should halt'
|
97
|
+
end
|
98
|
+
}
|
99
|
+
|
100
|
+
get '/'
|
101
|
+
assert_equal 501, status
|
102
|
+
assert_equal '', body
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'takes an optional body' do
|
106
|
+
mock_app {
|
107
|
+
get '/' do
|
108
|
+
error 501, 'FAIL'
|
109
|
+
fail 'error should halt'
|
110
|
+
end
|
111
|
+
}
|
112
|
+
|
113
|
+
get '/'
|
114
|
+
assert_equal 501, status
|
115
|
+
assert_equal 'FAIL', body
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'uses a 500 status code when first argument is a body' do
|
119
|
+
mock_app {
|
120
|
+
get '/' do
|
121
|
+
error 'FAIL'
|
122
|
+
fail 'error should halt'
|
123
|
+
end
|
124
|
+
}
|
295
125
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
assert_equal 'Hello World', body
|
126
|
+
get '/'
|
127
|
+
assert_equal 500, status
|
128
|
+
assert_equal 'FAIL', body
|
129
|
+
end
|
301
130
|
end
|
302
131
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
132
|
+
describe 'not_found' do
|
133
|
+
it 'halts with a 404 status' do
|
134
|
+
mock_app {
|
135
|
+
get '/' do
|
136
|
+
not_found
|
137
|
+
fail 'not_found should halt'
|
138
|
+
end
|
139
|
+
}
|
308
140
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
141
|
+
get '/'
|
142
|
+
assert_equal 404, status
|
143
|
+
assert_equal '', body
|
144
|
+
end
|
313
145
|
end
|
314
146
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
147
|
+
describe 'headers' do
|
148
|
+
it 'sets headers on the response object when given a Hash' do
|
149
|
+
mock_app {
|
150
|
+
get '/' do
|
151
|
+
headers 'X-Foo' => 'bar', 'X-Baz' => 'bling'
|
152
|
+
'kthx'
|
153
|
+
end
|
154
|
+
}
|
155
|
+
|
156
|
+
get '/'
|
157
|
+
assert ok?
|
158
|
+
assert_equal 'bar', response['X-Foo']
|
159
|
+
assert_equal 'bling', response['X-Baz']
|
160
|
+
assert_equal 'kthx', body
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'returns the response headers hash when no hash provided' do
|
164
|
+
mock_app {
|
165
|
+
get '/' do
|
166
|
+
headers['X-Foo'] = 'bar'
|
167
|
+
'kthx'
|
168
|
+
end
|
169
|
+
}
|
320
170
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
end
|
326
|
-
}
|
327
|
-
get '/'
|
328
|
-
assert not_found?
|
171
|
+
get '/'
|
172
|
+
assert ok?
|
173
|
+
assert_equal 'bar', response['X-Foo']
|
174
|
+
end
|
329
175
|
end
|
330
176
|
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
177
|
+
describe 'session' do
|
178
|
+
it 'uses the existing rack.session' do
|
179
|
+
mock_app {
|
180
|
+
get '/' do
|
181
|
+
session[:foo]
|
182
|
+
end
|
183
|
+
}
|
184
|
+
|
185
|
+
get '/', {}, { 'rack.session' => { :foo => 'bar' } }
|
186
|
+
assert_equal 'bar', body
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'creates a new session when none provided' do
|
190
|
+
mock_app {
|
191
|
+
get '/' do
|
192
|
+
assert session.empty?
|
193
|
+
session[:foo] = 'bar'
|
194
|
+
'Hi'
|
195
|
+
end
|
196
|
+
}
|
336
197
|
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
assert_equal 'attachment; filename="file.txt"', response['Content-Disposition']
|
198
|
+
get '/'
|
199
|
+
assert_equal 'Hi', body
|
200
|
+
end
|
341
201
|
end
|
342
202
|
|
343
|
-
|
344
|
-
|
345
|
-
get '/file.txt'
|
346
|
-
assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition']
|
347
|
-
end
|
348
|
-
end
|
203
|
+
describe 'media_type' do
|
204
|
+
include Sinatra::Helpers
|
349
205
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
last_modified now
|
357
|
-
'Boo!'
|
358
|
-
end
|
359
|
-
}
|
360
|
-
@now = now
|
361
|
-
end
|
206
|
+
it "looks up media types in Rack's MIME registry" do
|
207
|
+
Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
|
208
|
+
assert_equal 'application/foo', media_type('foo')
|
209
|
+
assert_equal 'application/foo', media_type('.foo')
|
210
|
+
assert_equal 'application/foo', media_type(:foo)
|
211
|
+
end
|
362
212
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
end
|
213
|
+
it 'returns nil when given nil' do
|
214
|
+
assert media_type(nil).nil?
|
215
|
+
end
|
367
216
|
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
assert_equal 'Boo!', body
|
372
|
-
end
|
217
|
+
it 'returns nil when media type not registered' do
|
218
|
+
assert media_type(:bizzle).nil?
|
219
|
+
end
|
373
220
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
assert_equal '', body
|
221
|
+
it 'returns the argument when given a media type string' do
|
222
|
+
assert_equal 'text/plain', media_type('text/plain')
|
223
|
+
end
|
378
224
|
end
|
379
|
-
end
|
380
225
|
|
381
|
-
describe '
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
226
|
+
describe 'content_type' do
|
227
|
+
it 'sets the Content-Type header' do
|
228
|
+
mock_app {
|
229
|
+
get '/' do
|
230
|
+
content_type 'text/plain'
|
231
|
+
'Hello World'
|
232
|
+
end
|
233
|
+
}
|
234
|
+
|
235
|
+
get '/'
|
236
|
+
assert_equal 'text/plain', response['Content-Type']
|
237
|
+
assert_equal 'Hello World', body
|
238
|
+
end
|
239
|
+
|
240
|
+
it 'takes media type parameters (like charset=)' do
|
241
|
+
mock_app {
|
242
|
+
get '/' do
|
243
|
+
content_type 'text/html', :charset => 'utf-8'
|
244
|
+
"<h1>Hello, World</h1>"
|
245
|
+
end
|
246
|
+
}
|
247
|
+
|
248
|
+
get '/'
|
249
|
+
assert ok?
|
250
|
+
assert_equal 'text/html;charset=utf-8', response['Content-Type']
|
251
|
+
assert_equal "<h1>Hello, World</h1>", body
|
252
|
+
end
|
253
|
+
|
254
|
+
it "looks up symbols in Rack's mime types dictionary" do
|
255
|
+
Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
|
256
|
+
mock_app {
|
257
|
+
get '/foo.xml' do
|
258
|
+
content_type :foo
|
259
|
+
"I AM FOO"
|
260
|
+
end
|
261
|
+
}
|
262
|
+
|
263
|
+
get '/foo.xml'
|
264
|
+
assert ok?
|
265
|
+
assert_equal 'application/foo', response['Content-Type']
|
266
|
+
assert_equal 'I AM FOO', body
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'fails when no mime type is registered for the argument provided' do
|
270
|
+
mock_app {
|
271
|
+
get '/foo.xml' do
|
272
|
+
content_type :bizzle
|
273
|
+
"I AM FOO"
|
274
|
+
end
|
275
|
+
}
|
391
276
|
|
392
|
-
|
393
|
-
|
394
|
-
assert_equal '"FOO"', response['ETag']
|
277
|
+
assert_raise(RuntimeError) { get '/foo.xml' }
|
278
|
+
end
|
395
279
|
end
|
396
280
|
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
281
|
+
describe 'send_file' do
|
282
|
+
setup do
|
283
|
+
@file = File.dirname(__FILE__) + '/file.txt'
|
284
|
+
File.open(@file, 'wb') { |io| io.write('Hello World') }
|
285
|
+
end
|
402
286
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
end
|
287
|
+
def teardown
|
288
|
+
File.unlink @file
|
289
|
+
@file = nil
|
290
|
+
end
|
408
291
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
292
|
+
def send_file_app(opts={})
|
293
|
+
path = @file
|
294
|
+
mock_app {
|
295
|
+
get '/file.txt' do
|
296
|
+
send_file path, opts
|
297
|
+
end
|
298
|
+
}
|
299
|
+
end
|
300
|
+
|
301
|
+
it "sends the contents of the file" do
|
302
|
+
send_file_app
|
303
|
+
get '/file.txt'
|
304
|
+
assert ok?
|
305
|
+
assert_equal 'Hello World', body
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'sets the Content-Type response header if a mime-type can be located' do
|
309
|
+
send_file_app
|
310
|
+
get '/file.txt'
|
311
|
+
assert_equal 'text/plain', response['Content-Type']
|
312
|
+
end
|
313
|
+
|
314
|
+
it 'sets the Content-Length response header' do
|
315
|
+
send_file_app
|
316
|
+
get '/file.txt'
|
317
|
+
assert_equal 'Hello World'.length.to_s, response['Content-Length']
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'sets the Last-Modified response header' do
|
321
|
+
send_file_app
|
322
|
+
get '/file.txt'
|
323
|
+
assert_equal File.mtime(@file).httpdate, response['Last-Modified']
|
324
|
+
end
|
325
|
+
|
326
|
+
it "returns a 404 when not found" do
|
327
|
+
mock_app {
|
328
|
+
get '/' do
|
329
|
+
send_file 'this-file-does-not-exist.txt'
|
330
|
+
end
|
331
|
+
}
|
332
|
+
get '/'
|
333
|
+
assert not_found?
|
334
|
+
end
|
335
|
+
|
336
|
+
it "does not set the Content-Disposition header by default" do
|
337
|
+
send_file_app
|
338
|
+
get '/file.txt'
|
339
|
+
assert_nil response['Content-Disposition']
|
340
|
+
end
|
341
|
+
|
342
|
+
it "sets the Content-Disposition header when :disposition set to 'attachment'" do
|
343
|
+
send_file_app :disposition => 'attachment'
|
344
|
+
get '/file.txt'
|
345
|
+
assert_equal 'attachment; filename="file.txt"', response['Content-Disposition']
|
346
|
+
end
|
347
|
+
|
348
|
+
it "sets the Content-Disposition header when :filename provided" do
|
349
|
+
send_file_app :filename => 'foo.txt'
|
350
|
+
get '/file.txt'
|
351
|
+
assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition']
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe 'last_modified' do
|
356
|
+
setup do
|
357
|
+
now = Time.now
|
358
|
+
mock_app {
|
359
|
+
get '/' do
|
360
|
+
body { 'Hello World' }
|
361
|
+
last_modified now
|
362
|
+
'Boo!'
|
363
|
+
end
|
364
|
+
}
|
365
|
+
@now = now
|
366
|
+
end
|
367
|
+
|
368
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
369
|
+
get '/'
|
370
|
+
assert_equal @now.httpdate, response['Last-Modified']
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'returns a body when conditional get misses' do
|
374
|
+
get '/'
|
375
|
+
assert_equal 200, status
|
376
|
+
assert_equal 'Boo!', body
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'halts when a conditional GET matches' do
|
380
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @now.httpdate }
|
381
|
+
assert_equal 304, status
|
382
|
+
assert_equal '', body
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe 'etag' do
|
387
|
+
setup do
|
388
|
+
mock_app {
|
389
|
+
get '/' do
|
390
|
+
body { 'Hello World' }
|
391
|
+
etag 'FOO'
|
392
|
+
'Boo!'
|
393
|
+
end
|
394
|
+
}
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'sets the ETag header' do
|
398
|
+
get '/'
|
399
|
+
assert_equal '"FOO"', response['ETag']
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'returns a body when conditional get misses' do
|
403
|
+
get '/'
|
404
|
+
assert_equal 200, status
|
405
|
+
assert_equal 'Boo!', body
|
406
|
+
end
|
407
|
+
|
408
|
+
it 'halts when a conditional GET matches' do
|
409
|
+
get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"FOO"' }
|
410
|
+
assert_equal 304, status
|
411
|
+
assert_equal '', body
|
412
|
+
end
|
413
|
+
|
414
|
+
it 'should handle multiple ETag values in If-None-Match header' do
|
415
|
+
get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"BAR", *' }
|
416
|
+
assert_equal 304, status
|
417
|
+
assert_equal '', body
|
418
|
+
end
|
419
|
+
|
420
|
+
it 'uses a weak etag with the :weak option' do
|
421
|
+
mock_app {
|
422
|
+
get '/' do
|
423
|
+
etag 'FOO', :weak
|
424
|
+
"that's weak, dude."
|
425
|
+
end
|
426
|
+
}
|
427
|
+
get '/'
|
428
|
+
assert_equal 'W/"FOO"', response['ETag']
|
429
|
+
end
|
413
430
|
end
|
414
431
|
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
get '/'
|
423
|
-
assert_equal 'W/"FOO"', response['ETag']
|
424
|
-
end
|
425
|
-
end
|
432
|
+
describe 'back' do
|
433
|
+
it "makes redirecting back pretty" do
|
434
|
+
mock_app {
|
435
|
+
get '/foo' do
|
436
|
+
redirect back
|
437
|
+
end
|
438
|
+
}
|
426
439
|
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
redirect back
|
432
|
-
end
|
433
|
-
}
|
434
|
-
|
435
|
-
get '/foo', {}, 'HTTP_REFERER' => 'http://github.com'
|
436
|
-
assert redirect?
|
437
|
-
assert_equal "http://github.com", response.location
|
440
|
+
get '/foo', {}, 'HTTP_REFERER' => 'http://github.com'
|
441
|
+
assert redirect?
|
442
|
+
assert_equal "http://github.com", response.location
|
443
|
+
end
|
438
444
|
end
|
439
|
-
end
|
440
445
|
|
441
|
-
module HelperOne; def one; '1'; end; end
|
442
|
-
module HelperTwo; def two; '2'; end; end
|
446
|
+
module ::HelperOne; def one; '1'; end; end
|
447
|
+
module ::HelperTwo; def two; '2'; end; end
|
443
448
|
|
444
|
-
describe 'Adding new helpers' do
|
445
|
-
|
446
|
-
|
447
|
-
|
449
|
+
describe 'Adding new helpers' do
|
450
|
+
it 'takes a list of modules to mix into the app' do
|
451
|
+
mock_app {
|
452
|
+
helpers ::HelperOne, ::HelperTwo
|
448
453
|
|
449
|
-
|
450
|
-
|
451
|
-
|
454
|
+
get '/one' do
|
455
|
+
one
|
456
|
+
end
|
452
457
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
458
|
+
get '/two' do
|
459
|
+
two
|
460
|
+
end
|
461
|
+
}
|
457
462
|
|
458
|
-
|
459
|
-
|
463
|
+
get '/one'
|
464
|
+
assert_equal '1', body
|
460
465
|
|
461
|
-
|
462
|
-
|
463
|
-
|
466
|
+
get '/two'
|
467
|
+
assert_equal '2', body
|
468
|
+
end
|
464
469
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
+
it 'takes a block to mix into the app' do
|
471
|
+
mock_app {
|
472
|
+
helpers do
|
473
|
+
def foo
|
474
|
+
'foo'
|
475
|
+
end
|
470
476
|
end
|
471
|
-
end
|
472
477
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
478
|
+
get '/' do
|
479
|
+
foo
|
480
|
+
end
|
481
|
+
}
|
477
482
|
|
478
|
-
|
479
|
-
|
480
|
-
|
483
|
+
get '/'
|
484
|
+
assert_equal 'foo', body
|
485
|
+
end
|
481
486
|
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
+
it 'evaluates the block in class context so that methods can be aliased' do
|
488
|
+
mock_app {
|
489
|
+
helpers do
|
490
|
+
alias_method :h, :escape_html
|
491
|
+
end
|
487
492
|
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
493
|
+
get '/' do
|
494
|
+
h('42 < 43')
|
495
|
+
end
|
496
|
+
}
|
492
497
|
|
493
|
-
|
494
|
-
|
495
|
-
|
498
|
+
get '/'
|
499
|
+
assert ok?
|
500
|
+
assert_equal '42 < 43', body
|
501
|
+
end
|
496
502
|
end
|
497
503
|
end
|