Use lower case normalisation for cookie attributes. (#1849)
[rack.git] / test / spec_response.rb
blob6cbdbf6d8d0eaa184ea277a2be2e3f36c8dadd88
1 # frozen_string_literal: true
3 require_relative 'helper'
5 separate_testing do
6   require_relative '../lib/rack/response'
7 end
9 describe Rack::Response do
10   it 'has standard constructor' do
11     headers = { "header" => "value" }
12     body = ["body"]
14     response = Rack::Response[200, headers, body]
16     response.status.must_equal 200
17     response.headers.must_equal headers
18     response.body.must_equal body
19   end
21   it 'has cache-control methods' do
22     response = Rack::Response.new
23     cc = 'foo'
24     response.cache_control = cc
25     assert_equal cc, response.cache_control
26     assert_equal cc, response.to_a[1]['cache-control']
27   end
29   it 'has an etag method' do
30     response = Rack::Response.new
31     etag = 'foo'
32     response.etag = etag
33     assert_equal etag, response.etag
34     assert_equal etag, response.to_a[1]['ETag']
35   end
37   it 'has a content-type method' do
38     response = Rack::Response.new
39     content_type = 'foo'
40     response.content_type = content_type
41     assert_equal content_type, response.content_type
42     assert_equal content_type, response.to_a[1]['Content-Type']
43   end
45   it "have sensible default values" do
46     response = Rack::Response.new
47     status, header, body = response.finish
48     status.must_equal 200
49     header.must_equal({})
50     body.each { |part|
51       part.must_equal ""
52     }
54     response = Rack::Response.new
55     status, header, body = *response
56     status.must_equal 200
57     header.must_equal({})
58     body.each { |part|
59       part.must_equal ""
60     }
61   end
63   it "can be written to inside finish block, but does not update Content-Length" do
64     response = Rack::Response.new('foo')
65     response.write "bar"
67     _, h, body = response.finish do
68       response.write "baz"
69     end
71     parts = []
72     body.each { |part| parts << part }
74     parts.must_equal ["foo", "bar", "baz"]
75     h['Content-Length'].must_equal '6'
76   end
78   it "can set and read headers" do
79     response = Rack::Response.new
80     response["Content-Type"].must_be_nil
81     response["Content-Type"] = "text/plain"
82     response["Content-Type"].must_equal "text/plain"
83   end
85   it "doesn't mutate given headers" do
86     headers = {}
88     response = Rack::Response.new([], 200, headers)
89     response.headers["Content-Type"] = "text/plain"
90     response.headers["Content-Type"].must_equal "text/plain"
92     headers.wont_include("Content-Type")
93   end
95   it "can override the initial Content-Type with a different case" do
96     response = Rack::Response.new("", 200, "content-type" => "text/plain")
97     response["Content-Type"].must_equal "text/plain"
98   end
100   it "can set cookies" do
101     response = Rack::Response.new
103     response.set_cookie "foo", "bar"
104     response["Set-Cookie"].must_equal "foo=bar"
105     response.set_cookie "foo2", "bar2"
106     response["Set-Cookie"].must_equal ["foo=bar", "foo2=bar2"]
107     response.set_cookie "foo3", "bar3"
108     response["Set-Cookie"].must_equal ["foo=bar", "foo2=bar2", "foo3=bar3"]
109   end
111   it "can set cookies with the same name for multiple domains" do
112     response = Rack::Response.new
113     response.set_cookie "foo", { value: "bar", domain: "sample.example.com" }
114     response.set_cookie "foo", { value: "bar", domain: ".example.com" }
115     response["Set-Cookie"].must_equal ["foo=bar; domain=sample.example.com", "foo=bar; domain=.example.com"]
116   end
118   it "formats the Cookie expiration date accordingly to RFC 6265" do
119     response = Rack::Response.new
121     response.set_cookie "foo", { value: "bar", expires: Time.now + 10 }
122     response["Set-Cookie"].must_match(
123       /expires=..., \d\d ... \d\d\d\d \d\d:\d\d:\d\d .../)
124   end
126   it "can set secure cookies" do
127     response = Rack::Response.new
128     response.set_cookie "foo", { value: "bar", secure: true }
129     response["Set-Cookie"].must_equal "foo=bar; secure"
130   end
132   it "can set http only cookies" do
133     response = Rack::Response.new
134     response.set_cookie "foo", { value: "bar", httponly: true }
135     response["Set-Cookie"].must_equal "foo=bar; httponly"
136   end
138   it "can set http only cookies with :http_only" do
139     response = Rack::Response.new
140     response.set_cookie "foo", { value: "bar", http_only: true }
141     response["Set-Cookie"].must_equal "foo=bar; httponly"
142   end
144   it "can set prefers :httponly for http only cookie setting when :httponly and :http_only provided" do
145     response = Rack::Response.new
146     response.set_cookie "foo", { value: "bar", httponly: false, http_only: true }
147     response["Set-Cookie"].must_equal "foo=bar"
148   end
150   it "can set SameSite cookies with symbol value :none" do
151     response = Rack::Response.new
152     response.set_cookie "foo", { value: "bar", same_site: :none }
153     response["Set-Cookie"].must_equal "foo=bar; SameSite=None"
154   end
156   it "can set SameSite cookies with symbol value :None" do
157     response = Rack::Response.new
158     response.set_cookie "foo", { value: "bar", same_site: :None }
159     response["Set-Cookie"].must_equal "foo=bar; SameSite=None"
160   end
162   it "can set SameSite cookies with string value 'None'" do
163     response = Rack::Response.new
164     response.set_cookie "foo", { value: "bar", same_site: "None" }
165     response["Set-Cookie"].must_equal "foo=bar; SameSite=None"
166   end
168   it "can set SameSite cookies with symbol value :lax" do
169     response = Rack::Response.new
170     response.set_cookie "foo", { value: "bar", same_site: :lax }
171     response["Set-Cookie"].must_equal "foo=bar; SameSite=Lax"
172   end
174   it "can set SameSite cookies with symbol value :Lax" do
175     response = Rack::Response.new
176     response.set_cookie "foo", { value: "bar", same_site: :lax }
177     response["Set-Cookie"].must_equal "foo=bar; SameSite=Lax"
178   end
180   it "can set SameSite cookies with string value 'Lax'" do
181     response = Rack::Response.new
182     response.set_cookie "foo", { value: "bar", same_site: "Lax" }
183     response["Set-Cookie"].must_equal "foo=bar; SameSite=Lax"
184   end
186   it "can set SameSite cookies with boolean value true" do
187     response = Rack::Response.new
188     response.set_cookie "foo", { value: "bar", same_site: true }
189     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
190   end
192   it "can set SameSite cookies with symbol value :strict" do
193     response = Rack::Response.new
194     response.set_cookie "foo", { value: "bar", same_site: :strict }
195     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
196   end
198   it "can set SameSite cookies with symbol value :Strict" do
199     response = Rack::Response.new
200     response.set_cookie "foo", { value: "bar", same_site: :Strict }
201     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
202   end
204   it "can set SameSite cookies with string value 'Strict'" do
205     response = Rack::Response.new
206     response.set_cookie "foo", { value: "bar", same_site: "Strict" }
207     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
208   end
210   it "validates the SameSite option value" do
211     response = Rack::Response.new
212     lambda {
213       response.set_cookie "foo", { value: "bar", same_site: "Foo" }
214     }.must_raise(ArgumentError).
215       message.must_match(/Invalid SameSite value: "Foo"/)
216   end
218   it "can set SameSite cookies with symbol value" do
219     response = Rack::Response.new
220     response.set_cookie "foo", { value: "bar", same_site: :Strict }
221     response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
222   end
224   [ nil, false ].each do |non_truthy|
225     it "omits SameSite attribute given a #{non_truthy.inspect} value" do
226       response = Rack::Response.new
227       response.set_cookie "foo", { value: "bar", same_site: non_truthy }
228       response["Set-Cookie"].must_equal "foo=bar"
229     end
230   end
232   it "can delete cookies" do
233     response = Rack::Response.new
234     response.set_cookie "foo", "bar"
235     response.set_cookie "foo2", "bar2"
236     response.delete_cookie "foo"
237     response["Set-Cookie"].must_equal [
238       "foo=bar",
239       "foo2=bar2",
240       "foo=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
241     ]
242   end
244   it "can delete cookies with the same name from multiple domains" do
245     response = Rack::Response.new
246     response.set_cookie "foo", { value: "bar", domain: "sample.example.com" }
247     response.set_cookie "foo", { value: "bar", domain: ".example.com" }
248     response["Set-Cookie"].must_equal [
249       "foo=bar; domain=sample.example.com",
250       "foo=bar; domain=.example.com"
251     ]
253     response.delete_cookie "foo", domain: ".example.com"
254     response["Set-Cookie"].must_equal [
255       "foo=bar; domain=sample.example.com",
256       "foo=bar; domain=.example.com",
257       "foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
258     ]
260     response.delete_cookie "foo", domain: "sample.example.com"
261     response["Set-Cookie"].must_equal [
262       "foo=bar; domain=sample.example.com",
263       "foo=bar; domain=.example.com",
264       "foo=; domain=.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT",
265       "foo=; domain=sample.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
266     ]
267   end
269   it "only deletes cookies for the domain specified" do
270     response = Rack::Response.new
271     response.set_cookie "foo", { value: "bar", domain: "example.com.example.com" }
272     response.set_cookie "foo", { value: "bar", domain: "example.com" }
273     response["Set-Cookie"].must_equal [
274       "foo=bar; domain=example.com.example.com",
275       "foo=bar; domain=example.com"
276     ]
277     
278     response.delete_cookie "foo", { domain: "example.com" }
279     response["Set-Cookie"].must_equal [
280       "foo=bar; domain=example.com.example.com",
281       "foo=bar; domain=example.com",
282       "foo=; domain=example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
283     ]
284     
285     response.delete_cookie "foo", { domain: "example.com.example.com" }
286     response["Set-Cookie"].must_equal [
287       "foo=bar; domain=example.com.example.com",
288       "foo=bar; domain=example.com",
289       "foo=; domain=example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT",
290       "foo=; domain=example.com.example.com; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
291     ]
292   end
294   it "can delete cookies with the same name with different paths" do
295     response = Rack::Response.new
296     response.set_cookie "foo", { value: "bar", path: "/" }
297     response.set_cookie "foo", { value: "bar", path: "/path" }
299     response["Set-Cookie"].must_equal [
300       "foo=bar; path=/",
301       "foo=bar; path=/path"
302     ]
304     response.delete_cookie "foo", path: "/path"
305     response["Set-Cookie"].must_equal [
306       "foo=bar; path=/",
307       "foo=bar; path=/path",
308       "foo=; path=/path; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
309     ]
310   end
312   it "only delete cookies with the path specified" do
313     response = Rack::Response.new
314     response.set_cookie "foo", value: "bar", path: "/a/b"
315     response["Set-Cookie"].must_equal(
316       "foo=bar; path=/a/b"
317     )
319     response.delete_cookie "foo", path: "/a"
320     response["Set-Cookie"].must_equal [
321       "foo=bar; path=/a/b",
322       "foo=; path=/a; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT"
323     ]
324   end
326   it "only delete cookies with the domain and path specified" do
327     response = Rack::Response.new
328     response.delete_cookie "foo", path: "/a", domain: "example.com"
329     response["Set-Cookie"].must_equal(
330       "foo=; domain=example.com; path=/a; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT",
331     )
333     response.delete_cookie "foo", path: "/a/b", domain: "example.com"
334     response["Set-Cookie"].must_equal [
335       "foo=; domain=example.com; path=/a; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT",
336       "foo=; domain=example.com; path=/a/b; max-age=0; expires=Thu, 01 Jan 1970 00:00:00 GMT",
337     ]
338   end
340   it "can do redirects" do
341     response = Rack::Response.new
342     response.redirect "/foo"
343     status, header = response.finish
344     status.must_equal 302
345     header["Location"].must_equal "/foo"
347     response = Rack::Response.new
348     response.redirect "/foo", 307
349     status, = response.finish
351     status.must_equal 307
352   end
354   it "has a useful constructor" do
355     r = Rack::Response.new("foo")
356     body = r.finish[2]
357     str = "".dup; body.each { |part| str << part }
358     str.must_equal "foo"
360     r = Rack::Response.new(["foo", "bar"])
361     body = r.finish[2]
362     str = "".dup; body.each { |part| str << part }
363     str.must_equal "foobar"
365     object_with_each = Object.new
366     def object_with_each.each
367       yield "foo"
368       yield "bar"
369     end
370     r = Rack::Response.new(object_with_each)
371     r.write "foo"
372     body = r.finish[2]
373     str = "".dup; body.each { |part| str << part }
374     str.must_equal "foobarfoo"
376     r = Rack::Response.new([], 500)
377     r.status.must_equal 500
379     r = Rack::Response.new([], "200 OK")
380     r.status.must_equal 200
381   end
383   it "has a constructor that can take a block" do
384     r = Rack::Response.new { |res|
385       res.status = 404
386       res.write "foo"
387     }
388     status, _, body = r.finish
389     str = "".dup; body.each { |part| str << part }
390     str.must_equal "foo"
391     status.must_equal 404
392   end
394   it "correctly updates Content-Type when writing when not initialized with body" do
395     r = Rack::Response.new
396     r.write('foo')
397     r.write('bar')
398     r.write('baz')
399     _, header, body = r.finish
400     str = "".dup; body.each { |part| str << part }
401     str.must_equal "foobarbaz"
402     header['Content-Length'].must_equal '9'
403   end
405   it "correctly updates Content-Type when writing when initialized with body" do
406     obj = Object.new
407     def obj.each
408       yield 'foo'
409       yield 'bar'
410     end
411     ["foobar", ["foo", "bar"], obj].each do
412       r = Rack::Response.new(["foo", "bar"])
413       r.write('baz')
414       _, header, body = r.finish
415       str = "".dup; body.each { |part| str << part }
416       str.must_equal "foobarbaz"
417       header['Content-Length'].must_equal '9'
418     end
419   end
421   it "doesn't return invalid responses" do
422     r = Rack::Response.new(["foo", "bar"], 204)
423     _, header, body = r.finish
424     str = "".dup; body.each { |part| str << part }
425     str.must_be :empty?
426     header["Content-Type"].must_be_nil
427     header['Content-Length'].must_be_nil
429     lambda {
430       Rack::Response.new(Object.new).each{}
431     }.must_raise(NoMethodError).
432       message.must_match(/undefined method .each. for/)
433   end
435   it "knows if it's empty" do
436     r = Rack::Response.new
437     r.must_be :empty?
438     r.write "foo"
439     r.wont_be :empty?
441     r = Rack::Response.new
442     r.must_be :empty?
443     r.finish
444     r.must_be :empty?
446     r = Rack::Response.new
447     r.must_be :empty?
448     r.finish { }
449     r.wont_be :empty?
450   end
452   it "provide access to the HTTP status" do
453     res = Rack::Response.new
454     res.status = 200
455     res.must_be :successful?
456     res.must_be :ok?
458     res.status = 201
459     res.must_be :successful?
460     res.must_be :created?
462     res.status = 202
463     res.must_be :successful?
464     res.must_be :accepted?
466     res.status = 204
467     res.must_be :successful?
468     res.must_be :no_content?
470     res.status = 301
471     res.must_be :redirect?
472     res.must_be :moved_permanently?
474     res.status = 302
475     res.must_be :redirect?
477     res.status = 303
478     res.must_be :redirect?
480     res.status = 307
481     res.must_be :redirect?
483     res.status = 308
484     res.must_be :redirect?
486     res.status = 400
487     res.wont_be :successful?
488     res.must_be :client_error?
489     res.must_be :bad_request?
491     res.status = 401
492     res.wont_be :successful?
493     res.must_be :client_error?
494     res.must_be :unauthorized?
496     res.status = 404
497     res.wont_be :successful?
498     res.must_be :client_error?
499     res.must_be :not_found?
501     res.status = 405
502     res.wont_be :successful?
503     res.must_be :client_error?
504     res.must_be :method_not_allowed?
506     res.status = 412
507     res.wont_be :successful?
508     res.must_be :client_error?
509     res.must_be :precondition_failed?
511     res.status = 422
512     res.wont_be :successful?
513     res.must_be :client_error?
514     res.must_be :unprocessable?
516     res.status = 501
517     res.wont_be :successful?
518     res.must_be :server_error?
519   end
521   it "provide access to the HTTP headers" do
522     res = Rack::Response.new
523     res["Content-Type"] = "text/yaml; charset=UTF-8"
525     res.must_include "Content-Type"
526     res.headers["Content-Type"].must_equal "text/yaml; charset=UTF-8"
527     res["Content-Type"].must_equal "text/yaml; charset=UTF-8"
528     res.content_type.must_equal "text/yaml; charset=UTF-8"
529     res.media_type.must_equal "text/yaml"
530     res.media_type_params.must_equal "charset" => "UTF-8"
531     res.content_length.must_be_nil
532     res.location.must_be_nil
533   end
535   it "does not add or change Content-Length when #finish()ing" do
536     res = Rack::Response.new
537     res.status = 200
538     res.finish
539     res.headers["Content-Length"].must_be_nil
541     res = Rack::Response.new
542     res.status = 200
543     res.headers["Content-Length"] = "10"
544     res.finish
545     res.headers["Content-Length"].must_equal "10"
546   end
548   it "updates Content-Length when body appended to using #write" do
549     res = Rack::Response.new
550     res.status = 200
551     res.headers["Content-Length"].must_be_nil
552     res.write "Hi"
553     res.headers["Content-Length"].must_equal "2"
554     res.write " there"
555     res.headers["Content-Length"].must_equal "8"
556   end
558   it "does not wrap body" do
559     body = Object.new
560     res = Rack::Response.new(body)
562     # It was passed through unchanged:
563     res.finish.last.must_equal body
564   end
566   it "does wraps body when using #write" do
567     body = ["Foo"]
568     res = Rack::Response.new(body)
570     # Write something using the response object:
571     res.write("Bar")
573     # The original body was not modified:
574     body.must_equal ["Foo"]
576     # But a new buffered body was created:
577     res.finish.last.must_equal ["Foo", "Bar"]
578   end
580   it "calls close on #body" do
581     res = Rack::Response.new
582     res.body = StringIO.new
583     res.close
584     res.body.must_be :closed?
585   end
587   it "calls close on #body when 204 or 304" do
588     res = Rack::Response.new
589     res.body = StringIO.new
590     res.finish
591     res.body.wont_be :closed?
593     res.status = 204
594     _, _, b = res.finish
595     res.body.must_be :closed?
596     b.wont_equal res.body
598     res.body = StringIO.new
599     res.status = 304
600     _, _, b = res.finish
601     res.body.must_be :closed?
602     b.wont_equal res.body
603   end
605   it "doesn't call close on #body when 205" do
606     res = Rack::Response.new
608     res.body = StringIO.new
609     res.status = 205
610     res.finish
611     res.body.wont_be :closed?
612   end
614   it "flatten doesn't cause infinite loop" do
615     # https://github.com/rack/rack/issues/419
616     res = Rack::Response.new("Hello World")
618     res.finish.flatten.must_be_kind_of(Array)
619   end
621   it "should specify not to cache content" do
622     response = Rack::Response.new
624     response.cache!(1000)
625     response.do_not_cache!
627     expect(response['cache-control']).must_equal "no-cache, must-revalidate"
629     expires_header = Time.parse(response['expires'])
630     expect(expires_header).must_be :<=, Time.now
631   end
633   it "should specify to cache content" do
634     response = Rack::Response.new
636     duration = 120
637     expires = Time.now + 100 # At least this far into the future
638     response.cache!(duration)
640     expect(response['cache-control']).must_equal "public, max-age=120"
642     expires_header = Time.parse(response['expires'])
643     expect(expires_header).must_be :>=, expires
644   end
647 describe Rack::Response, 'headers' do
648   before do
649     @response = Rack::Response.new([], 200, { 'foo' => '1' })
650   end
652   it 'has_header?' do
653     lambda { @response.has_header? nil }.must_raise ArgumentError
655     @response.has_header?('Foo').must_equal true
656     @response.has_header?('foo').must_equal true
657   end
659   it 'get_header' do
660     lambda { @response.get_header nil }.must_raise ArgumentError
662     @response.get_header('Foo').must_equal '1'
663     @response.get_header('foo').must_equal '1'
664   end
666   it 'set_header' do
667     lambda { @response.set_header nil, '1' }.must_raise ArgumentError
669     @response.set_header('Foo', '2').must_equal '2'
670     @response.has_header?('Foo').must_equal true
671     @response.get_header('Foo').must_equal('2')
673     @response.set_header('Foo', nil).must_be_nil
674     @response.has_header?('Foo').must_equal true
675     @response.get_header('Foo').must_be_nil
676   end
678   it 'add_header' do
679     lambda { @response.add_header nil, '1' }.must_raise ArgumentError
681     # Add a value to an existing header
682     @response.add_header('Foo', '2').must_equal ["1", "2"]
683     @response.get_header('Foo').must_equal ["1", "2"]
685     # Add nil to an existing header
686     @response.add_header('Foo', nil).must_equal ["1", "2"]
687     @response.get_header('Foo').must_equal ["1", "2"]
689     # Add nil to a nonexistent header
690     @response.add_header('Bar', nil).must_be_nil
691     @response.has_header?('Bar').must_equal false
692     @response.get_header('Bar').must_be_nil
694     # Add a value to a nonexistent header
695     @response.add_header('Bar', '1').must_equal '1'
696     @response.has_header?('Bar').must_equal true
697     @response.get_header('Bar').must_equal '1'
698   end
700   it 'delete_header' do
701     lambda { @response.delete_header nil }.must_raise ArgumentError
703     @response.delete_header('Foo').must_equal '1'
704     (!!@response.has_header?('Foo')).must_equal false
706     @response.delete_header('Foo').must_be_nil
707     @response.has_header?('Foo').must_equal false
709     @response.set_header('Foo', 1)
710     @response.delete_header('foo').must_equal 1
711     @response.has_header?('Foo').must_equal false
712   end