sinatra 1.4.8 → 2.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of sinatra might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -47
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +37 -49
- data/MAINTENANCE.md +42 -0
- data/README.de.md +5 -5
- data/README.es.md +5 -5
- data/README.fr.md +9 -9
- data/README.hu.md +3 -3
- data/README.ja.md +19 -8
- data/README.ko.md +8 -8
- data/README.md +90 -61
- data/README.pt-br.md +3 -3
- data/README.pt-pt.md +2 -2
- data/README.ru.md +42 -26
- data/README.zh.md +8 -8
- data/Rakefile +0 -6
- data/SECURITY.md +35 -0
- data/lib/sinatra/base.rb +113 -161
- data/lib/sinatra/main.rb +1 -0
- data/lib/sinatra/show_exceptions.rb +8 -8
- data/lib/sinatra/version.rb +1 -1
- data/sinatra.gemspec +7 -4
- metadata +34 -168
- data/lib/sinatra/ext.rb +0 -17
- data/test/asciidoctor_test.rb +0 -72
- data/test/base_test.rb +0 -167
- data/test/builder_test.rb +0 -91
- data/test/coffee_test.rb +0 -96
- data/test/compile_test.rb +0 -183
- data/test/contest.rb +0 -91
- data/test/creole_test.rb +0 -65
- data/test/delegator_test.rb +0 -160
- data/test/encoding_test.rb +0 -20
- data/test/erb_test.rb +0 -116
- data/test/extensions_test.rb +0 -98
- data/test/filter_test.rb +0 -487
- data/test/haml_test.rb +0 -109
- data/test/helper.rb +0 -132
- data/test/helpers_test.rb +0 -1917
- data/test/integration/app.rb +0 -79
- data/test/integration_helper.rb +0 -236
- data/test/integration_test.rb +0 -104
- data/test/less_test.rb +0 -69
- data/test/liquid_test.rb +0 -77
- data/test/mapped_error_test.rb +0 -285
- data/test/markaby_test.rb +0 -80
- data/test/markdown_test.rb +0 -85
- data/test/mediawiki_test.rb +0 -68
- data/test/middleware_test.rb +0 -68
- data/test/nokogiri_test.rb +0 -67
- data/test/public/favicon.ico +0 -0
- data/test/public/hello+world.txt +0 -1
- data/test/rabl_test.rb +0 -89
- data/test/rack_test.rb +0 -45
- data/test/radius_test.rb +0 -59
- data/test/rdoc_test.rb +0 -66
- data/test/readme_test.rb +0 -130
- data/test/request_test.rb +0 -100
- data/test/response_test.rb +0 -63
- data/test/result_test.rb +0 -76
- data/test/route_added_hook_test.rb +0 -59
- data/test/routing_test.rb +0 -1456
- data/test/sass_test.rb +0 -115
- data/test/scss_test.rb +0 -88
- data/test/server_test.rb +0 -56
- data/test/settings_test.rb +0 -582
- data/test/sinatra_test.rb +0 -12
- data/test/slim_test.rb +0 -102
- data/test/static_test.rb +0 -266
- data/test/streaming_test.rb +0 -149
- data/test/stylus_test.rb +0 -90
- data/test/templates_test.rb +0 -382
- data/test/textile_test.rb +0 -65
- data/test/views/a/in_a.str +0 -1
- data/test/views/ascii.erb +0 -2
- data/test/views/b/in_b.str +0 -1
- data/test/views/calc.html.erb +0 -1
- data/test/views/error.builder +0 -3
- data/test/views/error.erb +0 -3
- data/test/views/error.haml +0 -3
- data/test/views/error.sass +0 -2
- data/test/views/explicitly_nested.str +0 -1
- data/test/views/foo/hello.test +0 -1
- data/test/views/hello.asciidoc +0 -1
- data/test/views/hello.builder +0 -1
- data/test/views/hello.coffee +0 -1
- data/test/views/hello.creole +0 -1
- data/test/views/hello.erb +0 -1
- data/test/views/hello.haml +0 -1
- data/test/views/hello.less +0 -5
- data/test/views/hello.liquid +0 -1
- data/test/views/hello.mab +0 -1
- data/test/views/hello.md +0 -1
- data/test/views/hello.mediawiki +0 -1
- data/test/views/hello.nokogiri +0 -1
- data/test/views/hello.rabl +0 -2
- data/test/views/hello.radius +0 -1
- data/test/views/hello.rdoc +0 -1
- data/test/views/hello.sass +0 -2
- data/test/views/hello.scss +0 -3
- data/test/views/hello.slim +0 -1
- data/test/views/hello.str +0 -1
- data/test/views/hello.styl +0 -2
- data/test/views/hello.test +0 -1
- data/test/views/hello.textile +0 -1
- data/test/views/hello.wlang +0 -1
- data/test/views/hello.yajl +0 -1
- data/test/views/layout2.builder +0 -3
- data/test/views/layout2.erb +0 -2
- data/test/views/layout2.haml +0 -2
- data/test/views/layout2.liquid +0 -2
- data/test/views/layout2.mab +0 -2
- data/test/views/layout2.nokogiri +0 -3
- data/test/views/layout2.rabl +0 -3
- data/test/views/layout2.radius +0 -2
- data/test/views/layout2.slim +0 -3
- data/test/views/layout2.str +0 -2
- data/test/views/layout2.test +0 -1
- data/test/views/layout2.wlang +0 -2
- data/test/views/nested.str +0 -1
- data/test/views/utf8.erb +0 -2
- data/test/wlang_test.rb +0 -87
- data/test/yajl_test.rb +0 -86
data/test/haml_test.rb
DELETED
@@ -1,109 +0,0 @@
|
|
1
|
-
require File.expand_path('../helper', __FILE__)
|
2
|
-
|
3
|
-
begin
|
4
|
-
require 'haml'
|
5
|
-
|
6
|
-
class HAMLTest < Minitest::Test
|
7
|
-
def haml_app(&block)
|
8
|
-
mock_app do
|
9
|
-
set :views, File.dirname(__FILE__) + '/views'
|
10
|
-
get('/', &block)
|
11
|
-
end
|
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 do
|
29
|
-
layout { %q(%h1= 'THIS. IS. ' + yield.upcase) }
|
30
|
-
get('/') { haml '%em Sparta' }
|
31
|
-
end
|
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 { haml 'Hello World', :layout => :layout2 }
|
39
|
-
assert ok?
|
40
|
-
assert_equal "<h1>HAML Layout!</h1>\n<p>Hello World</p>\n", body
|
41
|
-
end
|
42
|
-
|
43
|
-
it "raises error if template not found" do
|
44
|
-
mock_app { get('/') { haml :no_such_template } }
|
45
|
-
assert_raises(Errno::ENOENT) { get('/') }
|
46
|
-
end
|
47
|
-
|
48
|
-
it "passes HAML options to the Haml engine" do
|
49
|
-
mock_app {
|
50
|
-
get('/') { haml "!!!\n%h1 Hello World", :format => :html5 }
|
51
|
-
}
|
52
|
-
get '/'
|
53
|
-
assert ok?
|
54
|
-
assert_equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n", body
|
55
|
-
end
|
56
|
-
|
57
|
-
it "passes default HAML options to the Haml engine" do
|
58
|
-
mock_app do
|
59
|
-
set :haml, {:format => :html5}
|
60
|
-
get('/') { haml "!!!\n%h1 Hello World" }
|
61
|
-
end
|
62
|
-
get '/'
|
63
|
-
assert ok?
|
64
|
-
assert_equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n", body
|
65
|
-
end
|
66
|
-
|
67
|
-
it "merges the default HAML options with the overrides and passes them to the Haml engine" do
|
68
|
-
mock_app do
|
69
|
-
set :haml, {:format => :html5, :attr_wrapper => '"'} # default HAML attr are <tag attr='single-quoted'>
|
70
|
-
get('/') { haml "!!!\n%h1{:class => :header} Hello World" }
|
71
|
-
get('/html4') {
|
72
|
-
haml "!!!\n%h1{:class => 'header'} Hello World", :format => :html4
|
73
|
-
}
|
74
|
-
end
|
75
|
-
get '/'
|
76
|
-
assert ok?
|
77
|
-
assert_equal "<!DOCTYPE html>\n<h1 class=\"header\">Hello World</h1>\n", body
|
78
|
-
get '/html4'
|
79
|
-
assert ok?
|
80
|
-
assert_match(/^<!DOCTYPE html PUBLIC (.*) HTML 4.01/, body)
|
81
|
-
end
|
82
|
-
|
83
|
-
it "is possible to pass locals" do
|
84
|
-
haml_app { haml "= foo", :locals => { :foo => 'bar' }}
|
85
|
-
assert_equal "bar\n", body
|
86
|
-
end
|
87
|
-
|
88
|
-
it "can render truly nested layouts by accepting a layout and a block with the contents" do
|
89
|
-
mock_app do
|
90
|
-
template(:main_outer_layout) { "%h1 Title\n= yield" }
|
91
|
-
template(:an_inner_layout) { "%h2 Subtitle\n= yield" }
|
92
|
-
template(:a_page) { "%p Contents." }
|
93
|
-
get('/') do
|
94
|
-
haml :main_outer_layout, :layout => false do
|
95
|
-
haml :an_inner_layout do
|
96
|
-
haml :a_page
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
get '/'
|
102
|
-
assert ok?
|
103
|
-
assert_body "<h1>Title</h1>\n<h2>Subtitle</h2>\n<p>Contents.</p>\n"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
rescue LoadError
|
108
|
-
warn "#{$!.to_s}: skipping haml tests"
|
109
|
-
end
|
data/test/helper.rb
DELETED
@@ -1,132 +0,0 @@
|
|
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 'minitest'
|
20
|
-
require 'contest'
|
21
|
-
require 'rack/test'
|
22
|
-
require 'sinatra/base'
|
23
|
-
|
24
|
-
class Sinatra::Base
|
25
|
-
include Minitest::Assertions
|
26
|
-
# Allow assertions in request context
|
27
|
-
def assertions
|
28
|
-
@assertions ||= 0
|
29
|
-
end
|
30
|
-
|
31
|
-
attr_writer :assertions
|
32
|
-
end
|
33
|
-
|
34
|
-
class Rack::Builder
|
35
|
-
def include?(middleware)
|
36
|
-
@ins.any? { |m| p m ; middleware === m }
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
Sinatra::Base.set :environment, :test
|
41
|
-
|
42
|
-
class Minitest::Test
|
43
|
-
include Rack::Test::Methods
|
44
|
-
|
45
|
-
class << self
|
46
|
-
alias_method :it, :test
|
47
|
-
alias_method :section, :context
|
48
|
-
end
|
49
|
-
|
50
|
-
def self.example(desc = nil, &block)
|
51
|
-
@example_count = 0 unless instance_variable_defined? :@example_count
|
52
|
-
@example_count += 1
|
53
|
-
it(desc || "Example #{@example_count}", &block)
|
54
|
-
end
|
55
|
-
|
56
|
-
alias_method :response, :last_response
|
57
|
-
|
58
|
-
setup do
|
59
|
-
Sinatra::Base.set :environment, :test
|
60
|
-
end
|
61
|
-
|
62
|
-
# Sets up a Sinatra::Base subclass defined with the block
|
63
|
-
# given. Used in setup or individual spec methods to establish
|
64
|
-
# the application.
|
65
|
-
def mock_app(base=Sinatra::Base, &block)
|
66
|
-
@app = Sinatra.new(base, &block)
|
67
|
-
end
|
68
|
-
|
69
|
-
def app
|
70
|
-
Rack::Lint.new(@app)
|
71
|
-
end
|
72
|
-
|
73
|
-
def body
|
74
|
-
response.body.to_s
|
75
|
-
end
|
76
|
-
|
77
|
-
def assert_body(value)
|
78
|
-
if value.respond_to? :to_str
|
79
|
-
assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "")
|
80
|
-
else
|
81
|
-
assert_match value, body
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def assert_status(expected)
|
86
|
-
assert_equal Integer(expected), Integer(status)
|
87
|
-
end
|
88
|
-
|
89
|
-
def assert_like(a,b)
|
90
|
-
pattern = /id=['"][^"']*["']|\s+/
|
91
|
-
assert_equal a.strip.gsub(pattern, ""), b.strip.gsub(pattern, "")
|
92
|
-
end
|
93
|
-
|
94
|
-
def assert_include(str, substr)
|
95
|
-
assert str.include?(substr), "expected #{str.inspect} to include #{substr.inspect}"
|
96
|
-
end
|
97
|
-
|
98
|
-
def options(uri, params = {}, env = {}, &block)
|
99
|
-
request(uri, env.merge(:method => "OPTIONS", :params => params), &block)
|
100
|
-
end
|
101
|
-
|
102
|
-
def patch(uri, params = {}, env = {}, &block)
|
103
|
-
request(uri, env.merge(:method => "PATCH", :params => params), &block)
|
104
|
-
end
|
105
|
-
|
106
|
-
def link(uri, params = {}, env = {}, &block)
|
107
|
-
request(uri, env.merge(:method => "LINK", :params => params), &block)
|
108
|
-
end
|
109
|
-
|
110
|
-
def unlink(uri, params = {}, env = {}, &block)
|
111
|
-
request(uri, env.merge(:method => "UNLINK", :params => params), &block)
|
112
|
-
end
|
113
|
-
|
114
|
-
# Delegate other missing methods to response.
|
115
|
-
def method_missing(name, *args, &block)
|
116
|
-
if response && response.respond_to?(name)
|
117
|
-
response.send(name, *args, &block)
|
118
|
-
else
|
119
|
-
super
|
120
|
-
end
|
121
|
-
rescue Rack::Test::Error
|
122
|
-
super
|
123
|
-
end
|
124
|
-
|
125
|
-
# Do not output warnings for the duration of the block.
|
126
|
-
def silence_warnings
|
127
|
-
$VERBOSE, v = nil, $VERBOSE
|
128
|
-
yield
|
129
|
-
ensure
|
130
|
-
$VERBOSE = v
|
131
|
-
end
|
132
|
-
end
|
data/test/helpers_test.rb
DELETED
@@ -1,1917 +0,0 @@
|
|
1
|
-
require File.expand_path('../helper', __FILE__)
|
2
|
-
require 'date'
|
3
|
-
require 'json'
|
4
|
-
|
5
|
-
class HelpersTest < Minitest::Test
|
6
|
-
def test_default
|
7
|
-
assert true
|
8
|
-
end
|
9
|
-
|
10
|
-
def status_app(code, &block)
|
11
|
-
code += 2 if [204, 205, 304].include? code
|
12
|
-
block ||= proc { }
|
13
|
-
mock_app do
|
14
|
-
get('/') do
|
15
|
-
status code
|
16
|
-
instance_eval(&block).inspect
|
17
|
-
end
|
18
|
-
end
|
19
|
-
get '/'
|
20
|
-
end
|
21
|
-
|
22
|
-
describe 'status' do
|
23
|
-
it 'sets the response status code' do
|
24
|
-
status_app 207
|
25
|
-
assert_equal 207, response.status
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
describe 'not_found?' do
|
30
|
-
it 'is true for status == 404' do
|
31
|
-
status_app(404) { not_found? }
|
32
|
-
assert_body 'true'
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'is false for status gt 404' do
|
36
|
-
status_app(405) { not_found? }
|
37
|
-
assert_body 'false'
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'is false for status lt 404' do
|
41
|
-
status_app(403) { not_found? }
|
42
|
-
assert_body 'false'
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
describe 'informational?' do
|
47
|
-
it 'is true for 1xx status' do
|
48
|
-
status_app(100 + rand(100)) { informational? }
|
49
|
-
assert_body 'true'
|
50
|
-
end
|
51
|
-
|
52
|
-
it 'is false for status > 199' do
|
53
|
-
status_app(200 + rand(400)) { informational? }
|
54
|
-
assert_body 'false'
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
describe 'success?' do
|
59
|
-
it 'is true for 2xx status' do
|
60
|
-
status_app(200 + rand(100)) { success? }
|
61
|
-
assert_body 'true'
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'is false for status < 200' do
|
65
|
-
status_app(100 + rand(100)) { success? }
|
66
|
-
assert_body 'false'
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'is false for status > 299' do
|
70
|
-
status_app(300 + rand(300)) { success? }
|
71
|
-
assert_body 'false'
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
describe 'redirect?' do
|
76
|
-
it 'is true for 3xx status' do
|
77
|
-
status_app(300 + rand(100)) { redirect? }
|
78
|
-
assert_body 'true'
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'is false for status < 300' do
|
82
|
-
status_app(200 + rand(100)) { redirect? }
|
83
|
-
assert_body 'false'
|
84
|
-
end
|
85
|
-
|
86
|
-
it 'is false for status > 399' do
|
87
|
-
status_app(400 + rand(200)) { redirect? }
|
88
|
-
assert_body 'false'
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
describe 'client_error?' do
|
93
|
-
it 'is true for 4xx status' do
|
94
|
-
status_app(400 + rand(100)) { client_error? }
|
95
|
-
assert_body 'true'
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'is false for status < 400' do
|
99
|
-
status_app(200 + rand(200)) { client_error? }
|
100
|
-
assert_body 'false'
|
101
|
-
end
|
102
|
-
|
103
|
-
it 'is false for status > 499' do
|
104
|
-
status_app(500 + rand(100)) { client_error? }
|
105
|
-
assert_body 'false'
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
describe 'server_error?' do
|
110
|
-
it 'is true for 5xx status' do
|
111
|
-
status_app(500 + rand(100)) { server_error? }
|
112
|
-
assert_body 'true'
|
113
|
-
end
|
114
|
-
|
115
|
-
it 'is false for status < 500' do
|
116
|
-
status_app(200 + rand(300)) { server_error? }
|
117
|
-
assert_body 'false'
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
describe 'body' do
|
122
|
-
it 'takes a block for deferred body generation' do
|
123
|
-
mock_app do
|
124
|
-
get('/') { body { 'Hello World' } }
|
125
|
-
end
|
126
|
-
|
127
|
-
get '/'
|
128
|
-
assert_equal 'Hello World', body
|
129
|
-
end
|
130
|
-
|
131
|
-
it 'takes a String, Array, or other object responding to #each' do
|
132
|
-
mock_app { get('/') { body 'Hello World' } }
|
133
|
-
|
134
|
-
get '/'
|
135
|
-
assert_equal 'Hello World', body
|
136
|
-
end
|
137
|
-
|
138
|
-
it 'can be used with other objects' do
|
139
|
-
mock_app do
|
140
|
-
get '/' do
|
141
|
-
body :hello => 'from json'
|
142
|
-
end
|
143
|
-
|
144
|
-
after do
|
145
|
-
if Hash === response.body
|
146
|
-
body response.body[:hello]
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
get '/'
|
152
|
-
assert_body 'from json'
|
153
|
-
end
|
154
|
-
|
155
|
-
it 'can be set in after filter' do
|
156
|
-
mock_app do
|
157
|
-
get('/') { body 'route' }
|
158
|
-
after { body 'filter' }
|
159
|
-
end
|
160
|
-
|
161
|
-
get '/'
|
162
|
-
assert_body 'filter'
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
describe 'redirect' do
|
167
|
-
it 'uses a 302 when only a path is given' do
|
168
|
-
mock_app do
|
169
|
-
get('/') do
|
170
|
-
redirect '/foo'
|
171
|
-
fail 'redirect should halt'
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
get '/'
|
176
|
-
assert_equal 302, status
|
177
|
-
assert_equal '', body
|
178
|
-
assert_equal 'http://example.org/foo', response['Location']
|
179
|
-
end
|
180
|
-
|
181
|
-
it 'uses the code given when specified' do
|
182
|
-
mock_app do
|
183
|
-
get('/') do
|
184
|
-
redirect '/foo', 301
|
185
|
-
fail 'redirect should halt'
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
get '/'
|
190
|
-
assert_equal 301, status
|
191
|
-
assert_equal '', body
|
192
|
-
assert_equal 'http://example.org/foo', response['Location']
|
193
|
-
end
|
194
|
-
|
195
|
-
it 'redirects back to request.referer when passed back' do
|
196
|
-
mock_app { get('/try_redirect') { redirect back } }
|
197
|
-
|
198
|
-
request = Rack::MockRequest.new(@app)
|
199
|
-
response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo')
|
200
|
-
assert_equal 302, response.status
|
201
|
-
assert_equal 'http://example.org/foo', response['Location']
|
202
|
-
end
|
203
|
-
|
204
|
-
it 'redirects using a non-standard HTTP port' do
|
205
|
-
mock_app { get('/') { redirect '/foo' } }
|
206
|
-
|
207
|
-
request = Rack::MockRequest.new(@app)
|
208
|
-
response = request.get('/', 'SERVER_PORT' => '81')
|
209
|
-
assert_equal 'http://example.org:81/foo', response['Location']
|
210
|
-
end
|
211
|
-
|
212
|
-
it 'redirects using a non-standard HTTPS port' do
|
213
|
-
mock_app { get('/') { redirect '/foo' } }
|
214
|
-
|
215
|
-
request = Rack::MockRequest.new(@app)
|
216
|
-
response = request.get('/', 'SERVER_PORT' => '444')
|
217
|
-
assert_equal 'http://example.org:444/foo', response['Location']
|
218
|
-
end
|
219
|
-
|
220
|
-
it 'uses 303 for post requests if request is HTTP 1.1' do
|
221
|
-
mock_app { post('/') { redirect '/'} }
|
222
|
-
post('/', {}, 'HTTP_VERSION' => 'HTTP/1.1')
|
223
|
-
assert_equal 303, status
|
224
|
-
assert_equal '', body
|
225
|
-
assert_equal 'http://example.org/', response['Location']
|
226
|
-
end
|
227
|
-
|
228
|
-
it 'uses 302 for post requests if request is HTTP 1.0' do
|
229
|
-
mock_app { post('/') { redirect '/'} }
|
230
|
-
post('/', {}, 'HTTP_VERSION' => 'HTTP/1.0')
|
231
|
-
assert_equal 302, status
|
232
|
-
assert_equal '', body
|
233
|
-
assert_equal 'http://example.org/', response['Location']
|
234
|
-
end
|
235
|
-
|
236
|
-
it 'works behind a reverse proxy' do
|
237
|
-
mock_app { get('/') { redirect '/foo' } }
|
238
|
-
|
239
|
-
request = Rack::MockRequest.new(@app)
|
240
|
-
response = request.get('/', 'HTTP_X_FORWARDED_HOST' => 'example.com', 'SERVER_PORT' => '8080')
|
241
|
-
assert_equal 'http://example.com/foo', response['Location']
|
242
|
-
end
|
243
|
-
|
244
|
-
it 'accepts absolute URIs' do
|
245
|
-
mock_app do
|
246
|
-
get('/') do
|
247
|
-
redirect 'http://google.com'
|
248
|
-
fail 'redirect should halt'
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
get '/'
|
253
|
-
assert_equal 302, status
|
254
|
-
assert_equal '', body
|
255
|
-
assert_equal 'http://google.com', response['Location']
|
256
|
-
end
|
257
|
-
|
258
|
-
it 'accepts absolute URIs with a different schema' do
|
259
|
-
mock_app do
|
260
|
-
get('/') do
|
261
|
-
redirect 'mailto:jsmith@example.com'
|
262
|
-
fail 'redirect should halt'
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
get '/'
|
267
|
-
assert_equal 302, status
|
268
|
-
assert_equal '', body
|
269
|
-
assert_equal 'mailto:jsmith@example.com', response['Location']
|
270
|
-
end
|
271
|
-
|
272
|
-
it 'accepts a URI object instead of a String' do
|
273
|
-
mock_app do
|
274
|
-
get('/') { redirect URI.parse('http://sinatrarb.com') }
|
275
|
-
end
|
276
|
-
|
277
|
-
get '/'
|
278
|
-
assert_equal 302, status
|
279
|
-
assert_equal '', body
|
280
|
-
assert_equal 'http://sinatrarb.com', response['Location']
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
describe 'error' do
|
285
|
-
it 'sets a status code and halts' do
|
286
|
-
mock_app do
|
287
|
-
get('/') do
|
288
|
-
error 501
|
289
|
-
fail 'error should halt'
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
get '/'
|
294
|
-
assert_equal 501, status
|
295
|
-
assert_equal '', body
|
296
|
-
end
|
297
|
-
|
298
|
-
it 'takes an optional body' do
|
299
|
-
mock_app do
|
300
|
-
get('/') do
|
301
|
-
error 501, 'FAIL'
|
302
|
-
fail 'error should halt'
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
get '/'
|
307
|
-
assert_equal 501, status
|
308
|
-
assert_equal 'FAIL', body
|
309
|
-
end
|
310
|
-
|
311
|
-
it 'should not invoke error handler when setting status inside an error handler' do
|
312
|
-
mock_app do
|
313
|
-
disable :raise_errors
|
314
|
-
not_found do
|
315
|
-
body "not_found handler"
|
316
|
-
status 404
|
317
|
-
end
|
318
|
-
|
319
|
-
error do
|
320
|
-
body "error handler"
|
321
|
-
status 404
|
322
|
-
end
|
323
|
-
|
324
|
-
get '/' do
|
325
|
-
raise
|
326
|
-
end
|
327
|
-
end
|
328
|
-
|
329
|
-
get '/'
|
330
|
-
assert_equal 404, status
|
331
|
-
assert_equal 'error handler', body
|
332
|
-
end
|
333
|
-
|
334
|
-
it 'should not reset the content-type to html for error handlers' do
|
335
|
-
mock_app do
|
336
|
-
disable :raise_errors
|
337
|
-
before { content_type "application/json" }
|
338
|
-
not_found { JSON.dump("error" => "Not Found") }
|
339
|
-
end
|
340
|
-
|
341
|
-
get '/'
|
342
|
-
assert_equal 404, status
|
343
|
-
assert_equal 'application/json', response.content_type
|
344
|
-
end
|
345
|
-
|
346
|
-
it 'should not invoke error handler when halting with 500 inside an error handler' do
|
347
|
-
mock_app do
|
348
|
-
disable :raise_errors
|
349
|
-
not_found do
|
350
|
-
body "not_found handler"
|
351
|
-
halt 404
|
352
|
-
end
|
353
|
-
|
354
|
-
error do
|
355
|
-
body "error handler"
|
356
|
-
halt 404
|
357
|
-
end
|
358
|
-
|
359
|
-
get '/' do
|
360
|
-
raise
|
361
|
-
end
|
362
|
-
end
|
363
|
-
|
364
|
-
get '/'
|
365
|
-
assert_equal 404, status
|
366
|
-
assert_equal 'error handler', body
|
367
|
-
end
|
368
|
-
|
369
|
-
it 'should not invoke not_found handler when halting with 404 inside a not found handler' do
|
370
|
-
mock_app do
|
371
|
-
disable :raise_errors
|
372
|
-
|
373
|
-
not_found do
|
374
|
-
body "not_found handler"
|
375
|
-
halt 500
|
376
|
-
end
|
377
|
-
|
378
|
-
error do
|
379
|
-
body "error handler"
|
380
|
-
halt 500
|
381
|
-
end
|
382
|
-
end
|
383
|
-
|
384
|
-
get '/'
|
385
|
-
assert_equal 500, status
|
386
|
-
assert_equal 'not_found handler', body
|
387
|
-
end
|
388
|
-
|
389
|
-
it 'uses a 500 status code when first argument is a body' do
|
390
|
-
mock_app do
|
391
|
-
get('/') do
|
392
|
-
error 'FAIL'
|
393
|
-
fail 'error should halt'
|
394
|
-
end
|
395
|
-
end
|
396
|
-
|
397
|
-
get '/'
|
398
|
-
assert_equal 500, status
|
399
|
-
assert_equal 'FAIL', body
|
400
|
-
end
|
401
|
-
end
|
402
|
-
|
403
|
-
describe 'not_found' do
|
404
|
-
it 'halts with a 404 status' do
|
405
|
-
mock_app do
|
406
|
-
get('/') do
|
407
|
-
not_found
|
408
|
-
fail 'not_found should halt'
|
409
|
-
end
|
410
|
-
end
|
411
|
-
|
412
|
-
get '/'
|
413
|
-
assert_equal 404, status
|
414
|
-
assert_equal '', body
|
415
|
-
end
|
416
|
-
|
417
|
-
it 'does not set a X-Cascade header' do
|
418
|
-
mock_app do
|
419
|
-
get('/') do
|
420
|
-
not_found
|
421
|
-
fail 'not_found should halt'
|
422
|
-
end
|
423
|
-
end
|
424
|
-
|
425
|
-
get '/'
|
426
|
-
assert_equal 404, status
|
427
|
-
assert_equal nil, response.headers['X-Cascade']
|
428
|
-
end
|
429
|
-
end
|
430
|
-
|
431
|
-
describe 'headers' do
|
432
|
-
it 'sets headers on the response object when given a Hash' do
|
433
|
-
mock_app do
|
434
|
-
get('/') do
|
435
|
-
headers 'X-Foo' => 'bar', 'X-Baz' => 'bling'
|
436
|
-
'kthx'
|
437
|
-
end
|
438
|
-
end
|
439
|
-
|
440
|
-
get '/'
|
441
|
-
assert ok?
|
442
|
-
assert_equal 'bar', response['X-Foo']
|
443
|
-
assert_equal 'bling', response['X-Baz']
|
444
|
-
assert_equal 'kthx', body
|
445
|
-
end
|
446
|
-
|
447
|
-
it 'returns the response headers hash when no hash provided' do
|
448
|
-
mock_app do
|
449
|
-
get('/') do
|
450
|
-
headers['X-Foo'] = 'bar'
|
451
|
-
'kthx'
|
452
|
-
end
|
453
|
-
end
|
454
|
-
|
455
|
-
get '/'
|
456
|
-
assert ok?
|
457
|
-
assert_equal 'bar', response['X-Foo']
|
458
|
-
end
|
459
|
-
end
|
460
|
-
|
461
|
-
describe 'session' do
|
462
|
-
it 'uses the existing rack.session' do
|
463
|
-
mock_app do
|
464
|
-
get('/') do
|
465
|
-
session[:foo]
|
466
|
-
end
|
467
|
-
end
|
468
|
-
|
469
|
-
get('/', {}, { 'rack.session' => { :foo => 'bar' } })
|
470
|
-
assert_equal 'bar', body
|
471
|
-
end
|
472
|
-
|
473
|
-
it 'creates a new session when none provided' do
|
474
|
-
mock_app do
|
475
|
-
enable :sessions
|
476
|
-
|
477
|
-
get('/') do
|
478
|
-
assert session[:foo].nil?
|
479
|
-
session[:foo] = 'bar'
|
480
|
-
redirect '/hi'
|
481
|
-
end
|
482
|
-
|
483
|
-
get('/hi') do
|
484
|
-
"hi #{session[:foo]}"
|
485
|
-
end
|
486
|
-
end
|
487
|
-
|
488
|
-
get '/'
|
489
|
-
follow_redirect!
|
490
|
-
assert_equal 'hi bar', body
|
491
|
-
end
|
492
|
-
|
493
|
-
it 'inserts session middleware' do
|
494
|
-
mock_app do
|
495
|
-
enable :sessions
|
496
|
-
|
497
|
-
get('/') do
|
498
|
-
assert env['rack.session']
|
499
|
-
assert env['rack.session.options']
|
500
|
-
'ok'
|
501
|
-
end
|
502
|
-
end
|
503
|
-
|
504
|
-
get '/'
|
505
|
-
assert_body 'ok'
|
506
|
-
end
|
507
|
-
|
508
|
-
it 'sets a default session secret' do
|
509
|
-
mock_app do
|
510
|
-
enable :sessions
|
511
|
-
|
512
|
-
get('/') do
|
513
|
-
secret = env['rack.session.options'][:secret]
|
514
|
-
assert secret
|
515
|
-
assert_equal secret, settings.session_secret
|
516
|
-
'ok'
|
517
|
-
end
|
518
|
-
end
|
519
|
-
|
520
|
-
get '/'
|
521
|
-
assert_body 'ok'
|
522
|
-
end
|
523
|
-
|
524
|
-
it 'allows disabling session secret' do
|
525
|
-
mock_app do
|
526
|
-
enable :sessions
|
527
|
-
disable :session_secret
|
528
|
-
|
529
|
-
get('/') do
|
530
|
-
assert !env['rack.session.options'].include?(:session_secret)
|
531
|
-
'ok'
|
532
|
-
end
|
533
|
-
end
|
534
|
-
|
535
|
-
# Silence warnings since Rack::Session::Cookie complains about the non-present session secret
|
536
|
-
silence_warnings do
|
537
|
-
get '/'
|
538
|
-
end
|
539
|
-
assert_body 'ok'
|
540
|
-
end
|
541
|
-
|
542
|
-
it 'accepts an options hash' do
|
543
|
-
mock_app do
|
544
|
-
set :sessions, :foo => :bar
|
545
|
-
|
546
|
-
get('/') do
|
547
|
-
assert_equal env['rack.session.options'][:foo], :bar
|
548
|
-
'ok'
|
549
|
-
end
|
550
|
-
end
|
551
|
-
|
552
|
-
get '/'
|
553
|
-
assert_body 'ok'
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
describe 'mime_type' do
|
558
|
-
include Sinatra::Helpers
|
559
|
-
|
560
|
-
it "looks up mime types in Rack's MIME registry" do
|
561
|
-
Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
|
562
|
-
assert_equal 'application/foo', mime_type('foo')
|
563
|
-
assert_equal 'application/foo', mime_type('.foo')
|
564
|
-
assert_equal 'application/foo', mime_type(:foo)
|
565
|
-
end
|
566
|
-
|
567
|
-
it 'returns nil when given nil' do
|
568
|
-
assert mime_type(nil).nil?
|
569
|
-
end
|
570
|
-
|
571
|
-
it 'returns nil when media type not registered' do
|
572
|
-
assert mime_type(:bizzle).nil?
|
573
|
-
end
|
574
|
-
|
575
|
-
it 'returns the argument when given a media type string' do
|
576
|
-
assert_equal 'text/plain', mime_type('text/plain')
|
577
|
-
end
|
578
|
-
|
579
|
-
it 'turns AcceptEntry into String' do
|
580
|
-
type = mime_type(Sinatra::Request::AcceptEntry.new('text/plain'))
|
581
|
-
assert_equal String, type.class
|
582
|
-
assert_equal 'text/plain', type
|
583
|
-
end
|
584
|
-
end
|
585
|
-
|
586
|
-
test 'Base.mime_type registers mime type' do
|
587
|
-
mock_app do
|
588
|
-
mime_type :foo, 'application/foo'
|
589
|
-
|
590
|
-
get('/') do
|
591
|
-
"foo is #{mime_type(:foo)}"
|
592
|
-
end
|
593
|
-
end
|
594
|
-
|
595
|
-
get '/'
|
596
|
-
assert_equal 'foo is application/foo', body
|
597
|
-
end
|
598
|
-
|
599
|
-
describe 'content_type' do
|
600
|
-
it 'sets the Content-Type header' do
|
601
|
-
mock_app do
|
602
|
-
get('/') do
|
603
|
-
content_type 'text/plain'
|
604
|
-
'Hello World'
|
605
|
-
end
|
606
|
-
end
|
607
|
-
|
608
|
-
get '/'
|
609
|
-
assert_equal 'text/plain;charset=utf-8', response['Content-Type']
|
610
|
-
assert_equal 'Hello World', body
|
611
|
-
end
|
612
|
-
|
613
|
-
it 'takes media type parameters (like charset=)' do
|
614
|
-
mock_app do
|
615
|
-
get('/') do
|
616
|
-
content_type 'text/html', :charset => 'latin1'
|
617
|
-
"<h1>Hello, World</h1>"
|
618
|
-
end
|
619
|
-
end
|
620
|
-
|
621
|
-
get '/'
|
622
|
-
assert ok?
|
623
|
-
assert_equal 'text/html;charset=latin1', response['Content-Type']
|
624
|
-
assert_equal "<h1>Hello, World</h1>", body
|
625
|
-
end
|
626
|
-
|
627
|
-
it "looks up symbols in Rack's mime types dictionary" do
|
628
|
-
Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
|
629
|
-
mock_app do
|
630
|
-
get('/foo.xml') do
|
631
|
-
content_type :foo
|
632
|
-
"I AM FOO"
|
633
|
-
end
|
634
|
-
end
|
635
|
-
|
636
|
-
get '/foo.xml'
|
637
|
-
assert ok?
|
638
|
-
assert_equal 'application/foo', response['Content-Type']
|
639
|
-
assert_equal 'I AM FOO', body
|
640
|
-
end
|
641
|
-
|
642
|
-
it 'fails when no mime type is registered for the argument provided' do
|
643
|
-
mock_app do
|
644
|
-
get('/foo.xml') do
|
645
|
-
content_type :bizzle
|
646
|
-
"I AM FOO"
|
647
|
-
end
|
648
|
-
end
|
649
|
-
|
650
|
-
assert_raises(RuntimeError) { get '/foo.xml' }
|
651
|
-
end
|
652
|
-
|
653
|
-
it 'only sets default charset for specific mime types' do
|
654
|
-
tests_ran = false
|
655
|
-
mock_app do
|
656
|
-
mime_type :foo, 'text/foo'
|
657
|
-
mime_type :bar, 'application/bar'
|
658
|
-
mime_type :baz, 'application/baz'
|
659
|
-
add_charset << mime_type(:baz)
|
660
|
-
get('/') do
|
661
|
-
assert_equal content_type(:txt), 'text/plain;charset=utf-8'
|
662
|
-
assert_equal content_type(:css), 'text/css;charset=utf-8'
|
663
|
-
assert_equal content_type(:html), 'text/html;charset=utf-8'
|
664
|
-
assert_equal content_type(:foo), 'text/foo;charset=utf-8'
|
665
|
-
assert_equal content_type(:xml), 'application/xml;charset=utf-8'
|
666
|
-
assert_equal content_type(:xhtml), 'application/xhtml+xml;charset=utf-8'
|
667
|
-
assert_equal content_type(:js), 'application/javascript;charset=utf-8'
|
668
|
-
assert_equal content_type(:json), 'application/json'
|
669
|
-
assert_equal content_type(:bar), 'application/bar'
|
670
|
-
assert_equal content_type(:png), 'image/png'
|
671
|
-
assert_equal content_type(:baz), 'application/baz;charset=utf-8'
|
672
|
-
tests_ran = true
|
673
|
-
"done"
|
674
|
-
end
|
675
|
-
end
|
676
|
-
|
677
|
-
get '/'
|
678
|
-
assert tests_ran
|
679
|
-
end
|
680
|
-
|
681
|
-
it 'handles already present params' do
|
682
|
-
mock_app do
|
683
|
-
get('/') do
|
684
|
-
content_type 'foo/bar;level=1', :charset => 'utf-8'
|
685
|
-
'ok'
|
686
|
-
end
|
687
|
-
end
|
688
|
-
|
689
|
-
get '/'
|
690
|
-
assert_equal 'foo/bar;level=1, charset=utf-8', response['Content-Type']
|
691
|
-
end
|
692
|
-
|
693
|
-
it 'does not add charset if present' do
|
694
|
-
mock_app do
|
695
|
-
get('/') do
|
696
|
-
content_type 'text/plain;charset=utf-16'
|
697
|
-
'ok'
|
698
|
-
end
|
699
|
-
end
|
700
|
-
|
701
|
-
get '/'
|
702
|
-
assert_equal 'text/plain;charset=utf-16', response['Content-Type']
|
703
|
-
end
|
704
|
-
|
705
|
-
it 'properly encodes parameters with delimiter characters' do
|
706
|
-
mock_app do
|
707
|
-
before '/comma' do
|
708
|
-
content_type 'image/png', :comment => 'Hello, world!'
|
709
|
-
end
|
710
|
-
before '/semicolon' do
|
711
|
-
content_type 'image/png', :comment => 'semi;colon'
|
712
|
-
end
|
713
|
-
before '/quote' do
|
714
|
-
content_type 'image/png', :comment => '"Whatever."'
|
715
|
-
end
|
716
|
-
|
717
|
-
get('*') { 'ok' }
|
718
|
-
end
|
719
|
-
|
720
|
-
get '/comma'
|
721
|
-
assert_equal 'image/png;comment="Hello, world!"', response['Content-Type']
|
722
|
-
get '/semicolon'
|
723
|
-
assert_equal 'image/png;comment="semi;colon"', response['Content-Type']
|
724
|
-
get '/quote'
|
725
|
-
assert_equal 'image/png;comment="\"Whatever.\""', response['Content-Type']
|
726
|
-
end
|
727
|
-
end
|
728
|
-
|
729
|
-
describe 'attachment' do
|
730
|
-
def attachment_app(filename=nil)
|
731
|
-
mock_app do
|
732
|
-
get('/attachment') do
|
733
|
-
attachment filename
|
734
|
-
response.write("<sinatra></sinatra>")
|
735
|
-
end
|
736
|
-
end
|
737
|
-
end
|
738
|
-
|
739
|
-
it 'sets the Content-Type response header' do
|
740
|
-
attachment_app('test.xml')
|
741
|
-
get '/attachment'
|
742
|
-
assert_equal 'application/xml;charset=utf-8', response['Content-Type']
|
743
|
-
assert_equal '<sinatra></sinatra>', body
|
744
|
-
end
|
745
|
-
|
746
|
-
it 'sets the Content-Type response header without extname' do
|
747
|
-
attachment_app('test')
|
748
|
-
get '/attachment'
|
749
|
-
assert_equal 'text/html;charset=utf-8', response['Content-Type']
|
750
|
-
assert_equal '<sinatra></sinatra>', body
|
751
|
-
end
|
752
|
-
|
753
|
-
it 'sets the Content-Type response header with extname' do
|
754
|
-
mock_app do
|
755
|
-
get('/attachment') do
|
756
|
-
content_type :atom
|
757
|
-
attachment 'test.xml'
|
758
|
-
response.write("<sinatra></sinatra>")
|
759
|
-
end
|
760
|
-
end
|
761
|
-
|
762
|
-
get '/attachment'
|
763
|
-
assert_equal 'application/atom+xml', response['Content-Type']
|
764
|
-
assert_equal '<sinatra></sinatra>', body
|
765
|
-
end
|
766
|
-
|
767
|
-
end
|
768
|
-
|
769
|
-
describe 'send_file' do
|
770
|
-
setup do
|
771
|
-
@file = File.dirname(__FILE__) + '/file.txt'
|
772
|
-
File.open(@file, 'wb') { |io| io.write('Hello World') }
|
773
|
-
end
|
774
|
-
|
775
|
-
def teardown
|
776
|
-
File.unlink @file
|
777
|
-
@file = nil
|
778
|
-
end
|
779
|
-
|
780
|
-
def send_file_app(opts={})
|
781
|
-
path = @file
|
782
|
-
mock_app {
|
783
|
-
get '/file.txt' do
|
784
|
-
send_file path, opts
|
785
|
-
end
|
786
|
-
}
|
787
|
-
end
|
788
|
-
|
789
|
-
it "sends the contents of the file" do
|
790
|
-
send_file_app
|
791
|
-
get '/file.txt'
|
792
|
-
assert ok?
|
793
|
-
assert_equal 'Hello World', body
|
794
|
-
end
|
795
|
-
|
796
|
-
it 'sets the Content-Type response header if a mime-type can be located' do
|
797
|
-
send_file_app
|
798
|
-
get '/file.txt'
|
799
|
-
assert_equal 'text/plain;charset=utf-8', response['Content-Type']
|
800
|
-
end
|
801
|
-
|
802
|
-
it 'sets the Content-Type response header if type option is set to a file extension' do
|
803
|
-
send_file_app :type => 'html'
|
804
|
-
get '/file.txt'
|
805
|
-
assert_equal 'text/html;charset=utf-8', response['Content-Type']
|
806
|
-
end
|
807
|
-
|
808
|
-
it 'sets the Content-Type response header if type option is set to a mime type' do
|
809
|
-
send_file_app :type => 'application/octet-stream'
|
810
|
-
get '/file.txt'
|
811
|
-
assert_equal 'application/octet-stream', response['Content-Type']
|
812
|
-
end
|
813
|
-
|
814
|
-
it 'sets the Content-Length response header' do
|
815
|
-
send_file_app
|
816
|
-
get '/file.txt'
|
817
|
-
assert_equal 'Hello World'.length.to_s, response['Content-Length']
|
818
|
-
end
|
819
|
-
|
820
|
-
it 'sets the Last-Modified response header' do
|
821
|
-
send_file_app
|
822
|
-
get '/file.txt'
|
823
|
-
assert_equal File.mtime(@file).httpdate, response['Last-Modified']
|
824
|
-
end
|
825
|
-
|
826
|
-
it 'allows passing in a different Last-Modified response header with :last_modified' do
|
827
|
-
time = Time.now
|
828
|
-
send_file_app :last_modified => time
|
829
|
-
get '/file.txt'
|
830
|
-
assert_equal time.httpdate, response['Last-Modified']
|
831
|
-
end
|
832
|
-
|
833
|
-
it "returns a 404 when not found" do
|
834
|
-
mock_app {
|
835
|
-
get('/') { send_file 'this-file-does-not-exist.txt' }
|
836
|
-
}
|
837
|
-
get '/'
|
838
|
-
assert not_found?
|
839
|
-
end
|
840
|
-
|
841
|
-
it "does not set the Content-Disposition header by default" do
|
842
|
-
send_file_app
|
843
|
-
get '/file.txt'
|
844
|
-
assert_nil response['Content-Disposition']
|
845
|
-
end
|
846
|
-
|
847
|
-
it "sets the Content-Disposition header when :disposition set to 'attachment'" do
|
848
|
-
send_file_app :disposition => 'attachment'
|
849
|
-
get '/file.txt'
|
850
|
-
assert_equal 'attachment; filename="file.txt"', response['Content-Disposition']
|
851
|
-
end
|
852
|
-
|
853
|
-
it "does not set add a file name if filename is false" do
|
854
|
-
send_file_app :disposition => 'inline', :filename => false
|
855
|
-
get '/file.txt'
|
856
|
-
assert_equal 'inline', response['Content-Disposition']
|
857
|
-
end
|
858
|
-
|
859
|
-
it "sets the Content-Disposition header when :disposition set to 'inline'" do
|
860
|
-
send_file_app :disposition => 'inline'
|
861
|
-
get '/file.txt'
|
862
|
-
assert_equal 'inline; filename="file.txt"', response['Content-Disposition']
|
863
|
-
end
|
864
|
-
|
865
|
-
it "sets the Content-Disposition header when :filename provided" do
|
866
|
-
send_file_app :filename => 'foo.txt'
|
867
|
-
get '/file.txt'
|
868
|
-
assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition']
|
869
|
-
end
|
870
|
-
|
871
|
-
it 'allows setting a custom status code' do
|
872
|
-
send_file_app :status => 201
|
873
|
-
get '/file.txt'
|
874
|
-
assert_status 201
|
875
|
-
end
|
876
|
-
|
877
|
-
it "is able to send files with unknown mime type" do
|
878
|
-
@file = File.dirname(__FILE__) + '/file.foobar'
|
879
|
-
File.open(@file, 'wb') { |io| io.write('Hello World') }
|
880
|
-
send_file_app
|
881
|
-
get '/file.txt'
|
882
|
-
assert_equal 'application/octet-stream', response['Content-Type']
|
883
|
-
end
|
884
|
-
|
885
|
-
it "does not override Content-Type if already set and no explicit type is given" do
|
886
|
-
path = @file
|
887
|
-
mock_app do
|
888
|
-
get('/') do
|
889
|
-
content_type :png
|
890
|
-
send_file path
|
891
|
-
end
|
892
|
-
end
|
893
|
-
get '/'
|
894
|
-
assert_equal 'image/png', response['Content-Type']
|
895
|
-
end
|
896
|
-
|
897
|
-
it "does override Content-Type even if already set, if explicit type is given" do
|
898
|
-
path = @file
|
899
|
-
mock_app do
|
900
|
-
get('/') do
|
901
|
-
content_type :png
|
902
|
-
send_file path, :type => :gif
|
903
|
-
end
|
904
|
-
end
|
905
|
-
get '/'
|
906
|
-
assert_equal 'image/gif', response['Content-Type']
|
907
|
-
end
|
908
|
-
|
909
|
-
it 'can have :status option as a string' do
|
910
|
-
path = @file
|
911
|
-
mock_app do
|
912
|
-
post '/' do
|
913
|
-
send_file path, :status => '422'
|
914
|
-
end
|
915
|
-
end
|
916
|
-
post '/'
|
917
|
-
assert_equal response.status, 422
|
918
|
-
end
|
919
|
-
end
|
920
|
-
|
921
|
-
describe 'cache_control' do
|
922
|
-
setup do
|
923
|
-
mock_app do
|
924
|
-
get('/foo') do
|
925
|
-
cache_control :public, :no_cache, :max_age => 60.0
|
926
|
-
'Hello World'
|
927
|
-
end
|
928
|
-
|
929
|
-
get('/bar') do
|
930
|
-
cache_control :public, :no_cache
|
931
|
-
'Hello World'
|
932
|
-
end
|
933
|
-
end
|
934
|
-
end
|
935
|
-
|
936
|
-
it 'sets the Cache-Control header' do
|
937
|
-
get '/foo'
|
938
|
-
assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
|
939
|
-
end
|
940
|
-
|
941
|
-
it 'last argument does not have to be a hash' do
|
942
|
-
get '/bar'
|
943
|
-
assert_equal ['public', 'no-cache'], response['Cache-Control'].split(', ')
|
944
|
-
end
|
945
|
-
end
|
946
|
-
|
947
|
-
describe 'expires' do
|
948
|
-
setup do
|
949
|
-
mock_app do
|
950
|
-
get('/foo') do
|
951
|
-
expires 60, :public, :no_cache
|
952
|
-
'Hello World'
|
953
|
-
end
|
954
|
-
|
955
|
-
get('/bar') { expires Time.now }
|
956
|
-
|
957
|
-
get('/baz') { expires Time.at(0) }
|
958
|
-
|
959
|
-
get('/blah') do
|
960
|
-
obj = Object.new
|
961
|
-
def obj.method_missing(*a, &b) 60.send(*a, &b) end
|
962
|
-
def obj.is_a?(thing) 60.is_a?(thing) end
|
963
|
-
expires obj, :public, :no_cache
|
964
|
-
'Hello World'
|
965
|
-
end
|
966
|
-
|
967
|
-
get('/boom') { expires '9999' }
|
968
|
-
end
|
969
|
-
end
|
970
|
-
|
971
|
-
it 'sets the Cache-Control header' do
|
972
|
-
get '/foo'
|
973
|
-
assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
|
974
|
-
end
|
975
|
-
|
976
|
-
it 'sets the Expires header' do
|
977
|
-
get '/foo'
|
978
|
-
refute_nil response['Expires']
|
979
|
-
end
|
980
|
-
|
981
|
-
it 'allows passing Time.now objects' do
|
982
|
-
get '/bar'
|
983
|
-
refute_nil response['Expires']
|
984
|
-
end
|
985
|
-
|
986
|
-
it 'allows passing Time.at objects' do
|
987
|
-
get '/baz'
|
988
|
-
assert_equal 'Thu, 01 Jan 1970 00:00:00 GMT', response['Expires']
|
989
|
-
end
|
990
|
-
|
991
|
-
it 'accepts values pretending to be a Numeric (like ActiveSupport::Duration)' do
|
992
|
-
get '/blah'
|
993
|
-
assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
|
994
|
-
end
|
995
|
-
|
996
|
-
it 'fails when Time.parse raises an ArgumentError' do
|
997
|
-
assert_raises(ArgumentError) { get '/boom' }
|
998
|
-
end
|
999
|
-
end
|
1000
|
-
|
1001
|
-
describe 'last_modified' do
|
1002
|
-
it 'ignores nil' do
|
1003
|
-
mock_app { get('/') { last_modified nil; 200; } }
|
1004
|
-
|
1005
|
-
get '/'
|
1006
|
-
assert ! response['Last-Modified']
|
1007
|
-
end
|
1008
|
-
|
1009
|
-
it 'does not change a status other than 200' do
|
1010
|
-
mock_app do
|
1011
|
-
get('/') do
|
1012
|
-
status 299
|
1013
|
-
last_modified Time.at(0)
|
1014
|
-
'ok'
|
1015
|
-
end
|
1016
|
-
end
|
1017
|
-
|
1018
|
-
get('/', {}, 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT')
|
1019
|
-
assert_status 299
|
1020
|
-
assert_body 'ok'
|
1021
|
-
end
|
1022
|
-
|
1023
|
-
[Time.now, DateTime.now, Date.today, Time.now.to_i,
|
1024
|
-
Struct.new(:to_time).new(Time.now) ].each do |last_modified_time|
|
1025
|
-
describe "with #{last_modified_time.class.name}" do
|
1026
|
-
setup do
|
1027
|
-
mock_app do
|
1028
|
-
get('/') do
|
1029
|
-
last_modified last_modified_time
|
1030
|
-
'Boo!'
|
1031
|
-
end
|
1032
|
-
end
|
1033
|
-
wrapper = Object.new.extend Sinatra::Helpers
|
1034
|
-
@last_modified_time = wrapper.time_for last_modified_time
|
1035
|
-
end
|
1036
|
-
|
1037
|
-
# fixes strange missing test error when running complete test suite.
|
1038
|
-
it("does not complain about missing tests") { }
|
1039
|
-
|
1040
|
-
context "when there's no If-Modified-Since header" do
|
1041
|
-
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
1042
|
-
get '/'
|
1043
|
-
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
1044
|
-
end
|
1045
|
-
|
1046
|
-
it 'conditional GET misses and returns a body' do
|
1047
|
-
get '/'
|
1048
|
-
assert_equal 200, status
|
1049
|
-
assert_equal 'Boo!', body
|
1050
|
-
end
|
1051
|
-
end
|
1052
|
-
|
1053
|
-
context "when there's an invalid If-Modified-Since header" do
|
1054
|
-
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
1055
|
-
get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' })
|
1056
|
-
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
1057
|
-
end
|
1058
|
-
|
1059
|
-
it 'conditional GET misses and returns a body' do
|
1060
|
-
get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' })
|
1061
|
-
assert_equal 200, status
|
1062
|
-
assert_equal 'Boo!', body
|
1063
|
-
end
|
1064
|
-
end
|
1065
|
-
|
1066
|
-
context "when the resource has been modified since the If-Modified-Since header date" do
|
1067
|
-
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
1068
|
-
get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate })
|
1069
|
-
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
1070
|
-
end
|
1071
|
-
|
1072
|
-
it 'conditional GET misses and returns a body' do
|
1073
|
-
get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate })
|
1074
|
-
assert_equal 200, status
|
1075
|
-
assert_equal 'Boo!', body
|
1076
|
-
end
|
1077
|
-
|
1078
|
-
it 'does not rely on string comparison' do
|
1079
|
-
mock_app do
|
1080
|
-
get('/compare') do
|
1081
|
-
last_modified "Mon, 18 Oct 2010 20:57:11 GMT"
|
1082
|
-
"foo"
|
1083
|
-
end
|
1084
|
-
end
|
1085
|
-
|
1086
|
-
get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2010 23:43:52 GMT' })
|
1087
|
-
assert_equal 200, status
|
1088
|
-
assert_equal 'foo', body
|
1089
|
-
get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' })
|
1090
|
-
assert_equal 304, status
|
1091
|
-
assert_equal '', body
|
1092
|
-
end
|
1093
|
-
end
|
1094
|
-
|
1095
|
-
context "when the resource has been modified on the exact If-Modified-Since header date" do
|
1096
|
-
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
1097
|
-
get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate })
|
1098
|
-
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
1099
|
-
end
|
1100
|
-
|
1101
|
-
it 'conditional GET matches and halts' do
|
1102
|
-
get( '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate })
|
1103
|
-
assert_equal 304, status
|
1104
|
-
assert_equal '', body
|
1105
|
-
end
|
1106
|
-
end
|
1107
|
-
|
1108
|
-
context "when the resource hasn't been modified since the If-Modified-Since header date" do
|
1109
|
-
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
1110
|
-
get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate })
|
1111
|
-
assert_equal @last_modified_time.httpdate, response['Last-Modified']
|
1112
|
-
end
|
1113
|
-
|
1114
|
-
it 'conditional GET matches and halts' do
|
1115
|
-
get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate })
|
1116
|
-
assert_equal 304, status
|
1117
|
-
assert_equal '', body
|
1118
|
-
end
|
1119
|
-
end
|
1120
|
-
|
1121
|
-
context "If-Unmodified-Since" do
|
1122
|
-
it 'results in 200 if resource has not been modified' do
|
1123
|
-
get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' })
|
1124
|
-
assert_equal 200, status
|
1125
|
-
assert_equal 'Boo!', body
|
1126
|
-
end
|
1127
|
-
|
1128
|
-
it 'results in 412 if resource has been modified' do
|
1129
|
-
get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.at(0).httpdate })
|
1130
|
-
assert_equal 412, status
|
1131
|
-
assert_equal '', body
|
1132
|
-
end
|
1133
|
-
end
|
1134
|
-
end
|
1135
|
-
end
|
1136
|
-
end
|
1137
|
-
|
1138
|
-
describe 'etag' do
|
1139
|
-
context "safe requests" do
|
1140
|
-
it 'returns 200 for normal requests' do
|
1141
|
-
mock_app do
|
1142
|
-
get('/') do
|
1143
|
-
etag 'foo'
|
1144
|
-
'ok'
|
1145
|
-
end
|
1146
|
-
end
|
1147
|
-
|
1148
|
-
get '/'
|
1149
|
-
assert_status 200
|
1150
|
-
assert_body 'ok'
|
1151
|
-
end
|
1152
|
-
|
1153
|
-
context "If-None-Match" do
|
1154
|
-
it 'returns 304 when If-None-Match is *' do
|
1155
|
-
mock_app do
|
1156
|
-
get('/') do
|
1157
|
-
etag 'foo'
|
1158
|
-
'ok'
|
1159
|
-
end
|
1160
|
-
end
|
1161
|
-
|
1162
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1163
|
-
assert_status 304
|
1164
|
-
assert_body ''
|
1165
|
-
end
|
1166
|
-
|
1167
|
-
it 'returns 200 when If-None-Match is * for new resources' do
|
1168
|
-
mock_app do
|
1169
|
-
get('/') do
|
1170
|
-
etag 'foo', :new_resource => true
|
1171
|
-
'ok'
|
1172
|
-
end
|
1173
|
-
end
|
1174
|
-
|
1175
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1176
|
-
assert_status 200
|
1177
|
-
assert_body 'ok'
|
1178
|
-
end
|
1179
|
-
|
1180
|
-
it 'returns 304 when If-None-Match is * for existing resources' do
|
1181
|
-
mock_app do
|
1182
|
-
get('/') do
|
1183
|
-
etag 'foo', :new_resource => false
|
1184
|
-
'ok'
|
1185
|
-
end
|
1186
|
-
end
|
1187
|
-
|
1188
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1189
|
-
assert_status 304
|
1190
|
-
assert_body ''
|
1191
|
-
end
|
1192
|
-
|
1193
|
-
it 'returns 304 when If-None-Match is the etag' do
|
1194
|
-
mock_app do
|
1195
|
-
get('/') do
|
1196
|
-
etag 'foo'
|
1197
|
-
'ok'
|
1198
|
-
end
|
1199
|
-
end
|
1200
|
-
|
1201
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1202
|
-
assert_status 304
|
1203
|
-
assert_body ''
|
1204
|
-
end
|
1205
|
-
|
1206
|
-
it 'returns 304 when If-None-Match includes the etag' do
|
1207
|
-
mock_app do
|
1208
|
-
get('/') do
|
1209
|
-
etag 'foo'
|
1210
|
-
'ok'
|
1211
|
-
end
|
1212
|
-
end
|
1213
|
-
|
1214
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
|
1215
|
-
assert_status 304
|
1216
|
-
assert_body ''
|
1217
|
-
end
|
1218
|
-
|
1219
|
-
it 'returns 200 when If-None-Match does not include the etag' do
|
1220
|
-
mock_app do
|
1221
|
-
get('/') do
|
1222
|
-
etag 'foo'
|
1223
|
-
'ok'
|
1224
|
-
end
|
1225
|
-
end
|
1226
|
-
|
1227
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1228
|
-
assert_status 200
|
1229
|
-
assert_body 'ok'
|
1230
|
-
end
|
1231
|
-
|
1232
|
-
it 'ignores If-Modified-Since if If-None-Match does not match' do
|
1233
|
-
mock_app do
|
1234
|
-
get('/') do
|
1235
|
-
etag 'foo'
|
1236
|
-
last_modified Time.at(0)
|
1237
|
-
'ok'
|
1238
|
-
end
|
1239
|
-
end
|
1240
|
-
|
1241
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1242
|
-
assert_status 200
|
1243
|
-
assert_body 'ok'
|
1244
|
-
end
|
1245
|
-
|
1246
|
-
it 'does not change a status code other than 2xx or 304' do
|
1247
|
-
mock_app do
|
1248
|
-
get('/') do
|
1249
|
-
status 499
|
1250
|
-
etag 'foo'
|
1251
|
-
'ok'
|
1252
|
-
end
|
1253
|
-
end
|
1254
|
-
|
1255
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1256
|
-
assert_status 499
|
1257
|
-
assert_body 'ok'
|
1258
|
-
end
|
1259
|
-
|
1260
|
-
it 'does change 2xx status codes' do
|
1261
|
-
mock_app do
|
1262
|
-
get('/') do
|
1263
|
-
status 299
|
1264
|
-
etag 'foo'
|
1265
|
-
'ok'
|
1266
|
-
end
|
1267
|
-
end
|
1268
|
-
|
1269
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1270
|
-
assert_status 304
|
1271
|
-
assert_body ''
|
1272
|
-
end
|
1273
|
-
|
1274
|
-
it 'does not send a body on 304 status codes' do
|
1275
|
-
mock_app do
|
1276
|
-
get('/') do
|
1277
|
-
status 304
|
1278
|
-
etag 'foo'
|
1279
|
-
'ok'
|
1280
|
-
end
|
1281
|
-
end
|
1282
|
-
|
1283
|
-
get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1284
|
-
assert_status 304
|
1285
|
-
assert_body ''
|
1286
|
-
end
|
1287
|
-
end
|
1288
|
-
|
1289
|
-
context "If-Match" do
|
1290
|
-
it 'returns 200 when If-Match is the etag' do
|
1291
|
-
mock_app do
|
1292
|
-
get('/') do
|
1293
|
-
etag 'foo'
|
1294
|
-
'ok'
|
1295
|
-
end
|
1296
|
-
end
|
1297
|
-
|
1298
|
-
get('/', {}, 'HTTP_IF_MATCH' => '"foo"')
|
1299
|
-
assert_status 200
|
1300
|
-
assert_body 'ok'
|
1301
|
-
end
|
1302
|
-
|
1303
|
-
it 'returns 200 when If-Match includes the etag' do
|
1304
|
-
mock_app do
|
1305
|
-
get('/') do
|
1306
|
-
etag 'foo'
|
1307
|
-
'ok'
|
1308
|
-
end
|
1309
|
-
end
|
1310
|
-
|
1311
|
-
get('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
|
1312
|
-
assert_status 200
|
1313
|
-
assert_body 'ok'
|
1314
|
-
end
|
1315
|
-
|
1316
|
-
it 'returns 200 when If-Match is *' do
|
1317
|
-
mock_app do
|
1318
|
-
get('/') do
|
1319
|
-
etag 'foo'
|
1320
|
-
'ok'
|
1321
|
-
end
|
1322
|
-
end
|
1323
|
-
|
1324
|
-
get('/', {}, 'HTTP_IF_MATCH' => '*')
|
1325
|
-
assert_status 200
|
1326
|
-
assert_body 'ok'
|
1327
|
-
end
|
1328
|
-
|
1329
|
-
it 'returns 412 when If-Match is * for new resources' do
|
1330
|
-
mock_app do
|
1331
|
-
get('/') do
|
1332
|
-
etag 'foo', :new_resource => true
|
1333
|
-
'ok'
|
1334
|
-
end
|
1335
|
-
end
|
1336
|
-
|
1337
|
-
get('/', {}, 'HTTP_IF_MATCH' => '*')
|
1338
|
-
assert_status 412
|
1339
|
-
assert_body ''
|
1340
|
-
end
|
1341
|
-
|
1342
|
-
it 'returns 200 when If-Match is * for existing resources' do
|
1343
|
-
mock_app do
|
1344
|
-
get('/') do
|
1345
|
-
etag 'foo', :new_resource => false
|
1346
|
-
'ok'
|
1347
|
-
end
|
1348
|
-
end
|
1349
|
-
|
1350
|
-
get('/', {}, 'HTTP_IF_MATCH' => '*')
|
1351
|
-
assert_status 200
|
1352
|
-
assert_body 'ok'
|
1353
|
-
end
|
1354
|
-
|
1355
|
-
it 'returns 412 when If-Match does not include the etag' do
|
1356
|
-
mock_app do
|
1357
|
-
get('/') do
|
1358
|
-
etag 'foo'
|
1359
|
-
'ok'
|
1360
|
-
end
|
1361
|
-
end
|
1362
|
-
|
1363
|
-
get('/', {}, 'HTTP_IF_MATCH' => '"bar"')
|
1364
|
-
assert_status 412
|
1365
|
-
assert_body ''
|
1366
|
-
end
|
1367
|
-
end
|
1368
|
-
end
|
1369
|
-
|
1370
|
-
context "idempotent requests" do
|
1371
|
-
it 'returns 200 for normal requests' do
|
1372
|
-
mock_app do
|
1373
|
-
put('/') do
|
1374
|
-
etag 'foo'
|
1375
|
-
'ok'
|
1376
|
-
end
|
1377
|
-
end
|
1378
|
-
|
1379
|
-
put '/'
|
1380
|
-
assert_status 200
|
1381
|
-
assert_body 'ok'
|
1382
|
-
end
|
1383
|
-
|
1384
|
-
context "If-None-Match" do
|
1385
|
-
it 'returns 412 when If-None-Match is *' do
|
1386
|
-
mock_app do
|
1387
|
-
put('/') do
|
1388
|
-
etag 'foo'
|
1389
|
-
'ok'
|
1390
|
-
end
|
1391
|
-
end
|
1392
|
-
|
1393
|
-
put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1394
|
-
assert_status 412
|
1395
|
-
assert_body ''
|
1396
|
-
end
|
1397
|
-
|
1398
|
-
it 'returns 200 when If-None-Match is * for new resources' do
|
1399
|
-
mock_app do
|
1400
|
-
put('/') do
|
1401
|
-
etag 'foo', :new_resource => true
|
1402
|
-
'ok'
|
1403
|
-
end
|
1404
|
-
end
|
1405
|
-
|
1406
|
-
put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1407
|
-
assert_status 200
|
1408
|
-
assert_body 'ok'
|
1409
|
-
end
|
1410
|
-
|
1411
|
-
it 'returns 412 when If-None-Match is * for existing resources' do
|
1412
|
-
mock_app do
|
1413
|
-
put('/') do
|
1414
|
-
etag 'foo', :new_resource => false
|
1415
|
-
'ok'
|
1416
|
-
end
|
1417
|
-
end
|
1418
|
-
|
1419
|
-
put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1420
|
-
assert_status 412
|
1421
|
-
assert_body ''
|
1422
|
-
end
|
1423
|
-
|
1424
|
-
it 'returns 412 when If-None-Match is the etag' do
|
1425
|
-
mock_app do
|
1426
|
-
put '/' do
|
1427
|
-
etag 'foo'
|
1428
|
-
'ok'
|
1429
|
-
end
|
1430
|
-
end
|
1431
|
-
|
1432
|
-
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1433
|
-
assert_status 412
|
1434
|
-
assert_body ''
|
1435
|
-
end
|
1436
|
-
|
1437
|
-
it 'returns 412 when If-None-Match includes the etag' do
|
1438
|
-
mock_app do
|
1439
|
-
put('/') do
|
1440
|
-
etag 'foo'
|
1441
|
-
'ok'
|
1442
|
-
end
|
1443
|
-
end
|
1444
|
-
|
1445
|
-
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
|
1446
|
-
assert_status 412
|
1447
|
-
assert_body ''
|
1448
|
-
end
|
1449
|
-
|
1450
|
-
it 'returns 200 when If-None-Match does not include the etag' do
|
1451
|
-
mock_app do
|
1452
|
-
put('/') do
|
1453
|
-
etag 'foo'
|
1454
|
-
'ok'
|
1455
|
-
end
|
1456
|
-
end
|
1457
|
-
|
1458
|
-
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1459
|
-
assert_status 200
|
1460
|
-
assert_body 'ok'
|
1461
|
-
end
|
1462
|
-
|
1463
|
-
it 'ignores If-Modified-Since if If-None-Match does not match' do
|
1464
|
-
mock_app do
|
1465
|
-
put('/') do
|
1466
|
-
etag 'foo'
|
1467
|
-
last_modified Time.at(0)
|
1468
|
-
'ok'
|
1469
|
-
end
|
1470
|
-
end
|
1471
|
-
|
1472
|
-
put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1473
|
-
assert_status 200
|
1474
|
-
assert_body 'ok'
|
1475
|
-
end
|
1476
|
-
end
|
1477
|
-
|
1478
|
-
context "If-Match" do
|
1479
|
-
it 'returns 200 when If-Match is the etag' do
|
1480
|
-
mock_app do
|
1481
|
-
put('/') do
|
1482
|
-
etag 'foo'
|
1483
|
-
'ok'
|
1484
|
-
end
|
1485
|
-
end
|
1486
|
-
|
1487
|
-
put('/', {}, 'HTTP_IF_MATCH' => '"foo"')
|
1488
|
-
assert_status 200
|
1489
|
-
assert_body 'ok'
|
1490
|
-
end
|
1491
|
-
|
1492
|
-
it 'returns 200 when If-Match includes the etag' do
|
1493
|
-
mock_app do
|
1494
|
-
put('/') do
|
1495
|
-
etag 'foo'
|
1496
|
-
'ok'
|
1497
|
-
end
|
1498
|
-
end
|
1499
|
-
|
1500
|
-
put('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
|
1501
|
-
assert_status 200
|
1502
|
-
assert_body 'ok'
|
1503
|
-
end
|
1504
|
-
|
1505
|
-
it 'returns 200 when If-Match is *' do
|
1506
|
-
mock_app do
|
1507
|
-
put('/') do
|
1508
|
-
etag 'foo'
|
1509
|
-
'ok'
|
1510
|
-
end
|
1511
|
-
end
|
1512
|
-
|
1513
|
-
put('/', {}, 'HTTP_IF_MATCH' => '*')
|
1514
|
-
assert_status 200
|
1515
|
-
assert_body 'ok'
|
1516
|
-
end
|
1517
|
-
|
1518
|
-
it 'returns 412 when If-Match is * for new resources' do
|
1519
|
-
mock_app do
|
1520
|
-
put('/') do
|
1521
|
-
etag 'foo', :new_resource => true
|
1522
|
-
'ok'
|
1523
|
-
end
|
1524
|
-
end
|
1525
|
-
|
1526
|
-
put('/', {}, 'HTTP_IF_MATCH' => '*')
|
1527
|
-
assert_status 412
|
1528
|
-
assert_body ''
|
1529
|
-
end
|
1530
|
-
|
1531
|
-
it 'returns 200 when If-Match is * for existing resources' do
|
1532
|
-
mock_app do
|
1533
|
-
put('/') do
|
1534
|
-
etag 'foo', :new_resource => false
|
1535
|
-
'ok'
|
1536
|
-
end
|
1537
|
-
end
|
1538
|
-
|
1539
|
-
put('/', {}, 'HTTP_IF_MATCH' => '*')
|
1540
|
-
assert_status 200
|
1541
|
-
assert_body 'ok'
|
1542
|
-
end
|
1543
|
-
|
1544
|
-
it 'returns 412 when If-Match does not include the etag' do
|
1545
|
-
mock_app do
|
1546
|
-
put('/') do
|
1547
|
-
etag 'foo'
|
1548
|
-
'ok'
|
1549
|
-
end
|
1550
|
-
end
|
1551
|
-
|
1552
|
-
put('/', {}, 'HTTP_IF_MATCH' => '"bar"')
|
1553
|
-
assert_status 412
|
1554
|
-
assert_body ''
|
1555
|
-
end
|
1556
|
-
end
|
1557
|
-
end
|
1558
|
-
|
1559
|
-
context "post requests" do
|
1560
|
-
it 'returns 200 for normal requests' do
|
1561
|
-
mock_app do
|
1562
|
-
post('/') do
|
1563
|
-
etag 'foo'
|
1564
|
-
'ok'
|
1565
|
-
end
|
1566
|
-
end
|
1567
|
-
|
1568
|
-
post('/')
|
1569
|
-
assert_status 200
|
1570
|
-
assert_body 'ok'
|
1571
|
-
end
|
1572
|
-
|
1573
|
-
context "If-None-Match" do
|
1574
|
-
it 'returns 200 when If-None-Match is *' do
|
1575
|
-
mock_app do
|
1576
|
-
post('/') do
|
1577
|
-
etag 'foo'
|
1578
|
-
'ok'
|
1579
|
-
end
|
1580
|
-
end
|
1581
|
-
|
1582
|
-
post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1583
|
-
assert_status 200
|
1584
|
-
assert_body 'ok'
|
1585
|
-
end
|
1586
|
-
|
1587
|
-
it 'returns 200 when If-None-Match is * for new resources' do
|
1588
|
-
mock_app do
|
1589
|
-
post('/') do
|
1590
|
-
etag 'foo', :new_resource => true
|
1591
|
-
'ok'
|
1592
|
-
end
|
1593
|
-
end
|
1594
|
-
|
1595
|
-
post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1596
|
-
assert_status 200
|
1597
|
-
assert_body 'ok'
|
1598
|
-
end
|
1599
|
-
|
1600
|
-
it 'returns 412 when If-None-Match is * for existing resources' do
|
1601
|
-
mock_app do
|
1602
|
-
post('/') do
|
1603
|
-
etag 'foo', :new_resource => false
|
1604
|
-
'ok'
|
1605
|
-
end
|
1606
|
-
end
|
1607
|
-
|
1608
|
-
post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
|
1609
|
-
assert_status 412
|
1610
|
-
assert_body ''
|
1611
|
-
end
|
1612
|
-
|
1613
|
-
it 'returns 412 when If-None-Match is the etag' do
|
1614
|
-
mock_app do
|
1615
|
-
post('/') do
|
1616
|
-
etag 'foo'
|
1617
|
-
'ok'
|
1618
|
-
end
|
1619
|
-
end
|
1620
|
-
|
1621
|
-
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
|
1622
|
-
assert_status 412
|
1623
|
-
assert_body ''
|
1624
|
-
end
|
1625
|
-
|
1626
|
-
it 'returns 412 when If-None-Match includes the etag' do
|
1627
|
-
mock_app do
|
1628
|
-
post('/') do
|
1629
|
-
etag 'foo'
|
1630
|
-
'ok'
|
1631
|
-
end
|
1632
|
-
end
|
1633
|
-
|
1634
|
-
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
|
1635
|
-
assert_status 412
|
1636
|
-
assert_body ''
|
1637
|
-
end
|
1638
|
-
|
1639
|
-
it 'returns 200 when If-None-Match does not include the etag' do
|
1640
|
-
mock_app do
|
1641
|
-
post('/') do
|
1642
|
-
etag 'foo'
|
1643
|
-
'ok'
|
1644
|
-
end
|
1645
|
-
end
|
1646
|
-
|
1647
|
-
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1648
|
-
assert_status 200
|
1649
|
-
assert_body 'ok'
|
1650
|
-
end
|
1651
|
-
|
1652
|
-
it 'ignores If-Modified-Since if If-None-Match does not match' do
|
1653
|
-
mock_app do
|
1654
|
-
post('/') do
|
1655
|
-
etag 'foo'
|
1656
|
-
last_modified Time.at(0)
|
1657
|
-
'ok'
|
1658
|
-
end
|
1659
|
-
end
|
1660
|
-
|
1661
|
-
post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
|
1662
|
-
assert_status 200
|
1663
|
-
assert_body 'ok'
|
1664
|
-
end
|
1665
|
-
end
|
1666
|
-
|
1667
|
-
context "If-Match" do
|
1668
|
-
it 'returns 200 when If-Match is the etag' do
|
1669
|
-
mock_app do
|
1670
|
-
post('/') do
|
1671
|
-
etag 'foo'
|
1672
|
-
'ok'
|
1673
|
-
end
|
1674
|
-
end
|
1675
|
-
|
1676
|
-
post('/', {}, 'HTTP_IF_MATCH' => '"foo"')
|
1677
|
-
assert_status 200
|
1678
|
-
assert_body 'ok'
|
1679
|
-
end
|
1680
|
-
|
1681
|
-
it 'returns 200 when If-Match includes the etag' do
|
1682
|
-
mock_app do
|
1683
|
-
post('/') do
|
1684
|
-
etag 'foo'
|
1685
|
-
'ok'
|
1686
|
-
end
|
1687
|
-
end
|
1688
|
-
|
1689
|
-
post('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
|
1690
|
-
assert_status 200
|
1691
|
-
assert_body 'ok'
|
1692
|
-
end
|
1693
|
-
|
1694
|
-
it 'returns 412 when If-Match is *' do
|
1695
|
-
mock_app do
|
1696
|
-
post('/') do
|
1697
|
-
etag 'foo'
|
1698
|
-
'ok'
|
1699
|
-
end
|
1700
|
-
end
|
1701
|
-
|
1702
|
-
post('/', {}, 'HTTP_IF_MATCH' => '*')
|
1703
|
-
assert_status 412
|
1704
|
-
assert_body ''
|
1705
|
-
end
|
1706
|
-
|
1707
|
-
it 'returns 412 when If-Match is * for new resources' do
|
1708
|
-
mock_app do
|
1709
|
-
post('/') do
|
1710
|
-
etag 'foo', :new_resource => true
|
1711
|
-
'ok'
|
1712
|
-
end
|
1713
|
-
end
|
1714
|
-
|
1715
|
-
post('/', {}, 'HTTP_IF_MATCH' => '*')
|
1716
|
-
assert_status 412
|
1717
|
-
assert_body ''
|
1718
|
-
end
|
1719
|
-
|
1720
|
-
it 'returns 200 when If-Match is * for existing resources' do
|
1721
|
-
mock_app do
|
1722
|
-
post('/') do
|
1723
|
-
etag 'foo', :new_resource => false
|
1724
|
-
'ok'
|
1725
|
-
end
|
1726
|
-
end
|
1727
|
-
|
1728
|
-
post('/', {}, 'HTTP_IF_MATCH' => '*')
|
1729
|
-
assert_status 200
|
1730
|
-
assert_body 'ok'
|
1731
|
-
end
|
1732
|
-
|
1733
|
-
it 'returns 412 when If-Match does not include the etag' do
|
1734
|
-
mock_app do
|
1735
|
-
post('/') do
|
1736
|
-
etag 'foo'
|
1737
|
-
'ok'
|
1738
|
-
end
|
1739
|
-
end
|
1740
|
-
|
1741
|
-
post('/', {}, 'HTTP_IF_MATCH' => '"bar"')
|
1742
|
-
assert_status 412
|
1743
|
-
assert_body ''
|
1744
|
-
end
|
1745
|
-
end
|
1746
|
-
end
|
1747
|
-
|
1748
|
-
it 'uses a weak etag with the :weak option' do
|
1749
|
-
mock_app do
|
1750
|
-
get('/') do
|
1751
|
-
etag 'FOO', :weak
|
1752
|
-
"that's weak, dude."
|
1753
|
-
end
|
1754
|
-
end
|
1755
|
-
get '/'
|
1756
|
-
assert_equal 'W/"FOO"', response['ETag']
|
1757
|
-
end
|
1758
|
-
|
1759
|
-
it 'raises an ArgumentError for an invalid strength' do
|
1760
|
-
mock_app do
|
1761
|
-
get('/') do
|
1762
|
-
etag 'FOO', :w00t
|
1763
|
-
"that's weak, dude."
|
1764
|
-
end
|
1765
|
-
end
|
1766
|
-
assert_raises(ArgumentError) { get('/') }
|
1767
|
-
end
|
1768
|
-
end
|
1769
|
-
|
1770
|
-
describe 'back' do
|
1771
|
-
it "makes redirecting back pretty" do
|
1772
|
-
mock_app { get('/foo') { redirect back } }
|
1773
|
-
|
1774
|
-
get('/foo', {}, 'HTTP_REFERER' => 'http://github.com')
|
1775
|
-
assert redirect?
|
1776
|
-
assert_equal "http://github.com", response.location
|
1777
|
-
end
|
1778
|
-
end
|
1779
|
-
|
1780
|
-
describe 'uri' do
|
1781
|
-
it 'generates absolute urls' do
|
1782
|
-
mock_app { get('/') { uri }}
|
1783
|
-
get '/'
|
1784
|
-
assert_equal 'http://example.org/', body
|
1785
|
-
end
|
1786
|
-
|
1787
|
-
it 'includes path_info' do
|
1788
|
-
mock_app { get('/:name') { uri }}
|
1789
|
-
get '/foo'
|
1790
|
-
assert_equal 'http://example.org/foo', body
|
1791
|
-
end
|
1792
|
-
|
1793
|
-
it 'allows passing an alternative to path_info' do
|
1794
|
-
mock_app { get('/:name') { uri '/bar' }}
|
1795
|
-
get '/foo'
|
1796
|
-
assert_equal 'http://example.org/bar', body
|
1797
|
-
end
|
1798
|
-
|
1799
|
-
it 'includes script_name' do
|
1800
|
-
mock_app { get('/:name') { uri '/bar' }}
|
1801
|
-
get '/foo', {}, { "SCRIPT_NAME" => '/foo' }
|
1802
|
-
assert_equal 'http://example.org/foo/bar', body
|
1803
|
-
end
|
1804
|
-
|
1805
|
-
it 'handles absolute URIs' do
|
1806
|
-
mock_app { get('/') { uri 'http://google.com' }}
|
1807
|
-
get '/'
|
1808
|
-
assert_equal 'http://google.com', body
|
1809
|
-
end
|
1810
|
-
|
1811
|
-
it 'handles different protocols' do
|
1812
|
-
mock_app { get('/') { uri 'mailto:jsmith@example.com' }}
|
1813
|
-
get '/'
|
1814
|
-
assert_equal 'mailto:jsmith@example.com', body
|
1815
|
-
end
|
1816
|
-
|
1817
|
-
it 'is aliased to #url' do
|
1818
|
-
mock_app { get('/') { url }}
|
1819
|
-
get '/'
|
1820
|
-
assert_equal 'http://example.org/', body
|
1821
|
-
end
|
1822
|
-
|
1823
|
-
it 'is aliased to #to' do
|
1824
|
-
mock_app { get('/') { to }}
|
1825
|
-
get '/'
|
1826
|
-
assert_equal 'http://example.org/', body
|
1827
|
-
end
|
1828
|
-
end
|
1829
|
-
|
1830
|
-
describe 'logger' do
|
1831
|
-
it 'logging works when logging is enabled' do
|
1832
|
-
mock_app do
|
1833
|
-
enable :logging
|
1834
|
-
get('/') do
|
1835
|
-
logger.info "Program started"
|
1836
|
-
logger.warn "Nothing to do!"
|
1837
|
-
end
|
1838
|
-
end
|
1839
|
-
io = StringIO.new
|
1840
|
-
get '/', {}, 'rack.errors' => io
|
1841
|
-
assert io.string.include?("INFO -- : Program started")
|
1842
|
-
assert io.string.include?("WARN -- : Nothing to do")
|
1843
|
-
end
|
1844
|
-
|
1845
|
-
it 'logging works when logging is disable, but no output is produced' do
|
1846
|
-
mock_app do
|
1847
|
-
disable :logging
|
1848
|
-
get('/') do
|
1849
|
-
logger.info "Program started"
|
1850
|
-
logger.warn "Nothing to do!"
|
1851
|
-
end
|
1852
|
-
end
|
1853
|
-
io = StringIO.new
|
1854
|
-
get '/', {}, 'rack.errors' => io
|
1855
|
-
assert !io.string.include?("INFO -- : Program started")
|
1856
|
-
assert !io.string.include?("WARN -- : Nothing to do")
|
1857
|
-
end
|
1858
|
-
|
1859
|
-
it 'does not create a logger when logging is set to nil' do
|
1860
|
-
mock_app do
|
1861
|
-
set :logging, nil
|
1862
|
-
get('/') { logger.inspect }
|
1863
|
-
end
|
1864
|
-
|
1865
|
-
get '/'
|
1866
|
-
assert_body 'nil'
|
1867
|
-
end
|
1868
|
-
end
|
1869
|
-
|
1870
|
-
module ::HelperOne; def one; '1'; end; end
|
1871
|
-
module ::HelperTwo; def two; '2'; end; end
|
1872
|
-
|
1873
|
-
describe 'Adding new helpers' do
|
1874
|
-
it 'takes a list of modules to mix into the app' do
|
1875
|
-
mock_app do
|
1876
|
-
helpers ::HelperOne, ::HelperTwo
|
1877
|
-
|
1878
|
-
get('/one') { one }
|
1879
|
-
|
1880
|
-
get('/two') { two }
|
1881
|
-
end
|
1882
|
-
|
1883
|
-
get '/one'
|
1884
|
-
assert_equal '1', body
|
1885
|
-
|
1886
|
-
get '/two'
|
1887
|
-
assert_equal '2', body
|
1888
|
-
end
|
1889
|
-
|
1890
|
-
it 'takes a block to mix into the app' do
|
1891
|
-
mock_app do
|
1892
|
-
helpers do
|
1893
|
-
def foo
|
1894
|
-
'foo'
|
1895
|
-
end
|
1896
|
-
end
|
1897
|
-
|
1898
|
-
get('/') { foo }
|
1899
|
-
end
|
1900
|
-
|
1901
|
-
get '/'
|
1902
|
-
assert_equal 'foo', body
|
1903
|
-
end
|
1904
|
-
|
1905
|
-
it 'evaluates the block in class context so that methods can be aliased' do
|
1906
|
-
mock_app do
|
1907
|
-
helpers { alias_method :h, :escape_html }
|
1908
|
-
|
1909
|
-
get('/') { h('42 < 43') }
|
1910
|
-
end
|
1911
|
-
|
1912
|
-
get '/'
|
1913
|
-
assert ok?
|
1914
|
-
assert_equal '42 < 43', body
|
1915
|
-
end
|
1916
|
-
end
|
1917
|
-
end
|