devcenter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +8 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +39 -0
  5. data/Rakefile +2 -0
  6. data/bin/devcenter +7 -0
  7. data/devcenter.gemspec +30 -0
  8. data/lib/devcenter.rb +6 -0
  9. data/lib/devcenter/cli.rb +45 -0
  10. data/lib/devcenter/coderay_extensions.rb +70 -0
  11. data/lib/devcenter/commands.rb +4 -0
  12. data/lib/devcenter/commands/base.rb +47 -0
  13. data/lib/devcenter/commands/open.rb +34 -0
  14. data/lib/devcenter/commands/preview.rb +28 -0
  15. data/lib/devcenter/commands/pull.rb +37 -0
  16. data/lib/devcenter/helpers.rb +41 -0
  17. data/lib/devcenter/layout.html +299 -0
  18. data/lib/devcenter/md_parser.rb +87 -0
  19. data/lib/devcenter/previewer.rb +36 -0
  20. data/lib/devcenter/previewer/assets/images/public/article-icon-large.png +0 -0
  21. data/lib/devcenter/previewer/assets/images/public/article-icon.png +0 -0
  22. data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_default.png +0 -0
  23. data/lib/devcenter/previewer/assets/images/public/aside_accordion_indicator_open.png +0 -0
  24. data/lib/devcenter/previewer/assets/images/public/body_bg.png +0 -0
  25. data/lib/devcenter/previewer/assets/images/public/callout_bg.png +0 -0
  26. data/lib/devcenter/previewer/assets/images/public/feed-icon-sprite.png +0 -0
  27. data/lib/devcenter/previewer/assets/images/public/heroku-header-logo-mobile.png +0 -0
  28. data/lib/devcenter/previewer/assets/images/public/heroku-header-logo.png +0 -0
  29. data/lib/devcenter/previewer/assets/images/public/heroku-logo.png +0 -0
  30. data/lib/devcenter/previewer/assets/images/public/icon_sprite_16.png +0 -0
  31. data/lib/devcenter/previewer/assets/images/public/index_li_bullet.png +0 -0
  32. data/lib/devcenter/previewer/assets/images/public/intro_bg.png +0 -0
  33. data/lib/devcenter/previewer/assets/images/public/jive_discussion_arrow.png +0 -0
  34. data/lib/devcenter/previewer/assets/images/public/jive_discussion_glyph.png +0 -0
  35. data/lib/devcenter/previewer/assets/images/public/line.png +0 -0
  36. data/lib/devcenter/previewer/assets/images/public/pre_code_background.png +0 -0
  37. data/lib/devcenter/previewer/assets/images/public/search-icon.png +0 -0
  38. data/lib/devcenter/previewer/assets/images/public/search_glyph.png +0 -0
  39. data/lib/devcenter/previewer/assets/images/public/search_return.png +0 -0
  40. data/lib/devcenter/previewer/assets/images/public/tag-icon-large.png +0 -0
  41. data/lib/devcenter/previewer/assets/images/public/toc-icon.png +0 -0
  42. data/lib/devcenter/previewer/assets/public.css +2125 -0
  43. data/lib/devcenter/previewer/assets/public/article-icon-large.png +0 -0
  44. data/lib/devcenter/previewer/assets/public/article-icon.png +0 -0
  45. data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_default.png +0 -0
  46. data/lib/devcenter/previewer/assets/public/aside_accordion_indicator_open.png +0 -0
  47. data/lib/devcenter/previewer/assets/public/body_bg.png +0 -0
  48. data/lib/devcenter/previewer/assets/public/callout_bg.png +0 -0
  49. data/lib/devcenter/previewer/assets/public/feed-icon-sprite.png +0 -0
  50. data/lib/devcenter/previewer/assets/public/heroku-header-logo-mobile.png +0 -0
  51. data/lib/devcenter/previewer/assets/public/heroku-header-logo.png +0 -0
  52. data/lib/devcenter/previewer/assets/public/heroku-logo.png +0 -0
  53. data/lib/devcenter/previewer/assets/public/icon_sprite_16.png +0 -0
  54. data/lib/devcenter/previewer/assets/public/index_li_bullet.png +0 -0
  55. data/lib/devcenter/previewer/assets/public/intro_bg.png +0 -0
  56. data/lib/devcenter/previewer/assets/public/jive_discussion_arrow.png +0 -0
  57. data/lib/devcenter/previewer/assets/public/jive_discussion_glyph.png +0 -0
  58. data/lib/devcenter/previewer/assets/public/line.png +0 -0
  59. data/lib/devcenter/previewer/assets/public/pre_code_background.png +0 -0
  60. data/lib/devcenter/previewer/assets/public/public.css +2125 -0
  61. data/lib/devcenter/previewer/assets/public/search-icon.png +0 -0
  62. data/lib/devcenter/previewer/assets/public/search_glyph.png +0 -0
  63. data/lib/devcenter/previewer/assets/public/search_return.png +0 -0
  64. data/lib/devcenter/previewer/assets/public/tag-icon-large.png +0 -0
  65. data/lib/devcenter/previewer/assets/public/toc-icon.png +0 -0
  66. data/lib/devcenter/previewer/file_listener.rb +23 -0
  67. data/lib/devcenter/previewer/views/article.erb +345 -0
  68. data/lib/devcenter/previewer/web_app.rb +53 -0
  69. data/lib/devcenter/previewer/web_server.rb +29 -0
  70. data/lib/devcenter/version.rb +3 -0
  71. data/vendor/sinatra/.gitignore +6 -0
  72. data/vendor/sinatra/.travis.yml +16 -0
  73. data/vendor/sinatra/.yardopts +4 -0
  74. data/vendor/sinatra/AUTHORS +61 -0
  75. data/vendor/sinatra/Gemfile +91 -0
  76. data/vendor/sinatra/LICENSE +22 -0
  77. data/vendor/sinatra/README.de.rdoc +2116 -0
  78. data/vendor/sinatra/README.es.rdoc +2106 -0
  79. data/vendor/sinatra/README.fr.rdoc +2133 -0
  80. data/vendor/sinatra/README.hu.rdoc +608 -0
  81. data/vendor/sinatra/README.jp.rdoc +1056 -0
  82. data/vendor/sinatra/README.ko.rdoc +1932 -0
  83. data/vendor/sinatra/README.pt-br.rdoc +778 -0
  84. data/vendor/sinatra/README.pt-pt.rdoc +647 -0
  85. data/vendor/sinatra/README.rdoc +2049 -0
  86. data/vendor/sinatra/README.ru.rdoc +2033 -0
  87. data/vendor/sinatra/README.zh.rdoc +1816 -0
  88. data/vendor/sinatra/Rakefile +182 -0
  89. data/vendor/sinatra/examples/chat.rb +61 -0
  90. data/vendor/sinatra/examples/simple.rb +3 -0
  91. data/vendor/sinatra/examples/stream.ru +26 -0
  92. data/vendor/sinatra/lib/sinatra.rb +5 -0
  93. data/vendor/sinatra/lib/sinatra/base.rb +1820 -0
  94. data/vendor/sinatra/lib/sinatra/images/404.png +0 -0
  95. data/vendor/sinatra/lib/sinatra/images/500.png +0 -0
  96. data/vendor/sinatra/lib/sinatra/main.rb +30 -0
  97. data/vendor/sinatra/lib/sinatra/showexceptions.rb +345 -0
  98. data/vendor/sinatra/lib/sinatra/version.rb +3 -0
  99. data/vendor/sinatra/sinatra.gemspec +18 -0
  100. data/vendor/sinatra/test/base_test.rb +172 -0
  101. data/vendor/sinatra/test/builder_test.rb +91 -0
  102. data/vendor/sinatra/test/coffee_test.rb +90 -0
  103. data/vendor/sinatra/test/compile_test.rb +139 -0
  104. data/vendor/sinatra/test/contest.rb +98 -0
  105. data/vendor/sinatra/test/creole_test.rb +65 -0
  106. data/vendor/sinatra/test/delegator_test.rb +160 -0
  107. data/vendor/sinatra/test/encoding_test.rb +20 -0
  108. data/vendor/sinatra/test/erb_test.rb +98 -0
  109. data/vendor/sinatra/test/extensions_test.rb +98 -0
  110. data/vendor/sinatra/test/filter_test.rb +437 -0
  111. data/vendor/sinatra/test/haml_test.rb +91 -0
  112. data/vendor/sinatra/test/helper.rb +123 -0
  113. data/vendor/sinatra/test/helpers_test.rb +1768 -0
  114. data/vendor/sinatra/test/integration/app.rb +62 -0
  115. data/vendor/sinatra/test/integration_helper.rb +222 -0
  116. data/vendor/sinatra/test/integration_test.rb +87 -0
  117. data/vendor/sinatra/test/less_test.rb +69 -0
  118. data/vendor/sinatra/test/liquid_test.rb +59 -0
  119. data/vendor/sinatra/test/mapped_error_test.rb +305 -0
  120. data/vendor/sinatra/test/markaby_test.rb +80 -0
  121. data/vendor/sinatra/test/markdown_test.rb +82 -0
  122. data/vendor/sinatra/test/middleware_test.rb +68 -0
  123. data/vendor/sinatra/test/nokogiri_test.rb +67 -0
  124. data/vendor/sinatra/test/public/favicon.ico +0 -0
  125. data/vendor/sinatra/test/rabl_test.rb +89 -0
  126. data/vendor/sinatra/test/rack_test.rb +45 -0
  127. data/vendor/sinatra/test/radius_test.rb +59 -0
  128. data/vendor/sinatra/test/rdoc_test.rb +66 -0
  129. data/vendor/sinatra/test/readme_test.rb +120 -0
  130. data/vendor/sinatra/test/request_test.rb +45 -0
  131. data/vendor/sinatra/test/response_test.rb +64 -0
  132. data/vendor/sinatra/test/result_test.rb +76 -0
  133. data/vendor/sinatra/test/route_added_hook_test.rb +59 -0
  134. data/vendor/sinatra/test/routing_test.rb +1175 -0
  135. data/vendor/sinatra/test/sass_test.rb +116 -0
  136. data/vendor/sinatra/test/scss_test.rb +89 -0
  137. data/vendor/sinatra/test/server_test.rb +48 -0
  138. data/vendor/sinatra/test/settings_test.rb +561 -0
  139. data/vendor/sinatra/test/sinatra_test.rb +12 -0
  140. data/vendor/sinatra/test/slim_test.rb +84 -0
  141. data/vendor/sinatra/test/static_test.rb +219 -0
  142. data/vendor/sinatra/test/streaming_test.rb +149 -0
  143. data/vendor/sinatra/test/templates_test.rb +333 -0
  144. data/vendor/sinatra/test/textile_test.rb +65 -0
  145. data/vendor/sinatra/test/views/a/in_a.str +1 -0
  146. data/vendor/sinatra/test/views/ascii.erb +2 -0
  147. data/vendor/sinatra/test/views/b/in_b.str +1 -0
  148. data/vendor/sinatra/test/views/calc.html.erb +1 -0
  149. data/vendor/sinatra/test/views/error.builder +3 -0
  150. data/vendor/sinatra/test/views/error.erb +3 -0
  151. data/vendor/sinatra/test/views/error.haml +3 -0
  152. data/vendor/sinatra/test/views/error.sass +2 -0
  153. data/vendor/sinatra/test/views/explicitly_nested.str +1 -0
  154. data/vendor/sinatra/test/views/foo/hello.test +1 -0
  155. data/vendor/sinatra/test/views/hello.builder +1 -0
  156. data/vendor/sinatra/test/views/hello.coffee +1 -0
  157. data/vendor/sinatra/test/views/hello.creole +1 -0
  158. data/vendor/sinatra/test/views/hello.erb +1 -0
  159. data/vendor/sinatra/test/views/hello.haml +1 -0
  160. data/vendor/sinatra/test/views/hello.less +5 -0
  161. data/vendor/sinatra/test/views/hello.liquid +1 -0
  162. data/vendor/sinatra/test/views/hello.mab +1 -0
  163. data/vendor/sinatra/test/views/hello.md +1 -0
  164. data/vendor/sinatra/test/views/hello.nokogiri +1 -0
  165. data/vendor/sinatra/test/views/hello.rabl +2 -0
  166. data/vendor/sinatra/test/views/hello.radius +1 -0
  167. data/vendor/sinatra/test/views/hello.rdoc +1 -0
  168. data/vendor/sinatra/test/views/hello.sass +2 -0
  169. data/vendor/sinatra/test/views/hello.scss +3 -0
  170. data/vendor/sinatra/test/views/hello.slim +1 -0
  171. data/vendor/sinatra/test/views/hello.str +1 -0
  172. data/vendor/sinatra/test/views/hello.test +1 -0
  173. data/vendor/sinatra/test/views/hello.textile +1 -0
  174. data/vendor/sinatra/test/views/hello.wlang +1 -0
  175. data/vendor/sinatra/test/views/hello.yajl +1 -0
  176. data/vendor/sinatra/test/views/layout2.builder +3 -0
  177. data/vendor/sinatra/test/views/layout2.erb +2 -0
  178. data/vendor/sinatra/test/views/layout2.haml +2 -0
  179. data/vendor/sinatra/test/views/layout2.liquid +2 -0
  180. data/vendor/sinatra/test/views/layout2.mab +2 -0
  181. data/vendor/sinatra/test/views/layout2.nokogiri +3 -0
  182. data/vendor/sinatra/test/views/layout2.rabl +3 -0
  183. data/vendor/sinatra/test/views/layout2.radius +2 -0
  184. data/vendor/sinatra/test/views/layout2.slim +3 -0
  185. data/vendor/sinatra/test/views/layout2.str +2 -0
  186. data/vendor/sinatra/test/views/layout2.test +1 -0
  187. data/vendor/sinatra/test/views/layout2.wlang +2 -0
  188. data/vendor/sinatra/test/views/nested.str +1 -0
  189. data/vendor/sinatra/test/views/utf8.erb +2 -0
  190. data/vendor/sinatra/test/wlang_test.rb +70 -0
  191. data/vendor/sinatra/test/yajl_test.rb +86 -0
  192. metadata +414 -0
@@ -0,0 +1,91 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+
3
+ begin
4
+ require 'haml'
5
+
6
+ class HAMLTest < Test::Unit::TestCase
7
+ def haml_app(&block)
8
+ mock_app do
9
+ set :views, File.dirname(__FILE__) + '/views'
10
+ get('/', &block)
11
+ end
12
+ get '/'
13
+ end
14
+
15
+ it 'renders inline HAML strings' do
16
+ haml_app { haml '%h1 Hiya' }
17
+ assert ok?
18
+ assert_equal "<h1>Hiya</h1>\n", body
19
+ end
20
+
21
+ it 'renders .haml files in views path' do
22
+ haml_app { haml :hello }
23
+ assert ok?
24
+ assert_equal "<h1>Hello From Haml</h1>\n", body
25
+ end
26
+
27
+ it "renders with inline layouts" do
28
+ mock_app do
29
+ layout { %q(%h1= 'THIS. IS. ' + yield.upcase) }
30
+ get('/') { haml '%em Sparta' }
31
+ end
32
+ get '/'
33
+ assert ok?
34
+ assert_equal "<h1>THIS. IS. <EM>SPARTA</EM></h1>\n", body
35
+ end
36
+
37
+ it "renders with file layouts" do
38
+ haml_app { haml 'Hello World', :layout => :layout2 }
39
+ assert ok?
40
+ assert_equal "<h1>HAML Layout!</h1>\n<p>Hello World</p>\n", body
41
+ end
42
+
43
+ it "raises error if template not found" do
44
+ mock_app { get('/') { haml :no_such_template } }
45
+ assert_raise(Errno::ENOENT) { get('/') }
46
+ end
47
+
48
+ it "passes HAML options to the Haml engine" do
49
+ mock_app {
50
+ get('/') { haml "!!!\n%h1 Hello World", :format => :html5 }
51
+ }
52
+ get '/'
53
+ assert ok?
54
+ assert_equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n", body
55
+ end
56
+
57
+ it "passes default HAML options to the Haml engine" do
58
+ mock_app do
59
+ set :haml, {:format => :html5}
60
+ get('/') { haml "!!!\n%h1 Hello World" }
61
+ end
62
+ get '/'
63
+ assert ok?
64
+ assert_equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n", body
65
+ end
66
+
67
+ it "merges the default HAML options with the overrides and passes them to the Haml engine" do
68
+ mock_app do
69
+ set :haml, {:format => :html5, :attr_wrapper => '"'} # default HAML attr are <tag attr='single-quoted'>
70
+ get('/') { haml "!!!\n%h1{:class => :header} Hello World" }
71
+ get('/html4') {
72
+ haml "!!!\n%h1{:class => 'header'} Hello World", :format => :html4
73
+ }
74
+ end
75
+ get '/'
76
+ assert ok?
77
+ assert_equal "<!DOCTYPE html>\n<h1 class=\"header\">Hello World</h1>\n", body
78
+ get '/html4'
79
+ assert ok?
80
+ assert_match(/^<!DOCTYPE html PUBLIC (.*) HTML 4.01/, body)
81
+ end
82
+
83
+ it "is possible to pass locals" do
84
+ haml_app { haml "= foo", :locals => { :foo => 'bar' }}
85
+ assert_equal "bar\n", body
86
+ end
87
+ end
88
+
89
+ rescue LoadError
90
+ warn "#{$!.to_s}: skipping haml tests"
91
+ end
@@ -0,0 +1,123 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+ Encoding.default_external = "UTF-8" if defined? Encoding
3
+
4
+ RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE
5
+
6
+ begin
7
+ require 'rack'
8
+ rescue LoadError
9
+ require 'rubygems'
10
+ require 'rack'
11
+ end
12
+
13
+ testdir = File.dirname(__FILE__)
14
+ $LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir)
15
+
16
+ libdir = File.dirname(File.dirname(__FILE__)) + '/lib'
17
+ $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
18
+
19
+ require 'contest'
20
+ require 'rack/test'
21
+ require 'sinatra/base'
22
+
23
+ class Sinatra::Base
24
+ # Allow assertions in request context
25
+ include Test::Unit::Assertions
26
+ end
27
+
28
+ class Rack::Builder
29
+ def include?(middleware)
30
+ @ins.any? { |m| p m ; middleware === m }
31
+ end
32
+ end
33
+
34
+ Sinatra::Base.set :environment, :test
35
+
36
+ class Test::Unit::TestCase
37
+ include Rack::Test::Methods
38
+
39
+ class << self
40
+ alias_method :it, :test
41
+ alias_method :section, :context
42
+ end
43
+
44
+ def self.example(desc = nil, &block)
45
+ @example_count = 0 unless instance_variable_defined? :@example_count
46
+ @example_count += 1
47
+ it(desc || "Example #{@example_count}", &block)
48
+ end
49
+
50
+ alias_method :response, :last_response
51
+
52
+ setup do
53
+ Sinatra::Base.set :environment, :test
54
+ end
55
+
56
+ # Sets up a Sinatra::Base subclass defined with the block
57
+ # given. Used in setup or individual spec methods to establish
58
+ # the application.
59
+ def mock_app(base=Sinatra::Base, &block)
60
+ @app = Sinatra.new(base, &block)
61
+ end
62
+
63
+ def app
64
+ Rack::Lint.new(@app)
65
+ end
66
+
67
+ def body
68
+ response.body.to_s
69
+ end
70
+
71
+ def assert_body(value)
72
+ if value.respond_to? :to_str
73
+ assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "")
74
+ else
75
+ assert_match value, body
76
+ end
77
+ end
78
+
79
+ def assert_status(expected)
80
+ assert_equal Integer(expected), Integer(status)
81
+ end
82
+
83
+ def assert_like(a,b)
84
+ pattern = /id=['"][^"']*["']|\s+/
85
+ assert_equal a.strip.gsub(pattern, ""), b.strip.gsub(pattern, "")
86
+ end
87
+
88
+ def assert_include(str, substr)
89
+ assert str.include?(substr), "expected #{str.inspect} to include #{substr.inspect}"
90
+ end
91
+
92
+ def options(uri, params = {}, env = {}, &block)
93
+ request(uri, env.merge(:method => "OPTIONS", :params => params), &block)
94
+ end
95
+
96
+ def patch(uri, params = {}, env = {}, &block)
97
+ request(uri, env.merge(:method => "PATCH", :params => params), &block)
98
+ end
99
+
100
+ # Delegate other missing methods to response.
101
+ def method_missing(name, *args, &block)
102
+ if response && response.respond_to?(name)
103
+ response.send(name, *args, &block)
104
+ else
105
+ super
106
+ end
107
+ rescue Rack::Test::Error
108
+ super
109
+ end
110
+
111
+ # Also check response since we delegate there.
112
+ def respond_to?(symbol, include_private=false)
113
+ super || (response && response.respond_to?(symbol, include_private))
114
+ end
115
+
116
+ # Do not output warnings for the duration of the block.
117
+ def silence_warnings
118
+ $VERBOSE, v = nil, $VERBOSE
119
+ yield
120
+ ensure
121
+ $VERBOSE = v
122
+ end
123
+ end
@@ -0,0 +1,1768 @@
1
+ require File.expand_path('../helper', __FILE__)
2
+ require 'date'
3
+
4
+ class HelpersTest < Test::Unit::TestCase
5
+ def test_default
6
+ assert true
7
+ end
8
+
9
+ def status_app(code, &block)
10
+ code += 2 if [204, 205, 304].include? code
11
+ block ||= proc { }
12
+ mock_app do
13
+ get('/') do
14
+ status code
15
+ instance_eval(&block).inspect
16
+ end
17
+ end
18
+ get '/'
19
+ end
20
+
21
+ describe 'status' do
22
+ it 'sets the response status code' do
23
+ status_app 207
24
+ assert_equal 207, response.status
25
+ end
26
+ end
27
+
28
+ describe 'not_found?' do
29
+ it 'is true for status == 404' do
30
+ status_app(404) { not_found? }
31
+ assert_body 'true'
32
+ end
33
+
34
+ it 'is false for status gt 404' do
35
+ status_app(405) { not_found? }
36
+ assert_body 'false'
37
+ end
38
+
39
+ it 'is false for status lt 404' do
40
+ status_app(403) { not_found? }
41
+ assert_body 'false'
42
+ end
43
+ end
44
+
45
+ describe 'informational?' do
46
+ it 'is true for 1xx status' do
47
+ status_app(100 + rand(100)) { informational? }
48
+ assert_body 'true'
49
+ end
50
+
51
+ it 'is false for status > 199' do
52
+ status_app(200 + rand(400)) { informational? }
53
+ assert_body 'false'
54
+ end
55
+ end
56
+
57
+ describe 'success?' do
58
+ it 'is true for 2xx status' do
59
+ status_app(200 + rand(100)) { success? }
60
+ assert_body 'true'
61
+ end
62
+
63
+ it 'is false for status < 200' do
64
+ status_app(100 + rand(100)) { success? }
65
+ assert_body 'false'
66
+ end
67
+
68
+ it 'is false for status > 299' do
69
+ status_app(300 + rand(300)) { success? }
70
+ assert_body 'false'
71
+ end
72
+ end
73
+
74
+ describe 'redirect?' do
75
+ it 'is true for 3xx status' do
76
+ status_app(300 + rand(100)) { redirect? }
77
+ assert_body 'true'
78
+ end
79
+
80
+ it 'is false for status < 300' do
81
+ status_app(200 + rand(100)) { redirect? }
82
+ assert_body 'false'
83
+ end
84
+
85
+ it 'is false for status > 399' do
86
+ status_app(400 + rand(200)) { redirect? }
87
+ assert_body 'false'
88
+ end
89
+ end
90
+
91
+ describe 'client_error?' do
92
+ it 'is true for 4xx status' do
93
+ status_app(400 + rand(100)) { client_error? }
94
+ assert_body 'true'
95
+ end
96
+
97
+ it 'is false for status < 400' do
98
+ status_app(200 + rand(200)) { client_error? }
99
+ assert_body 'false'
100
+ end
101
+
102
+ it 'is false for status > 499' do
103
+ status_app(500 + rand(100)) { client_error? }
104
+ assert_body 'false'
105
+ end
106
+ end
107
+
108
+ describe 'server_error?' do
109
+ it 'is true for 5xx status' do
110
+ status_app(500 + rand(100)) { server_error? }
111
+ assert_body 'true'
112
+ end
113
+
114
+ it 'is false for status < 500' do
115
+ status_app(200 + rand(300)) { server_error? }
116
+ assert_body 'false'
117
+ end
118
+ end
119
+
120
+ describe 'body' do
121
+ it 'takes a block for defered body generation' do
122
+ mock_app do
123
+ get('/') { body { 'Hello World' } }
124
+ end
125
+
126
+ get '/'
127
+ assert_equal 'Hello World', body
128
+ end
129
+
130
+ it 'takes a String, Array, or other object responding to #each' do
131
+ mock_app { get('/') { body 'Hello World' } }
132
+
133
+ get '/'
134
+ assert_equal 'Hello World', body
135
+ end
136
+ end
137
+
138
+ describe 'redirect' do
139
+ it 'uses a 302 when only a path is given' do
140
+ mock_app do
141
+ get('/') do
142
+ redirect '/foo'
143
+ fail 'redirect should halt'
144
+ end
145
+ end
146
+
147
+ get '/'
148
+ assert_equal 302, status
149
+ assert_equal '', body
150
+ assert_equal 'http://example.org/foo', response['Location']
151
+ end
152
+
153
+ it 'uses the code given when specified' do
154
+ mock_app do
155
+ get('/') do
156
+ redirect '/foo', 301
157
+ fail 'redirect should halt'
158
+ end
159
+ end
160
+
161
+ get '/'
162
+ assert_equal 301, status
163
+ assert_equal '', body
164
+ assert_equal 'http://example.org/foo', response['Location']
165
+ end
166
+
167
+ it 'redirects back to request.referer when passed back' do
168
+ mock_app { get('/try_redirect') { redirect back } }
169
+
170
+ request = Rack::MockRequest.new(@app)
171
+ response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo')
172
+ assert_equal 302, response.status
173
+ assert_equal 'http://example.org/foo', response['Location']
174
+ end
175
+
176
+ it 'redirects using a non-standard HTTP port' do
177
+ mock_app { get('/') { redirect '/foo' } }
178
+
179
+ request = Rack::MockRequest.new(@app)
180
+ response = request.get('/', 'SERVER_PORT' => '81')
181
+ assert_equal 'http://example.org:81/foo', response['Location']
182
+ end
183
+
184
+ it 'redirects using a non-standard HTTPS port' do
185
+ mock_app { get('/') { redirect '/foo' } }
186
+
187
+ request = Rack::MockRequest.new(@app)
188
+ response = request.get('/', 'SERVER_PORT' => '444')
189
+ assert_equal 'http://example.org:444/foo', response['Location']
190
+ end
191
+
192
+ it 'uses 303 for post requests if request is HTTP 1.1' do
193
+ mock_app { post('/') { redirect '/'} }
194
+ post('/', {}, 'HTTP_VERSION' => 'HTTP/1.1')
195
+ assert_equal 303, status
196
+ assert_equal '', body
197
+ assert_equal 'http://example.org/', response['Location']
198
+ end
199
+
200
+ it 'uses 302 for post requests if request is HTTP 1.0' do
201
+ mock_app { post('/') { redirect '/'} }
202
+ post('/', {}, 'HTTP_VERSION' => 'HTTP/1.0')
203
+ assert_equal 302, status
204
+ assert_equal '', body
205
+ assert_equal 'http://example.org/', response['Location']
206
+ end
207
+
208
+ it 'works behind a reverse proxy' do
209
+ mock_app { get('/') { redirect '/foo' } }
210
+
211
+ request = Rack::MockRequest.new(@app)
212
+ response = request.get('/', 'HTTP_X_FORWARDED_HOST' => 'example.com', 'SERVER_PORT' => '8080')
213
+ assert_equal 'http://example.com/foo', response['Location']
214
+ end
215
+
216
+ it 'accepts absolute URIs' do
217
+ mock_app do
218
+ get('/') do
219
+ redirect 'http://google.com'
220
+ fail 'redirect should halt'
221
+ end
222
+ end
223
+
224
+ get '/'
225
+ assert_equal 302, status
226
+ assert_equal '', body
227
+ assert_equal 'http://google.com', response['Location']
228
+ end
229
+
230
+ it 'accepts absolute URIs with a different schema' do
231
+ mock_app do
232
+ get('/') do
233
+ redirect 'mailto:jsmith@example.com'
234
+ fail 'redirect should halt'
235
+ end
236
+ end
237
+
238
+ get '/'
239
+ assert_equal 302, status
240
+ assert_equal '', body
241
+ assert_equal 'mailto:jsmith@example.com', response['Location']
242
+ end
243
+
244
+ it 'accepts a URI object instead of a String' do
245
+ mock_app do
246
+ get('/') { redirect URI.parse('http://sinatrarb.com') }
247
+ end
248
+
249
+ get '/'
250
+ assert_equal 302, status
251
+ assert_equal '', body
252
+ assert_equal 'http://sinatrarb.com', response['Location']
253
+ end
254
+ end
255
+
256
+ describe 'error' do
257
+ it 'sets a status code and halts' do
258
+ mock_app do
259
+ get('/') do
260
+ error 501
261
+ fail 'error should halt'
262
+ end
263
+ end
264
+
265
+ get '/'
266
+ assert_equal 501, status
267
+ assert_equal '', body
268
+ end
269
+
270
+ it 'takes an optional body' do
271
+ mock_app do
272
+ get('/') do
273
+ error 501, 'FAIL'
274
+ fail 'error should halt'
275
+ end
276
+ end
277
+
278
+ get '/'
279
+ assert_equal 501, status
280
+ assert_equal 'FAIL', body
281
+ end
282
+
283
+ it 'uses a 500 status code when first argument is a body' do
284
+ mock_app do
285
+ get('/') do
286
+ error 'FAIL'
287
+ fail 'error should halt'
288
+ end
289
+ end
290
+
291
+ get '/'
292
+ assert_equal 500, status
293
+ assert_equal 'FAIL', body
294
+ end
295
+ end
296
+
297
+ describe 'not_found' do
298
+ it 'halts with a 404 status' do
299
+ mock_app do
300
+ get('/') do
301
+ not_found
302
+ fail 'not_found should halt'
303
+ end
304
+ end
305
+
306
+ get '/'
307
+ assert_equal 404, status
308
+ assert_equal '', body
309
+ end
310
+
311
+ it 'does not set a X-Cascade header' do
312
+ mock_app do
313
+ get('/') do
314
+ not_found
315
+ fail 'not_found should halt'
316
+ end
317
+ end
318
+
319
+ get '/'
320
+ assert_equal 404, status
321
+ assert_equal nil, response.headers['X-Cascade']
322
+ end
323
+ end
324
+
325
+ describe 'headers' do
326
+ it 'sets headers on the response object when given a Hash' do
327
+ mock_app do
328
+ get('/') do
329
+ headers 'X-Foo' => 'bar', 'X-Baz' => 'bling'
330
+ 'kthx'
331
+ end
332
+ end
333
+
334
+ get '/'
335
+ assert ok?
336
+ assert_equal 'bar', response['X-Foo']
337
+ assert_equal 'bling', response['X-Baz']
338
+ assert_equal 'kthx', body
339
+ end
340
+
341
+ it 'returns the response headers hash when no hash provided' do
342
+ mock_app do
343
+ get('/') do
344
+ headers['X-Foo'] = 'bar'
345
+ 'kthx'
346
+ end
347
+ end
348
+
349
+ get '/'
350
+ assert ok?
351
+ assert_equal 'bar', response['X-Foo']
352
+ end
353
+ end
354
+
355
+ describe 'session' do
356
+ it 'uses the existing rack.session' do
357
+ mock_app do
358
+ get('/') do
359
+ session[:foo]
360
+ end
361
+ end
362
+
363
+ get('/', {}, { 'rack.session' => { :foo => 'bar' } })
364
+ assert_equal 'bar', body
365
+ end
366
+
367
+ it 'creates a new session when none provided' do
368
+ mock_app do
369
+ enable :sessions
370
+
371
+ get('/') do
372
+ assert session[:foo].nil?
373
+ session[:foo] = 'bar'
374
+ redirect '/hi'
375
+ end
376
+
377
+ get('/hi') do
378
+ "hi #{session[:foo]}"
379
+ end
380
+ end
381
+
382
+ get '/'
383
+ follow_redirect!
384
+ assert_equal 'hi bar', body
385
+ end
386
+
387
+ it 'inserts session middleware' do
388
+ mock_app do
389
+ enable :sessions
390
+
391
+ get('/') do
392
+ assert env['rack.session']
393
+ assert env['rack.session.options']
394
+ 'ok'
395
+ end
396
+ end
397
+
398
+ get '/'
399
+ assert_body 'ok'
400
+ end
401
+
402
+ it 'sets a default session secret' do
403
+ mock_app do
404
+ enable :sessions
405
+
406
+ get('/') do
407
+ secret = env['rack.session.options'][:secret]
408
+ assert secret
409
+ assert_equal secret, settings.session_secret
410
+ 'ok'
411
+ end
412
+ end
413
+
414
+ get '/'
415
+ assert_body 'ok'
416
+ end
417
+
418
+ it 'allows disabling session secret' do
419
+ mock_app do
420
+ enable :sessions
421
+ disable :session_secret
422
+
423
+ get('/') do
424
+ assert !env['rack.session.options'].include?(:session_secret)
425
+ 'ok'
426
+ end
427
+ end
428
+
429
+ get '/'
430
+ assert_body 'ok'
431
+ end
432
+
433
+ it 'accepts an options hash' do
434
+ mock_app do
435
+ set :sessions, :foo => :bar
436
+
437
+ get('/') do
438
+ assert_equal env['rack.session.options'][:foo], :bar
439
+ 'ok'
440
+ end
441
+ end
442
+
443
+ get '/'
444
+ assert_body 'ok'
445
+ end
446
+ end
447
+
448
+ describe 'mime_type' do
449
+ include Sinatra::Helpers
450
+
451
+ it "looks up mime types in Rack's MIME registry" do
452
+ Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
453
+ assert_equal 'application/foo', mime_type('foo')
454
+ assert_equal 'application/foo', mime_type('.foo')
455
+ assert_equal 'application/foo', mime_type(:foo)
456
+ end
457
+
458
+ it 'returns nil when given nil' do
459
+ assert mime_type(nil).nil?
460
+ end
461
+
462
+ it 'returns nil when media type not registered' do
463
+ assert mime_type(:bizzle).nil?
464
+ end
465
+
466
+ it 'returns the argument when given a media type string' do
467
+ assert_equal 'text/plain', mime_type('text/plain')
468
+ end
469
+ end
470
+
471
+ test 'Base.mime_type registers mime type' do
472
+ mock_app do
473
+ mime_type :foo, 'application/foo'
474
+
475
+ get('/') do
476
+ "foo is #{mime_type(:foo)}"
477
+ end
478
+ end
479
+
480
+ get '/'
481
+ assert_equal 'foo is application/foo', body
482
+ end
483
+
484
+ describe 'content_type' do
485
+ it 'sets the Content-Type header' do
486
+ mock_app do
487
+ get('/') do
488
+ content_type 'text/plain'
489
+ 'Hello World'
490
+ end
491
+ end
492
+
493
+ get '/'
494
+ assert_equal 'text/plain;charset=utf-8', response['Content-Type']
495
+ assert_equal 'Hello World', body
496
+ end
497
+
498
+ it 'takes media type parameters (like charset=)' do
499
+ mock_app do
500
+ get('/') do
501
+ content_type 'text/html', :charset => 'latin1'
502
+ "<h1>Hello, World</h1>"
503
+ end
504
+ end
505
+
506
+ get '/'
507
+ assert ok?
508
+ assert_equal 'text/html;charset=latin1', response['Content-Type']
509
+ assert_equal "<h1>Hello, World</h1>", body
510
+ end
511
+
512
+ it "looks up symbols in Rack's mime types dictionary" do
513
+ Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
514
+ mock_app do
515
+ get('/foo.xml') do
516
+ content_type :foo
517
+ "I AM FOO"
518
+ end
519
+ end
520
+
521
+ get '/foo.xml'
522
+ assert ok?
523
+ assert_equal 'application/foo', response['Content-Type']
524
+ assert_equal 'I AM FOO', body
525
+ end
526
+
527
+ it 'fails when no mime type is registered for the argument provided' do
528
+ mock_app do
529
+ get('/foo.xml') do
530
+ content_type :bizzle
531
+ "I AM FOO"
532
+ end
533
+ end
534
+
535
+ assert_raise(RuntimeError) { get '/foo.xml' }
536
+ end
537
+
538
+ it 'only sets default charset for specific mime types' do
539
+ tests_ran = false
540
+ mock_app do
541
+ mime_type :foo, 'text/foo'
542
+ mime_type :bar, 'application/bar'
543
+ mime_type :baz, 'application/baz'
544
+ add_charset << mime_type(:baz)
545
+ get('/') do
546
+ assert_equal content_type(:txt), 'text/plain;charset=utf-8'
547
+ assert_equal content_type(:css), 'text/css;charset=utf-8'
548
+ assert_equal content_type(:html), 'text/html;charset=utf-8'
549
+ assert_equal content_type(:foo), 'text/foo;charset=utf-8'
550
+ assert_equal content_type(:xml), 'application/xml;charset=utf-8'
551
+ assert_equal content_type(:xhtml), 'application/xhtml+xml;charset=utf-8'
552
+ assert_equal content_type(:js), 'application/javascript;charset=utf-8'
553
+ assert_equal content_type(:json), 'application/json;charset=utf-8'
554
+ assert_equal content_type(:bar), 'application/bar'
555
+ assert_equal content_type(:png), 'image/png'
556
+ assert_equal content_type(:baz), 'application/baz;charset=utf-8'
557
+ tests_ran = true
558
+ "done"
559
+ end
560
+ end
561
+
562
+ get '/'
563
+ assert tests_ran
564
+ end
565
+
566
+ it 'handles already present params' do
567
+ mock_app do
568
+ get('/') do
569
+ content_type 'foo/bar;level=1', :charset => 'utf-8'
570
+ 'ok'
571
+ end
572
+ end
573
+
574
+ get '/'
575
+ assert_equal 'foo/bar;level=1, charset=utf-8', response['Content-Type']
576
+ end
577
+
578
+ it 'does not add charset if present' do
579
+ mock_app do
580
+ get('/') do
581
+ content_type 'text/plain;charset=utf-16'
582
+ 'ok'
583
+ end
584
+ end
585
+
586
+ get '/'
587
+ assert_equal 'text/plain;charset=utf-16', response['Content-Type']
588
+ end
589
+ end
590
+
591
+ describe 'attachment' do
592
+ def attachment_app(filename=nil)
593
+ mock_app do
594
+ get('/attachment') do
595
+ attachment filename
596
+ response.write("<sinatra></sinatra>")
597
+ end
598
+ end
599
+ end
600
+
601
+ it 'sets the Content-Type response header' do
602
+ attachment_app('test.xml')
603
+ get '/attachment'
604
+ assert_equal 'application/xml;charset=utf-8', response['Content-Type']
605
+ assert_equal '<sinatra></sinatra>', body
606
+ end
607
+
608
+ it 'sets the Content-Type response header without extname' do
609
+ attachment_app('test')
610
+ get '/attachment'
611
+ assert_equal 'text/html;charset=utf-8', response['Content-Type']
612
+ assert_equal '<sinatra></sinatra>', body
613
+ end
614
+
615
+ it 'sets the Content-Type response header with extname' do
616
+ mock_app do
617
+ get('/attachment') do
618
+ content_type :atom
619
+ attachment 'test.xml'
620
+ response.write("<sinatra></sinatra>")
621
+ end
622
+ end
623
+
624
+ get '/attachment'
625
+ assert_equal 'application/atom+xml', response['Content-Type']
626
+ assert_equal '<sinatra></sinatra>', body
627
+ end
628
+
629
+ end
630
+
631
+ describe 'send_file' do
632
+ setup do
633
+ @file = File.dirname(__FILE__) + '/file.txt'
634
+ File.open(@file, 'wb') { |io| io.write('Hello World') }
635
+ end
636
+
637
+ def teardown
638
+ File.unlink @file
639
+ @file = nil
640
+ end
641
+
642
+ def send_file_app(opts={})
643
+ path = @file
644
+ mock_app {
645
+ get '/file.txt' do
646
+ send_file path, opts
647
+ end
648
+ }
649
+ end
650
+
651
+ it "sends the contents of the file" do
652
+ send_file_app
653
+ get '/file.txt'
654
+ assert ok?
655
+ assert_equal 'Hello World', body
656
+ end
657
+
658
+ it 'sets the Content-Type response header if a mime-type can be located' do
659
+ send_file_app
660
+ get '/file.txt'
661
+ assert_equal 'text/plain;charset=utf-8', response['Content-Type']
662
+ end
663
+
664
+ it 'sets the Content-Type response header if type option is set to a file extesion' do
665
+ send_file_app :type => 'html'
666
+ get '/file.txt'
667
+ assert_equal 'text/html;charset=utf-8', response['Content-Type']
668
+ end
669
+
670
+ it 'sets the Content-Type response header if type option is set to a mime type' do
671
+ send_file_app :type => 'application/octet-stream'
672
+ get '/file.txt'
673
+ assert_equal 'application/octet-stream', response['Content-Type']
674
+ end
675
+
676
+ it 'sets the Content-Length response header' do
677
+ send_file_app
678
+ get '/file.txt'
679
+ assert_equal 'Hello World'.length.to_s, response['Content-Length']
680
+ end
681
+
682
+ it 'sets the Last-Modified response header' do
683
+ send_file_app
684
+ get '/file.txt'
685
+ assert_equal File.mtime(@file).httpdate, response['Last-Modified']
686
+ end
687
+
688
+ it 'allows passing in a differen Last-Modified response header with :last_modified' do
689
+ time = Time.now
690
+ send_file_app :last_modified => time
691
+ get '/file.txt'
692
+ assert_equal time.httpdate, response['Last-Modified']
693
+ end
694
+
695
+ it "returns a 404 when not found" do
696
+ mock_app {
697
+ get('/') { send_file 'this-file-does-not-exist.txt' }
698
+ }
699
+ get '/'
700
+ assert not_found?
701
+ end
702
+
703
+ it "does not set the Content-Disposition header by default" do
704
+ send_file_app
705
+ get '/file.txt'
706
+ assert_nil response['Content-Disposition']
707
+ end
708
+
709
+ it "sets the Content-Disposition header when :disposition set to 'attachment'" do
710
+ send_file_app :disposition => 'attachment'
711
+ get '/file.txt'
712
+ assert_equal 'attachment; filename="file.txt"', response['Content-Disposition']
713
+ end
714
+
715
+ it "does not set add a file name if filename is false" do
716
+ send_file_app :disposition => 'inline', :filename => false
717
+ get '/file.txt'
718
+ assert_equal 'inline', response['Content-Disposition']
719
+ end
720
+
721
+ it "sets the Content-Disposition header when :disposition set to 'inline'" do
722
+ send_file_app :disposition => 'inline'
723
+ get '/file.txt'
724
+ assert_equal 'inline; filename="file.txt"', response['Content-Disposition']
725
+ end
726
+
727
+ it "sets the Content-Disposition header when :filename provided" do
728
+ send_file_app :filename => 'foo.txt'
729
+ get '/file.txt'
730
+ assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition']
731
+ end
732
+
733
+ it 'allows setting a custom status code' do
734
+ send_file_app :status => 201
735
+ get '/file.txt'
736
+ assert_status 201
737
+ end
738
+
739
+ it "is able to send files with unkown mime type" do
740
+ @file = File.dirname(__FILE__) + '/file.foobar'
741
+ File.open(@file, 'wb') { |io| io.write('Hello World') }
742
+ send_file_app
743
+ get '/file.txt'
744
+ assert_equal 'application/octet-stream', response['Content-Type']
745
+ end
746
+
747
+ it "does not override Content-Type if already set and no explicit type is given" do
748
+ path = @file
749
+ mock_app do
750
+ get('/') do
751
+ content_type :png
752
+ send_file path
753
+ end
754
+ end
755
+ get '/'
756
+ assert_equal 'image/png', response['Content-Type']
757
+ end
758
+
759
+ it "does override Content-Type even if already set, if explicit type is given" do
760
+ path = @file
761
+ mock_app do
762
+ get('/') do
763
+ content_type :png
764
+ send_file path, :type => :gif
765
+ end
766
+ end
767
+ get '/'
768
+ assert_equal 'image/gif', response['Content-Type']
769
+ end
770
+ end
771
+
772
+ describe 'cache_control' do
773
+ setup do
774
+ mock_app do
775
+ get('/foo') do
776
+ cache_control :public, :no_cache, :max_age => 60.0
777
+ 'Hello World'
778
+ end
779
+
780
+ get('/bar') do
781
+ cache_control :public, :no_cache
782
+ 'Hello World'
783
+ end
784
+ end
785
+ end
786
+
787
+ it 'sets the Cache-Control header' do
788
+ get '/foo'
789
+ assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
790
+ end
791
+
792
+ it 'last argument does not have to be a hash' do
793
+ get '/bar'
794
+ assert_equal ['public', 'no-cache'], response['Cache-Control'].split(', ')
795
+ end
796
+ end
797
+
798
+ describe 'expires' do
799
+ setup do
800
+ mock_app do
801
+ get('/foo') do
802
+ expires 60, :public, :no_cache
803
+ 'Hello World'
804
+ end
805
+
806
+ get('/bar') { expires Time.now }
807
+
808
+ get('/baz') { expires Time.at(0) }
809
+
810
+ get('/blah') do
811
+ obj = Object.new
812
+ def obj.method_missing(*a, &b) 60.send(*a, &b) end
813
+ def obj.is_a?(thing) 60.is_a?(thing) end
814
+ expires obj, :public, :no_cache
815
+ 'Hello World'
816
+ end
817
+
818
+ get('/boom') { expires '9999' }
819
+ end
820
+ end
821
+
822
+ it 'sets the Cache-Control header' do
823
+ get '/foo'
824
+ assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
825
+ end
826
+
827
+ it 'sets the Expires header' do
828
+ get '/foo'
829
+ assert_not_nil response['Expires']
830
+ end
831
+
832
+ it 'allows passing Time.now objects' do
833
+ get '/bar'
834
+ assert_not_nil response['Expires']
835
+ end
836
+
837
+ it 'allows passing Time.at objects' do
838
+ get '/baz'
839
+ assert_equal 'Thu, 01 Jan 1970 00:00:00 GMT', response['Expires']
840
+ end
841
+
842
+ it 'accepts values pretending to be a Numeric (like ActiveSupport::Duration)' do
843
+ get '/blah'
844
+ assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
845
+ end
846
+
847
+ it 'fails when Time.parse raises an ArgumentError' do
848
+ assert_raise(ArgumentError) { get '/boom' }
849
+ end
850
+ end
851
+
852
+ describe 'last_modified' do
853
+ it 'ignores nil' do
854
+ mock_app { get('/') { last_modified nil; 200; } }
855
+
856
+ get '/'
857
+ assert ! response['Last-Modified']
858
+ end
859
+
860
+ it 'does not change a status other than 200' do
861
+ mock_app do
862
+ get('/') do
863
+ status 299
864
+ last_modified Time.at(0)
865
+ 'ok'
866
+ end
867
+ end
868
+
869
+ get('/', {}, 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT')
870
+ assert_status 299
871
+ assert_body 'ok'
872
+ end
873
+
874
+ [Time.now, DateTime.now, Date.today, Time.now.to_i,
875
+ Struct.new(:to_time).new(Time.now) ].each do |last_modified_time|
876
+ describe "with #{last_modified_time.class.name}" do
877
+ setup do
878
+ mock_app do
879
+ get('/') do
880
+ last_modified last_modified_time
881
+ 'Boo!'
882
+ end
883
+ end
884
+ wrapper = Object.new.extend Sinatra::Helpers
885
+ @last_modified_time = wrapper.time_for last_modified_time
886
+ end
887
+
888
+ # fixes strange missing test error when running complete test suite.
889
+ it("does not complain about missing tests") { }
890
+
891
+ context "when there's no If-Modified-Since header" do
892
+ it 'sets the Last-Modified header to a valid RFC 2616 date value' do
893
+ get '/'
894
+ assert_equal @last_modified_time.httpdate, response['Last-Modified']
895
+ end
896
+
897
+ it 'conditional GET misses and returns a body' do
898
+ get '/'
899
+ assert_equal 200, status
900
+ assert_equal 'Boo!', body
901
+ end
902
+ end
903
+
904
+ context "when there's an invalid If-Modified-Since header" do
905
+ it 'sets the Last-Modified header to a valid RFC 2616 date value' do
906
+ get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' })
907
+ assert_equal @last_modified_time.httpdate, response['Last-Modified']
908
+ end
909
+
910
+ it 'conditional GET misses and returns a body' do
911
+ get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' })
912
+ assert_equal 200, status
913
+ assert_equal 'Boo!', body
914
+ end
915
+ end
916
+
917
+ context "when the resource has been modified since the If-Modified-Since header date" do
918
+ it 'sets the Last-Modified header to a valid RFC 2616 date value' do
919
+ get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate })
920
+ assert_equal @last_modified_time.httpdate, response['Last-Modified']
921
+ end
922
+
923
+ it 'conditional GET misses and returns a body' do
924
+ get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate })
925
+ assert_equal 200, status
926
+ assert_equal 'Boo!', body
927
+ end
928
+
929
+ it 'does not rely on string comparison' do
930
+ mock_app do
931
+ get('/compare') do
932
+ last_modified "Mon, 18 Oct 2010 20:57:11 GMT"
933
+ "foo"
934
+ end
935
+ end
936
+
937
+ get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2010 23:43:52 GMT' })
938
+ assert_equal 200, status
939
+ assert_equal 'foo', body
940
+ get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' })
941
+ assert_equal 304, status
942
+ assert_equal '', body
943
+ end
944
+ end
945
+
946
+ context "when the resource has been modified on the exact If-Modified-Since header date" do
947
+ it 'sets the Last-Modified header to a valid RFC 2616 date value' do
948
+ get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate })
949
+ assert_equal @last_modified_time.httpdate, response['Last-Modified']
950
+ end
951
+
952
+ it 'conditional GET matches and halts' do
953
+ get( '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate })
954
+ assert_equal 304, status
955
+ assert_equal '', body
956
+ end
957
+ end
958
+
959
+ context "when the resource hasn't been modified since the If-Modified-Since header date" do
960
+ it 'sets the Last-Modified header to a valid RFC 2616 date value' do
961
+ get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate })
962
+ assert_equal @last_modified_time.httpdate, response['Last-Modified']
963
+ end
964
+
965
+ it 'conditional GET matches and halts' do
966
+ get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate })
967
+ assert_equal 304, status
968
+ assert_equal '', body
969
+ end
970
+ end
971
+
972
+ context "If-Unmodified-Since" do
973
+ it 'results in 200 if resource has not been modified' do
974
+ get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' })
975
+ assert_equal 200, status
976
+ assert_equal 'Boo!', body
977
+ end
978
+
979
+ it 'results in 412 if resource has been modified' do
980
+ get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.at(0).httpdate })
981
+ assert_equal 412, status
982
+ assert_equal '', body
983
+ end
984
+ end
985
+ end
986
+ end
987
+ end
988
+
989
+ describe 'etag' do
990
+ context "safe requests" do
991
+ it 'returns 200 for normal requests' do
992
+ mock_app do
993
+ get('/') do
994
+ etag 'foo'
995
+ 'ok'
996
+ end
997
+ end
998
+
999
+ get '/'
1000
+ assert_status 200
1001
+ assert_body 'ok'
1002
+ end
1003
+
1004
+ context "If-None-Match" do
1005
+ it 'returns 304 when If-None-Match is *' do
1006
+ mock_app do
1007
+ get('/') do
1008
+ etag 'foo'
1009
+ 'ok'
1010
+ end
1011
+ end
1012
+
1013
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
1014
+ assert_status 304
1015
+ assert_body ''
1016
+ end
1017
+
1018
+ it 'returns 200 when If-None-Match is * for new resources' do
1019
+ mock_app do
1020
+ get('/') do
1021
+ etag 'foo', :new_resource => true
1022
+ 'ok'
1023
+ end
1024
+ end
1025
+
1026
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
1027
+ assert_status 200
1028
+ assert_body 'ok'
1029
+ end
1030
+
1031
+ it 'returns 304 when If-None-Match is * for existing resources' do
1032
+ mock_app do
1033
+ get('/') do
1034
+ etag 'foo', :new_resource => false
1035
+ 'ok'
1036
+ end
1037
+ end
1038
+
1039
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
1040
+ assert_status 304
1041
+ assert_body ''
1042
+ end
1043
+
1044
+ it 'returns 304 when If-None-Match is the etag' do
1045
+ mock_app do
1046
+ get('/') do
1047
+ etag 'foo'
1048
+ 'ok'
1049
+ end
1050
+ end
1051
+
1052
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
1053
+ assert_status 304
1054
+ assert_body ''
1055
+ end
1056
+
1057
+ it 'returns 304 when If-None-Match includes the etag' do
1058
+ mock_app do
1059
+ get('/') do
1060
+ etag 'foo'
1061
+ 'ok'
1062
+ end
1063
+ end
1064
+
1065
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
1066
+ assert_status 304
1067
+ assert_body ''
1068
+ end
1069
+
1070
+ it 'returns 200 when If-None-Match does not include the etag' do
1071
+ mock_app do
1072
+ get('/') do
1073
+ etag 'foo'
1074
+ 'ok'
1075
+ end
1076
+ end
1077
+
1078
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
1079
+ assert_status 200
1080
+ assert_body 'ok'
1081
+ end
1082
+
1083
+ it 'ignores If-Modified-Since if If-None-Match does not match' do
1084
+ mock_app do
1085
+ get('/') do
1086
+ etag 'foo'
1087
+ last_modified Time.at(0)
1088
+ 'ok'
1089
+ end
1090
+ end
1091
+
1092
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
1093
+ assert_status 200
1094
+ assert_body 'ok'
1095
+ end
1096
+
1097
+ it 'does not change a status code other than 2xx or 304' do
1098
+ mock_app do
1099
+ get('/') do
1100
+ status 499
1101
+ etag 'foo'
1102
+ 'ok'
1103
+ end
1104
+ end
1105
+
1106
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
1107
+ assert_status 499
1108
+ assert_body 'ok'
1109
+ end
1110
+
1111
+ it 'does change 2xx status codes' do
1112
+ mock_app do
1113
+ get('/') do
1114
+ status 299
1115
+ etag 'foo'
1116
+ 'ok'
1117
+ end
1118
+ end
1119
+
1120
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
1121
+ assert_status 304
1122
+ assert_body ''
1123
+ end
1124
+
1125
+ it 'does not send a body on 304 status codes' do
1126
+ mock_app do
1127
+ get('/') do
1128
+ status 304
1129
+ etag 'foo'
1130
+ 'ok'
1131
+ end
1132
+ end
1133
+
1134
+ get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
1135
+ assert_status 304
1136
+ assert_body ''
1137
+ end
1138
+ end
1139
+
1140
+ context "If-Match" do
1141
+ it 'returns 200 when If-Match is the etag' do
1142
+ mock_app do
1143
+ get('/') do
1144
+ etag 'foo'
1145
+ 'ok'
1146
+ end
1147
+ end
1148
+
1149
+ get('/', {}, 'HTTP_IF_MATCH' => '"foo"')
1150
+ assert_status 200
1151
+ assert_body 'ok'
1152
+ end
1153
+
1154
+ it 'returns 200 when If-Match includes the etag' do
1155
+ mock_app do
1156
+ get('/') do
1157
+ etag 'foo'
1158
+ 'ok'
1159
+ end
1160
+ end
1161
+
1162
+ get('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
1163
+ assert_status 200
1164
+ assert_body 'ok'
1165
+ end
1166
+
1167
+ it 'returns 200 when If-Match is *' do
1168
+ mock_app do
1169
+ get('/') do
1170
+ etag 'foo'
1171
+ 'ok'
1172
+ end
1173
+ end
1174
+
1175
+ get('/', {}, 'HTTP_IF_MATCH' => '*')
1176
+ assert_status 200
1177
+ assert_body 'ok'
1178
+ end
1179
+
1180
+ it 'returns 412 when If-Match is * for new resources' do
1181
+ mock_app do
1182
+ get('/') do
1183
+ etag 'foo', :new_resource => true
1184
+ 'ok'
1185
+ end
1186
+ end
1187
+
1188
+ get('/', {}, 'HTTP_IF_MATCH' => '*')
1189
+ assert_status 412
1190
+ assert_body ''
1191
+ end
1192
+
1193
+ it 'returns 200 when If-Match is * for existing resources' do
1194
+ mock_app do
1195
+ get('/') do
1196
+ etag 'foo', :new_resource => false
1197
+ 'ok'
1198
+ end
1199
+ end
1200
+
1201
+ get('/', {}, 'HTTP_IF_MATCH' => '*')
1202
+ assert_status 200
1203
+ assert_body 'ok'
1204
+ end
1205
+
1206
+ it 'returns 412 when If-Match does not include the etag' do
1207
+ mock_app do
1208
+ get('/') do
1209
+ etag 'foo'
1210
+ 'ok'
1211
+ end
1212
+ end
1213
+
1214
+ get('/', {}, 'HTTP_IF_MATCH' => '"bar"')
1215
+ assert_status 412
1216
+ assert_body ''
1217
+ end
1218
+ end
1219
+ end
1220
+
1221
+ context "idempotent requests" do
1222
+ it 'returns 200 for normal requests' do
1223
+ mock_app do
1224
+ put('/') do
1225
+ etag 'foo'
1226
+ 'ok'
1227
+ end
1228
+ end
1229
+
1230
+ put '/'
1231
+ assert_status 200
1232
+ assert_body 'ok'
1233
+ end
1234
+
1235
+ context "If-None-Match" do
1236
+ it 'returns 412 when If-None-Match is *' do
1237
+ mock_app do
1238
+ put('/') do
1239
+ etag 'foo'
1240
+ 'ok'
1241
+ end
1242
+ end
1243
+
1244
+ put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
1245
+ assert_status 412
1246
+ assert_body ''
1247
+ end
1248
+
1249
+ it 'returns 200 when If-None-Match is * for new resources' do
1250
+ mock_app do
1251
+ put('/') do
1252
+ etag 'foo', :new_resource => true
1253
+ 'ok'
1254
+ end
1255
+ end
1256
+
1257
+ put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
1258
+ assert_status 200
1259
+ assert_body 'ok'
1260
+ end
1261
+
1262
+ it 'returns 412 when If-None-Match is * for existing resources' do
1263
+ mock_app do
1264
+ put('/') do
1265
+ etag 'foo', :new_resource => false
1266
+ 'ok'
1267
+ end
1268
+ end
1269
+
1270
+ put('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
1271
+ assert_status 412
1272
+ assert_body ''
1273
+ end
1274
+
1275
+ it 'returns 412 when If-None-Match is the etag' do
1276
+ mock_app do
1277
+ put '/' do
1278
+ etag 'foo'
1279
+ 'ok'
1280
+ end
1281
+ end
1282
+
1283
+ put('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
1284
+ assert_status 412
1285
+ assert_body ''
1286
+ end
1287
+
1288
+ it 'returns 412 when If-None-Match includes the etag' do
1289
+ mock_app do
1290
+ put('/') do
1291
+ etag 'foo'
1292
+ 'ok'
1293
+ end
1294
+ end
1295
+
1296
+ put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
1297
+ assert_status 412
1298
+ assert_body ''
1299
+ end
1300
+
1301
+ it 'returns 200 when If-None-Match does not include the etag' do
1302
+ mock_app do
1303
+ put('/') do
1304
+ etag 'foo'
1305
+ 'ok'
1306
+ end
1307
+ end
1308
+
1309
+ put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
1310
+ assert_status 200
1311
+ assert_body 'ok'
1312
+ end
1313
+
1314
+ it 'ignores If-Modified-Since if If-None-Match does not match' do
1315
+ mock_app do
1316
+ put('/') do
1317
+ etag 'foo'
1318
+ last_modified Time.at(0)
1319
+ 'ok'
1320
+ end
1321
+ end
1322
+
1323
+ put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
1324
+ assert_status 200
1325
+ assert_body 'ok'
1326
+ end
1327
+ end
1328
+
1329
+ context "If-Match" do
1330
+ it 'returns 200 when If-Match is the etag' do
1331
+ mock_app do
1332
+ put('/') do
1333
+ etag 'foo'
1334
+ 'ok'
1335
+ end
1336
+ end
1337
+
1338
+ put('/', {}, 'HTTP_IF_MATCH' => '"foo"')
1339
+ assert_status 200
1340
+ assert_body 'ok'
1341
+ end
1342
+
1343
+ it 'returns 200 when If-Match includes the etag' do
1344
+ mock_app do
1345
+ put('/') do
1346
+ etag 'foo'
1347
+ 'ok'
1348
+ end
1349
+ end
1350
+
1351
+ put('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
1352
+ assert_status 200
1353
+ assert_body 'ok'
1354
+ end
1355
+
1356
+ it 'returns 200 when If-Match is *' do
1357
+ mock_app do
1358
+ put('/') do
1359
+ etag 'foo'
1360
+ 'ok'
1361
+ end
1362
+ end
1363
+
1364
+ put('/', {}, 'HTTP_IF_MATCH' => '*')
1365
+ assert_status 200
1366
+ assert_body 'ok'
1367
+ end
1368
+
1369
+ it 'returns 412 when If-Match is * for new resources' do
1370
+ mock_app do
1371
+ put('/') do
1372
+ etag 'foo', :new_resource => true
1373
+ 'ok'
1374
+ end
1375
+ end
1376
+
1377
+ put('/', {}, 'HTTP_IF_MATCH' => '*')
1378
+ assert_status 412
1379
+ assert_body ''
1380
+ end
1381
+
1382
+ it 'returns 200 when If-Match is * for existing resources' do
1383
+ mock_app do
1384
+ put('/') do
1385
+ etag 'foo', :new_resource => false
1386
+ 'ok'
1387
+ end
1388
+ end
1389
+
1390
+ put('/', {}, 'HTTP_IF_MATCH' => '*')
1391
+ assert_status 200
1392
+ assert_body 'ok'
1393
+ end
1394
+
1395
+ it 'returns 412 when If-Match does not include the etag' do
1396
+ mock_app do
1397
+ put('/') do
1398
+ etag 'foo'
1399
+ 'ok'
1400
+ end
1401
+ end
1402
+
1403
+ put('/', {}, 'HTTP_IF_MATCH' => '"bar"')
1404
+ assert_status 412
1405
+ assert_body ''
1406
+ end
1407
+ end
1408
+ end
1409
+
1410
+ context "post requests" do
1411
+ it 'returns 200 for normal requests' do
1412
+ mock_app do
1413
+ post('/') do
1414
+ etag 'foo'
1415
+ 'ok'
1416
+ end
1417
+ end
1418
+
1419
+ post('/')
1420
+ assert_status 200
1421
+ assert_body 'ok'
1422
+ end
1423
+
1424
+ context "If-None-Match" do
1425
+ it 'returns 200 when If-None-Match is *' do
1426
+ mock_app do
1427
+ post('/') do
1428
+ etag 'foo'
1429
+ 'ok'
1430
+ end
1431
+ end
1432
+
1433
+ post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
1434
+ assert_status 200
1435
+ assert_body 'ok'
1436
+ end
1437
+
1438
+ it 'returns 200 when If-None-Match is * for new resources' do
1439
+ mock_app do
1440
+ post('/') do
1441
+ etag 'foo', :new_resource => true
1442
+ 'ok'
1443
+ end
1444
+ end
1445
+
1446
+ post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
1447
+ assert_status 200
1448
+ assert_body 'ok'
1449
+ end
1450
+
1451
+ it 'returns 412 when If-None-Match is * for existing resources' do
1452
+ mock_app do
1453
+ post('/') do
1454
+ etag 'foo', :new_resource => false
1455
+ 'ok'
1456
+ end
1457
+ end
1458
+
1459
+ post('/', {}, 'HTTP_IF_NONE_MATCH' => '*')
1460
+ assert_status 412
1461
+ assert_body ''
1462
+ end
1463
+
1464
+ it 'returns 412 when If-None-Match is the etag' do
1465
+ mock_app do
1466
+ post('/') do
1467
+ etag 'foo'
1468
+ 'ok'
1469
+ end
1470
+ end
1471
+
1472
+ post('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"')
1473
+ assert_status 412
1474
+ assert_body ''
1475
+ end
1476
+
1477
+ it 'returns 412 when If-None-Match includes the etag' do
1478
+ mock_app do
1479
+ post('/') do
1480
+ etag 'foo'
1481
+ 'ok'
1482
+ end
1483
+ end
1484
+
1485
+ post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"')
1486
+ assert_status 412
1487
+ assert_body ''
1488
+ end
1489
+
1490
+ it 'returns 200 when If-None-Match does not include the etag' do
1491
+ mock_app do
1492
+ post('/') do
1493
+ etag 'foo'
1494
+ 'ok'
1495
+ end
1496
+ end
1497
+
1498
+ post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
1499
+ assert_status 200
1500
+ assert_body 'ok'
1501
+ end
1502
+
1503
+ it 'ignores If-Modified-Since if If-None-Match does not match' do
1504
+ mock_app do
1505
+ post('/') do
1506
+ etag 'foo'
1507
+ last_modified Time.at(0)
1508
+ 'ok'
1509
+ end
1510
+ end
1511
+
1512
+ post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"')
1513
+ assert_status 200
1514
+ assert_body 'ok'
1515
+ end
1516
+ end
1517
+
1518
+ context "If-Match" do
1519
+ it 'returns 200 when If-Match is the etag' do
1520
+ mock_app do
1521
+ post('/') do
1522
+ etag 'foo'
1523
+ 'ok'
1524
+ end
1525
+ end
1526
+
1527
+ post('/', {}, 'HTTP_IF_MATCH' => '"foo"')
1528
+ assert_status 200
1529
+ assert_body 'ok'
1530
+ end
1531
+
1532
+ it 'returns 200 when If-Match includes the etag' do
1533
+ mock_app do
1534
+ post('/') do
1535
+ etag 'foo'
1536
+ 'ok'
1537
+ end
1538
+ end
1539
+
1540
+ post('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"')
1541
+ assert_status 200
1542
+ assert_body 'ok'
1543
+ end
1544
+
1545
+ it 'returns 412 when If-Match is *' do
1546
+ mock_app do
1547
+ post('/') do
1548
+ etag 'foo'
1549
+ 'ok'
1550
+ end
1551
+ end
1552
+
1553
+ post('/', {}, 'HTTP_IF_MATCH' => '*')
1554
+ assert_status 412
1555
+ assert_body ''
1556
+ end
1557
+
1558
+ it 'returns 412 when If-Match is * for new resources' do
1559
+ mock_app do
1560
+ post('/') do
1561
+ etag 'foo', :new_resource => true
1562
+ 'ok'
1563
+ end
1564
+ end
1565
+
1566
+ post('/', {}, 'HTTP_IF_MATCH' => '*')
1567
+ assert_status 412
1568
+ assert_body ''
1569
+ end
1570
+
1571
+ it 'returns 200 when If-Match is * for existing resources' do
1572
+ mock_app do
1573
+ post('/') do
1574
+ etag 'foo', :new_resource => false
1575
+ 'ok'
1576
+ end
1577
+ end
1578
+
1579
+ post('/', {}, 'HTTP_IF_MATCH' => '*')
1580
+ assert_status 200
1581
+ assert_body 'ok'
1582
+ end
1583
+
1584
+ it 'returns 412 when If-Match does not include the etag' do
1585
+ mock_app do
1586
+ post('/') do
1587
+ etag 'foo'
1588
+ 'ok'
1589
+ end
1590
+ end
1591
+
1592
+ post('/', {}, 'HTTP_IF_MATCH' => '"bar"')
1593
+ assert_status 412
1594
+ assert_body ''
1595
+ end
1596
+ end
1597
+ end
1598
+
1599
+ it 'uses a weak etag with the :weak option' do
1600
+ mock_app do
1601
+ get('/') do
1602
+ etag 'FOO', :weak
1603
+ "that's weak, dude."
1604
+ end
1605
+ end
1606
+ get '/'
1607
+ assert_equal 'W/"FOO"', response['ETag']
1608
+ end
1609
+
1610
+ it 'raises an ArgumentError for an invalid strength' do
1611
+ mock_app do
1612
+ get('/') do
1613
+ etag 'FOO', :w00t
1614
+ "that's weak, dude."
1615
+ end
1616
+ end
1617
+ assert_raise(ArgumentError) { get('/') }
1618
+ end
1619
+ end
1620
+
1621
+ describe 'back' do
1622
+ it "makes redirecting back pretty" do
1623
+ mock_app { get('/foo') { redirect back } }
1624
+
1625
+ get('/foo', {}, 'HTTP_REFERER' => 'http://github.com')
1626
+ assert redirect?
1627
+ assert_equal "http://github.com", response.location
1628
+ end
1629
+ end
1630
+
1631
+ describe 'uri' do
1632
+ it 'generates absolute urls' do
1633
+ mock_app { get('/') { uri }}
1634
+ get '/'
1635
+ assert_equal 'http://example.org/', body
1636
+ end
1637
+
1638
+ it 'includes path_info' do
1639
+ mock_app { get('/:name') { uri }}
1640
+ get '/foo'
1641
+ assert_equal 'http://example.org/foo', body
1642
+ end
1643
+
1644
+ it 'allows passing an alternative to path_info' do
1645
+ mock_app { get('/:name') { uri '/bar' }}
1646
+ get '/foo'
1647
+ assert_equal 'http://example.org/bar', body
1648
+ end
1649
+
1650
+ it 'includes script_name' do
1651
+ mock_app { get('/:name') { uri '/bar' }}
1652
+ get '/foo', {}, { "SCRIPT_NAME" => '/foo' }
1653
+ assert_equal 'http://example.org/foo/bar', body
1654
+ end
1655
+
1656
+ it 'handles absolute URIs' do
1657
+ mock_app { get('/') { uri 'http://google.com' }}
1658
+ get '/'
1659
+ assert_equal 'http://google.com', body
1660
+ end
1661
+
1662
+ it 'handles different protocols' do
1663
+ mock_app { get('/') { uri 'mailto:jsmith@example.com' }}
1664
+ get '/'
1665
+ assert_equal 'mailto:jsmith@example.com', body
1666
+ end
1667
+
1668
+ it 'is aliased to #url' do
1669
+ mock_app { get('/') { url }}
1670
+ get '/'
1671
+ assert_equal 'http://example.org/', body
1672
+ end
1673
+
1674
+ it 'is aliased to #to' do
1675
+ mock_app { get('/') { to }}
1676
+ get '/'
1677
+ assert_equal 'http://example.org/', body
1678
+ end
1679
+ end
1680
+
1681
+ describe 'logger' do
1682
+ it 'logging works when logging is enabled' do
1683
+ mock_app do
1684
+ enable :logging
1685
+ get('/') do
1686
+ logger.info "Program started"
1687
+ logger.warn "Nothing to do!"
1688
+ end
1689
+ end
1690
+ io = StringIO.new
1691
+ get '/', {}, 'rack.errors' => io
1692
+ assert io.string.include?("INFO -- : Program started")
1693
+ assert io.string.include?("WARN -- : Nothing to do")
1694
+ end
1695
+
1696
+ it 'logging works when logging is disable, but no output is produced' do
1697
+ mock_app do
1698
+ disable :logging
1699
+ get('/') do
1700
+ logger.info "Program started"
1701
+ logger.warn "Nothing to do!"
1702
+ end
1703
+ end
1704
+ io = StringIO.new
1705
+ get '/', {}, 'rack.errors' => io
1706
+ assert !io.string.include?("INFO -- : Program started")
1707
+ assert !io.string.include?("WARN -- : Nothing to do")
1708
+ end
1709
+
1710
+ it 'does not create a logger when logging is set to nil' do
1711
+ mock_app do
1712
+ set :logging, nil
1713
+ get('/') { logger.inspect }
1714
+ end
1715
+
1716
+ get '/'
1717
+ assert_body 'nil'
1718
+ end
1719
+ end
1720
+
1721
+ module ::HelperOne; def one; '1'; end; end
1722
+ module ::HelperTwo; def two; '2'; end; end
1723
+
1724
+ describe 'Adding new helpers' do
1725
+ it 'takes a list of modules to mix into the app' do
1726
+ mock_app do
1727
+ helpers ::HelperOne, ::HelperTwo
1728
+
1729
+ get('/one') { one }
1730
+
1731
+ get('/two') { two }
1732
+ end
1733
+
1734
+ get '/one'
1735
+ assert_equal '1', body
1736
+
1737
+ get '/two'
1738
+ assert_equal '2', body
1739
+ end
1740
+
1741
+ it 'takes a block to mix into the app' do
1742
+ mock_app do
1743
+ helpers do
1744
+ def foo
1745
+ 'foo'
1746
+ end
1747
+ end
1748
+
1749
+ get('/') { foo }
1750
+ end
1751
+
1752
+ get '/'
1753
+ assert_equal 'foo', body
1754
+ end
1755
+
1756
+ it 'evaluates the block in class context so that methods can be aliased' do
1757
+ mock_app do
1758
+ helpers { alias_method :h, :escape_html }
1759
+
1760
+ get('/') { h('42 < 43') }
1761
+ end
1762
+
1763
+ get '/'
1764
+ assert ok?
1765
+ assert_equal '42 &lt; 43', body
1766
+ end
1767
+ end
1768
+ end