diff --git a/test/test_middleware_static.rb b/test/test_middleware_static.rb
index f236894..46ed335 100644
--- a/test/test_middleware_static.rb
+++ b/test/test_middleware_static.rb
@@ -17,8 +17,10 @@ class MiddlewareStaticTest < Minitest::Test
FileUtils.remove_entry(@root)
end
- def env_for(path, method: 'GET')
- { 'REQUEST_METHOD' => method, 'PATH_INFO' => path }
+ def env_for(path, method: 'GET', range: nil)
+ env = { 'REQUEST_METHOD' => method, 'PATH_INFO' => path }
+ env['HTTP_RANGE'] = range if range
+ env
end
def test_serves_existing_file
@@ -68,4 +70,144 @@ class MiddlewareStaticTest < Minitest::Test
assert_equal 404, status
assert_equal 'app: /index.html', body.join
end
+
+ def test_full_response_advertises_accept_ranges
+ status, headers, = @app.call(env_for('/index.html'))
+ assert_equal 200, status
+ assert_equal 'bytes', headers['Accept-Ranges']
+ end
+
+ def test_serves_a_byte_range_as_206
+ File.write(File.join(@root, 'data.bin'), '0123456789')
+
+ status, headers, body = @app.call(env_for('/data.bin', range: 'bytes=0-3'))
+
+ assert_equal 206, status
+ assert_equal '0123', body.join
+ assert_equal 'bytes 0-3/10', headers['Content-Range']
+ assert_equal '4', headers['Content-Length']
+ end
+
+ def test_serves_a_suffix_range
+ File.write(File.join(@root, 'data.bin'), '0123456789')
+
+ status, headers, body = @app.call(env_for('/data.bin', range: 'bytes=-3'))
+
+ assert_equal 206, status
+ assert_equal '789', body.join
+ assert_equal 'bytes 7-9/10', headers['Content-Range']
+ end
+
+ def test_unsatisfiable_range_returns_416
+ File.write(File.join(@root, 'data.bin'), '0123456789')
+
+ status, headers, body = @app.call(env_for('/data.bin', range: 'bytes=50-60'))
+
+ assert_equal 416, status
+ assert_equal 'bytes */10', headers['Content-Range']
+ assert_equal [], body
+ end
+
+ def test_multi_range_request_falls_back_to_full_200
+ File.write(File.join(@root, 'data.bin'), '0123456789')
+
+ status, _, body = @app.call(env_for('/data.bin', range: 'bytes=0-1,3-4'))
+
+ assert_equal 200, status
+ assert_equal '0123456789', body.join
+ end
+
+ def test_head_request_omits_body_but_reports_full_length
+ status, headers, body = @app.call(env_for('/index.html', method: 'HEAD'))
+
+ assert_equal 200, status
+ assert_equal [], body
+ assert_equal '
static
'.bytesize.to_s, headers['Content-Length']
+ end
+
+ def test_full_response_carries_etag_and_last_modified
+ _status, headers, = @app.call(env_for('/index.html'))
+
+ assert_match(/\AW\/"\d+-\d+"\z/, headers['ETag'])
+ refute_nil headers['Last-Modified']
+ end
+
+ def env_with_header(path, name, value, method: 'GET')
+ env = env_for(path, method: method)
+ env[name] = value
+ env
+ end
+
+ def test_if_none_match_matching_etag_returns_304_without_body
+ _status, headers, = @app.call(env_for('/index.html'))
+ etag = headers['ETag']
+
+ status, headers_304, body = @app.call(env_with_header('/index.html', 'HTTP_IF_NONE_MATCH', etag))
+
+ assert_equal 304, status
+ assert_equal [], body
+ assert_equal etag, headers_304['ETag']
+ assert_nil headers_304['Content-Type']
+ assert_nil headers_304['Content-Length']
+ end
+
+ def test_if_none_match_stale_etag_returns_full_200
+ status, headers, body = @app.call(env_with_header('/index.html', 'HTTP_IF_NONE_MATCH', '"stale"'))
+
+ assert_equal 200, status
+ assert_equal 'static
', body.join
+ refute_nil headers['ETag']
+ end
+
+ def test_if_modified_since_in_the_future_returns_304
+ status, _, body = @app.call(env_with_header('/index.html', 'HTTP_IF_MODIFIED_SINCE', (Time.now + 3600).utc.httpdate))
+
+ assert_equal 304, status
+ assert_equal [], body
+ end
+
+ def test_if_modified_since_in_the_past_returns_full_200
+ status, _, body = @app.call(env_with_header('/index.html', 'HTTP_IF_MODIFIED_SINCE', Time.at(0).utc.httpdate))
+
+ assert_equal 200, status
+ assert_equal 'static
', body.join
+ end
+
+ def test_if_range_with_weak_etag_never_satisfies_so_range_is_ignored
+ File.write(File.join(@root, 'data.bin'), '0123456789')
+ _status, headers, = @app.call(env_for('/data.bin'))
+ weak_etag = headers['ETag']
+
+ env = env_with_header('/data.bin', 'HTTP_IF_RANGE', weak_etag)
+ env['HTTP_RANGE'] = 'bytes=0-3'
+ status, _, body = @app.call(env)
+
+ assert_equal 200, status
+ assert_equal '0123456789', body.join
+ end
+
+ def test_if_range_with_stale_date_falls_back_to_full_200
+ File.write(File.join(@root, 'data.bin'), '0123456789')
+
+ env = env_with_header('/data.bin', 'HTTP_IF_RANGE', Time.at(0).utc.httpdate)
+ env['HTTP_RANGE'] = 'bytes=0-3'
+ status, _, body = @app.call(env)
+
+ assert_equal 200, status
+ assert_equal '0123456789', body.join
+ end
+
+ def test_if_range_with_exact_matching_date_honors_the_range
+ File.write(File.join(@root, 'data.bin'), '0123456789')
+ _status, headers, = @app.call(env_for('/data.bin'))
+ last_modified = headers['Last-Modified']
+
+ env = env_with_header('/data.bin', 'HTTP_IF_RANGE', last_modified)
+ env['HTTP_RANGE'] = 'bytes=0-3'
+ status, headers_206, body = @app.call(env)
+
+ assert_equal 206, status
+ assert_equal '0123', body.join
+ assert_equal 'bytes 0-3/10', headers_206['Content-Range']
+ end
end
diff --git a/test/test_mime.rb b/test/test_mime.rb
new file mode 100644
index 0000000..58e7819
--- /dev/null
+++ b/test/test_mime.rb
@@ -0,0 +1,53 @@
+require 'minitest/autorun'
+require 'prism'
+
+class MimeTest < Minitest::Test
+ def test_lookup_by_symbol
+ assert_equal 'application/json', Prism::Mime[:json]
+ assert_equal 'text/html', Prism::Mime[:html]
+ end
+
+ def test_unknown_symbol_raises
+ assert_raises(Prism::Mime::UnknownMimeType) { Prism::Mime[:yaml] }
+ end
+
+ def test_known_predicate
+ assert Prism::Mime.known?(:json)
+ refute Prism::Mime.known?(:yaml)
+ end
+
+ def test_symbol_for_content_type_strips_charset
+ assert_equal :json, Prism::Mime.symbol_for_content_type('application/json; charset=utf-8')
+ assert_equal :html, Prism::Mime.symbol_for_content_type('text/html')
+ assert_nil Prism::Mime.symbol_for_content_type('application/octet-stream')
+ assert_nil Prism::Mime.symbol_for_content_type(nil)
+ end
+
+ def test_best_from_accept_picks_highest_q
+ header = 'text/html;q=0.5, application/json;q=0.9, application/xml;q=0.1'
+ assert_equal :json, Prism::Mime.best_from_accept(header)
+ end
+
+ def test_best_from_accept_defaults_q_to_one
+ header = 'application/xml;q=0.5, text/html'
+ assert_equal :html, Prism::Mime.best_from_accept(header)
+ end
+
+ def test_best_from_accept_preserves_order_on_tie
+ header = 'application/xml, application/json'
+ assert_equal :xml, Prism::Mime.best_from_accept(header)
+ end
+
+ def test_best_from_accept_wildcard_defaults_to_html
+ assert_equal :html, Prism::Mime.best_from_accept('*/*')
+ end
+
+ def test_best_from_accept_returns_nil_for_unknown_types_only
+ assert_nil Prism::Mime.best_from_accept('application/octet-stream')
+ end
+
+ def test_best_from_accept_returns_nil_for_blank_header
+ assert_nil Prism::Mime.best_from_accept(nil)
+ assert_nil Prism::Mime.best_from_accept('')
+ end
+end