test: cover ScopedIndexController, buggy_index and ContentForTest

This commit is contained in:
dualfroz committed 2026-09-09 22:39:24 +02:00
1 parent 330cd1f35f
commit a4701cba33
2 files changed
+164

No files matched your search

@@ -0,0 +1,93 @@
require 'minitest/autorun'
require 'prism'
require 'support'
# Session 42's documented "etag/current_user audit" debt, closed here after
# being deferred across sessions 42-61. The audit itself: grep every
# fresh_when call in examples/blog/app/controllers and check whether the
# view it guards renders anything current_user-dependent. Two of the three
# call sites already included current_user&.id in their etag components
# (PostsController#show, CommentsController#index) - PostsController#index
# was the one real gap, found and fixed directly in
# examples/blog/app/controllers/posts_controller.rb: its view gates the
# "new post" form and the layout's signed-in-as banner on
# user_signed_in?/current_user, but the etag never accounted for viewer
# identity, so a shared cache keying on URL+ETag alone (no per-user Vary)
# could serve one visitor's signed-in body back as a 304 to a different, or
# signed-out, visitor whose If-None-Match happened to match.
#
# This file does not require examples/blog/app.rb directly - see
# test_integration_preview.rb's fixture comment: that file calls
# Prism::Orm.connect as a process-wide global that would leak into every
# other test in this suite. Instead it reproduces the exact shape of the
# bug (buggy_index) and the fix (fixed_index) against an isolated app, the
# same convention test_cors_conditional_get.rb already established for
# ConditionalGet.
class ScopedIndexController < Prism::Controller
def buggy_index
viewer = request.env['HTTP_X_TEST_USER']
fresh_when(etag: ['same-page-content']) # the bug: omits viewer identity
html(viewer ? "signed in as #{viewer}" : 'anonymous visitor')
end
def fixed_index
viewer = request.env['HTTP_X_TEST_USER']
fresh_when(etag: ['same-page-content', viewer]) # the fix
html(viewer ? "signed in as #{viewer}" : 'anonymous visitor')
end
end
class ScopedIndexApp < Prism::Application
get '/buggy', to: 'scoped_index#buggy_index'
get '/fixed', to: 'scoped_index#fixed_index'
end
class ConditionalGetUserScopedEtagTest < Minitest::Test
include TestSupport
def call(path, headers = {})
ScopedIndexApp.call(env_for('GET', path, headers: headers))
end
def test_buggy_etag_is_identical_across_different_viewers
_, anon_headers, = call('/buggy')
_, alice_headers, = call('/buggy', 'X-Test-User' => 'alice')
assert_equal anon_headers['ETag'], alice_headers['ETag'],
'demonstrates the bug: rendered content differs by viewer but the etag does not'
end
def test_buggy_etag_lets_a_cached_signed_in_body_leak_back_to_an_anonymous_request
_, alice_headers, alice_body = call('/buggy', 'X-Test-User' => 'alice')
assert_equal 'signed in as alice', alice_body.join
status, _, anon_body = call('/buggy', 'If-None-Match' => alice_headers['ETag'])
assert_equal 304, status,
"the bug: an anonymous visitor whose If-None-Match happens to equal alice's is told nothing changed"
assert_equal '', anon_body.join
end
def test_fixed_etag_differs_by_viewer
_, anon_headers, = call('/fixed')
_, alice_headers, = call('/fixed', 'X-Test-User' => 'alice')
refute_equal anon_headers['ETag'], alice_headers['ETag']
end
def test_fixed_etag_never_returns_a_304_across_different_viewers
_, alice_headers, = call('/fixed', 'X-Test-User' => 'alice')
status, _, anon_body = call('/fixed', 'If-None-Match' => alice_headers['ETag'])
assert_equal 200, status, "an anonymous visitor must get a fresh render, never alice's cached 304"
assert_equal 'anonymous visitor', anon_body.join
end
def test_fixed_etag_still_returns_304_for_the_same_viewer
_, alice_headers, = call('/fixed', 'X-Test-User' => 'alice')
status, headers, body = call('/fixed', 'X-Test-User' => 'alice', 'If-None-Match' => alice_headers['ETag'])
assert_equal 304, status
assert_equal '', body.join
assert_equal alice_headers['ETag'], headers['ETag']
end
end
+71
View File
@@ -0,0 +1,71 @@
require 'minitest/autorun'
require 'prism'
require 'tmpdir'
require 'fileutils'
class ContentForTest < Minitest::Test
def setup
@dir = Dir.mktmpdir('prism-content-for')
FileUtils.mkdir_p(File.join(@dir, 'pages'))
FileUtils.mkdir_p(File.join(@dir, 'layouts'))
@view = Prism::View.new(@dir)
end
def teardown
FileUtils.remove_entry(@dir)
end
def write(relative, source)
File.write(File.join(@dir, relative), source)
end
def test_content_for_slot_is_readable_in_the_layout_rendered_after_it
write('pages/show.erb', '<% content_for(:title) do %>Custom Title<% end %>Body text')
write('layouts/application.erb', '[<%= content_for(:title) %>] <%== content %>')
result = @view.render('pages/show', layout: :application)
assert_equal '[Custom Title] Body text', result
end
def test_content_for_without_a_call_reads_back_empty_string
write('pages/show.erb', 'Body only')
write('layouts/application.erb', 'title=[<%= content_for(:title) %>]<%== content %>')
result = @view.render('pages/show', layout: :application)
assert_equal 'title=[]Body only', result
end
def test_content_for_question_mark_reports_presence
write('pages/show.erb', '<% content_for(:sidebar) do %>widgets<% end %>')
write('layouts/application.erb', '<% if content_for?(:sidebar) %>has-sidebar<% else %>no-sidebar<% end %>')
assert_equal 'has-sidebar', @view.render('pages/show', layout: :application)
end
def test_content_for_question_mark_is_false_when_never_set
write('pages/show.erb', 'nothing here')
write('layouts/application.erb', '<% if content_for?(:sidebar) %>has-sidebar<% else %>no-sidebar<% end %>')
assert_equal 'no-sidebar', @view.render('pages/show', layout: :application)
end
def test_multiple_content_for_calls_for_the_same_slot_accumulate
write('pages/show.erb', <<~ERB)
<% content_for(:scripts) do %>a.js<% end %>
<% content_for(:scripts) do %>b.js<% end %>
ERB
write('layouts/application.erb', '<%= content_for(:scripts) %>')
assert_equal 'a.jsb.js', @view.render('pages/show', layout: :application)
end
def test_content_for_state_does_not_leak_between_separate_render_calls
write('pages/one.erb', '<% content_for(:title) do %>One<% end %>')
write('pages/two.erb', 'no title here')
write('layouts/application.erb', '[<%= content_for(:title) %>]<%== content %>')
@view.render('pages/one', layout: :application)
result = @view.render('pages/two', layout: :application)
assert_equal '[]no title here', result
end
end