/spec/thingfish/handler/staticcontent_spec.rb

https://bitbucket.org/laika/thingfish · Ruby · 212 lines · 175 code · 32 blank · 5 comment · 1 complexity · 40087e19e5b835af410988c391a2dd03 MD5 · raw file

  1. #!/usr/bin/env ruby
  2. BEGIN {
  3. require 'pathname'
  4. basedir = Pathname.new( __FILE__ ).dirname.parent.parent.parent
  5. libdir = basedir + "lib"
  6. $LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
  7. $LOAD_PATH.unshift( libdir ) unless $LOAD_PATH.include?( libdir )
  8. }
  9. require 'rspec'
  10. require 'spec/lib/helpers'
  11. require 'thingfish/handler'
  12. require 'thingfish/handler/staticcontent'
  13. require 'thingfish/behavior/handler'
  14. require 'thingfish/testconstants'
  15. #####################################################################
  16. ### C O N T E X T S
  17. #####################################################################
  18. describe ThingFish::StaticContentHandler do
  19. before( :all ) do
  20. setup_logging( :fatal )
  21. end
  22. let( :handler ) do
  23. resdir = Pathname.new( __FILE__ ).expand_path.dirname.parent + '/tmp'
  24. ThingFish::Handler.create( 'staticcontent', '/foom', resdir )
  25. end
  26. before( :each ) do
  27. @daemon = mock( "thingfish daemon" ).as_null_object
  28. self.handler.on_startup( @daemon )
  29. @request = mock( "request" ).as_null_object
  30. @request_headers = mock( "request headers" ).as_null_object
  31. @response = mock( "response" ).as_null_object
  32. @response_headers = mock( "response headers" ).as_null_object
  33. @request.stub!( :headers ).and_return( @request_headers )
  34. @response.stub!( :headers ).and_return( @response_headers )
  35. @pathname = mock( "Pathname" ).as_null_object
  36. end
  37. after( :all ) do
  38. reset_logging()
  39. end
  40. ### Shared behaviors
  41. it_should_behave_like "a handler"
  42. it "does nothing if the requested path doesn't exist" do
  43. self.handler.stub!( :get_safe_path ).with( '/blorg.txt' ).and_return( @pathname )
  44. @pathname.should_receive( :exist? ).and_return( false )
  45. @response.should_not_receive( :status= )
  46. self.handler.find_static_file( '/blorg.txt', @request, @response )
  47. end
  48. it "refuses to serve directories" do
  49. self.handler.stub!( :get_safe_path ).with( '/blorgdir' ).and_return( @pathname )
  50. @pathname.should_receive( :exist? ).and_return( true )
  51. @pathname.should_receive( :file? ).and_return( false )
  52. @response.should_receive( :status= ).with( HTTP::FORBIDDEN )
  53. self.handler.find_static_file( '/blorgdir', @request, @response )
  54. end
  55. it "refuses to serve files outside of its resource directory" do
  56. @response.should_not_receive( :status= )
  57. self.handler.find_static_file( '/../../etc/shadow', @request, @response )
  58. end
  59. it "respects file access permissions" do
  60. self.handler.stub!( :get_safe_path ).with( '/blorg.txt' ).and_return( @pathname )
  61. @pathname.should_receive( :exist? ).and_return( true )
  62. @pathname.should_receive( :file? ).and_return( true )
  63. @pathname.should_receive( :readable? ).and_return( false )
  64. @response.should_receive( :status= ).with( HTTP::FORBIDDEN )
  65. self.handler.find_static_file( '/blorg.txt', @request, @response )
  66. end
  67. it "sets the appropriate content type for the file" do
  68. self.handler.stub!( :get_safe_path ).with( '/blorg.html' ).and_return( @pathname )
  69. @pathname.should_receive( :extname ).and_return( '.html' )
  70. @response.should_receive( :content_type= ).with( MIMETYPE_MAP['.html'] )
  71. stat = stub( "file stat", :mtime => 6, :size => 10, :ino => 23452 )
  72. @pathname.should_receive( :stat ).at_least( :once ).and_return( stat )
  73. @request.should_receive( :is_cached_by_client? ).and_return( false )
  74. @response_headers.
  75. should_receive( :[]= ).
  76. with( :content_length, 10 )
  77. @response.should_receive( :status= ).with( HTTP::OK )
  78. @pathname.should_receive( :open ).with('r').and_return( :an_IO )
  79. @response.should_receive( :body= ).with( :an_IO )
  80. self.handler.find_static_file( '/blorg.html', @request, @response )
  81. end
  82. it "defaults to a generic content type as fallback" do
  83. self.handler.stub!( :get_safe_path ).with( '/blorg.floop' ).and_return( @pathname )
  84. @pathname.should_receive( :extname ).and_return( '.floop' )
  85. @response.should_receive( :content_type= ).
  86. with( ThingFish::StaticContentHandler::DEFAULT_CONTENT_TYPE )
  87. stat = stub( "file stat", :mtime => 6, :size => 10, :ino => 23452 )
  88. @pathname.should_receive( :stat ).at_least( :once ).and_return( stat )
  89. @request.should_receive( :is_cached_by_client? ).and_return( false )
  90. @response_headers.
  91. should_receive( :[]= ).
  92. with( :content_length, 10 )
  93. @response.should_receive( :status= ).with( HTTP::OK )
  94. @pathname.should_receive( :open ).with('r').and_return( :an_IO )
  95. @response.should_receive( :body= ).with( :an_IO )
  96. self.handler.find_static_file( '/blorg.floop', @request, @response )
  97. end
  98. INDEX_FILE = ThingFish::StaticContentHandler::DEFAULT_INDEX_FILE
  99. it "serves index files" do
  100. self.handler.stub!( :get_safe_path ).
  101. with( '/this_is_a_directory_with_an_index_file' ).
  102. and_return( @pathname )
  103. @pathname.should_receive( :directory? ).and_return( true )
  104. @indexpath = mock( "index file Pathname object" )
  105. @pathname.should_receive( :+ ).with( INDEX_FILE ).and_return( @indexpath )
  106. @indexpath.should_receive( :exist? ).and_return( true )
  107. @indexpath.should_receive( :file? ).and_return( true )
  108. @indexpath.should_receive( :readable? ).and_return( true )
  109. @indexpath.should_receive( :extname ).and_return( '.floop' )
  110. @response.should_receive( :content_type= ).
  111. with( ThingFish::StaticContentHandler::DEFAULT_CONTENT_TYPE )
  112. stat = stub( "file stat", :mtime => 6, :size => 10, :ino => 23452 )
  113. @indexpath.should_receive( :stat ).at_least( :once ).and_return( stat )
  114. @request.should_receive( :is_cached_by_client? ).and_return( false )
  115. @response_headers.
  116. should_receive( :[]= ).
  117. with( :content_length, 10 )
  118. @response.should_receive( :status= ).with( HTTP::OK )
  119. @indexpath.should_receive( :open ).with('r').and_return( :an_IO )
  120. @response.should_receive( :body= ).with( :an_IO )
  121. self.handler.find_static_file( '/this_is_a_directory_with_an_index_file', @request, @response )
  122. end
  123. it "sends a 304 NOT MODIFIED response if the request's etag header matches" do
  124. self.handler.stub!( :get_safe_path ).
  125. with( '/barrel/o/pork.html' ).
  126. and_return( @pathname )
  127. mtime = 3.days.ago
  128. @pathname.should_receive( :directory? ).and_return( false )
  129. @pathname.should_receive( :exist? ).and_return( true )
  130. @pathname.should_receive( :file? ).and_return( true )
  131. @pathname.should_receive( :readable? ).and_return( true )
  132. stat = stub( "file stat", :mtime => mtime, :size => 10, :ino => 23452 )
  133. @pathname.should_receive( :stat ).at_least( :once ).and_return( stat )
  134. @request.should_receive( :is_cached_by_client? ).
  135. with( "%d-%d-%d" % [ mtime.to_i, 10, 23452 ], mtime ).
  136. and_return( true )
  137. @response.should_receive( :status= ).with( HTTP::NOT_MODIFIED )
  138. @pathname.should_not_receive( :open ).with('r').and_return( :an_IO )
  139. @response.should_not_receive( :body= ).with( :an_IO )
  140. self.handler.find_static_file( '/barrel/o/pork.html', @request, @response )
  141. end
  142. end