From 21a8601dfc8c2131decbc54e3709ffd22235d7b5 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 10 Sep 2026 12:47:39 +0200 Subject: [PATCH] test: cover CookieJarTest and jar_for --- test/test_cookie_jar.rb | 124 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 test/test_cookie_jar.rb diff --git a/test/test_cookie_jar.rb b/test/test_cookie_jar.rb new file mode 100644 index 0000000..e5bd737 --- /dev/null +++ b/test/test_cookie_jar.rb @@ -0,0 +1,124 @@ +require 'minitest/autorun' +require 'prism' +require 'stringio' + +class CookieJarTest < Minitest::Test + def setup + @secret = 'jar-secret-key' + end + + def jar_for(cookie_header, secret: @secret, previous_secrets: []) + env = { + 'REQUEST_METHOD' => 'GET', + 'PATH_INFO' => '/', + 'QUERY_STRING' => '', + 'rack.input' => StringIO.new(''), + 'CONTENT_LENGTH' => '0' + } + env['HTTP_COOKIE'] = cookie_header if cookie_header + request = Prism::Request.new(env) + response = Prism::Response.new + [Prism::CookieJar.new(request, response, secret: secret, previous_secrets: previous_secrets), response] + end + + def test_plain_set_and_read + jar, response = jar_for(nil) + jar[:theme] = 'dark' + assert_match(/theme=dark/, response['Set-Cookie']) + end + + def test_signed_round_trip_via_two_requests + jar, response = jar_for(nil) + jar.signed[:user_id] = 42 + cookie_header = extract_cookie_header(response, 'user_id') + + reader_jar, = jar_for(cookie_header) + assert_equal 42, reader_jar.signed[:user_id] + end + + def test_signed_read_rejects_tampered_cookie + jar, response = jar_for(nil) + jar.signed[:user_id] = 42 + cookie_header = extract_cookie_header(response, 'user_id') + tampered = cookie_header.sub('user_id=', 'user_id=tampered') + + reader_jar, = jar_for(tampered) + assert_nil reader_jar.signed[:user_id] + end + + def test_encrypted_round_trip_hides_plaintext + jar, response = jar_for(nil) + jar.encrypted[:secret] = 'super-sensitive-value' + cookie_header = extract_cookie_header(response, 'secret') + refute_includes cookie_header, 'super-sensitive-value' + + reader_jar, = jar_for(cookie_header) + assert_equal 'super-sensitive-value', reader_jar.encrypted[:secret] + end + + def test_encrypted_read_rejects_tampered_cookie + jar, response = jar_for(nil) + jar.encrypted[:secret] = 'value' + cookie_header = extract_cookie_header(response, 'secret') + tampered = cookie_header + 'AA' + + reader_jar, = jar_for(tampered) + assert_nil reader_jar.encrypted[:secret] + end + + def test_missing_cookie_returns_nil_for_signed_and_encrypted + jar, = jar_for(nil) + assert_nil jar.signed[:missing] + assert_nil jar.encrypted[:missing] + end + + def test_value_hash_form_passes_through_cookie_options + jar, response = jar_for(nil) + jar.signed[:remember] = { value: 'token', max_age: 3600 } + header = response['Set-Cookie'] + assert_match(/Max-Age=3600/, header) + end + + def test_signed_previous_secret_still_decodes_after_rotation + old_jar, response = jar_for(nil, secret: 'old-jar-secret') + old_jar.signed[:user_id] = 42 + cookie_header = extract_cookie_header(response, 'user_id') + + rotated_jar, = jar_for(cookie_header, secret: 'new-jar-secret', previous_secrets: ['old-jar-secret']) + assert_equal 42, rotated_jar.signed[:user_id] + end + + def test_encrypted_previous_secret_still_decodes_after_rotation + old_jar, response = jar_for(nil, secret: 'old-jar-secret') + old_jar.encrypted[:secret] = 'value-from-before-rotation' + cookie_header = extract_cookie_header(response, 'secret') + + rotated_jar, = jar_for(cookie_header, secret: 'new-jar-secret', previous_secrets: ['old-jar-secret']) + assert_equal 'value-from-before-rotation', rotated_jar.encrypted[:secret] + end + + def test_signed_read_rejects_a_secret_that_was_never_current_or_previous + old_jar, response = jar_for(nil, secret: 'unrelated-secret') + old_jar.signed[:user_id] = 42 + cookie_header = extract_cookie_header(response, 'user_id') + + rotated_jar, = jar_for(cookie_header, secret: 'new-jar-secret', previous_secrets: ['old-jar-secret']) + assert_nil rotated_jar.signed[:user_id] + end + + def test_writes_after_rotation_use_the_current_secret_not_a_previous_one + jar, response = jar_for(nil, secret: 'new-jar-secret', previous_secrets: ['old-jar-secret']) + jar.signed[:user_id] = 42 + cookie_header = extract_cookie_header(response, 'user_id') + + reader_on_old_secret_only, = jar_for(cookie_header, secret: 'old-jar-secret') + assert_nil reader_on_old_secret_only.signed[:user_id] + end + + private + + def extract_cookie_header(response, name) + line = response['Set-Cookie'].split("\n").find { |entry| entry.start_with?("#{name}=") } + line.split(';').first + end +end