From 3fe62a3f2f8df210cd292274279d5ba3d818fa18 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 18:26:26 +0200 Subject: [PATCH] test: cover env_for, test_full_response_advertises_accept_ranges --- test/test_middleware_static.rb | 146 ++++++++++++++++++++++++++++++++- test/test_mime.rb | 53 ++++++++++++ 2 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 test/test_mime.rb 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