1
0
mirror of https://github.com/jcwimer/wrestlingApp synced 2026-03-25 01:14:43 +00:00

Ensuring good caching for the most popular pages. Added tests.

This commit is contained in:
2026-03-02 18:34:12 -05:00
parent 18d39c6c8f
commit 679fc2fcb9
16 changed files with 609 additions and 114 deletions

View File

@@ -0,0 +1,143 @@
require "test_helper"
class SchoolShowCacheTest < ActionController::TestCase
tests SchoolsController
setup do
create_double_elim_tournament_single_weight_1_6(8)
@tournament.update!(user_id: users(:one).id)
@school = @tournament.schools.first
sign_in users(:one)
@original_perform_caching = ActionController::Base.perform_caching
ActionController::Base.perform_caching = true
Rails.cache.clear
end
teardown do
Rails.cache.clear
ActionController::Base.perform_caching = @original_perform_caching
end
test "school show wrestler cell fragments hit cache and invalidate after wrestler update" do
first_events = cache_events_for_school_show do
get :show, params: { id: @school.id }
assert_response :success
end
assert_operator cache_writes(first_events), :>, 0, "Expected initial school show render to write wrestler cell fragments"
second_events = cache_events_for_school_show do
get :show, params: { id: @school.id }
assert_response :success
end
assert_equal 0, cache_writes(second_events), "Expected repeat school show render to reuse wrestler cell fragments"
assert_operator cache_hits(second_events), :>, 0, "Expected repeat school show render to hit wrestler cell cache"
wrestler = @school.wrestlers.first
third_events = cache_events_for_school_show do
wrestler.touch
get :show, params: { id: @school.id }
assert_response :success
end
assert_operator cache_writes(third_events), :>, 0, "Expected wrestler update to invalidate school show wrestler cell cache"
end
test "school show does not leak manage-only controls from cache across users" do
get :show, params: { id: @school.id }
assert_response :success
assert_includes response.body, "New Wrestler"
assert_match(/fa-trash-alt/, response.body)
assert_match(/fa-edit/, response.body)
sign_out
spectator_events = cache_events_for_school_show do
get :show, params: { id: @school.id }
assert_response :success
end
assert_operator cache_hits(spectator_events), :>, 0, "Expected spectator request to hit wrestler cell cache warmed by owner"
assert_not_includes response.body, "New Wrestler"
assert_no_match(/fa-trash-alt/, response.body)
assert_no_match(/fa-edit/, response.body)
end
test "school show with school_permission_key bypasses cached wrestler cell fragments" do
@school.update!(permission_key: SecureRandom.uuid)
sign_out
key_request_events = cache_events_for_school_show do
get :show, params: { id: @school.id, school_permission_key: @school.permission_key }
assert_response :success
end
assert_equal 0, cache_writes(key_request_events), "Expected school_permission_key request to bypass cached wrestler cells"
assert_equal 0, cache_hits(key_request_events), "Expected school_permission_key request to avoid reading cached wrestler cells"
end
test "completing a match expires school show wrestler cell caches" do
warm_events = cache_events_for_school_show do
get :show, params: { id: @school.id }
assert_response :success
end
assert_operator cache_writes(warm_events), :>, 0, "Expected initial school show render to warm wrestler cell cache"
wrestler = @school.wrestlers.first
assert wrestler, "Expected a wrestler for match-completion cache test"
match = wrestler.unfinished_matches.first || wrestler.all_matches.first
assert match, "Expected a match involving school wrestler"
winner_id = match.w1 || match.w2
assert winner_id, "Expected match to have at least one wrestler slot"
match.update!(
finished: 1,
winner_id: winner_id,
win_type: "Decision",
score: "1-0"
)
post_action_events = cache_events_for_school_show do
get :show, params: { id: @school.id }
assert_response :success
end
assert_operator cache_writes(post_action_events), :>, 0, "Expected completed match to expire school show wrestler cell cache"
end
private
def sign_out
@request.session[:user_id] = nil
@controller.instance_variable_set(:@current_user, nil)
@controller.instance_variable_set(:@current_ability, nil)
end
def cache_events_for_school_show
events = []
subscriber = lambda do |name, _start, _finish, _id, payload|
key = payload[:key].to_s
next unless key.include?("school_show_wrestler_cells")
events << { name: name, hit: payload[:hit] }
end
ActiveSupport::Notifications.subscribed(
subscriber,
/cache_(read|write|fetch_hit|generate)\.active_support/
) do
yield
end
events
end
def cache_writes(events)
events.count { |event| event[:name] == "cache_write.active_support" }
end
def cache_hits(events)
events.count do |event|
event[:name] == "cache_fetch_hit.active_support" ||
(event[:name] == "cache_read.active_support" && event[:hit])
end
end
end

View File

@@ -0,0 +1,164 @@
require "test_helper"
class TournamentPagesCacheTest < ActionController::TestCase
tests TournamentsController
setup do
create_double_elim_tournament_single_weight_1_6(8)
@tournament.update!(user_id: users(:one).id)
@weight = @tournament.weights.first
sign_in users(:one)
@original_perform_caching = ActionController::Base.perform_caching
ActionController::Base.perform_caching = true
Rails.cache.clear
end
teardown do
Rails.cache.clear
ActionController::Base.perform_caching = @original_perform_caching
end
test "team_scores cache hits on repeat render and rewrites after school update" do
first_events = cache_events_for(%w[team_scores team_score_row]) do
get :team_scores, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(first_events), :>, 0, "Expected initial team_scores render to write fragments"
second_events = cache_events_for(%w[team_scores team_score_row]) do
get :team_scores, params: { id: @tournament.id }
assert_response :success
end
assert_equal 0, cache_writes(second_events), "Expected repeat team_scores render to reuse fragments"
assert_operator cache_hits(second_events), :>, 0, "Expected repeat team_scores render to hit cache"
school = @tournament.schools.first
third_events = cache_events_for(%w[team_scores team_score_row]) do
school.update!(score: (school.score || 0) + 1)
get :team_scores, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(third_events), :>, 0, "Expected school score update to invalidate team_scores cache"
end
test "bracket cache hits on repeat render and rewrites after match update" do
key_markers = [@weight.id.to_s + "_bracket", "bracket_round_match", "bracket_final_match"]
first_events = cache_events_for(key_markers) do
get :bracket, params: { id: @tournament.id, weight: @weight.id }
assert_response :success
end
assert_operator cache_writes(first_events), :>, 0, "Expected initial bracket render to write fragments"
second_events = cache_events_for(key_markers) do
get :bracket, params: { id: @tournament.id, weight: @weight.id }
assert_response :success
end
assert_equal 0, cache_writes(second_events), "Expected repeat bracket render to reuse fragments"
assert_operator cache_hits(second_events), :>, 0, "Expected repeat bracket render to hit cache"
match = @weight.matches.first
third_events = cache_events_for(key_markers) do
match.touch
get :bracket, params: { id: @tournament.id, weight: @weight.id }
assert_response :success
end
assert_operator cache_writes(third_events), :>, 0, "Expected match update to invalidate bracket cache"
end
test "bracket cache separates print and non-print variants" do
key_markers = [@weight.id.to_s + "_bracket"]
non_print_events = cache_events_for(key_markers) do
get :bracket, params: { id: @tournament.id, weight: @weight.id }
assert_response :success
end
assert_operator cache_writes(non_print_events), :>, 0, "Expected non-print bracket render to write a page fragment"
assert_match(%r{\/matches\/\d+\/spectate}, response.body, "Expected non-print bracket view to include spectate links")
first_print_events = cache_events_for(key_markers) do
get :bracket, params: { id: @tournament.id, weight: @weight.id, print: true }
assert_response :success
end
assert_operator cache_writes(first_print_events), :>, 0, "Expected first print bracket render to write a separate page fragment"
assert_no_match(%r{\/matches\/\d+\/spectate}, response.body, "Expected print bracket view to omit spectate links")
second_print_events = cache_events_for(key_markers) do
get :bracket, params: { id: @tournament.id, weight: @weight.id, print: true }
assert_response :success
end
assert_equal 0, cache_writes(second_print_events), "Expected repeat print bracket render to reuse print cache fragment"
assert_operator cache_hits(second_print_events), :>, 0, "Expected repeat print bracket render to hit cache"
end
test "completing a match expires team_scores and bracket caches" do
team_warm_events = cache_events_for(%w[team_scores team_score_row]) do
get :team_scores, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(team_warm_events), :>, 0, "Expected initial team_scores render to warm cache"
bracket_key_markers = [@weight.id.to_s + "_bracket", "bracket_round_match", "bracket_final_match"]
bracket_warm_events = cache_events_for(bracket_key_markers) do
get :bracket, params: { id: @tournament.id, weight: @weight.id }
assert_response :success
end
assert_operator cache_writes(bracket_warm_events), :>, 0, "Expected initial bracket render to warm cache"
match = @weight.matches.where(finished: [nil, 0]).first || @weight.matches.first
assert match, "Expected a match to complete for expiration test"
match.update!(
finished: 1,
winner_id: match.w1 || match.w2,
win_type: "Decision",
score: "1-0"
)
team_post_events = cache_events_for(%w[team_scores team_score_row]) do
get :team_scores, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(team_post_events), :>, 0, "Expected completed match to expire team_scores cache"
bracket_post_events = cache_events_for(bracket_key_markers) do
get :bracket, params: { id: @tournament.id, weight: @weight.id }
assert_response :success
end
assert_operator cache_writes(bracket_post_events), :>, 0, "Expected completed match to expire bracket cache"
end
private
def cache_events_for(key_markers)
events = []
subscriber = lambda do |name, _start, _finish, _id, payload|
key = payload[:key].to_s
next unless key_markers.any? { |marker| key.include?(marker) }
events << { name: name, hit: payload[:hit] }
end
ActiveSupport::Notifications.subscribed(
subscriber,
/cache_(read|write|fetch_hit|generate)\.active_support/
) do
yield
end
events
end
def cache_writes(events)
events.count { |event| event[:name] == "cache_write.active_support" }
end
def cache_hits(events)
events.count do |event|
event[:name] == "cache_fetch_hit.active_support" ||
(event[:name] == "cache_read.active_support" && event[:hit])
end
end
end

View File

@@ -73,13 +73,86 @@ class UpMatchesCacheTest < ActionController::TestCase
assert_operator cache_hits(repeat_events), :>, 0, "Expected cache hits after queue clear rewrite"
end
test "up_matches unassigned row fragments hit cache and invalidate after unassigned match update" do
key_markers = %w[up_matches_unassigned_row]
first_events = cache_events_for_up_matches(key_markers) do
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(first_events), :>, 0, "Expected initial unassigned row render to write fragments"
second_events = cache_events_for_up_matches(key_markers) do
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_equal 0, cache_writes(second_events), "Expected repeat unassigned row render to reuse cached fragments"
assert_operator cache_hits(second_events), :>, 0, "Expected repeat unassigned row render to hit cache"
unassigned_match = @tournament.up_matches_unassigned_matches.first
assert unassigned_match, "Expected at least one unassigned match for cache invalidation test"
third_events = cache_events_for_up_matches(key_markers) do
unassigned_match.touch
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(third_events), :>, 0, "Expected unassigned match update to invalidate unassigned row fragment"
end
test "completing an on-mat match expires up_matches cached fragments" do
warm_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(warm_events), :>, 0, "Expected initial up_matches render to warm caches"
mat = @tournament.mats.detect { |m| m.queue1_match.present? }
assert mat, "Expected a mat with a queued match"
match = mat.queue1_match
assert match, "Expected queue1 match to complete"
post_action_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
match.update!(
finished: 1,
winner_id: match.w1 || match.w2,
win_type: "Decision",
score: "1-0"
)
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(post_action_events), :>, 0, "Expected completed match to expire and rewrite up_matches caches"
end
test "manually assigning an unassigned match to a mat queue expires up_matches caches" do
warm_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(warm_events), :>, 0, "Expected initial up_matches render to warm caches"
unassigned_match = @tournament.up_matches_unassigned_matches.first
assert unassigned_match, "Expected at least one unassigned match to manually place on a mat"
target_mat = @tournament.mats.first
post_action_events = cache_events_for_up_matches(%w[up_matches_mat_row up_matches_unassigned_row]) do
target_mat.assign_match_to_queue!(unassigned_match, 4)
get :up_matches, params: { id: @tournament.id }
assert_response :success
end
assert_operator cache_writes(post_action_events), :>, 0, "Expected manual mat assignment to expire and rewrite up_matches caches"
end
private
def cache_events_for_up_matches
def cache_events_for_up_matches(key_markers = %w[up_matches_mat_row up_matches_unassigned_row])
events = []
subscriber = lambda do |name, _start, _finish, _id, payload|
key = payload[:key].to_s
next unless key.include?("up_matches_mat_row")
next unless key_markers.any? { |marker| key.include?(marker) }
events << { name: name, hit: payload[:hit] }
end

View File

@@ -0,0 +1,107 @@
require "test_helper"
class WeightShowCacheTest < ActionController::TestCase
tests WeightsController
setup do
create_a_tournament_with_single_weight("Regular Double Elimination 1-6", 8)
@tournament.update!(user_id: users(:one).id)
@weight = @tournament.weights.first
@original_perform_caching = ActionController::Base.perform_caching
ActionController::Base.perform_caching = true
Rails.cache.clear
end
teardown do
Rails.cache.clear
ActionController::Base.perform_caching = @original_perform_caching
end
test "weight show readonly row fragments hit cache and invalidate after wrestler update" do
first_events = cache_events_for_weight_show do
get :show, params: { id: @weight.id }
assert_response :success
end
assert_operator cache_writes(first_events), :>, 0, "Expected initial weight show render to write readonly row fragments"
second_events = cache_events_for_weight_show do
get :show, params: { id: @weight.id }
assert_response :success
end
assert_equal 0, cache_writes(second_events), "Expected repeat weight show render to reuse readonly row fragments"
assert_operator cache_hits(second_events), :>, 0, "Expected repeat weight show render to hit readonly row cache"
wrestler = @weight.wrestlers.first
third_events = cache_events_for_weight_show do
wrestler.touch
get :show, params: { id: @weight.id }
assert_response :success
end
assert_operator cache_writes(third_events), :>, 0, "Expected wrestler update to invalidate weight show readonly row cache"
end
test "weight show does not leak manage-only controls from cache across users" do
sign_in users(:one)
get :show, params: { id: @weight.id }
assert_response :success
assert_includes response.body, "Save Seeds"
assert_match(/fa-trash-alt/, response.body)
assert_match(/name="wrestler\[\d+\]\[original_seed\]"/, response.body)
sign_out
get :show, params: { id: @weight.id }
assert_response :success
assert_not_includes response.body, "Save Seeds"
assert_no_match(/fa-trash-alt/, response.body)
assert_no_match(/name="wrestler\[\d+\]\[original_seed\]"/, response.body)
spectator_cache_events = cache_events_for_weight_show do
get :show, params: { id: @weight.id }
assert_response :success
end
assert_operator cache_hits(spectator_cache_events), :>, 0, "Expected repeat spectator request to hit readonly wrestler row cache"
assert_not_includes response.body, "Save Seeds"
assert_no_match(/fa-trash-alt/, response.body)
assert_no_match(/name="wrestler\[\d+\]\[original_seed\]"/, response.body)
end
private
def sign_out
@request.session[:user_id] = nil
@controller.instance_variable_set(:@current_user, nil)
@controller.instance_variable_set(:@current_ability, nil)
end
def cache_events_for_weight_show
events = []
subscriber = lambda do |name, _start, _finish, _id, payload|
key = payload[:key].to_s
next unless key.include?("weight_show_wrestler_row")
events << { name: name, hit: payload[:hit] }
end
ActiveSupport::Notifications.subscribed(
subscriber,
/cache_(read|write|fetch_hit|generate)\.active_support/
) do
yield
end
events
end
def cache_writes(events)
events.count { |event| event[:name] == "cache_write.active_support" }
end
def cache_hits(events)
events.count do |event|
event[:name] == "cache_fetch_hit.active_support" ||
(event[:name] == "cache_read.active_support" && event[:hit])
end
end
end