render_test.rb 19.3 KB
Newer Older
1
require 'abstract_unit'
2
require 'controller/fake_models'
D
Initial  
David Heinemeier Hansson 已提交
3

4 5 6 7 8 9 10 11
class TestControllerWithExtraEtags < ActionController::Base
  etag { nil  }
  etag { 'ab' }
  etag { :cde }
  etag { [:f] }
  etag { nil  }

  def fresh
12
    render plain: "stale" if stale?(etag: '123', template: false)
13
  end
14 15

  def array
16
    render plain: "stale" if stale?(etag: %w(1 2 3), template: false)
17 18 19 20
  end

  def with_template
    if stale? template: 'test/hello_world'
21
      render plain: 'stale'
22
    end
23
  end
24 25
end

26 27 28 29 30
class ImplicitRenderTestController < ActionController::Base
  def empty_action
  end
end

31
class TestController < ActionController::Base
P
Pratik Naik 已提交
32 33
  protect_from_forgery

34
  before_action :set_variable_for_layout
L
lest 已提交
35

J
Joshua Peek 已提交
36 37 38
  class LabellingFormBuilder < ActionView::Helpers::FormBuilder
  end

39
  layout :determine_layout
D
Initial  
David Heinemeier Hansson 已提交
40

41 42 43 44 45 46 47
  def name
    nil
  end

  private :name
  helper_method :name

48 49
  def hello_world
  end
D
Initial  
David Heinemeier Hansson 已提交
50

51
  def conditional_hello
52
    if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123])
53 54
      render :action => 'hello_world'
    end
55
  end
J
Joshua Peek 已提交
56

57
  def conditional_hello_with_record
58
    record = Struct.new(:updated_at, :cache_key).new(Time.now.utc.beginning_of_day, "foo/123")
59

60
    if stale?(record)
Ł
Łukasz Strzałkowski 已提交
61
      render :action => 'hello_world'
J
Joshua Peek 已提交
62 63 64
    end
  end

65 66 67 68 69 70 71 72 73 74
  def dynamic_render
    render params[:id] # => String, AC:Params
  end

  def dynamic_render_with_file
    # This is extremely bad, but should be possible to do.
    file = params[:id] # => String, AC:Params
    render file: file
  end

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
  class Collection
    def initialize(records)
      @records = records
    end

    def maximum(attribute)
      @records.max_by(&attribute).public_send(attribute)
    end
  end

  def conditional_hello_with_collection_of_records
    ts = Time.now.utc.beginning_of_day

    record = Struct.new(:updated_at, :cache_key).new(ts, "foo/123")
    old_record = Struct.new(:updated_at, :cache_key).new(ts - 1.day, "bar/123")

    if stale?(Collection.new([record, old_record]))
      render action: 'hello_world'
    end
  end

Ł
Łukasz Strzałkowski 已提交
96 97 98
  def conditional_hello_with_expires_in
    expires_in 60.1.seconds
    render :action => 'hello_world'
J
Joshua Peek 已提交
99 100
  end

Ł
Łukasz Strzałkowski 已提交
101 102 103
  def conditional_hello_with_expires_in_with_public
    expires_in 1.minute, :public => true
    render :action => 'hello_world'
J
Joshua Peek 已提交
104 105
  end

Ł
Łukasz Strzałkowski 已提交
106 107 108
  def conditional_hello_with_expires_in_with_must_revalidate
    expires_in 1.minute, :must_revalidate => true
    render :action => 'hello_world'
109 110
  end

Ł
Łukasz Strzałkowski 已提交
111 112 113
  def conditional_hello_with_expires_in_with_public_and_must_revalidate
    expires_in 1.minute, :public => true, :must_revalidate => true
    render :action => 'hello_world'
114 115
  end

Ł
Łukasz Strzałkowski 已提交
116 117 118
  def conditional_hello_with_expires_in_with_public_with_more_keys
    expires_in 1.minute, :public => true, 's-maxage' => 5.hours
    render :action => 'hello_world'
119 120
  end

Ł
Łukasz Strzałkowski 已提交
121 122 123
  def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
    expires_in 1.minute, :public => true, :private => nil, 's-maxage' => 5.hours
    render :action => 'hello_world'
J
Joshua Peek 已提交
124 125
  end

Ł
Łukasz Strzałkowski 已提交
126 127 128
  def conditional_hello_with_expires_now
    expires_now
    render :action => 'hello_world'
129 130
  end

Ł
Łukasz Strzałkowski 已提交
131 132 133 134
  def conditional_hello_with_cache_control_headers
    response.headers['Cache-Control'] = 'no-transform'
    expires_now
    render :action => 'hello_world'
135 136
  end

137 138 139 140
  def respond_with_empty_body
    render nothing: true
  end

Ł
Łukasz Strzałkowski 已提交
141 142
  def conditional_hello_with_bangs
    render :action => 'hello_world'
143
  end
Ł
Łukasz Strzałkowski 已提交
144
  before_action :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs
145

Ł
Łukasz Strzałkowski 已提交
146 147
  def handle_last_modified_and_etags
    fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ])
J
Joshua Peek 已提交
148 149
  end

150 151 152 153 154 155 156 157
  def head_with_status_hash
    head status: :created
  end

  def head_with_hash_does_not_include_status
    head warning: :deprecated
  end

Ł
Łukasz Strzałkowski 已提交
158 159
  def head_created
    head :created
J
Joshua Peek 已提交
160 161
  end

Ł
Łukasz Strzałkowski 已提交
162 163
  def head_created_with_application_json_content_type
    head :created, :content_type => "application/json"
164 165
  end

Ł
Łukasz Strzałkowski 已提交
166 167
  def head_ok_with_image_png_content_type
    head :ok, :content_type => "image/png"
J
Joshua Peek 已提交
168 169
  end

Ł
Łukasz Strzałkowski 已提交
170
  def head_with_location_header
171
    head :ok, :location => "/foo"
172 173
  end

Ł
Łukasz Strzałkowski 已提交
174
  def head_with_location_object
175
    head :ok, :location => Customer.new("david", 1)
J
Joshua Peek 已提交
176 177
  end

Ł
Łukasz Strzałkowski 已提交
178
  def head_with_symbolic_status
179
    head params[:status].intern
J
Joshua Peek 已提交
180 181
  end

Ł
Łukasz Strzałkowski 已提交
182
  def head_with_integer_status
183
    head params[:status].to_i
J
Joshua Peek 已提交
184 185
  end

Ł
Łukasz Strzałkowski 已提交
186
  def head_with_string_status
187
    head params[:status]
J
Joshua Peek 已提交
188 189
  end

Ł
Łukasz Strzałkowski 已提交
190
  def head_with_custom_header
191
    head :ok, :x_custom_header => "something"
192 193
  end

Ł
Łukasz Strzałkowski 已提交
194
  def head_with_www_authenticate_header
195
    head :ok, 'WWW-Authenticate' => 'something'
196 197
  end

Ł
Łukasz Strzałkowski 已提交
198 199
  def head_with_status_code_first
    head :forbidden, :x_custom_header => "something"
J
Joshua Peek 已提交
200 201
  end

J
Joel Hayhurst 已提交
202 203 204 205 206
  def head_and_return
    head :ok and return
    raise 'should not reach this line'
  end

207 208 209 210 211 212 213 214 215
  def head_with_no_content
    # Fill in the headers with dummy data to make
    # sure they get removed during the testing
    response.headers["Content-Type"] = "dummy"
    response.headers["Content-Length"] = 42

    head 204
  end

Ł
Łukasz Strzałkowski 已提交
216
  private
J
Joshua Peek 已提交
217

Ł
Łukasz Strzałkowski 已提交
218 219
    def set_variable_for_layout
      @variable_for_layout = nil
J
Joshua Peek 已提交
220 221
    end

Ł
Łukasz Strzałkowski 已提交
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    def determine_layout
      case action_name
        when "hello_world", "layout_test", "rendering_without_layout",
             "rendering_nothing_on_layout", "render_text_hello_world",
             "render_text_hello_world_with_layout",
             "hello_world_with_layout_false",
             "partial_only", "accessing_params_in_template",
             "accessing_params_in_template_with_layout",
             "render_with_explicit_template",
             "render_with_explicit_string_template",
             "update_page", "update_page_with_instance_variables"

          "layouts/standard"
        when "action_talk_to_layout", "layout_overriding_layout"
          "layouts/talk_from_action"
        when "render_implicit_html_template_from_xhr_request"
          (request.xhr? ? 'layouts/xhr' : 'layouts/standard')
      end
    end
end

class MetalTestController < ActionController::Metal
  include AbstractController::Rendering
  include ActionView::Rendering
  include ActionController::Rendering
J
Joshua Peek 已提交
247

Ł
Łukasz Strzałkowski 已提交
248 249
  def accessing_logger_in_template
    render :inline =>  "<%= logger.class %>"
250
  end
251 252
end

253 254 255
class ExpiresInRenderTest < ActionController::TestCase
  tests TestController

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
  def test_dynamic_render_with_file
    # This is extremely bad, but should be possible to do.
    assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb'))
    response = get :dynamic_render_with_file, params: { id: '../\\../test/abstract_unit.rb' }
    assert_equal File.read(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb')),
      response.body
  end

  def test_dynamic_render
    assert File.exist?(File.join(File.dirname(__FILE__), '../../test/abstract_unit.rb'))
    assert_raises ActionView::MissingTemplate do
      get :dynamic_render, params: { id: '../\\../test/abstract_unit.rb' }
    end
  end

  def test_dynamic_render_file_hash
    assert_raises ArgumentError do
      get :dynamic_render, params: { id: { file: '../\\../test/abstract_unit.rb' } }
    end
  end

277 278 279 280
  def test_expires_in_header
    get :conditional_hello_with_expires_in
    assert_equal "max-age=60, private", @response.headers["Cache-Control"]
  end
J
Joshua Peek 已提交
281

282
  def test_expires_in_header_with_public
283 284 285
    get :conditional_hello_with_expires_in_with_public
    assert_equal "max-age=60, public", @response.headers["Cache-Control"]
  end
J
Joshua Peek 已提交
286

287 288 289 290 291 292 293 294 295 296
  def test_expires_in_header_with_must_revalidate
    get :conditional_hello_with_expires_in_with_must_revalidate
    assert_equal "max-age=60, private, must-revalidate", @response.headers["Cache-Control"]
  end

  def test_expires_in_header_with_public_and_must_revalidate
    get :conditional_hello_with_expires_in_with_public_and_must_revalidate
    assert_equal "max-age=60, public, must-revalidate", @response.headers["Cache-Control"]
  end

297 298
  def test_expires_in_header_with_additional_headers
    get :conditional_hello_with_expires_in_with_public_with_more_keys
299
    assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
300
  end
J
Joshua Peek 已提交
301

302 303
  def test_expires_in_old_syntax
    get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax
304
    assert_equal "max-age=60, public, s-maxage=18000", @response.headers["Cache-Control"]
305
  end
306 307 308 309 310

  def test_expires_now
    get :conditional_hello_with_expires_now
    assert_equal "no-cache", @response.headers["Cache-Control"]
  end
311

A
Arun Agrawal 已提交
312
  def test_expires_now_with_cache_control_headers
313
    get :conditional_hello_with_cache_control_headers
A
Arun Agrawal 已提交
314 315
    assert_match(/no-cache/, @response.headers["Cache-Control"])
    assert_match(/no-transform/, @response.headers["Cache-Control"])
316 317
  end

318 319 320 321 322 323
  def test_render_nothing_deprecated
    assert_deprecated do
      get :respond_with_empty_body
    end
  end

324 325
  def test_date_header_when_expires_in
    time = Time.mktime(2011,10,30)
326 327 328 329
    Time.stub :now, time do
      get :conditional_hello_with_expires_in
      assert_equal Time.now.httpdate, @response.headers["Date"]
    end
330
  end
331 332
end

333 334
class LastModifiedRenderTest < ActionController::TestCase
  tests TestController
335

336
  def setup
337
    super
338
    @last_modified = Time.now.utc.beginning_of_day.httpdate
339 340
  end

341 342 343
  def test_responds_with_last_modified
    get :conditional_hello
    assert_equal @last_modified, @response.headers['Last-Modified']
344 345
  end

346
  def test_request_not_modified
347
    @request.if_modified_since = @last_modified
348
    get :conditional_hello
349
    assert_equal 304, @response.status.to_i
350
    assert @response.body.blank?
351
    assert_equal @last_modified, @response.headers['Last-Modified']
352 353
  end

354 355 356 357 358 359 360
  def test_request_not_modified_but_etag_differs
    @request.if_modified_since = @last_modified
    @request.if_none_match = "234"
    get :conditional_hello
    assert_response :success
  end

361
  def test_request_modified
362
    @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
363
    get :conditional_hello
364
    assert_equal 200, @response.status.to_i
365
    assert @response.body.present?
366
    assert_equal @last_modified, @response.headers['Last-Modified']
367
  end
368

369 370 371 372 373 374 375 376 377
  def test_responds_with_last_modified_with_record
    get :conditional_hello_with_record
    assert_equal @last_modified, @response.headers['Last-Modified']
  end

  def test_request_not_modified_with_record
    @request.if_modified_since = @last_modified
    get :conditional_hello_with_record
    assert_equal 304, @response.status.to_i
378
    assert @response.body.blank?
C
claudiob 已提交
379
    assert_not_nil @response.etag
380 381 382 383 384 385 386 387 388 389 390 391 392 393
    assert_equal @last_modified, @response.headers['Last-Modified']
  end

  def test_request_not_modified_but_etag_differs_with_record
    @request.if_modified_since = @last_modified
    @request.if_none_match = "234"
    get :conditional_hello_with_record
    assert_response :success
  end

  def test_request_modified_with_record
    @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
    get :conditional_hello_with_record
    assert_equal 200, @response.status.to_i
394
    assert @response.body.present?
395 396 397
    assert_equal @last_modified, @response.headers['Last-Modified']
  end

398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
  def test_responds_with_last_modified_with_collection_of_records
    get :conditional_hello_with_collection_of_records
    assert_equal @last_modified, @response.headers['Last-Modified']
  end

  def test_request_not_modified_with_collection_of_records
    @request.if_modified_since = @last_modified
    get :conditional_hello_with_collection_of_records
    assert_equal 304, @response.status.to_i
    assert @response.body.blank?
    assert_equal @last_modified, @response.headers['Last-Modified']
  end

  def test_request_not_modified_but_etag_differs_with_collection_of_records
    @request.if_modified_since = @last_modified
    @request.if_none_match = "234"
    get :conditional_hello_with_collection_of_records
    assert_response :success
  end

  def test_request_modified_with_collection_of_records
    @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT'
    get :conditional_hello_with_collection_of_records
    assert_equal 200, @response.status.to_i
    assert @response.body.present?
    assert_equal @last_modified, @response.headers['Last-Modified']
  end

426 427 428 429 430
  def test_request_with_bang_gets_last_modified
    get :conditional_hello_with_bangs
    assert_equal @last_modified, @response.headers['Last-Modified']
    assert_response :success
  end
431

432 433 434 435 436
  def test_request_with_bang_obeys_last_modified
    @request.if_modified_since = @last_modified
    get :conditional_hello_with_bangs
    assert_response :not_modified
  end
437 438 439 440

  def test_last_modified_works_with_less_than_too
    @request.if_modified_since = 5.years.ago.httpdate
    get :conditional_hello_with_bangs
441
    assert_response :success
442
  end
443
end
444

445 446 447 448
class EtagRenderTest < ActionController::TestCase
  tests TestControllerWithExtraEtags

  def test_multiple_etags
449
    @request.if_none_match = etag(["123", 'ab', :cde, [:f]])
450 451 452 453 454 455 456
    get :fresh
    assert_response :not_modified

    @request.if_none_match = %("nomatch")
    get :fresh
    assert_response :success
  end
457 458 459 460 461 462 463 464 465 466 467

  def test_array
    @request.if_none_match = etag([%w(1 2 3), 'ab', :cde, [:f]])
    get :array
    assert_response :not_modified

    @request.if_none_match = %("nomatch")
    get :array
    assert_response :success
  end

468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
  def test_etag_reflects_template_digest
    get :with_template
    assert_response :ok
    assert_not_nil etag = @response.etag

    request.if_none_match = etag
    get :with_template
    assert_response :not_modified

    # Modify the template digest
    path = File.expand_path('../../fixtures/test/hello_world.erb', __FILE__)
    old = File.read(path)

    begin
      File.write path, 'foo'
      ActionView::Digestor.cache.clear

      request.if_none_match = etag
      get :with_template
      assert_response :ok
      assert_not_equal etag, @response.etag
    ensure
      File.write path, old
    end
  end

494
  def etag(record)
495
    %(W/"#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}")
496
  end
497 498
end

499 500 501 502 503 504 505 506
class MetalRenderTest < ActionController::TestCase
  tests MetalTestController

  def test_access_to_logger_in_view
    get :accessing_logger_in_template
    assert_equal "NilClass", @response.body
  end
end
Ł
Łukasz Strzałkowski 已提交
507

508 509 510 511 512 513 514 515 516
class ImplicitRenderTest < ActionController::TestCase
  tests ImplicitRenderTestController

  def test_implicit_no_content_response
    get :empty_action
    assert_response :no_content
  end
end

Ł
Łukasz Strzałkowski 已提交
517 518 519 520 521 522 523 524 525 526 527 528 529
class HeadRenderTest < ActionController::TestCase
  tests TestController

  def setup
    @request.host = "www.nextangle.com"
  end

  def test_head_created
    post :head_created
    assert @response.body.blank?
    assert_response :created
  end

530 531 532 533 534 535 536 537 538 539 540 541 542
  def test_passing_hash_to_head_as_first_parameter_deprecated
    assert_deprecated do
      get :head_with_status_hash
    end
  end

  def test_head_with_default_value_is_deprecated
    assert_deprecated do
      get :head_with_hash_does_not_include_status
      assert_response :ok
    end
  end

Ł
Łukasz Strzałkowski 已提交
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
  def test_head_created_with_application_json_content_type
    post :head_created_with_application_json_content_type
    assert @response.body.blank?
    assert_equal "application/json", @response.header["Content-Type"]
    assert_response :created
  end

  def test_head_ok_with_image_png_content_type
    post :head_ok_with_image_png_content_type
    assert @response.body.blank?
    assert_equal "image/png", @response.header["Content-Type"]
    assert_response :ok
  end

  def test_head_with_location_header
    get :head_with_location_header
    assert @response.body.blank?
    assert_equal "/foo", @response.headers["Location"]
    assert_response :ok
  end

  def test_head_with_location_object
    with_routing do |set|
      set.draw do
        resources :customers
        get ':controller/:action'
      end

      get :head_with_location_object
      assert @response.body.blank?
      assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"]
      assert_response :ok
    end
  end

  def test_head_with_custom_header
    get :head_with_custom_header
    assert @response.body.blank?
    assert_equal "something", @response.headers["X-Custom-Header"]
    assert_response :ok
  end

  def test_head_with_www_authenticate_header
    get :head_with_www_authenticate_header
    assert @response.body.blank?
    assert_equal "something", @response.headers["WWW-Authenticate"]
    assert_response :ok
  end

  def test_head_with_symbolic_status
593
    get :head_with_symbolic_status, params: { status: "ok" }
Ł
Łukasz Strzałkowski 已提交
594 595 596
    assert_equal 200, @response.status
    assert_response :ok

597
    get :head_with_symbolic_status, params: { status: "not_found" }
Ł
Łukasz Strzałkowski 已提交
598 599 600
    assert_equal 404, @response.status
    assert_response :not_found

601
    get :head_with_symbolic_status, params: { status: "no_content" }
Ł
Łukasz Strzałkowski 已提交
602 603 604 605 606
    assert_equal 204, @response.status
    assert !@response.headers.include?('Content-Length')
    assert_response :no_content

    Rack::Utils::SYMBOL_TO_STATUS_CODE.each do |status, code|
607
      get :head_with_symbolic_status, params: { status: status.to_s }
Ł
Łukasz Strzałkowski 已提交
608 609 610 611 612 613 614
      assert_equal code, @response.response_code
      assert_response status
    end
  end

  def test_head_with_integer_status
    Rack::Utils::HTTP_STATUS_CODES.each do |code, message|
615
      get :head_with_integer_status, params: { status: code.to_s }
Ł
Łukasz Strzałkowski 已提交
616 617 618 619
      assert_equal message, @response.message
    end
  end

620 621 622 623 624 625 626 627
  def test_head_with_no_content
    get :head_with_no_content

    assert_equal 204, @response.status
    assert_nil @response.headers["Content-Type"]
    assert_nil @response.headers["Content-Length"]
  end

Ł
Łukasz Strzałkowski 已提交
628
  def test_head_with_string_status
629
    get :head_with_string_status, params: { status: "404 Eat Dirt" }
Ł
Łukasz Strzałkowski 已提交
630 631 632 633 634 635 636 637 638 639 640 641
    assert_equal 404, @response.response_code
    assert_equal "Not Found", @response.message
    assert_response :not_found
  end

  def test_head_with_status_code_first
    get :head_with_status_code_first
    assert_equal 403, @response.response_code
    assert_equal "Forbidden", @response.message
    assert_equal "something", @response.headers["X-Custom-Header"]
    assert_response :forbidden
  end
J
Joel Hayhurst 已提交
642 643 644 645 646 647

  def test_head_returns_truthy_value
    assert_nothing_raised do
      get :head_and_return
    end
  end
648
end
649 650 651 652 653

class HttpCacheForeverTest < ActionController::TestCase
  class HttpCacheForeverController < ActionController::Base
    def cache_me_forever
      http_cache_forever(public: params[:public], version: params[:version] || 'v1') do
654
        render plain: 'hello'
655 656 657 658 659 660 661 662
      end
    end
  end

  tests HttpCacheForeverController

  def test_cache_with_public
    get :cache_me_forever, params: {public: true}
663
    assert_equal "max-age=#{100.years}, public", @response.headers["Cache-Control"]
664 665 666 667 668
    assert_not_nil @response.etag
  end

  def test_cache_with_private
    get :cache_me_forever
669
    assert_equal "max-age=#{100.years}, private", @response.headers["Cache-Control"]
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701
    assert_not_nil @response.etag
    assert_response :success
  end

  def test_cache_response_code_with_if_modified_since
    get :cache_me_forever
    assert_response :success
    @request.if_modified_since = @response.headers['Last-Modified']
    get :cache_me_forever
    assert_response :not_modified
  end

  def test_cache_response_code_with_etag
    get :cache_me_forever
    assert_response :success
    @request.if_modified_since = @response.headers['Last-Modified']
    @request.if_none_match = @response.etag

    get :cache_me_forever
    assert_response :not_modified
    @request.if_modified_since = @response.headers['Last-Modified']
    @request.if_none_match = @response.etag

    get :cache_me_forever, params: {version: 'v2'}
    assert_response :success
    @request.if_modified_since = @response.headers['Last-Modified']
    @request.if_none_match = @response.etag

    get :cache_me_forever, params: {version: 'v2'}
    assert_response :not_modified
  end
end