1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionpack/test/dispatch/response_test.rb
Masaru Nomura bddf83bfcf Add tests for 1xx, 204 and 304 responses to response_test.rb
In response_test.rb, we haven't had a test to make sure that

1) these responses don't have a message-body as described in RFC7231[1]

2) 1xx and 204 responses must not have a Content-Length header field
   as described in RFC7230-section3.3.2[2]

[1] https://tools.ietf.org/html/rfc7231
[2] https://tools.ietf.org/html/rfc7230#section-3.3.2

Even though our implementation doesn't allow users to send
a Content-Length header field in a 304 response, sending the
header field is valid as mentioned in RFC7230-section3.3.2[2].
So I've decided not to test whether or not a 304 response has
the header.

The citation from the section is as follows;
```
A server MAY send a Content-Length header field in a 304 (Not
Modified) response to a conditional GET request (Section 4.1 of
[RFC7232]); a server MUST NOT send Content-Length in such a response
unless its field-value equals the decimal number of octets that would
have been sent in the payload body of a 200 (OK) response to the same
request.
```
2016-07-09 19:37:06 +09:00

534 lines
16 KiB
Ruby

require 'abstract_unit'
require 'timeout'
require 'rack/content_length'
class ResponseTest < ActiveSupport::TestCase
def setup
@response = ActionDispatch::Response.create
@response.request = ActionDispatch::Request.empty
end
def test_can_wait_until_commit
t = Thread.new {
@response.await_commit
}
@response.commit!
assert @response.committed?
assert t.join(0.5)
end
def test_stream_close
@response.stream.close
assert @response.stream.closed?
end
def test_stream_write
@response.stream.write "foo"
@response.stream.close
assert_equal "foo", @response.body
end
def test_write_after_close
@response.stream.close
e = assert_raises(IOError) do
@response.stream.write "omg"
end
assert_equal "closed stream", e.message
end
def test_each_isnt_called_if_str_body_is_written
# Controller writes and reads response body
each_counter = 0
@response.body = Object.new.tap {|o| o.singleton_class.send(:define_method, :each) { |&block| each_counter += 1; block.call 'foo' } }
@response['X-Foo'] = @response.body
assert_equal 1, each_counter, "#each was not called once"
# Build response
status, headers, body = @response.to_a
assert_equal 200, status
assert_equal "foo", headers['X-Foo']
assert_equal "foo", body.each.to_a.join
# Show that #each was not called twice
assert_equal 1, each_counter, "#each was not called once"
end
def test_set_header_after_read_body_during_action
@response.body
# set header after the action reads back @response.body
@response['x-header'] = "Best of all possible worlds."
# the response can be built.
status, headers, body = @response.to_a
assert_equal 200, status
assert_equal "", body.body
assert_equal "Best of all possible worlds.", headers['x-header']
end
def test_read_body_during_action
@response.body = "Hello, World!"
# even though there's no explicitly set content-type,
assert_equal nil, @response.content_type
# after the action reads back @response.body,
assert_equal "Hello, World!", @response.body
# the response can be built.
status, headers, body = @response.to_a
assert_equal 200, status
assert_equal({
"Content-Type" => "text/html; charset=utf-8"
}, headers)
parts = []
body.each { |part| parts << part }
assert_equal ["Hello, World!"], parts
end
def test_response_body_encoding
body = ["hello".encode(Encoding::UTF_8)]
response = ActionDispatch::Response.new 200, {}, body
response.request = ActionDispatch::Request.empty
assert_equal Encoding::UTF_8, response.body.encoding
end
def test_response_charset_writer
@response.charset = 'utf-16'
assert_equal 'utf-16', @response.charset
@response.charset = nil
assert_equal 'utf-8', @response.charset
end
def test_setting_content_type_header_impacts_content_type_method
@response.headers['Content-Type'] = "application/aaron"
assert_equal 'application/aaron', @response.content_type
end
test "simple output" do
@response.body = "Hello, World!"
status, headers, body = @response.to_a
assert_equal 200, status
assert_equal({
"Content-Type" => "text/html; charset=utf-8"
}, headers)
parts = []
body.each { |part| parts << part }
assert_equal ["Hello, World!"], parts
end
test "status handled properly in initialize" do
assert_equal 200, ActionDispatch::Response.new('200 OK').status
end
def test_only_set_charset_still_defaults_to_text_html
response = ActionDispatch::Response.new
response.charset = "utf-16"
_,headers,_ = response.to_a
assert_equal "text/html; charset=utf-16", headers['Content-Type']
end
test "utf8 output" do
@response.body = [1090, 1077, 1089, 1090].pack("U*")
status, headers, _ = @response.to_a
assert_equal 200, status
assert_equal({
"Content-Type" => "text/html; charset=utf-8"
}, headers)
end
test "content length" do
[100, 101, 102, 204].each do |c|
@response = ActionDispatch::Response.new
@response.status = c.to_s
@response.set_header "Content-Length", "0"
_, headers, _ = @response.to_a
assert !headers.has_key?("Content-Length"), "#{c} must not have a Content-Length header field"
end
end
test "does not contain a message-body" do
[100, 101, 102, 204, 304].each do |c|
@response = ActionDispatch::Response.new
@response.status = c.to_s
@response.body = "Body must not be included"
_, _, body = @response.to_a
assert_empty body, "#{c} must not have a message-body but actually contains #{body}"
end
end
test "content type" do
[204, 304].each do |c|
@response = ActionDispatch::Response.new
@response.status = c.to_s
_, headers, _ = @response.to_a
assert !headers.has_key?("Content-Type"), "#{c} should not have Content-Type header"
end
[200, 302, 404, 500].each do |c|
@response = ActionDispatch::Response.new
@response.status = c.to_s
_, headers, _ = @response.to_a
assert headers.has_key?("Content-Type"), "#{c} did not have Content-Type header"
end
end
test "does not include Status header" do
@response.status = "200 OK"
_, headers, _ = @response.to_a
assert !headers.has_key?('Status')
end
test "response code" do
@response.status = "200 OK"
assert_equal 200, @response.response_code
@response.status = "200"
assert_equal 200, @response.response_code
@response.status = 200
assert_equal 200, @response.response_code
end
test "code" do
@response.status = "200 OK"
assert_equal "200", @response.code
@response.status = "200"
assert_equal "200", @response.code
@response.status = 200
assert_equal "200", @response.code
end
test "message" do
@response.status = "200 OK"
assert_equal "OK", @response.message
@response.status = "200"
assert_equal "OK", @response.message
@response.status = 200
assert_equal "OK", @response.message
end
test "cookies" do
@response.set_cookie("user_name", :value => "david", :path => "/")
_status, headers, _body = @response.to_a
assert_equal "user_name=david; path=/", headers["Set-Cookie"]
assert_equal({"user_name" => "david"}, @response.cookies)
end
test "multiple cookies" do
@response.set_cookie("user_name", :value => "david", :path => "/")
@response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5))
_status, headers, _body = @response.to_a
assert_equal "user_name=david; path=/\nlogin=foo%26bar; path=/; expires=Mon, 10 Oct 2005 05:00:00 -0000", headers["Set-Cookie"]
assert_equal({"login" => "foo&bar", "user_name" => "david"}, @response.cookies)
end
test "delete cookies" do
@response.set_cookie("user_name", :value => "david", :path => "/")
@response.set_cookie("login", :value => "foo&bar", :path => "/", :expires => Time.utc(2005, 10, 10,5))
@response.delete_cookie("login")
assert_equal({"user_name" => "david", "login" => nil}, @response.cookies)
end
test "read ETag and Cache-Control" do
resp = ActionDispatch::Response.new.tap { |response|
response.cache_control[:public] = true
response.etag = '123'
response.body = 'Hello'
}
resp.to_a
assert resp.etag?
assert resp.weak_etag?
assert_not resp.strong_etag?
assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.etag)
assert_equal({:public => true}, resp.cache_control)
assert_equal('public', resp.headers['Cache-Control'])
assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.headers['ETag'])
end
test "read strong ETag" do
resp = ActionDispatch::Response.new.tap { |response|
response.cache_control[:public] = true
response.strong_etag = '123'
response.body = 'Hello'
}
resp.to_a
assert resp.etag?
assert_not resp.weak_etag?
assert resp.strong_etag?
assert_equal('"202cb962ac59075b964b07152d234b70"', resp.etag)
end
test "read charset and content type" do
resp = ActionDispatch::Response.new.tap { |response|
response.charset = 'utf-16'
response.content_type = Mime[:xml]
response.body = 'Hello'
}
resp.to_a
assert_equal('utf-16', resp.charset)
assert_equal(Mime[:xml], resp.content_type)
assert_equal('application/xml; charset=utf-16', resp.headers['Content-Type'])
end
test "read content type with default charset utf-8" do
original = ActionDispatch::Response.default_charset
begin
resp = ActionDispatch::Response.new(200, { "Content-Type" => "text/xml" })
assert_equal('utf-8', resp.charset)
ensure
ActionDispatch::Response.default_charset = original
end
end
test "read content type with charset utf-16" do
original = ActionDispatch::Response.default_charset
begin
ActionDispatch::Response.default_charset = 'utf-16'
resp = ActionDispatch::Response.new(200, { "Content-Type" => "text/xml" })
assert_equal('utf-16', resp.charset)
ensure
ActionDispatch::Response.default_charset = original
end
end
test "read x_frame_options, x_content_type_options and x_xss_protection" do
original_default_headers = ActionDispatch::Response.default_headers
begin
ActionDispatch::Response.default_headers = {
'X-Frame-Options' => 'DENY',
'X-Content-Type-Options' => 'nosniff',
'X-XSS-Protection' => '1;'
}
resp = ActionDispatch::Response.create.tap { |response|
response.body = 'Hello'
}
resp.to_a
assert_equal('DENY', resp.headers['X-Frame-Options'])
assert_equal('nosniff', resp.headers['X-Content-Type-Options'])
assert_equal('1;', resp.headers['X-XSS-Protection'])
ensure
ActionDispatch::Response.default_headers = original_default_headers
end
end
test "read custom default_header" do
original_default_headers = ActionDispatch::Response.default_headers
begin
ActionDispatch::Response.default_headers = {
'X-XX-XXXX' => 'Here is my phone number'
}
resp = ActionDispatch::Response.create.tap { |response|
response.body = 'Hello'
}
resp.to_a
assert_equal('Here is my phone number', resp.headers['X-XX-XXXX'])
ensure
ActionDispatch::Response.default_headers = original_default_headers
end
end
test "respond_to? accepts include_private" do
assert_not @response.respond_to?(:method_missing)
assert @response.respond_to?(:method_missing, true)
end
test "can be explicitly destructured into status, headers and an enumerable body" do
response = ActionDispatch::Response.new(404, { 'Content-Type' => 'text/plain' }, ['Not Found'])
response.request = ActionDispatch::Request.empty
status, headers, body = *response
assert_equal 404, status
assert_equal({ 'Content-Type' => 'text/plain' }, headers)
assert_equal ['Not Found'], body.each.to_a
end
test "[response.to_a].flatten does not recurse infinitely" do
Timeout.timeout(1) do # use a timeout to prevent it stalling indefinitely
status, headers, body = [@response.to_a].flatten
assert_equal @response.status, status
assert_equal @response.headers, headers
assert_equal @response.body, body.each.to_a.join
end
end
test "compatibility with Rack::ContentLength" do
@response.body = 'Hello'
app = lambda { |env| @response.to_a }
env = Rack::MockRequest.env_for("/")
status, headers, body = app.call(env)
assert_nil headers['Content-Length']
status, headers, body = Rack::ContentLength.new(app).call(env)
assert_equal '5', headers['Content-Length']
end
end
class ResponseHeadersTest < ActiveSupport::TestCase
def setup
@response = ActionDispatch::Response.create
@response.set_header 'Foo', '1'
end
test 'has_header?' do
assert @response.has_header? 'Foo'
assert_not @response.has_header? 'foo'
assert_not @response.has_header? nil
end
test 'get_header' do
assert_equal '1', @response.get_header('Foo')
assert_nil @response.get_header('foo')
assert_nil @response.get_header(nil)
end
test 'set_header' do
assert_equal '2', @response.set_header('Foo', '2')
assert @response.has_header?('Foo')
assert_equal '2', @response.get_header('Foo')
assert_nil @response.set_header('Foo', nil)
assert @response.has_header?('Foo')
assert_nil @response.get_header('Foo')
end
test 'delete_header' do
assert_nil @response.delete_header(nil)
assert_nil @response.delete_header('foo')
assert @response.has_header?('Foo')
assert_equal '1', @response.delete_header('Foo')
assert_not @response.has_header?('Foo')
end
test 'add_header' do
# Add a value to an existing header
assert_equal '1,2', @response.add_header('Foo', '2')
assert_equal '1,2', @response.get_header('Foo')
# Add nil to an existing header
assert_equal '1,2', @response.add_header('Foo', nil)
assert_equal '1,2', @response.get_header('Foo')
# Add nil to a nonexistent header
assert_nil @response.add_header('Bar', nil)
assert_not @response.has_header?('Bar')
assert_nil @response.get_header('Bar')
# Add a value to a nonexistent header
assert_equal '1', @response.add_header('Bar', '1')
assert @response.has_header?('Bar')
assert_equal '1', @response.get_header('Bar')
end
end
class ResponseIntegrationTest < ActionDispatch::IntegrationTest
test "response cache control from railsish app" do
@app = lambda { |env|
ActionDispatch::Response.new.tap { |resp|
resp.cache_control[:public] = true
resp.etag = '123'
resp.body = 'Hello'
resp.request = ActionDispatch::Request.empty
}.to_a
}
get '/'
assert_response :success
assert_equal('public', @response.headers['Cache-Control'])
assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag)
assert_equal({:public => true}, @response.cache_control)
end
test "response cache control from rackish app" do
@app = lambda { |env|
[200,
{'ETag' => 'W/"202cb962ac59075b964b07152d234b70"',
'Cache-Control' => 'public'}, ['Hello']]
}
get '/'
assert_response :success
assert_equal('public', @response.headers['Cache-Control'])
assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag)
assert_equal({:public => true}, @response.cache_control)
end
test "response charset and content type from railsish app" do
@app = lambda { |env|
ActionDispatch::Response.new.tap { |resp|
resp.charset = 'utf-16'
resp.content_type = Mime[:xml]
resp.body = 'Hello'
resp.request = ActionDispatch::Request.empty
}.to_a
}
get '/'
assert_response :success
assert_equal('utf-16', @response.charset)
assert_equal(Mime[:xml], @response.content_type)
assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
end
test "response charset and content type from rackish app" do
@app = lambda { |env|
[200,
{'Content-Type' => 'application/xml; charset=utf-16'},
['Hello']]
}
get '/'
assert_response :success
assert_equal('utf-16', @response.charset)
assert_equal(Mime[:xml], @response.content_type)
assert_equal('application/xml; charset=utf-16', @response.headers['Content-Type'])
end
test "strong ETag validator" do
@app = lambda { |env|
ActionDispatch::Response.new.tap { |resp|
resp.strong_etag = '123'
resp.body = 'Hello'
resp.request = ActionDispatch::Request.empty
}.to_a
}
get '/'
assert_response :ok
assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
end
end