test/unit/runner_tests.rb in deas-0.38.0 vs test/unit/runner_tests.rb in deas-0.39.0
- old
+ new
@@ -1,63 +1,548 @@
require 'assert'
require 'deas/runner'
+require 'rack/utils'
require 'deas/logger'
require 'deas/router'
-require 'test/support/view_handlers'
+require 'deas/template_source'
+require 'test/support/empty_view_handler'
class Deas::Runner
class UnitTests < Assert::Context
desc "Deas::Runner"
setup do
- @runner_class = Deas::Runner
+ @handler_class = EmptyViewHandler
+ @runner_class = Deas::Runner
end
subject{ @runner_class }
+ should "know its default mime type" do
+ assert_equal 'application/octet-stream', subject::DEFAULT_MIME_TYPE
+ end
+
+ should "know its default charset" do
+ assert_equal 'utf-8', subject::DEFAULT_CHARSET
+ end
+
+ should "know its default status" do
+ assert_equal 200, subject::DEFAULT_STATUS
+ end
+
+ should "know its default body" do
+ assert_equal [], subject::DEFAULT_BODY
+ end
+
end
class InitTests < UnitTests
desc "when init"
setup do
- @runner = @runner_class.new(EmptyViewHandler)
+ @request = Factory.request
+ @runner = @runner_class.new(@handler_class, :request => @request)
end
subject{ @runner }
should have_readers :handler_class, :handler
- should have_readers :request, :response, :session
- should have_readers :params, :logger, :router, :template_source
- should have_imeths :halt, :redirect, :content_type, :status, :headers
+ should have_readers :logger, :router, :template_source
+ should have_readers :request, :session, :params
+ should have_imeths :run, :to_rack
+ should have_imeths :status, :headers, :body, :content_type
+ should have_imeths :halt, :redirect, :send_file
should have_imeths :render, :source_render, :partial, :source_partial
- should have_imeths :send_file
should "know its handler and handler class" do
- assert_equal EmptyViewHandler, subject.handler_class
+ assert_equal @handler_class, subject.handler_class
assert_instance_of subject.handler_class, subject.handler
end
- should "default its settings" do
- assert_nil subject.request
- assert_nil subject.response
- assert_nil subject.session
- assert_equal ::Hash.new, subject.params
- assert_kind_of Deas::NullLogger, subject.logger
- assert_kind_of Deas::Router, subject.router
- assert_kind_of Deas::NullTemplateSource, subject.template_source
+ should "default its attrs" do
+ runner = @runner_class.new(@handler_class)
+ assert_kind_of Deas::NullLogger, runner.logger
+ assert_kind_of Deas::Router, runner.router
+ assert_kind_of Deas::NullTemplateSource, runner.template_source
+
+ assert_nil runner.request
+ assert_nil runner.session
+
+ assert_equal({}, runner.params)
end
- should "not implement its non-rendering actions" do
- assert_raises(NotImplementedError){ subject.halt }
- assert_raises(NotImplementedError){ subject.redirect }
- assert_raises(NotImplementedError){ subject.content_type }
- assert_raises(NotImplementedError){ subject.status }
- assert_raises(NotImplementedError){ subject.headers }
- assert_raises(NotImplementedError){ subject.send_file }
+ should "know its attrs" do
+ args = {
+ :logger => 'a-logger',
+ :router => 'a-router',
+ :template_source => 'a-source',
+ :request => 'a-request',
+ :session => 'a-session',
+ :params => {}
+ }
+
+ runner = @runner_class.new(@handler_class, args)
+
+ assert_equal args[:logger], runner.logger
+ assert_equal args[:router], runner.router
+ assert_equal args[:template_source], runner.template_source
+ assert_equal args[:request], runner.request
+ assert_equal args[:session], runner.session
+ assert_equal args[:params], runner.params
end
+ should "not implement its run method" do
+ assert_raises(NotImplementedError){ subject.run }
+ end
+
+ should "know its `to_rack` representation" do
+ exp = [
+ subject.class::DEFAULT_STATUS,
+ subject.headers.to_hash,
+ subject.class::DEFAULT_BODY
+ ]
+ assert_equal exp, subject.to_rack
+
+ status = Factory.integer
+ Assert.stub(subject, :status){ status }
+
+ headers = { Factory.string => Factory.string }
+ Assert.stub(subject, :headers){ headers }
+
+ body = [Factory.string]
+ Assert.stub(subject, :body){ body }
+
+ exp = [status, headers, body]
+ assert_equal exp, subject.to_rack
+ end
+
+ should "know and set its response status" do
+ assert_nil subject.status
+
+ exp = Factory.integer
+ subject.status exp
+ assert_equal exp, subject.status
+ end
+
+ should "know and merge values on its response headers" do
+ assert_kind_of Rack::Utils::HeaderHash, subject.headers
+ assert_equal({}, subject.headers)
+
+ new_header_values = { Factory.string => Factory.string }
+ subject.headers(new_header_values)
+ assert_kind_of Rack::Utils::HeaderHash, subject.headers
+ assert_equal new_header_values, subject.headers
+
+ location = Factory.string
+ subject.headers['Location'] = location
+ exp = new_header_values.merge('Location' => location)
+ assert_equal exp, subject.headers
+ end
+
+ should "know and set its response body" do
+ assert_nil subject.body
+
+ exp = Factory.string
+ subject.body exp
+ assert_equal exp, subject.body
+
+ assert_raises(ArgumentError) do
+ subject.body Factory.integer
+ end
+ end
+
+ should "know and set its response content type header" do
+ extname = ".#{Factory.string}"
+
+ subject.content_type(extname) # unknown mime type extname
+ exp = subject.class::DEFAULT_MIME_TYPE
+ assert_equal exp, subject.headers['Content-Type']
+
+ mime_type = "#{Factory.string}/#{Factory.string}"
+ Assert.stub(Rack::Mime, :mime_type).with(
+ extname,
+ subject.class::DEFAULT_MIME_TYPE
+ ){ mime_type }
+ subject.content_type(extname) # known mime type extname
+ assert_equal mime_type, subject.headers['Content-Type']
+
+ params = {
+ Factory.string => Factory.string,
+ Factory.string => Factory.string
+ }
+ subject.content_type(extname, params)
+ exp = "#{mime_type};#{params.map{ |k,v| k + '=' + v }.join(',')}"
+ assert_equal exp, subject.headers['Content-Type']
+ end
+
end
+ class HaltTests < InitTests
+ desc "the `halt` method"
+ setup do
+ @status = Factory.integer
+ @headers = { Factory.string => Factory.string }
+ @body = [Factory.string]
+ end
+
+ should "set response attrs and halt execution" do
+ runner = runner_halted_with()
+ assert_nil runner.status
+ assert_equal({}, runner.headers)
+ assert_nil runner.body
+
+ runner = runner_halted_with(@status)
+ assert_equal @status, runner.status
+ assert_equal({}, runner.headers)
+ assert_nil runner.body
+
+ runner = runner_halted_with(@headers)
+ assert_nil runner.status
+ assert_equal @headers, runner.headers
+ assert_nil runner.body
+
+ runner = runner_halted_with(@body)
+ assert_nil runner.status
+ assert_equal({}, runner.headers)
+ assert_equal @body, runner.body
+
+ runner = runner_halted_with(@status, @headers)
+ assert_equal @status, runner.status
+ assert_equal @headers, runner.headers
+ assert_nil runner.body
+
+ runner = runner_halted_with(@status, @body)
+ assert_equal @status, runner.status
+ assert_equal({}, runner.headers)
+ assert_equal @body, runner.body
+
+ runner = runner_halted_with(@headers, @body)
+ assert_nil runner.status
+ assert_equal @headers, runner.headers
+ assert_equal @body, runner.body
+
+ runner = runner_halted_with(@status, @headers, @body)
+ assert_equal @status, runner.status
+ assert_equal @headers, runner.headers
+ assert_equal @body, runner.body
+ end
+
+ private
+
+ def runner_halted_with(*halt_args)
+ @runner_class.new(@handler_class).tap do |runner|
+ catch(:halt){ runner.halt(*halt_args) }
+ end
+ end
+
+ end
+
+ class HaltCalledWithTests < InitTests
+ setup do
+ @halt_called_with = nil
+ Assert.stub(@runner, :halt){ |*args| @halt_called_with = args; throw :halt }
+ end
+
+ end
+
+ class RedirectTests < HaltCalledWithTests
+ desc "the `redirect` method"
+ setup do
+ @location = Factory.boolean ? Factory.string : "/#{Factory.string}"
+ end
+
+ should "set response attrs and halt execution" do
+ catch(:halt){ subject.redirect(@location) }
+
+ assert_equal 302, subject.status
+
+ exp = get_absolute_url(@location)
+ assert_equal exp, subject.headers['Location']
+
+ assert_nil subject.body
+ assert_equal [], @halt_called_with
+ end
+
+ should "set response attrs and halt execution if called with halt args" do
+ halt_args = Factory.integer(3).times.map{ Factory.string }
+ catch(:halt){ subject.redirect(@location, *halt_args) }
+
+ assert_equal 302, subject.status
+
+ exp = get_absolute_url(@location)
+ assert_equal exp, subject.headers['Location']
+
+ assert_nil subject.body
+ assert_equal halt_args, @halt_called_with
+ end
+
+ private
+
+ def get_absolute_url(url)
+ File.join("#{@request.env['rack.url_scheme']}://#{@request.env['HTTP_HOST']}", url)
+ end
+
+ end
+
+ class SendFileSetupTests < HaltCalledWithTests
+ desc "the `send_file` method"
+
+ end
+
+ class NotFoundSendFileTests < SendFileSetupTests
+ desc "called with a file path that doesn't exist"
+ setup do
+ @not_found_path = Factory.path
+ end
+
+ should "halt 404" do
+ catch(:halt){ subject.send_file(@not_found_path) }
+ assert_equal [404, []], @halt_called_with
+ end
+
+ end
+
+ class ExistingFileSendFileTests < SendFileSetupTests
+ setup do
+ @file_path = TEST_SUPPORT_ROOT.join('file1.txt')
+ @path_name = Pathname.new(@file_path)
+ end
+
+ end
+
+ class NotModifiedSendFileTests < ExistingFileSendFileTests
+ desc "called with a file path that isn't modified"
+ setup do
+ @request.env['HTTP_IF_MODIFIED_SINCE'] = @path_name.mtime.httpdate.to_s
+ end
+
+ should "halt 304" do
+ catch(:halt){ subject.send_file(@file_path) }
+ assert_equal [304, []], @halt_called_with
+ end
+
+ end
+
+ class ModifiedSendFileTests < ExistingFileSendFileTests
+ desc "called with a modified file path"
+ setup do
+ path_name = @path_name
+ @file_content_type = @runner.instance_eval{ get_content_type(path_name.extname) }
+ @send_file_body = SendFileBody.new(@request.env, @path_name)
+ end
+
+ should "halt 200 with the proper headers and body" do
+ catch(:halt){ subject.send_file(@file_path) }
+
+ assert_equal [], @halt_called_with
+ assert_equal 200, subject.status
+
+ exp = {
+ 'Last-Modified' => @path_name.mtime.httpdate.to_s,
+ 'Content-Type' => @file_content_type,
+ 'Content-Length' => @send_file_body.size.to_s
+ }
+ assert_equal exp, subject.headers
+
+ assert_equal @send_file_body, subject.body
+ end
+
+ should "only update the content type header if it is not already set" do
+ custom_content_type = Factory.string
+ subject.headers['Content-Type'] = custom_content_type
+
+ catch(:halt){ subject.send_file(@file_path) }
+ assert_equal custom_content_type, subject.headers['Content-Type']
+ end
+
+ should "only update the content length header if it is not already set" do
+ custom_content_length = Factory.integer.to_s
+ subject.headers['Content-Length'] = custom_content_length
+
+ catch(:halt){ subject.send_file(@file_path) }
+ assert_equal custom_content_length, subject.headers['Content-Length']
+ end
+
+ should "halt with the proper header if the `:disposition` option is given" do
+ disposition = Factory.string
+ catch(:halt){ subject.send_file(@file_path, :disposition => disposition) }
+
+ exp = "#{disposition};filename=\"#{@path_name.basename}\""
+ assert_equal exp, subject.headers['Content-Disposition']
+ end
+
+ should "halt with the proper header if the `:filename` option is given" do
+ filename = Factory.string
+ catch(:halt){ subject.send_file(@file_path, :filename => filename) }
+
+ exp = "attachment;filename=\"#{filename}\""
+ assert_equal exp, subject.headers['Content-Disposition']
+ end
+
+ should "halt with the proper header if the `:disposition` and `:filename` options are given" do
+ disposition = Factory.string
+ filename = Factory.string
+ catch(:halt) do
+ subject.send_file(@file_path, {
+ :disposition => disposition,
+ :filename => filename
+ })
+ end
+
+ exp = "#{disposition};filename=\"#{filename}\""
+ assert_equal exp, subject.headers['Content-Disposition']
+ end
+
+ should "only update the content dispostion header if it is not already set" do
+ custom_disposition = Factory.string
+ subject.headers['Content-Disposition'] = custom_disposition
+
+ catch(:halt) do
+ subject.send_file(@file_path, {
+ :disposition => Factory.string,
+ :filename => Factory.string
+ })
+ end
+ assert_equal custom_disposition, subject.headers['Content-Disposition']
+ end
+
+ end
+
+ class PartialSendFileTests < ModifiedSendFileTests
+ desc "for a partial response"
+ setup do
+ Assert.stub(@send_file_body, :partial?){ true }
+ @content_range = Factory.string
+ Assert.stub(@send_file_body, :content_range){ @content_range }
+ Assert.stub(SendFileBody, :new){ @send_file_body }
+ end
+
+ should "halt 206 with the proper headers and body for partial requests" do
+ catch(:halt){ subject.send_file(@file_path) }
+
+ assert_equal [], @halt_called_with
+ assert_equal 206, subject.status
+ assert_equal @content_range, subject.headers['Content-Range']
+ assert_equal @send_file_body, subject.body
+ end
+
+ should "only update the content range header if it is not already set" do
+ custom_range = Factory.string
+ subject.headers['Content-Range'] = custom_range
+
+ catch(:halt){ subject.send_file(@file_path) }
+ assert_equal custom_range, subject.headers['Content-Range']
+ end
+
+ end
+
+ class RenderSetupTests < InitTests
+ setup do
+ @template_name = Factory.path
+ @locals = { Factory.string => Factory.string }
+ end
+
+ end
+
+ class RenderTests < RenderSetupTests
+ desc "render method"
+ setup do
+ @source_render_called_with = nil
+ Assert.stub(@runner, :source_render){ |*args| @source_render_called_with = args }
+ end
+
+ should "call to its `source_render` method with its template source" do
+ subject.render(@template_name, @locals)
+ exp = [subject.template_source, @template_name, @locals]
+ assert_equal exp, @source_render_called_with
+
+ subject.render(@template_name)
+ exp = [subject.template_source, @template_name, nil]
+ assert_equal exp, @source_render_called_with
+ end
+
+ end
+
+ class SourceRenderTests < RenderSetupTests
+ desc "source render method"
+ setup do
+ body = @body = Factory.text
+ @render_called_with = nil
+ @source = Deas::TemplateSource.new(Factory.path)
+ Assert.stub(@source, :render){ |*args| @render_called_with = args; body }
+ end
+
+ should "call to the given source's render method and set the return value as the body" do
+ subject.source_render(@source, @template_name, @locals)
+
+ exp = [@template_name, subject.handler, @locals]
+ assert_equal exp, @render_called_with
+
+ template_name = @template_name
+ exp = subject.instance_eval do
+ get_content_type(File.extname(template_name), 'charset' => DEFAULT_CHARSET)
+ end
+ assert_equal exp, subject.headers['Content-Type']
+
+ assert_equal @body, subject.body
+ end
+
+ should "default the locals if none given" do
+ subject.source_render(@source, @template_name)
+ exp = [@template_name, subject.handler, {}]
+ assert_equal exp, @render_called_with
+ end
+
+ should "only update the content type header if it is not already set" do
+ custom_content_type = Factory.string
+ subject.headers['Content-Type'] = custom_content_type
+
+ subject.source_render(@source, @template_name, @locals)
+ assert_equal custom_content_type, subject.headers['Content-Type']
+ end
+
+ end
+
+ class PartialTests < RenderSetupTests
+ desc "partial method"
+ setup do
+ @source_partial_called_with = nil
+ Assert.stub(@runner, :source_partial){ |*args| @source_partial_called_with = args }
+ end
+
+ should "call to its `source_partial` method with its template source" do
+ subject.partial(@template_name, @locals)
+ exp = [subject.template_source, @template_name, @locals]
+ assert_equal exp, @source_partial_called_with
+
+ subject.partial(@template_name)
+ exp = [subject.template_source, @template_name, nil]
+ assert_equal exp, @source_partial_called_with
+ end
+
+ end
+
+ class SourcePartialTests < RenderSetupTests
+ desc "source partial method"
+ setup do
+ @partial_called_with = nil
+ @source = Deas::TemplateSource.new(Factory.path)
+ Assert.stub(@source, :partial){ |*args| @partial_called_with = args }
+ end
+
+ should "call to the given source's partial method" do
+ subject.source_partial(@source, @template_name, @locals)
+ exp = [@template_name, @locals]
+ assert_equal exp, @partial_called_with
+ end
+
+ should "default the locals if none given" do
+ subject.source_partial(@source, @template_name)
+ exp = [@template_name, {}]
+ assert_equal exp, @partial_called_with
+ end
+
+ end
+
class NormalizedParamsTests < UnitTests
desc "NormalizedParams"
should "convert any non-Array or non-Hash values to strings" do
exp_params = {
@@ -119,91 +604,149 @@
def file_type?(value); false; end
end
end
- class RenderSetupTests < InitTests
+ class SendFileBodyTests < UnitTests
+ desc "SendFileBody"
setup do
- @template_name = Factory.path
- @locals = { Factory.string => Factory.string }
+ @env = Factory.request.env
+ @path_name = Pathname.new(TEST_SUPPORT_ROOT.join('file1.txt'))
+
+ @body = SendFileBody.new(@env, @path_name)
end
+ subject{ @body }
- end
+ should have_readers :path_name, :size, :content_range
+ should have_imeths :partial?, :range_begin, :range_end
+ should have_imeths :each
- class RenderTests < RenderSetupTests
- desc "render method"
- setup do
- @render_args = nil
- Assert.stub(@runner.template_source, :render){ |*args| @render_args = args }
+ should "know its chunk size" do
+ assert_equal 8192, SendFileBody::CHUNK_SIZE
end
- should "call to its template source render method" do
- subject.render(@template_name, @locals)
- exp = [@template_name, subject.handler, @locals]
- assert_equal exp, @render_args
+ should "know its path name" do
+ assert_equal @path_name, subject.path_name
+ end
- subject.render(@template_name)
- exp = [@template_name, subject.handler, {}]
- assert_equal exp, @render_args
+ should "know if it is equal to another body" do
+ same_path_same_range = SendFileBody.new(@env, @path_name)
+ Assert.stub(same_path_same_range, :range_begin){ subject.range_begin }
+ Assert.stub(same_path_same_range, :range_end){ subject.range_end }
+ assert_equal same_path_same_range, subject
+
+ other_path = Pathname.new(TEST_SUPPORT_ROOT.join('file2.txt'))
+ other_path_same_range = SendFileBody.new(@env, other_path)
+ Assert.stub(other_path_same_range, :range_begin){ subject.range_begin }
+ Assert.stub(other_path_same_range, :range_end){ subject.range_end }
+ assert_not_equal other_path_same_range, subject
+
+ same_path_other_range = SendFileBody.new(@env, @path_name)
+
+ Assert.stub(same_path_other_range, :range_begin){ Factory.integer }
+ Assert.stub(same_path_other_range, :range_end){ subject.range_end }
+ assert_not_equal same_path_other_range, subject
+
+ Assert.stub(same_path_other_range, :range_begin){ subject.range_begin }
+ Assert.stub(same_path_other_range, :range_end){ Factory.integer }
+ assert_not_equal same_path_other_range, subject
end
end
- class SourceRenderTests < RenderSetupTests
- desc "source render method"
+ class SendFileBodyIOTests < SendFileBodyTests
setup do
- @source_render_args = nil
- @source = Deas::TemplateSource.new(Factory.path)
- Assert.stub(@source, :render){ |*args| @source_render_args = args }
- end
+ @min_num_chunks = 3
+ @num_chunks = @min_num_chunks + Factory.integer(3)
+ @path_name = Pathname.new(TEST_SUPPORT_ROOT.join('file3.txt'))
- should "call to the given source's render method" do
- subject.source_render(@source, @template_name, @locals)
- exp = [@template_name, subject.handler, @locals]
- assert_equal exp, @source_render_args
-
- subject.source_render(@source, @template_name)
- exp = [@template_name, subject.handler, {}]
- assert_equal exp, @source_render_args
+ @path_name.open('w') do |io|
+ io.write('a' * (@num_chunks * SendFileBody::CHUNK_SIZE))
+ end
end
+ teardown do
+ @path_name.delete
+ end
end
- class PartialTests < RenderSetupTests
- desc "partial method"
+ class NonPartialSendFileBodyTests < SendFileBodyIOTests
+ desc "for non/multi/invalid partial content requests"
setup do
- @partial_args = nil
- Assert.stub(@runner.template_source, :partial){ |*args| @partial_args = args }
+ range = [nil, 'bytes=', 'bytes=0-1,2-3', 'bytes=3-2', 'bytes=abc'].choice
+ env = range.nil? ? {} : { 'HTTP_RANGE' => range }
+ @body = SendFileBody.new(env, @path_name)
end
- should "call to its template source partial method" do
- subject.partial(@template_name, @locals)
- exp = [@template_name, @locals]
- assert_equal exp, @partial_args
+ should "not be partial" do
+ assert_false subject.partial?
+ end
- subject.partial(@template_name)
- exp = [@template_name, {}]
- assert_equal exp, @partial_args
+ should "be the full content size" do
+ assert_equal @path_name.size, subject.size
end
+ should "have no content range" do
+ assert_nil subject.content_range
+ end
+
+ should "have the full content size as its range" do
+ assert_equal 0, subject.range_begin
+ assert_equal subject.size-1, subject.range_end
+ end
+
+ should "chunk the full content when iterated" do
+ chunks = []
+ subject.each{ |chunk| chunks << chunk }
+
+ assert_equal @num_chunks, chunks.size
+ assert_equal subject.class::CHUNK_SIZE, chunks.first.size
+ assert_equal @path_name.read, chunks.join('')
+ end
+
end
- class SourcePartialTests < RenderSetupTests
- desc "source partial method"
+ class PartialSendFileBodyTests < SendFileBodyIOTests
+ desc "for a partial content request"
setup do
- @source_partial_args = nil
- @source = Deas::TemplateSource.new(Factory.path)
- Assert.stub(@source, :partial){ |*args| @source_partial_args = args }
+ @start_chunk = Factory.boolean ? 0 : 1
+ @partial_begin = @start_chunk * SendFileBody::CHUNK_SIZE
+ @partial_chunks = @num_chunks - Factory.integer(@min_num_chunks)
+ @partial_size = @partial_chunks * SendFileBody::CHUNK_SIZE
+ @partial_end = @partial_begin + (@partial_size-1)
+
+ env = { 'HTTP_RANGE' => "bytes=#{@partial_begin}-#{@partial_end}" }
+ @body = SendFileBody.new(env, @path_name)
end
+ subject{ @body }
- should "call to the given source's partial method" do
- subject.source_partial(@source, @template_name, @locals)
- exp = [@template_name, @locals]
- assert_equal exp, @source_partial_args
+ should "be partial" do
+ assert_true subject.partial?
+ end
- subject.source_partial(@source, @template_name)
- exp = [@template_name, {}]
- assert_equal exp, @source_partial_args
+ should "be the specified partial size" do
+ assert_equal @partial_size, subject.size
+ end
+
+ should "know its content range" do
+ exp = "bytes #{@partial_begin}-#{@partial_end}/#{@path_name.size}"
+ assert_equal exp, subject.content_range
+ end
+
+ should "know its range" do
+ assert_equal @partial_begin, subject.range_begin
+ assert_equal @partial_end, subject.range_end
+ end
+
+ should "chunk the range when iterated" do
+ chunks = []
+ subject.each{ |chunk| chunks << chunk }
+
+ assert_equal @partial_chunks, chunks.size
+ assert_equal subject.class::CHUNK_SIZE, chunks.first.size
+
+ exp = @path_name.read[@partial_begin..@partial_end]
+ assert_equal exp, chunks.join('')
end
end
end