aldebaran 1.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/.gitignore +13 -0
- data/.travis.yml +16 -0
- data/.yardopts +4 -0
- data/AUTHORS +4 -0
- data/Gemfile +77 -0
- data/KNOWN_ISSUES +5 -0
- data/LICENSE +22 -0
- data/README.rdoc +1900 -0
- data/Rakefile +175 -0
- data/aldebaran.gemspec +19 -0
- data/lib/aldebaran.rb +7 -0
- data/lib/aldebaran/base.rb +1600 -0
- data/lib/aldebaran/images/404.png +0 -0
- data/lib/aldebaran/images/500.png +0 -0
- data/lib/aldebaran/main.rb +28 -0
- data/lib/aldebaran/showexceptions.rb +340 -0
- data/lib/aldebaran/version.rb +3 -0
- data/test/aldebaran_test.rb +17 -0
- data/test/base_test.rb +160 -0
- data/test/builder_test.rb +95 -0
- data/test/coffee_test.rb +92 -0
- data/test/contest.rb +98 -0
- data/test/creole_test.rb +65 -0
- data/test/delegator_test.rb +162 -0
- data/test/encoding_test.rb +20 -0
- data/test/erb_test.rb +104 -0
- data/test/extensions_test.rb +100 -0
- data/test/filter_test.rb +397 -0
- data/test/haml_test.rb +101 -0
- data/test/helper.rb +115 -0
- data/test/helpers_test.rb +1192 -0
- data/test/less_test.rb +67 -0
- data/test/liquid_test.rb +59 -0
- data/test/mapped_error_test.rb +259 -0
- data/test/markaby_test.rb +80 -0
- data/test/markdown_test.rb +81 -0
- data/test/middleware_test.rb +68 -0
- data/test/nokogiri_test.rb +69 -0
- data/test/public/favicon.ico +0 -0
- data/test/radius_test.rb +59 -0
- data/test/rdoc_test.rb +65 -0
- data/test/readme_test.rb +136 -0
- data/test/request_test.rb +45 -0
- data/test/response_test.rb +61 -0
- data/test/result_test.rb +98 -0
- data/test/route_added_hook_test.rb +59 -0
- data/test/routing_test.rb +1096 -0
- data/test/sass_test.rb +115 -0
- data/test/scss_test.rb +88 -0
- data/test/server_test.rb +48 -0
- data/test/settings_test.rb +493 -0
- data/test/slim_test.rb +98 -0
- data/test/static_test.rb +178 -0
- data/test/streaming_test.rb +100 -0
- data/test/templates_test.rb +298 -0
- data/test/textile_test.rb +65 -0
- data/test/views/a/in_a.str +1 -0
- data/test/views/ascii.erb +2 -0
- data/test/views/b/in_b.str +1 -0
- data/test/views/calc.html.erb +1 -0
- 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
- data/test/views/explicitly_nested.str +1 -0
- data/test/views/foo/hello.test +1 -0
- data/test/views/hello.builder +1 -0
- data/test/views/hello.coffee +1 -0
- data/test/views/hello.creole +1 -0
- data/test/views/hello.erb +1 -0
- data/test/views/hello.haml +1 -0
- data/test/views/hello.less +5 -0
- data/test/views/hello.liquid +1 -0
- data/test/views/hello.mab +1 -0
- data/test/views/hello.md +1 -0
- data/test/views/hello.nokogiri +1 -0
- data/test/views/hello.radius +1 -0
- data/test/views/hello.rdoc +1 -0
- data/test/views/hello.sass +2 -0
- data/test/views/hello.scss +3 -0
- data/test/views/hello.slim +1 -0
- data/test/views/hello.str +1 -0
- data/test/views/hello.test +1 -0
- data/test/views/hello.textile +1 -0
- data/test/views/layout2.builder +3 -0
- data/test/views/layout2.erb +2 -0
- data/test/views/layout2.haml +2 -0
- data/test/views/layout2.liquid +2 -0
- data/test/views/layout2.mab +2 -0
- data/test/views/layout2.nokogiri +3 -0
- data/test/views/layout2.radius +2 -0
- data/test/views/layout2.slim +3 -0
- data/test/views/layout2.str +2 -0
- data/test/views/layout2.test +1 -0
- data/test/views/nested.str +1 -0
- data/test/views/utf8.erb +2 -0
- metadata +231 -0
data/test/haml_test.rb
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'haml'
|
5
|
+
|
6
|
+
class HAMLTest < Test::Unit::TestCase
|
7
|
+
def haml_app(&block)
|
8
|
+
mock_app {
|
9
|
+
set :views, File.dirname(__FILE__) + '/views'
|
10
|
+
get '/', &block
|
11
|
+
}
|
12
|
+
get '/'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'renders inline HAML strings' do
|
16
|
+
haml_app { haml '%h1 Hiya' }
|
17
|
+
assert ok?
|
18
|
+
assert_equal "<h1>Hiya</h1>\n", body
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'renders .haml files in views path' do
|
22
|
+
haml_app { haml :hello }
|
23
|
+
assert ok?
|
24
|
+
assert_equal "<h1>Hello From Haml</h1>\n", body
|
25
|
+
end
|
26
|
+
|
27
|
+
it "renders with inline layouts" do
|
28
|
+
mock_app {
|
29
|
+
layout { %q(%h1= 'THIS. IS. ' + yield.upcase) }
|
30
|
+
get('/') { haml '%em Sparta' }
|
31
|
+
}
|
32
|
+
get '/'
|
33
|
+
assert ok?
|
34
|
+
assert_equal "<h1>THIS. IS. <EM>SPARTA</EM></h1>\n", body
|
35
|
+
end
|
36
|
+
|
37
|
+
it "renders with file layouts" do
|
38
|
+
haml_app {
|
39
|
+
haml 'Hello World', :layout => :layout2
|
40
|
+
}
|
41
|
+
assert ok?
|
42
|
+
assert_equal "<h1>HAML Layout!</h1>\n<p>Hello World</p>\n", body
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raises error if template not found" do
|
46
|
+
mock_app {
|
47
|
+
get('/') { haml :no_such_template }
|
48
|
+
}
|
49
|
+
assert_raise(Errno::ENOENT) { get('/') }
|
50
|
+
end
|
51
|
+
|
52
|
+
it "passes HAML options to the Haml engine" do
|
53
|
+
mock_app {
|
54
|
+
get '/' do
|
55
|
+
haml "!!!\n%h1 Hello World", :format => :html5
|
56
|
+
end
|
57
|
+
}
|
58
|
+
get '/'
|
59
|
+
assert ok?
|
60
|
+
assert_equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n", body
|
61
|
+
end
|
62
|
+
|
63
|
+
it "passes default HAML options to the Haml engine" do
|
64
|
+
mock_app {
|
65
|
+
set :haml, {:format => :html5}
|
66
|
+
get '/' do
|
67
|
+
haml "!!!\n%h1 Hello World"
|
68
|
+
end
|
69
|
+
}
|
70
|
+
get '/'
|
71
|
+
assert ok?
|
72
|
+
assert_equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n", body
|
73
|
+
end
|
74
|
+
|
75
|
+
it "merges the default HAML options with the overrides and passes them to the Haml engine" do
|
76
|
+
mock_app {
|
77
|
+
set :haml, {:format => :html5, :attr_wrapper => '"'} # default HAML attr are <tag attr='single-quoted'>
|
78
|
+
get '/' do
|
79
|
+
haml "!!!\n%h1{:class => :header} Hello World"
|
80
|
+
end
|
81
|
+
get '/html4' do
|
82
|
+
haml "!!!\n%h1{:class => 'header'} Hello World", :format => :html4
|
83
|
+
end
|
84
|
+
}
|
85
|
+
get '/'
|
86
|
+
assert ok?
|
87
|
+
assert_equal "<!DOCTYPE html>\n<h1 class=\"header\">Hello World</h1>\n", body
|
88
|
+
get '/html4'
|
89
|
+
assert ok?
|
90
|
+
assert_match(/^<!DOCTYPE html PUBLIC (.*) HTML 4.01/, body)
|
91
|
+
end
|
92
|
+
|
93
|
+
it "is possible to pass locals" do
|
94
|
+
haml_app { haml "= foo", :locals => { :foo => 'bar' }}
|
95
|
+
assert_equal "bar\n", body
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
rescue LoadError
|
100
|
+
warn "#{$!.to_s}: skipping haml tests"
|
101
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
ENV['RACK_ENV'] = 'test'
|
2
|
+
Encoding.default_external = "UTF-8" if defined? Encoding
|
3
|
+
|
4
|
+
RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'rack'
|
8
|
+
rescue LoadError
|
9
|
+
require 'rubygems'
|
10
|
+
require 'rack'
|
11
|
+
end
|
12
|
+
|
13
|
+
testdir = File.dirname(__FILE__)
|
14
|
+
$LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir)
|
15
|
+
|
16
|
+
libdir = File.dirname(File.dirname(__FILE__)) + '/lib'
|
17
|
+
$LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
|
18
|
+
|
19
|
+
require 'contest'
|
20
|
+
require 'rack/test'
|
21
|
+
require 'aldebaran/base'
|
22
|
+
|
23
|
+
class aldebaran::Base
|
24
|
+
# Allow assertions in request context
|
25
|
+
include Test::Unit::Assertions
|
26
|
+
end
|
27
|
+
|
28
|
+
class Rack::Builder
|
29
|
+
def include?(middleware)
|
30
|
+
@ins.any? { |m| p m ; middleware === m }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
aldebaran::Base.set :environment, :test
|
35
|
+
|
36
|
+
class Test::Unit::TestCase
|
37
|
+
include Rack::Test::Methods
|
38
|
+
|
39
|
+
class << self
|
40
|
+
alias_method :it, :test
|
41
|
+
alias_method :section, :context
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.example(desc = nil, &block)
|
45
|
+
@example_count = 0 unless instance_variable_defined? :@example_count
|
46
|
+
@example_count += 1
|
47
|
+
it(desc || "Example #{@example_count}", &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :response, :last_response
|
51
|
+
|
52
|
+
setup do
|
53
|
+
aldebaran::Base.set :environment, :test
|
54
|
+
end
|
55
|
+
|
56
|
+
# Sets up a aldebaran::Base subclass defined with the block
|
57
|
+
# given. Used in setup or individual spec methods to establish
|
58
|
+
# the application.
|
59
|
+
def mock_app(base=aldebaran::Base, &block)
|
60
|
+
@app = aldebaran.new(base, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def app
|
64
|
+
Rack::Lint.new(@app)
|
65
|
+
end
|
66
|
+
|
67
|
+
def body
|
68
|
+
response.body.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
def assert_body(value)
|
72
|
+
assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "")
|
73
|
+
end
|
74
|
+
|
75
|
+
def assert_like(a,b)
|
76
|
+
pattern = /id=['"][^"']*["']|\s+/
|
77
|
+
assert_equal a.strip.gsub(pattern, ""), b.strip.gsub(pattern, "")
|
78
|
+
end
|
79
|
+
|
80
|
+
def assert_include(str, substr)
|
81
|
+
assert str.include?(substr), "expected #{str.inspect} to include #{substr.inspect}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def options(uri, params = {}, env = {}, &block)
|
85
|
+
request(uri, env.merge(:method => "OPTIONS", :params => params), &block)
|
86
|
+
end
|
87
|
+
|
88
|
+
def patch(uri, params = {}, env = {}, &block)
|
89
|
+
request(uri, env.merge(:method => "PATCH", :params => params), &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Delegate other missing methods to response.
|
93
|
+
def method_missing(name, *args, &block)
|
94
|
+
if response && response.respond_to?(name)
|
95
|
+
response.send(name, *args, &block)
|
96
|
+
else
|
97
|
+
super
|
98
|
+
end
|
99
|
+
rescue Rack::Test::Error
|
100
|
+
super
|
101
|
+
end
|
102
|
+
|
103
|
+
# Also check response since we delegate there.
|
104
|
+
def respond_to?(symbol, include_private=false)
|
105
|
+
super || (response && response.respond_to?(symbol, include_private))
|
106
|
+
end
|
107
|
+
|
108
|
+
# Do not output warnings for the duration of the block.
|
109
|
+
def silence_warnings
|
110
|
+
$VERBOSE, v = nil, $VERBOSE
|
111
|
+
yield
|
112
|
+
ensure
|
113
|
+
$VERBOSE = v
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,1192 @@
|
|
1
|
+
require File.expand_path('../helper', __FILE__)
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
class HelpersTest < Test::Unit::TestCase
|
5
|
+
def test_default
|
6
|
+
assert true
|
7
|
+
end
|
8
|
+
|
9
|
+
def status_app(code, &block)
|
10
|
+
code += 2 if [204, 205, 304].include? code
|
11
|
+
block ||= proc { }
|
12
|
+
mock_app do
|
13
|
+
get '/' do
|
14
|
+
status code
|
15
|
+
instance_eval(&block).inspect
|
16
|
+
end
|
17
|
+
end
|
18
|
+
get '/'
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'status' do
|
22
|
+
it 'sets the response status code' do
|
23
|
+
status_app 207
|
24
|
+
assert_equal 207, response.status
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'not_found?' do
|
29
|
+
it 'is true for status == 404' do
|
30
|
+
status_app(404) { not_found? }
|
31
|
+
assert_body 'true'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'is false for status > 404' do
|
35
|
+
status_app(405) { not_found? }
|
36
|
+
assert_body 'false'
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'is false for status < 404' do
|
40
|
+
status_app(403) { not_found? }
|
41
|
+
assert_body 'false'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'informational?' do
|
46
|
+
it 'is true for 1xx status' do
|
47
|
+
status_app(100 + rand(100)) { informational? }
|
48
|
+
assert_body 'true'
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'is false for status > 199' do
|
52
|
+
status_app(200 + rand(400)) { informational? }
|
53
|
+
assert_body 'false'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'success?' do
|
58
|
+
it 'is true for 2xx status' do
|
59
|
+
status_app(200 + rand(100)) { success? }
|
60
|
+
assert_body 'true'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'is false for status < 200' do
|
64
|
+
status_app(100 + rand(100)) { success? }
|
65
|
+
assert_body 'false'
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'is false for status > 299' do
|
69
|
+
status_app(300 + rand(300)) { success? }
|
70
|
+
assert_body 'false'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe 'redirect?' do
|
75
|
+
it 'is true for 3xx status' do
|
76
|
+
status_app(300 + rand(100)) { redirect? }
|
77
|
+
assert_body 'true'
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'is false for status < 300' do
|
81
|
+
status_app(200 + rand(100)) { redirect? }
|
82
|
+
assert_body 'false'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'is false for status > 399' do
|
86
|
+
status_app(400 + rand(200)) { redirect? }
|
87
|
+
assert_body 'false'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'client_error?' do
|
92
|
+
it 'is true for 4xx status' do
|
93
|
+
status_app(400 + rand(100)) { client_error? }
|
94
|
+
assert_body 'true'
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'is false for status < 400' do
|
98
|
+
status_app(200 + rand(200)) { client_error? }
|
99
|
+
assert_body 'false'
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'is false for status > 499' do
|
103
|
+
status_app(500 + rand(100)) { client_error? }
|
104
|
+
assert_body 'false'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'server_error?' do
|
109
|
+
it 'is true for 5xx status' do
|
110
|
+
status_app(500 + rand(100)) { server_error? }
|
111
|
+
assert_body 'true'
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'is false for status < 500' do
|
115
|
+
status_app(200 + rand(300)) { server_error? }
|
116
|
+
assert_body 'false'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'body' do
|
121
|
+
it 'takes a block for defered body generation' do
|
122
|
+
mock_app {
|
123
|
+
get '/' do
|
124
|
+
body { 'Hello World' }
|
125
|
+
end
|
126
|
+
}
|
127
|
+
|
128
|
+
get '/'
|
129
|
+
assert_equal 'Hello World', body
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'takes a String, Array, or other object responding to #each' do
|
133
|
+
mock_app {
|
134
|
+
get '/' do
|
135
|
+
body 'Hello World'
|
136
|
+
end
|
137
|
+
}
|
138
|
+
|
139
|
+
get '/'
|
140
|
+
assert_equal 'Hello World', body
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
describe 'redirect' do
|
145
|
+
it 'uses a 302 when only a path is given' do
|
146
|
+
mock_app {
|
147
|
+
get '/' do
|
148
|
+
redirect '/foo'
|
149
|
+
fail 'redirect should halt'
|
150
|
+
end
|
151
|
+
}
|
152
|
+
|
153
|
+
get '/'
|
154
|
+
assert_equal 302, status
|
155
|
+
assert_equal '', body
|
156
|
+
assert_equal 'http://example.org/foo', response['Location']
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'uses the code given when specified' do
|
160
|
+
mock_app {
|
161
|
+
get '/' do
|
162
|
+
redirect '/foo', 301
|
163
|
+
fail 'redirect should halt'
|
164
|
+
end
|
165
|
+
}
|
166
|
+
|
167
|
+
get '/'
|
168
|
+
assert_equal 301, status
|
169
|
+
assert_equal '', body
|
170
|
+
assert_equal 'http://example.org/foo', response['Location']
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'redirects back to request.referer when passed back' do
|
174
|
+
mock_app {
|
175
|
+
get '/try_redirect' do
|
176
|
+
redirect back
|
177
|
+
end
|
178
|
+
}
|
179
|
+
|
180
|
+
request = Rack::MockRequest.new(@app)
|
181
|
+
response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo')
|
182
|
+
assert_equal 302, response.status
|
183
|
+
assert_equal 'http://example.org/foo', response['Location']
|
184
|
+
end
|
185
|
+
|
186
|
+
it 'redirects using a non-standard HTTP port' do
|
187
|
+
mock_app {
|
188
|
+
get '/' do
|
189
|
+
redirect '/foo'
|
190
|
+
end
|
191
|
+
}
|
192
|
+
|
193
|
+
request = Rack::MockRequest.new(@app)
|
194
|
+
response = request.get('/', 'SERVER_PORT' => '81')
|
195
|
+
assert_equal 'http://example.org:81/foo', response['Location']
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'redirects using a non-standard HTTPS port' do
|
199
|
+
mock_app {
|
200
|
+
get '/' do
|
201
|
+
redirect '/foo'
|
202
|
+
end
|
203
|
+
}
|
204
|
+
|
205
|
+
request = Rack::MockRequest.new(@app)
|
206
|
+
response = request.get('/', 'SERVER_PORT' => '444')
|
207
|
+
assert_equal 'http://example.org:444/foo', response['Location']
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'uses 303 for post requests if request is HTTP 1.1' do
|
211
|
+
mock_app { post('/') { redirect '/'} }
|
212
|
+
post '/', {}, 'HTTP_VERSION' => 'HTTP/1.1'
|
213
|
+
assert_equal 303, status
|
214
|
+
assert_equal '', body
|
215
|
+
assert_equal 'http://example.org/', response['Location']
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'uses 302 for post requests if request is HTTP 1.0' do
|
219
|
+
mock_app { post('/') { redirect '/'} }
|
220
|
+
post '/', {}, 'HTTP_VERSION' => 'HTTP/1.0'
|
221
|
+
assert_equal 302, status
|
222
|
+
assert_equal '', body
|
223
|
+
assert_equal 'http://example.org/', response['Location']
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'works behind a reverse proxy' do
|
227
|
+
mock_app do
|
228
|
+
get '/' do
|
229
|
+
redirect '/foo'
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
request = Rack::MockRequest.new(@app)
|
234
|
+
response = request.get('/', 'HTTP_X_FORWARDED_HOST' => 'example.com', 'SERVER_PORT' => '8080')
|
235
|
+
assert_equal 'http://example.com/foo', response['Location']
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'accepts absolute URIs' do
|
239
|
+
mock_app do
|
240
|
+
get '/' do
|
241
|
+
redirect 'http://google.com'
|
242
|
+
fail 'redirect should halt'
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
get '/'
|
247
|
+
assert_equal 302, status
|
248
|
+
assert_equal '', body
|
249
|
+
assert_equal 'http://google.com', response['Location']
|
250
|
+
end
|
251
|
+
|
252
|
+
it 'accepts absolute URIs with a different schema' do
|
253
|
+
mock_app do
|
254
|
+
get '/' do
|
255
|
+
redirect 'mailto:jsmith@example.com'
|
256
|
+
fail 'redirect should halt'
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
get '/'
|
261
|
+
assert_equal 302, status
|
262
|
+
assert_equal '', body
|
263
|
+
assert_equal 'mailto:jsmith@example.com', response['Location']
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
describe 'error' do
|
268
|
+
it 'sets a status code and halts' do
|
269
|
+
mock_app {
|
270
|
+
get '/' do
|
271
|
+
error 501
|
272
|
+
fail 'error should halt'
|
273
|
+
end
|
274
|
+
}
|
275
|
+
|
276
|
+
get '/'
|
277
|
+
assert_equal 501, status
|
278
|
+
assert_equal '', body
|
279
|
+
end
|
280
|
+
|
281
|
+
it 'takes an optional body' do
|
282
|
+
mock_app {
|
283
|
+
get '/' do
|
284
|
+
error 501, 'FAIL'
|
285
|
+
fail 'error should halt'
|
286
|
+
end
|
287
|
+
}
|
288
|
+
|
289
|
+
get '/'
|
290
|
+
assert_equal 501, status
|
291
|
+
assert_equal 'FAIL', body
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'uses a 500 status code when first argument is a body' do
|
295
|
+
mock_app {
|
296
|
+
get '/' do
|
297
|
+
error 'FAIL'
|
298
|
+
fail 'error should halt'
|
299
|
+
end
|
300
|
+
}
|
301
|
+
|
302
|
+
get '/'
|
303
|
+
assert_equal 500, status
|
304
|
+
assert_equal 'FAIL', body
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe 'not_found' do
|
309
|
+
it 'halts with a 404 status' do
|
310
|
+
mock_app {
|
311
|
+
get '/' do
|
312
|
+
not_found
|
313
|
+
fail 'not_found should halt'
|
314
|
+
end
|
315
|
+
}
|
316
|
+
|
317
|
+
get '/'
|
318
|
+
assert_equal 404, status
|
319
|
+
assert_equal '', body
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'does not set a X-Cascade header' do
|
323
|
+
mock_app {
|
324
|
+
get '/' do
|
325
|
+
not_found
|
326
|
+
fail 'not_found should halt'
|
327
|
+
end
|
328
|
+
}
|
329
|
+
|
330
|
+
get '/'
|
331
|
+
assert_equal 404, status
|
332
|
+
assert_equal nil, response.headers['X-Cascade']
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
describe 'headers' do
|
337
|
+
it 'sets headers on the response object when given a Hash' do
|
338
|
+
mock_app {
|
339
|
+
get '/' do
|
340
|
+
headers 'X-Foo' => 'bar', 'X-Baz' => 'bling'
|
341
|
+
'kthx'
|
342
|
+
end
|
343
|
+
}
|
344
|
+
|
345
|
+
get '/'
|
346
|
+
assert ok?
|
347
|
+
assert_equal 'bar', response['X-Foo']
|
348
|
+
assert_equal 'bling', response['X-Baz']
|
349
|
+
assert_equal 'kthx', body
|
350
|
+
end
|
351
|
+
|
352
|
+
it 'returns the response headers hash when no hash provided' do
|
353
|
+
mock_app {
|
354
|
+
get '/' do
|
355
|
+
headers['X-Foo'] = 'bar'
|
356
|
+
'kthx'
|
357
|
+
end
|
358
|
+
}
|
359
|
+
|
360
|
+
get '/'
|
361
|
+
assert ok?
|
362
|
+
assert_equal 'bar', response['X-Foo']
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
describe 'session' do
|
367
|
+
it 'uses the existing rack.session' do
|
368
|
+
mock_app {
|
369
|
+
get '/' do
|
370
|
+
session[:foo]
|
371
|
+
end
|
372
|
+
}
|
373
|
+
|
374
|
+
get '/', {}, { 'rack.session' => { :foo => 'bar' } }
|
375
|
+
assert_equal 'bar', body
|
376
|
+
end
|
377
|
+
|
378
|
+
it 'creates a new session when none provided' do
|
379
|
+
mock_app {
|
380
|
+
enable :sessions
|
381
|
+
|
382
|
+
get '/' do
|
383
|
+
assert session.empty?
|
384
|
+
session[:foo] = 'bar'
|
385
|
+
redirect '/hi'
|
386
|
+
end
|
387
|
+
|
388
|
+
get '/hi' do
|
389
|
+
"hi #{session[:foo]}"
|
390
|
+
end
|
391
|
+
}
|
392
|
+
|
393
|
+
get '/'
|
394
|
+
follow_redirect!
|
395
|
+
assert_equal 'hi bar', body
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'inserts session middleware' do
|
399
|
+
mock_app do
|
400
|
+
enable :sessions
|
401
|
+
get '/' do
|
402
|
+
assert env['rack.session']
|
403
|
+
assert env['rack.session.options']
|
404
|
+
'ok'
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
get '/'
|
409
|
+
assert_body 'ok'
|
410
|
+
end
|
411
|
+
|
412
|
+
it 'sets a default session secret' do
|
413
|
+
mock_app do
|
414
|
+
enable :sessions
|
415
|
+
get '/' do
|
416
|
+
secret = env['rack.session.options'][:secret]
|
417
|
+
assert secret
|
418
|
+
assert_equal secret, settings.session_secret
|
419
|
+
'ok'
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
get '/'
|
424
|
+
assert_body 'ok'
|
425
|
+
end
|
426
|
+
|
427
|
+
it 'allows disabling session secret' do
|
428
|
+
mock_app do
|
429
|
+
enable :sessions
|
430
|
+
disable :session_secret
|
431
|
+
get '/' do
|
432
|
+
assert !env['rack.session.options'].include?(:session_secret)
|
433
|
+
'ok'
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
get '/'
|
438
|
+
assert_body 'ok'
|
439
|
+
end
|
440
|
+
|
441
|
+
it 'accepts an options hash' do
|
442
|
+
mock_app do
|
443
|
+
set :sessions, :foo => :bar
|
444
|
+
get '/' do
|
445
|
+
assert_equal env['rack.session.options'][:foo], :bar
|
446
|
+
'ok'
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
get '/'
|
451
|
+
assert_body 'ok'
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
describe 'mime_type' do
|
456
|
+
include aldebaran::Helpers
|
457
|
+
|
458
|
+
it "looks up mime types in Rack's MIME registry" do
|
459
|
+
Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
|
460
|
+
assert_equal 'application/foo', mime_type('foo')
|
461
|
+
assert_equal 'application/foo', mime_type('.foo')
|
462
|
+
assert_equal 'application/foo', mime_type(:foo)
|
463
|
+
end
|
464
|
+
|
465
|
+
it 'returns nil when given nil' do
|
466
|
+
assert mime_type(nil).nil?
|
467
|
+
end
|
468
|
+
|
469
|
+
it 'returns nil when media type not registered' do
|
470
|
+
assert mime_type(:bizzle).nil?
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'returns the argument when given a media type string' do
|
474
|
+
assert_equal 'text/plain', mime_type('text/plain')
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
test 'Base.mime_type registers mime type' do
|
479
|
+
mock_app {
|
480
|
+
mime_type :foo, 'application/foo'
|
481
|
+
|
482
|
+
get '/' do
|
483
|
+
"foo is #{mime_type(:foo)}"
|
484
|
+
end
|
485
|
+
}
|
486
|
+
|
487
|
+
get '/'
|
488
|
+
assert_equal 'foo is application/foo', body
|
489
|
+
end
|
490
|
+
|
491
|
+
describe 'content_type' do
|
492
|
+
it 'sets the Content-Type header' do
|
493
|
+
mock_app {
|
494
|
+
get '/' do
|
495
|
+
content_type 'text/plain'
|
496
|
+
'Hello World'
|
497
|
+
end
|
498
|
+
}
|
499
|
+
|
500
|
+
get '/'
|
501
|
+
assert_equal 'text/plain;charset=utf-8', response['Content-Type']
|
502
|
+
assert_equal 'Hello World', body
|
503
|
+
end
|
504
|
+
|
505
|
+
it 'takes media type parameters (like charset=)' do
|
506
|
+
mock_app {
|
507
|
+
get '/' do
|
508
|
+
content_type 'text/html', :charset => 'latin1'
|
509
|
+
"<h1>Hello, World</h1>"
|
510
|
+
end
|
511
|
+
}
|
512
|
+
|
513
|
+
get '/'
|
514
|
+
assert ok?
|
515
|
+
assert_equal 'text/html;charset=latin1', response['Content-Type']
|
516
|
+
assert_equal "<h1>Hello, World</h1>", body
|
517
|
+
end
|
518
|
+
|
519
|
+
it "looks up symbols in Rack's mime types dictionary" do
|
520
|
+
Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
|
521
|
+
mock_app {
|
522
|
+
get '/foo.xml' do
|
523
|
+
content_type :foo
|
524
|
+
"I AM FOO"
|
525
|
+
end
|
526
|
+
}
|
527
|
+
|
528
|
+
get '/foo.xml'
|
529
|
+
assert ok?
|
530
|
+
assert_equal 'application/foo', response['Content-Type']
|
531
|
+
assert_equal 'I AM FOO', body
|
532
|
+
end
|
533
|
+
|
534
|
+
it 'fails when no mime type is registered for the argument provided' do
|
535
|
+
mock_app {
|
536
|
+
get '/foo.xml' do
|
537
|
+
content_type :bizzle
|
538
|
+
"I AM FOO"
|
539
|
+
end
|
540
|
+
}
|
541
|
+
|
542
|
+
assert_raise(RuntimeError) { get '/foo.xml' }
|
543
|
+
end
|
544
|
+
|
545
|
+
it 'only sets default charset for specific mime types' do
|
546
|
+
tests_ran = false
|
547
|
+
mock_app do
|
548
|
+
mime_type :foo, 'text/foo'
|
549
|
+
mime_type :bar, 'application/bar'
|
550
|
+
mime_type :baz, 'application/baz'
|
551
|
+
add_charset << mime_type(:baz)
|
552
|
+
get '/' do
|
553
|
+
assert_equal content_type(:txt), 'text/plain;charset=utf-8'
|
554
|
+
assert_equal content_type(:css), 'text/css;charset=utf-8'
|
555
|
+
assert_equal content_type(:html), 'text/html;charset=utf-8'
|
556
|
+
assert_equal content_type(:foo), 'text/foo;charset=utf-8'
|
557
|
+
assert_equal content_type(:xml), 'application/xml;charset=utf-8'
|
558
|
+
assert_equal content_type(:xhtml), 'application/xhtml+xml;charset=utf-8'
|
559
|
+
assert_equal content_type(:js), 'application/javascript;charset=utf-8'
|
560
|
+
assert_equal content_type(:json), 'application/json;charset=utf-8'
|
561
|
+
assert_equal content_type(:bar), 'application/bar'
|
562
|
+
assert_equal content_type(:png), 'image/png'
|
563
|
+
assert_equal content_type(:baz), 'application/baz;charset=utf-8'
|
564
|
+
tests_ran = true
|
565
|
+
"done"
|
566
|
+
end
|
567
|
+
end
|
568
|
+
get '/'
|
569
|
+
assert tests_ran
|
570
|
+
end
|
571
|
+
|
572
|
+
it 'handles already present params' do
|
573
|
+
mock_app do
|
574
|
+
get '/' do
|
575
|
+
content_type 'foo/bar;level=1', :charset => 'utf-8'
|
576
|
+
'ok'
|
577
|
+
end
|
578
|
+
end
|
579
|
+
get '/'
|
580
|
+
assert_equal 'foo/bar;level=1, charset=utf-8', response['Content-Type']
|
581
|
+
end
|
582
|
+
|
583
|
+
it 'does not add charset if present' do
|
584
|
+
mock_app do
|
585
|
+
get '/' do
|
586
|
+
content_type 'text/plain;charset=utf-16'
|
587
|
+
'ok'
|
588
|
+
end
|
589
|
+
end
|
590
|
+
get '/'
|
591
|
+
assert_equal 'text/plain;charset=utf-16', response['Content-Type']
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
describe 'attachment' do
|
596
|
+
def attachment_app(filename=nil)
|
597
|
+
mock_app {
|
598
|
+
get '/attachment' do
|
599
|
+
attachment filename
|
600
|
+
response.write("<aldebaran></aldebaran>")
|
601
|
+
end
|
602
|
+
}
|
603
|
+
end
|
604
|
+
|
605
|
+
it 'sets the Content-Type response header' do
|
606
|
+
attachment_app('test.xml')
|
607
|
+
get '/attachment'
|
608
|
+
assert_equal 'application/xml;charset=utf-8', response['Content-Type']
|
609
|
+
assert_equal '<aldebaran></aldebaran>', body
|
610
|
+
end
|
611
|
+
|
612
|
+
it 'sets the Content-Type response header without extname' do
|
613
|
+
attachment_app('test')
|
614
|
+
get '/attachment'
|
615
|
+
assert_equal 'text/html;charset=utf-8', response['Content-Type']
|
616
|
+
assert_equal '<aldebaran></aldebaran>', body
|
617
|
+
end
|
618
|
+
|
619
|
+
it 'sets the Content-Type response header without extname' do
|
620
|
+
mock_app do
|
621
|
+
get '/attachment' do
|
622
|
+
content_type :atom
|
623
|
+
attachment 'test.xml'
|
624
|
+
response.write("<aldebaran></aldebaran>")
|
625
|
+
end
|
626
|
+
end
|
627
|
+
get '/attachment'
|
628
|
+
assert_equal 'application/atom+xml', response['Content-Type']
|
629
|
+
assert_equal '<aldebaran></aldebaran>', body
|
630
|
+
end
|
631
|
+
|
632
|
+
end
|
633
|
+
|
634
|
+
describe 'send_file' do
|
635
|
+
setup do
|
636
|
+
@file = File.dirname(__FILE__) + '/file.txt'
|
637
|
+
File.open(@file, 'wb') { |io| io.write('Hello World') }
|
638
|
+
end
|
639
|
+
|
640
|
+
def teardown
|
641
|
+
File.unlink @file
|
642
|
+
@file = nil
|
643
|
+
end
|
644
|
+
|
645
|
+
def send_file_app(opts={})
|
646
|
+
path = @file
|
647
|
+
mock_app {
|
648
|
+
get '/file.txt' do
|
649
|
+
send_file path, opts
|
650
|
+
end
|
651
|
+
}
|
652
|
+
end
|
653
|
+
|
654
|
+
it "sends the contents of the file" do
|
655
|
+
send_file_app
|
656
|
+
get '/file.txt'
|
657
|
+
assert ok?
|
658
|
+
assert_equal 'Hello World', body
|
659
|
+
end
|
660
|
+
|
661
|
+
it 'sets the Content-Type response header if a mime-type can be located' do
|
662
|
+
send_file_app
|
663
|
+
get '/file.txt'
|
664
|
+
assert_equal 'text/plain;charset=utf-8', response['Content-Type']
|
665
|
+
end
|
666
|
+
|
667
|
+
it 'sets the Content-Type response header if type option is set to a file extesion' do
|
668
|
+
send_file_app :type => 'html'
|
669
|
+
get '/file.txt'
|
670
|
+
assert_equal 'text/html;charset=utf-8', response['Content-Type']
|
671
|
+
end
|
672
|
+
|
673
|
+
it 'sets the Content-Type response header if type option is set to a mime type' do
|
674
|
+
send_file_app :type => 'application/octet-stream'
|
675
|
+
get '/file.txt'
|
676
|
+
assert_equal 'application/octet-stream', response['Content-Type']
|
677
|
+
end
|
678
|
+
|
679
|
+
it 'sets the Content-Length response header' do
|
680
|
+
send_file_app
|
681
|
+
get '/file.txt'
|
682
|
+
assert_equal 'Hello World'.length.to_s, response['Content-Length']
|
683
|
+
end
|
684
|
+
|
685
|
+
it 'sets the Last-Modified response header' do
|
686
|
+
send_file_app
|
687
|
+
get '/file.txt'
|
688
|
+
assert_equal File.mtime(@file).httpdate, response['Last-Modified']
|
689
|
+
end
|
690
|
+
|
691
|
+
it 'allows passing in a differen Last-Modified response header with :last_modified' do
|
692
|
+
time = Time.now
|
693
|
+
send_file_app :last_modified => time
|
694
|
+
get '/file.txt'
|
695
|
+
assert_equal time.httpdate, response['Last-Modified']
|
696
|
+
end
|
697
|
+
|
698
|
+
it "returns a 404 when not found" do
|
699
|
+
mock_app {
|
700
|
+
get '/' do
|
701
|
+
send_file 'this-file-does-not-exist.txt'
|
702
|
+
end
|
703
|
+
}
|
704
|
+
get '/'
|
705
|
+
assert not_found?
|
706
|
+
end
|
707
|
+
|
708
|
+
it "does not set the Content-Disposition header by default" do
|
709
|
+
send_file_app
|
710
|
+
get '/file.txt'
|
711
|
+
assert_nil response['Content-Disposition']
|
712
|
+
end
|
713
|
+
|
714
|
+
it "sets the Content-Disposition header when :disposition set to 'attachment'" do
|
715
|
+
send_file_app :disposition => 'attachment'
|
716
|
+
get '/file.txt'
|
717
|
+
assert_equal 'attachment; filename="file.txt"', response['Content-Disposition']
|
718
|
+
end
|
719
|
+
|
720
|
+
it "sets the Content-Disposition header when :disposition set to 'inline'" do
|
721
|
+
send_file_app :disposition => 'inline'
|
722
|
+
get '/file.txt'
|
723
|
+
assert_equal 'inline', response['Content-Disposition']
|
724
|
+
end
|
725
|
+
|
726
|
+
it "sets the Content-Disposition header when :filename provided" do
|
727
|
+
send_file_app :filename => 'foo.txt'
|
728
|
+
get '/file.txt'
|
729
|
+
assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition']
|
730
|
+
end
|
731
|
+
|
732
|
+
it "is able to send files with unkown mime type" do
|
733
|
+
@file = File.dirname(__FILE__) + '/file.foobar'
|
734
|
+
File.open(@file, 'wb') { |io| io.write('Hello World') }
|
735
|
+
send_file_app
|
736
|
+
get '/file.txt'
|
737
|
+
assert_equal 'application/octet-stream', response['Content-Type']
|
738
|
+
end
|
739
|
+
|
740
|
+
it "does not override Content-Type if already set and no explicit type is given" do
|
741
|
+
path = @file
|
742
|
+
mock_app do
|
743
|
+
get '/' do
|
744
|
+
content_type :png
|
745
|
+
send_file path
|
746
|
+
end
|
747
|
+
end
|
748
|
+
get '/'
|
749
|
+
assert_equal 'image/png', response['Content-Type']
|
750
|
+
end
|
751
|
+
|
752
|
+
it "does override Content-Type even if already set, if explicit type is given" do
|
753
|
+
path = @file
|
754
|
+
mock_app do
|
755
|
+
get '/' do
|
756
|
+
content_type :png
|
757
|
+
send_file path, :type => :gif
|
758
|
+
end
|
759
|
+
end
|
760
|
+
get '/'
|
761
|
+
assert_equal 'image/gif', response['Content-Type']
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
describe 'cache_control' do
|
766
|
+
setup do
|
767
|
+
mock_app do
|
768
|
+
get '/foo' do
|
769
|
+
cache_control :public, :no_cache, :max_age => 60.0
|
770
|
+
'Hello World'
|
771
|
+
end
|
772
|
+
|
773
|
+
get '/bar' do
|
774
|
+
cache_control :public, :no_cache
|
775
|
+
'Hello World'
|
776
|
+
end
|
777
|
+
end
|
778
|
+
end
|
779
|
+
|
780
|
+
it 'sets the Cache-Control header' do
|
781
|
+
get '/foo'
|
782
|
+
assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
|
783
|
+
end
|
784
|
+
|
785
|
+
it 'last argument does not have to be a hash' do
|
786
|
+
get '/bar'
|
787
|
+
assert_equal ['public', 'no-cache'], response['Cache-Control'].split(', ')
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
describe 'expires' do
|
792
|
+
setup do
|
793
|
+
mock_app do
|
794
|
+
get '/foo' do
|
795
|
+
expires 60, :public, :no_cache
|
796
|
+
'Hello World'
|
797
|
+
end
|
798
|
+
|
799
|
+
get '/bar' do
|
800
|
+
expires Time.now
|
801
|
+
end
|
802
|
+
|
803
|
+
get '/baz' do
|
804
|
+
expires Time.at(0)
|
805
|
+
end
|
806
|
+
|
807
|
+
get '/blah' do
|
808
|
+
obj = Object.new
|
809
|
+
def obj.method_missing(*a, &b) 60.send(*a, &b) end
|
810
|
+
def obj.is_a?(thing) 60.is_a?(thing) end
|
811
|
+
expires obj, :public, :no_cache
|
812
|
+
'Hello World'
|
813
|
+
end
|
814
|
+
|
815
|
+
get '/boom' do
|
816
|
+
expires '9999'
|
817
|
+
end
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
821
|
+
it 'sets the Cache-Control header' do
|
822
|
+
get '/foo'
|
823
|
+
assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
|
824
|
+
end
|
825
|
+
|
826
|
+
it 'sets the Expires header' do
|
827
|
+
get '/foo'
|
828
|
+
assert_not_nil response['Expires']
|
829
|
+
end
|
830
|
+
|
831
|
+
it 'allows passing time objects' do
|
832
|
+
get '/bar'
|
833
|
+
assert_not_nil response['Expires']
|
834
|
+
end
|
835
|
+
|
836
|
+
it 'allows passing time objects' do
|
837
|
+
get '/baz'
|
838
|
+
assert_equal 'Thu, 01 Jan 1970 00:00:00 GMT', response['Expires']
|
839
|
+
end
|
840
|
+
|
841
|
+
it 'accepts values pretending to be a Numeric (like ActiveSupport::Duration)' do
|
842
|
+
get '/blah'
|
843
|
+
assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
|
844
|
+
end
|
845
|
+
|
846
|
+
it 'fails when Time.parse raises an ArgumentError' do
|
847
|
+
assert_raise(ArgumentError) { get '/boom' }
|
848
|
+
end
|
849
|
+
end
|
850
|
+
|
851
|
+
describe 'last_modified' do
|
852
|
+
it 'ignores nil' do
|
853
|
+
mock_app do
|
854
|
+
get '/' do last_modified nil; 200; end
|
855
|
+
end
|
856
|
+
|
857
|
+
get '/'
|
858
|
+
assert ! response['Last-Modified']
|
859
|
+
end
|
860
|
+
|
861
|
+
[Time.now, DateTime.now, Date.today, Time.now.to_i,
|
862
|
+
Struct.new(:to_time).new(Time.now) ].each do |last_modified_time|
|
863
|
+
describe "with #{last_modified_time.class.name}" do
|
864
|
+
setup do
|
865
|
+
mock_app do
|
866
|
+
get '/' do
|
867
|
+
last_modified last_modified_time
|
868
|
+
'Boo!'
|
869
|
+
end
|
870
|
+
end
|
871
|
+
wrapper = Object.new.extend aldebaran::Helpers
|
872
|
+
@last_modified_time = wrapper.time_for last_modified_time
|
873
|
+
end
|
874
|
+
|
875
|
+
# fixes strange missing test error when running complete test suite.
|
876
|
+
it("does not complain about missing tests") { }
|
877
|
+
|
878
|
+
context "when there's no If-Modified-Since header" do
|
879
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
880
|
+
get '/'
|
881
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
882
|
+
end
|
883
|
+
|
884
|
+
it 'conditional GET misses and returns a body' do
|
885
|
+
get '/'
|
886
|
+
assert_equal 200, status
|
887
|
+
assert_equal 'Boo!', body
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
context "when there's an invalid If-Modified-Since header" do
|
892
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
893
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }
|
894
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
895
|
+
end
|
896
|
+
|
897
|
+
it 'conditional GET misses and returns a body' do
|
898
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }
|
899
|
+
assert_equal 200, status
|
900
|
+
assert_equal 'Boo!', body
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
context "when the resource has been modified since the If-Modified-Since header date" do
|
905
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
906
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }
|
907
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
908
|
+
end
|
909
|
+
|
910
|
+
it 'conditional GET misses and returns a body' do
|
911
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }
|
912
|
+
assert_equal 200, status
|
913
|
+
assert_equal 'Boo!', body
|
914
|
+
end
|
915
|
+
|
916
|
+
it 'does not rely on string comparison' do
|
917
|
+
mock_app do
|
918
|
+
get '/compare' do
|
919
|
+
last_modified "Mon, 18 Oct 2010 20:57:11 GMT"
|
920
|
+
"foo"
|
921
|
+
end
|
922
|
+
end
|
923
|
+
|
924
|
+
get '/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2010 23:43:52 GMT' }
|
925
|
+
assert_equal 200, status
|
926
|
+
assert_equal 'foo', body
|
927
|
+
get '/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }
|
928
|
+
assert_equal 304, status
|
929
|
+
assert_equal '', body
|
930
|
+
end
|
931
|
+
end
|
932
|
+
|
933
|
+
context "when the resource has been modified on the exact If-Modified-Since header date" do
|
934
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
935
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }
|
936
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
937
|
+
end
|
938
|
+
|
939
|
+
it 'conditional GET matches and halts' do
|
940
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }
|
941
|
+
assert_equal 304, status
|
942
|
+
assert_equal '', body
|
943
|
+
end
|
944
|
+
end
|
945
|
+
|
946
|
+
context "when the resource hasn't been modified since the If-Modified-Since header date" do
|
947
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
948
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }
|
949
|
+
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
950
|
+
end
|
951
|
+
|
952
|
+
it 'conditional GET matches and halts' do
|
953
|
+
get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }
|
954
|
+
assert_equal 304, status
|
955
|
+
assert_equal '', body
|
956
|
+
end
|
957
|
+
end
|
958
|
+
end
|
959
|
+
end
|
960
|
+
end
|
961
|
+
|
962
|
+
describe 'etag' do
|
963
|
+
setup do
|
964
|
+
mock_app {
|
965
|
+
get '/' do
|
966
|
+
body { 'Hello World' }
|
967
|
+
etag 'FOO'
|
968
|
+
'Boo!'
|
969
|
+
end
|
970
|
+
|
971
|
+
post '/' do
|
972
|
+
etag 'FOO'
|
973
|
+
'Matches!'
|
974
|
+
end
|
975
|
+
}
|
976
|
+
end
|
977
|
+
|
978
|
+
it 'sets the ETag header' do
|
979
|
+
get '/'
|
980
|
+
assert_equal '"FOO"', response['ETag']
|
981
|
+
end
|
982
|
+
|
983
|
+
it 'returns a body when conditional get misses' do
|
984
|
+
get '/'
|
985
|
+
assert_equal 200, status
|
986
|
+
assert_equal 'Boo!', body
|
987
|
+
end
|
988
|
+
|
989
|
+
it 'returns a body when posting with no If-None-Match header' do
|
990
|
+
post '/'
|
991
|
+
assert_equal 200, status
|
992
|
+
assert_equal 'Matches!', body
|
993
|
+
end
|
994
|
+
|
995
|
+
it 'returns a body when conditional post matches' do
|
996
|
+
post '/', {}, { 'HTTP_IF_NONE_MATCH' => '"FOO"' }
|
997
|
+
assert_equal 200, status
|
998
|
+
assert_equal 'Matches!', body
|
999
|
+
end
|
1000
|
+
|
1001
|
+
it 'halts with 412 when conditional post misses' do
|
1002
|
+
post '/', {}, { 'HTTP_IF_NONE_MATCH' => '"BAR"' }
|
1003
|
+
assert_equal 412, status
|
1004
|
+
assert_equal '', body
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
it 'halts when a conditional GET matches' do
|
1008
|
+
get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"FOO"' }
|
1009
|
+
assert_equal 304, status
|
1010
|
+
assert_equal '', body
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
it 'should handle multiple ETag values in If-None-Match header' do
|
1014
|
+
get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"BAR", *' }
|
1015
|
+
assert_equal 304, status
|
1016
|
+
assert_equal '', body
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
it 'uses a weak etag with the :weak option' do
|
1020
|
+
mock_app {
|
1021
|
+
get '/' do
|
1022
|
+
etag 'FOO', :weak
|
1023
|
+
"that's weak, dude."
|
1024
|
+
end
|
1025
|
+
}
|
1026
|
+
get '/'
|
1027
|
+
assert_equal 'W/"FOO"', response['ETag']
|
1028
|
+
end
|
1029
|
+
|
1030
|
+
it 'raises an ArgumentError for an invalid strength' do
|
1031
|
+
mock_app do
|
1032
|
+
get '/' do
|
1033
|
+
etag 'FOO', :w00t
|
1034
|
+
"that's weak, dude."
|
1035
|
+
end
|
1036
|
+
end
|
1037
|
+
assert_raise(ArgumentError) { get '/' }
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
describe 'back' do
|
1042
|
+
it "makes redirecting back pretty" do
|
1043
|
+
mock_app {
|
1044
|
+
get '/foo' do
|
1045
|
+
redirect back
|
1046
|
+
end
|
1047
|
+
}
|
1048
|
+
|
1049
|
+
get '/foo', {}, 'HTTP_REFERER' => 'http://github.com'
|
1050
|
+
assert redirect?
|
1051
|
+
assert_equal "http://github.com", response.location
|
1052
|
+
end
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
describe 'uri' do
|
1056
|
+
it 'generates absolute urls' do
|
1057
|
+
mock_app { get('/') { uri }}
|
1058
|
+
get '/'
|
1059
|
+
assert_equal 'http://example.org/', body
|
1060
|
+
end
|
1061
|
+
|
1062
|
+
it 'includes path_info' do
|
1063
|
+
mock_app { get('/:name') { uri }}
|
1064
|
+
get '/foo'
|
1065
|
+
assert_equal 'http://example.org/foo', body
|
1066
|
+
end
|
1067
|
+
|
1068
|
+
it 'allows passing an alternative to path_info' do
|
1069
|
+
mock_app { get('/:name') { uri '/bar' }}
|
1070
|
+
get '/foo'
|
1071
|
+
assert_equal 'http://example.org/bar', body
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
it 'includes script_name' do
|
1075
|
+
mock_app { get('/:name') { uri '/bar' }}
|
1076
|
+
get '/foo', {}, { "SCRIPT_NAME" => '/foo' }
|
1077
|
+
assert_equal 'http://example.org/foo/bar', body
|
1078
|
+
end
|
1079
|
+
|
1080
|
+
it 'handles absolute URIs' do
|
1081
|
+
mock_app { get('/') { uri 'http://google.com' }}
|
1082
|
+
get '/'
|
1083
|
+
assert_equal 'http://google.com', body
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
it 'handles different protocols' do
|
1087
|
+
mock_app { get('/') { uri 'mailto:jsmith@example.com' }}
|
1088
|
+
get '/'
|
1089
|
+
assert_equal 'mailto:jsmith@example.com', body
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
it 'is aliased to #url' do
|
1093
|
+
mock_app { get('/') { url }}
|
1094
|
+
get '/'
|
1095
|
+
assert_equal 'http://example.org/', body
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
it 'is aliased to #to' do
|
1099
|
+
mock_app { get('/') { to }}
|
1100
|
+
get '/'
|
1101
|
+
assert_equal 'http://example.org/', body
|
1102
|
+
end
|
1103
|
+
end
|
1104
|
+
|
1105
|
+
describe 'logger' do
|
1106
|
+
it 'logging works when logging is enabled' do
|
1107
|
+
mock_app do
|
1108
|
+
enable :logging
|
1109
|
+
get '/' do
|
1110
|
+
logger.info "Program started"
|
1111
|
+
logger.warn "Nothing to do!"
|
1112
|
+
end
|
1113
|
+
end
|
1114
|
+
io = StringIO.new
|
1115
|
+
get '/', {}, 'rack.errors' => io
|
1116
|
+
assert io.string.include?("INFO -- : Program started")
|
1117
|
+
assert io.string.include?("WARN -- : Nothing to do")
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
it 'logging works when logging is disable, but no output is produced' do
|
1121
|
+
mock_app do
|
1122
|
+
disable :logging
|
1123
|
+
get '/' do
|
1124
|
+
logger.info "Program started"
|
1125
|
+
logger.warn "Nothing to do!"
|
1126
|
+
end
|
1127
|
+
end
|
1128
|
+
io = StringIO.new
|
1129
|
+
get '/', {}, 'rack.errors' => io
|
1130
|
+
assert !io.string.include?("INFO -- : Program started")
|
1131
|
+
assert !io.string.include?("WARN -- : Nothing to do")
|
1132
|
+
end
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
module ::HelperOne; def one; '1'; end; end
|
1136
|
+
module ::HelperTwo; def two; '2'; end; end
|
1137
|
+
|
1138
|
+
describe 'Adding new helpers' do
|
1139
|
+
it 'takes a list of modules to mix into the app' do
|
1140
|
+
mock_app {
|
1141
|
+
helpers ::HelperOne, ::HelperTwo
|
1142
|
+
|
1143
|
+
get '/one' do
|
1144
|
+
one
|
1145
|
+
end
|
1146
|
+
|
1147
|
+
get '/two' do
|
1148
|
+
two
|
1149
|
+
end
|
1150
|
+
}
|
1151
|
+
|
1152
|
+
get '/one'
|
1153
|
+
assert_equal '1', body
|
1154
|
+
|
1155
|
+
get '/two'
|
1156
|
+
assert_equal '2', body
|
1157
|
+
end
|
1158
|
+
|
1159
|
+
it 'takes a block to mix into the app' do
|
1160
|
+
mock_app {
|
1161
|
+
helpers do
|
1162
|
+
def foo
|
1163
|
+
'foo'
|
1164
|
+
end
|
1165
|
+
end
|
1166
|
+
|
1167
|
+
get '/' do
|
1168
|
+
foo
|
1169
|
+
end
|
1170
|
+
}
|
1171
|
+
|
1172
|
+
get '/'
|
1173
|
+
assert_equal 'foo', body
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
it 'evaluates the block in class context so that methods can be aliased' do
|
1177
|
+
mock_app {
|
1178
|
+
helpers do
|
1179
|
+
alias_method :h, :escape_html
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
get '/' do
|
1183
|
+
h('42 < 43')
|
1184
|
+
end
|
1185
|
+
}
|
1186
|
+
|
1187
|
+
get '/'
|
1188
|
+
assert ok?
|
1189
|
+
assert_equal '42 < 43', body
|
1190
|
+
end
|
1191
|
+
end
|
1192
|
+
end
|