test: cover ScopedIndexController, buggy_index and ContentForTest
This commit is contained in:
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
|
||||
@@ -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
|
||||
Reference in new issue
Block a user