# frozen_string_literal: true require "fileutils" require "abstract_unit" require "lib/controller/fake_models" CACHE_DIR = "test_cache" # Don't change '/../temp/' cavalierly or you might hose something you don't want hosed FILE_STORE_PATH = File.join(__dir__, "../temp/", CACHE_DIR) class FragmentCachingMetalTestController < ActionController::Metal abstract! include ActionController::Caching def some_action; end end class FragmentCachingMetalTest < ActionController::TestCase def setup super @store = ActiveSupport::Cache::MemoryStore.new @controller = FragmentCachingMetalTestController.new @controller.perform_caching = true @controller.cache_store = @store @params = { controller: "posts", action: "index" } @controller.params = @params @controller.request = @request @controller.response = @response end end class CachingController < ActionController::Base abstract! self.cache_store = :file_store, FILE_STORE_PATH end class FragmentCachingTestController < CachingController def some_action; end end class FragmentCachingTest < ActionController::TestCase ModelWithKeyAndVersion = Struct.new(:cache_key, :cache_version) def setup super @store = ActiveSupport::Cache::MemoryStore.new @controller = FragmentCachingTestController.new @controller.perform_caching = true @controller.cache_store = @store @params = { controller: "posts", action: "index" } @controller.params = @params @controller.request = @request @controller.response = @response @m1v1 = ModelWithKeyAndVersion.new("model/1", "1") @m1v2 = ModelWithKeyAndVersion.new("model/1", "2") @m2v1 = ModelWithKeyAndVersion.new("model/2", "1") @m2v2 = ModelWithKeyAndVersion.new("model/2", "2") end def test_combined_fragment_cache_key assert_equal [ :views, "what a key" ], @controller.combined_fragment_cache_key("what a key") assert_equal [ :views, "test.host/fragment_caching_test/some_action" ], @controller.combined_fragment_cache_key(controller: "fragment_caching_test", action: "some_action") end def test_read_fragment_with_caching_enabled @store.write("views/name", "value") assert_equal "value", @controller.read_fragment("name") end def test_read_fragment_with_caching_disabled @controller.perform_caching = false @store.write("views/name", "value") assert_nil @controller.read_fragment("name") end def test_read_fragment_with_versioned_model @controller.write_fragment([ "stuff", @m1v1 ], "hello") assert_equal "hello", @controller.read_fragment([ "stuff", @m1v1 ]) assert_nil @controller.read_fragment([ "stuff", @m1v2 ]) end def test_fragment_exist_with_caching_enabled @store.write("views/name", "value") assert @controller.fragment_exist?("name") assert_not @controller.fragment_exist?("other_name") end def test_fragment_exist_with_caching_disabled @controller.perform_caching = false @store.write("views/name", "value") assert_not @controller.fragment_exist?("name") assert_not @controller.fragment_exist?("other_name") end def test_write_fragment_with_caching_enabled assert_nil @store.read("views/name") assert_equal "value", @controller.write_fragment("name", "value") assert_equal "value", @store.read("views/name") end def test_write_fragment_with_caching_disabled assert_nil @store.read("views/name") @controller.perform_caching = false assert_equal "value", @controller.write_fragment("name", "value") assert_nil @store.read("views/name") end def test_expire_fragment_with_simple_key @store.write("views/name", "value") @controller.expire_fragment "name" assert_nil @store.read("views/name") end def test_expire_fragment_with_regexp @store.write("views/name", "value") @store.write("views/another_name", "another_value") @store.write("views/primalgrasp", "will not expire ;-)") @controller.expire_fragment(/name/) assert_nil @store.read("views/name") assert_nil @store.read("views/another_name") assert_equal "will not expire ;-)", @store.read("views/primalgrasp") end def test_fragment_for @store.write("views/expensive", "fragment content") fragment_computed = false view_context = @controller.view_context buffer = "generated till now -> ".html_safe buffer << view_context.send(:fragment_for, "expensive") { fragment_computed = true } assert_not fragment_computed assert_equal "generated till now -> fragment content", buffer end def test_html_safety assert_nil @store.read("views/name") content = "value".html_safe assert_equal content, @controller.write_fragment("name", content) cached = @store.read("views/name") assert_equal content, cached assert_equal String, cached.class html_safe = @controller.read_fragment("name") assert_equal content, html_safe assert_predicate html_safe, :html_safe? end end class FunctionalCachingController < CachingController def fragment_cached end def html_fragment_cached_with_partial respond_to do |format| format.html end end def xml_fragment_cached_with_html_partial end def formatted_fragment_cached respond_to do |format| format.html format.xml end end def formatted_fragment_cached_with_variant request.variant = :phone if params[:v] == "phone" respond_to do |format| format.html.phone format.html end end def fragment_cached_without_digest end def fragment_cached_with_options end end class FunctionalFragmentCachingTest < ActionController::TestCase def setup super @store = ActiveSupport::Cache::MemoryStore.new @controller = FunctionalCachingController.new @controller.perform_caching = true @controller.cache_store = @store @controller.enable_fragment_cache_logging = true end def test_fragment_caching get :fragment_cached assert_response :success expected_body = <<-CACHED Hello This bit's fragment cached Ciao CACHED assert_equal expected_body, @response.body assert_equal "This bit's fragment cached", @store.read("views/functional_caching/fragment_cached:#{template_digest("functional_caching/fragment_cached", "html")}/fragment") end def test_fragment_caching_in_partials get :html_fragment_cached_with_partial assert_response :success assert_match(/Old fragment caching in a partial/, @response.body) assert_match("Old fragment caching in a partial", @store.read("views/functional_caching/_partial:#{template_digest("functional_caching/_partial", "html")}/test.host/functional_caching/html_fragment_cached_with_partial")) end def test_skipping_fragment_cache_digesting get :fragment_cached_without_digest, format: "html" assert_response :success expected_body = "\n

ERB

\n\n" assert_equal expected_body, @response.body assert_equal "

ERB

", @store.read("views/nodigest") end def test_fragment_caching_with_options time = Time.now get :fragment_cached_with_options assert_response :success expected_body = "\n

ERB

\n\n" assert_equal expected_body, @response.body Time.stub(:now, time + 11) do assert_nil @store.read("views/with_options") end end def test_render_inline_before_fragment_caching get :inline_fragment_cached assert_response :success assert_match(/Some inline content/, @response.body) assert_match(/Some cached content/, @response.body) assert_match("Some cached content", @store.read("views/functional_caching/inline_fragment_cached:#{template_digest("functional_caching/inline_fragment_cached", "html")}/test.host/functional_caching/inline_fragment_cached")) end def test_fragment_cache_instrumentation payload = nil subscriber = proc do |*args| event = ActiveSupport::Notifications::Event.new(*args) payload = event.payload end ActiveSupport::Notifications.subscribed(subscriber, "read_fragment.action_controller") do get :inline_fragment_cached end assert_equal "functional_caching", payload[:controller] assert_equal "inline_fragment_cached", payload[:action] end def test_html_formatted_fragment_caching format = "html" get :formatted_fragment_cached, format: format assert_response :success expected_body = "\n

ERB

\n\n" assert_equal expected_body, @response.body assert_equal "

ERB

", @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached", format)}/fragment") end def test_xml_formatted_fragment_caching format = "xml" get :formatted_fragment_cached, format: format assert_response :success expected_body = "\n

Builder

\n\n" assert_equal expected_body, @response.body assert_equal "

Builder

\n", @store.read("views/functional_caching/formatted_fragment_cached:#{template_digest("functional_caching/formatted_fragment_cached", format)}/fragment") end def test_fragment_caching_with_variant format = "html" get :formatted_fragment_cached_with_variant, format: format, params: { v: :phone } assert_response :success expected_body = "\n

PHONE

\n\n" assert_equal expected_body, @response.body assert_equal "

PHONE

", @store.read("views/functional_caching/formatted_fragment_cached_with_variant:#{template_digest("functional_caching/formatted_fragment_cached_with_variant", format)}/fragment") end def test_fragment_caching_with_html_partials_in_xml get :xml_fragment_cached_with_html_partial, format: "*/*" assert_response :success end private def template_digest(name, format) ActionView::Digestor.digest(name: name, format: format, finder: @controller.lookup_context) end end class CacheHelperOutputBufferTest < ActionController::TestCase class MockController def read_fragment(name, options) false end def write_fragment(name, fragment, options) fragment end end def setup super end def test_output_buffer output_buffer = ActionView::OutputBuffer.new controller = MockController.new cache_helper = Class.new do def self.controller; end def self.output_buffer; end def self.output_buffer=; end end cache_helper.extend(ActionView::Helpers::CacheHelper) cache_helper.stub :controller, controller do cache_helper.stub :output_buffer, output_buffer do assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do assert_nothing_raised do cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil } end end end end end def test_safe_buffer output_buffer = ActiveSupport::SafeBuffer.new controller = MockController.new cache_helper = Class.new do def self.controller; end def self.output_buffer; end def self.output_buffer=; end end cache_helper.extend(ActionView::Helpers::CacheHelper) cache_helper.stub :controller, controller do cache_helper.stub :output_buffer, output_buffer do assert_called_with cache_helper, :output_buffer=, [output_buffer.class.new(output_buffer)] do assert_nothing_raised do cache_helper.send :fragment_for, "Test fragment name", "Test fragment", &Proc.new { nil } end end end end end end class ViewCacheDependencyTest < ActionController::TestCase class NoDependenciesController < ActionController::Base end class HasDependenciesController < ActionController::Base view_cache_dependency { "trombone" } view_cache_dependency { "flute" } end def test_view_cache_dependencies_are_empty_by_default assert_empty NoDependenciesController.new.view_cache_dependencies end def test_view_cache_dependencies_are_listed_in_declaration_order assert_equal %w(trombone flute), HasDependenciesController.new.view_cache_dependencies end end class CollectionCacheController < ActionController::Base attr_accessor :partial_rendered_times def index @customers = [Customer.new("david", params[:id] || 1)] end def index_ordered @customers = [Customer.new("david", 1), Customer.new("david", 2), Customer.new("david", 3)] render "index" end def index_explicit_render_in_controller @customers = [Customer.new("david", 1)] render partial: "customers/customer", collection: @customers, cached: true end def index_with_comment @customers = [Customer.new("david", 1)] render partial: "customers/commented_customer", collection: @customers, as: :customer, cached: true end def index_with_callable_cache_key @customers = [Customer.new("david", 1)] render partial: "customers/customer", collection: @customers, cached: -> customer { "cached_david" } end end class CollectionCacheTest < ActionController::TestCase def setup super @controller = CollectionCacheController.new @controller.perform_caching = true @controller.partial_rendered_times = 0 @controller.cache_store = ActiveSupport::Cache::MemoryStore.new ActionView::PartialRenderer.collection_cache = ActiveSupport::Cache::MemoryStore.new end def test_collection_fetches_cached_views get :index assert_equal 1, @controller.partial_rendered_times assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/david/1") get :index assert_equal 1, @controller.partial_rendered_times end def test_preserves_order_when_reading_from_cache_plus_rendering get :index, params: { id: 2 } assert_equal 1, @controller.partial_rendered_times assert_select ":root", "david, 2" get :index_ordered assert_equal 3, @controller.partial_rendered_times assert_select ":root", "david, 1\n david, 2\n david, 3" end def test_explicit_render_call_with_options get :index_explicit_render_in_controller assert_select ":root", "david, 1" end def test_caching_works_with_beginning_comment get :index_with_comment assert_equal 1, @controller.partial_rendered_times get :index_with_comment assert_equal 1, @controller.partial_rendered_times end def test_caching_with_callable_cache_key get :index_with_callable_cache_key assert_match "david, 1", ActionView::PartialRenderer.collection_cache.read("views/customers/_customer:7c228ab609f0baf0b1f2367469210937/cached_david") end end class FragmentCacheKeyTestController < CachingController attr_accessor :account_id fragment_cache_key "v1" fragment_cache_key { account_id } end class FragmentCacheKeyTest < ActionController::TestCase def setup super @store = ActiveSupport::Cache::MemoryStore.new @controller = FragmentCacheKeyTestController.new @controller.perform_caching = true @controller.cache_store = @store end def test_combined_fragment_cache_key @controller.account_id = "123" assert_equal [ :views, "v1", "123", "what a key" ], @controller.combined_fragment_cache_key("what a key") @controller.account_id = nil assert_equal [ :views, "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") end def test_combined_fragment_cache_key_with_envs ENV["RAILS_APP_VERSION"] = "55" assert_equal [ :views, "55", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") ENV["RAILS_CACHE_ID"] = "66" assert_equal [ :views, "66", "v1", "what a key" ], @controller.combined_fragment_cache_key("what a key") ensure ENV["RAILS_CACHE_ID"] = ENV["RAILS_APP_VERSION"] = nil end end