rack-zippy 1.2.1 → 2.0.0
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.
- data/.gitignore +1 -1
- data/CHANGELOG.md +10 -0
- data/Gemfile +2 -1
- data/Guardfile +7 -0
- data/README.md +56 -3
- data/lib/rack-zippy.rb +34 -82
- data/lib/rack-zippy/asset_compiler.rb +39 -0
- data/lib/rack-zippy/serveable_file.rb +138 -0
- data/lib/rack-zippy/version.rb +1 -1
- data/test/asset_server_test.rb +331 -353
- data/test/null_asset_compiler_test.rb +14 -0
- data/test/public/foo/bar.html +8 -0
- data/test/public/foo/index.html +11 -0
- data/test/public/index.html +8 -0
- data/test/public/thanks.html +1 -0
- data/test/rails_asset_compiler_test.rb +46 -0
- data/test/serveable_file_test.rb +468 -0
- data/test/test_helper.rb +40 -2
- metadata +17 -8
data/lib/rack-zippy/version.rb
CHANGED
data/test/asset_server_test.rb
CHANGED
@@ -1,359 +1,337 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Rack
|
4
|
+
module Zippy
|
5
|
+
class AssetServerTest < TestCase
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
def setup
|
9
|
+
ensure_correct_working_directory
|
10
|
+
enter_rails_env
|
11
|
+
::Rails.public_path = Pathname.new(asset_root)
|
12
|
+
::Rails.configuration.assets.compile = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def teardown
|
16
|
+
revert_to_original_working_directory
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_serves_static_file_as_directory
|
20
|
+
expected_html_file = 'public/foo/bar.html'
|
21
|
+
assert_responds_with_html_file '/foo/bar.html', expected_html_file
|
22
|
+
assert_responds_with_html_file '/foo/bar/', expected_html_file
|
23
|
+
assert_responds_with_html_file '/foo/bar', expected_html_file
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_serves_static_index_in_directory
|
27
|
+
directory_index_html = 'public/foo/index.html'
|
28
|
+
assert_responds_with_html_file '/foo/index.html', directory_index_html
|
29
|
+
assert_responds_with_html_file '/foo/', directory_index_html
|
30
|
+
assert_responds_with_html_file '/foo', directory_index_html
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_serves_static_index_at_root
|
34
|
+
index_html_file = 'public/index.html'
|
35
|
+
assert_responds_with_html_file '/index.html', 'public/index.html'
|
36
|
+
assert_responds_with_html_file '/index', 'public/index.html'
|
37
|
+
assert_responds_with_html_file '/', 'public/index.html'
|
38
|
+
assert_responds_with_html_file '', 'public/index.html'
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_static_extension_regexp_available_in_established_constant_for_monkey_patching
|
42
|
+
assert AssetServer.const_defined?(:STATIC_EXTENSION_REGEX)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_rails_asset_compiler_set_when_rails_environment_detected
|
46
|
+
asset_compiler = app.instance_variable_get('@asset_compiler')
|
47
|
+
assert_equal RailsAssetCompiler, asset_compiler.class
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_null_asset_compiler_set_when_no_rails_environment_detected
|
51
|
+
exit_rails_env
|
52
|
+
asset_compiler = app.instance_variable_get('@asset_compiler')
|
53
|
+
assert_equal NullAssetCompiler, asset_compiler.class
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_request_for_non_asset_path_beginning_with_assets_dir_name_bypasses_middleware
|
57
|
+
get '/assets-are-great-but-im-not-one'
|
58
|
+
assert_underlying_app_responded
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_request_for_subdir_of_assets_passed_onto_app
|
62
|
+
['/assets/blog', '/assets/blog/logos/'].each do |path|
|
63
|
+
local_path = "public#{path}"
|
64
|
+
assert ::File.directory?(local_path)
|
65
|
+
get path
|
66
|
+
assert_underlying_app_responded
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_request_for_non_existent_subdir_of_assets_passed_onto_app
|
71
|
+
['/assets/ghost', '/assets/does/not/exist', '/assets/nothing-here/with-trailing-slash/'].each do |path|
|
72
|
+
local_path = "public#{path}"
|
73
|
+
assert !::File.exists?(local_path)
|
74
|
+
get path
|
75
|
+
assert_underlying_app_responded
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_request_for_assets_root_passed_onto_app
|
80
|
+
['/assets/', '/assets'].each do |assets_root|
|
81
|
+
get assets_root
|
82
|
+
assert_underlying_app_responded
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_cache_friendly_last_modified_is_not_set_for_files_outside_of_assets_subdir
|
87
|
+
get '/robots.txt'
|
88
|
+
assert_response_ok
|
89
|
+
assert_last_modified nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_cache_friendly_last_modified_is_set_for_root_favicon_as_it_rarely_changes
|
93
|
+
get '/favicon.ico'
|
94
|
+
assert_response_ok
|
95
|
+
assert_cache_friendly_last_modified
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_does_not_serve_assets_subdir_request_when_assets_compile_enabled
|
99
|
+
::Rails.configuration.assets.compile = true
|
100
|
+
get '/assets/application.css'
|
101
|
+
assert_underlying_app_responded
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_responds_with_gzipped_css_to_gzip_capable_clients
|
105
|
+
params = {}
|
106
|
+
get '/assets/application.css', params, env_for_gzip_capable_client
|
107
|
+
assert_response_ok
|
108
|
+
assert_content_length 'public/assets/application.css.gz'
|
109
|
+
assert_content_type 'text/css'
|
110
|
+
assert_cache_max_age :year
|
111
|
+
assert_cache_friendly_last_modified
|
112
|
+
assert_equal 'gzip', last_response.headers['content-encoding']
|
113
|
+
assert_vary_accept_encoding_header
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_responds_with_gzipped_js_to_gzip_capable_clients
|
117
|
+
params = {}
|
118
|
+
get '/assets/application.js', params, env_for_gzip_capable_client
|
119
|
+
assert_response_ok
|
120
|
+
assert_content_length 'public/assets/application.js.gz'
|
121
|
+
assert_content_type 'application/javascript'
|
122
|
+
assert_cache_max_age :year
|
123
|
+
assert_cache_friendly_last_modified
|
124
|
+
assert_equal 'gzip', last_response.headers['content-encoding']
|
125
|
+
assert_vary_accept_encoding_header
|
126
|
+
end
|
127
|
+
|
128
|
+
def test_responds_with_maximum_cache_headers_for_assets_subdir_requests
|
129
|
+
get '/assets/favicon.ico'
|
130
|
+
assert_response_ok
|
131
|
+
assert_cache_max_age :year
|
132
|
+
assert_cache_friendly_last_modified
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_responds_with_month_long_cache_headers_for_root_favicon
|
136
|
+
get '/favicon.ico'
|
137
|
+
assert_response_ok
|
138
|
+
assert_cache_max_age :month
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_responds_with_day_long_cache_headers_for_robots_txt
|
142
|
+
get '/robots.txt'
|
143
|
+
assert_response_ok
|
144
|
+
assert_cache_max_age :day
|
145
|
+
assert_last_modified nil
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_responds_with_day_long_cache_headers_for_root_html_requests
|
149
|
+
get '/thanks.html'
|
150
|
+
assert_response_ok
|
151
|
+
assert_cache_max_age :day
|
152
|
+
assert_last_modified nil
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_max_cache_and_vary_accept_encoding_headers_present_for_css_requests_by_non_gzip_clients
|
156
|
+
get '/assets/application.css'
|
157
|
+
assert_response_ok
|
158
|
+
assert_content_length 'public/assets/application.css'
|
159
|
+
assert_content_type 'text/css'
|
160
|
+
assert_cache_max_age :year
|
161
|
+
assert_cache_friendly_last_modified
|
162
|
+
assert_nil last_response.headers['content-encoding']
|
163
|
+
assert_vary_accept_encoding_header
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_max_cache_and_vary_accept_encoding_headers_present_for_js_requests_by_non_gzip_clients
|
167
|
+
get '/assets/application.js'
|
168
|
+
assert_response_ok
|
169
|
+
assert_content_type 'application/javascript'
|
170
|
+
assert_content_length 'public/assets/application.js'
|
171
|
+
assert_cache_max_age :year
|
172
|
+
assert_cache_friendly_last_modified
|
173
|
+
assert_nil last_response.headers['content-encoding']
|
174
|
+
assert_vary_accept_encoding_header
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_vary_header_not_present_if_gzipped_asset_unavailable
|
178
|
+
get '/assets/rails.png'
|
179
|
+
assert_response_ok
|
180
|
+
assert_nil last_response.headers['vary']
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_responds_not_found_if_path_contains_hidden_dir
|
184
|
+
paths = ['.confidential/secret-plans.pdf', '/.you-aint-seen-me/index.html', '/nothing/.to/see/here.jpg']
|
185
|
+
paths.each do |path|
|
186
|
+
get path
|
187
|
+
assert_not_found
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def test_responds_not_found_if_path_ends_with_hidden_file
|
192
|
+
hidden_files = ['.htaccess', '/.top-secret', '/assets/.shhh']
|
193
|
+
hidden_files.each do |path|
|
194
|
+
get path
|
195
|
+
assert_not_found
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def test_responds_not_found_if_path_contains_consecutive_periods
|
200
|
+
["/hello/../sensitive/file", "/..", "/..."].each do |dotty_path|
|
201
|
+
get dotty_path
|
202
|
+
assert_not_found
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_serves_html
|
207
|
+
assert_responds_with_html_file '/thanks.html', 'public/thanks.html'
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_serves_robots_txt
|
211
|
+
get '/robots.txt'
|
212
|
+
assert_response_ok
|
213
|
+
assert_content_type 'text/plain'
|
214
|
+
assert_content_length 'public/robots.txt'
|
215
|
+
end
|
216
|
+
|
217
|
+
def test_passes_non_asset_requests_onto_app
|
218
|
+
get '/about'
|
219
|
+
assert_underlying_app_responded
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_passes_not_found_asset_requests_onto_app
|
223
|
+
get '/foo.html'
|
224
|
+
assert_underlying_app_responded
|
225
|
+
end
|
226
|
+
|
227
|
+
def test_responds_with_favicon_in_assets_dir
|
228
|
+
get '/assets/favicon.ico'
|
229
|
+
assert_response_ok
|
230
|
+
assert_content_type 'image/vnd.microsoft.icon'
|
231
|
+
assert_content_length 'public/assets/favicon.ico'
|
232
|
+
end
|
233
|
+
|
234
|
+
def test_responds_with_favicon_at_root
|
235
|
+
get '/favicon.ico'
|
236
|
+
assert_response_ok
|
237
|
+
assert_content_type 'image/vnd.microsoft.icon'
|
238
|
+
assert_content_length 'public/favicon.ico'
|
239
|
+
end
|
240
|
+
|
241
|
+
def test_request_for_non_existent_image_passed_onto_app
|
242
|
+
get '/assets/pot-of-gold.png'
|
243
|
+
assert_underlying_app_responded
|
244
|
+
end
|
245
|
+
|
246
|
+
def test_request_for_non_existent_css_passed_onto_app
|
247
|
+
get '/assets/unicorn.css'
|
248
|
+
assert_underlying_app_responded
|
249
|
+
end
|
250
|
+
|
251
|
+
def test_request_for_non_existent_js_passed_onto_app
|
252
|
+
get '/assets/dragon.js'
|
253
|
+
assert_underlying_app_responded
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_asset_root_constructor_arg_accepts_string
|
257
|
+
asset_server = AssetServer.new(create_rack_app, '/custom/asset/root')
|
258
|
+
assert_equal '/custom/asset/root', asset_server.instance_variable_get('@asset_root')
|
259
|
+
end
|
260
|
+
|
261
|
+
def test_default_asset_root_is_rails_public_path
|
262
|
+
Rails.public_path = '/unexpected/absolute/path/to/public'
|
263
|
+
asset_server = AssetServer.new(create_rack_app)
|
264
|
+
assert_equal '/unexpected/absolute/path/to/public', asset_server.instance_variable_get('@asset_root')
|
265
|
+
end
|
266
|
+
|
267
|
+
private
|
268
|
+
|
269
|
+
def app
|
270
|
+
if in_rails_env?
|
271
|
+
return AssetServer.new(create_rack_app)
|
272
|
+
end
|
273
|
+
# In a pure rack app, non-Rails env
|
274
|
+
return AssetServer.new(create_rack_app, asset_root)
|
275
|
+
end
|
276
|
+
|
277
|
+
def create_rack_app
|
278
|
+
status = 200
|
279
|
+
headers = {}
|
280
|
+
response = 'Up above the streets and houses'
|
281
|
+
lambda { |env| [status, headers, response] }
|
282
|
+
end
|
283
|
+
|
284
|
+
def assert_last_modified(expected)
|
285
|
+
assert_equal expected, last_response.headers['last-modified']
|
286
|
+
end
|
287
|
+
|
288
|
+
def env_for_gzip_capable_client
|
289
|
+
{'HTTP_ACCEPT_ENCODING' => 'deflate,gzip,sdch'}
|
290
|
+
end
|
291
|
+
|
292
|
+
def assert_vary_accept_encoding_header
|
293
|
+
assert_equal 'Accept-Encoding', last_response.headers['vary']
|
294
|
+
end
|
295
|
+
|
296
|
+
def assert_cache_max_age(duration)
|
297
|
+
assert_equal "public, max-age=#{DURATIONS_IN_SECS[duration]}", last_response.headers['cache-control']
|
298
|
+
end
|
299
|
+
|
300
|
+
# Browser caching heuristics favour assets with older Last Modified dates IIRC
|
301
|
+
def assert_cache_friendly_last_modified
|
302
|
+
assert_last_modified 'Mon, 10 Jan 2005 10:00:00 GMT'
|
303
|
+
end
|
304
|
+
|
305
|
+
def assert_underlying_app_responded
|
306
|
+
assert_response_ok
|
307
|
+
assert_equal 'Up above the streets and houses', last_response.body
|
308
|
+
end
|
309
|
+
|
310
|
+
def assert_responds_with_html_file(path_info, expected_html_file)
|
311
|
+
get path_info
|
312
|
+
assert_response_ok
|
313
|
+
assert_content_type 'text/html', "Wrong content type for GET '#{path_info}'"
|
314
|
+
assert_content_length expected_html_file
|
315
|
+
assert_equal ::IO.read(expected_html_file), last_response.body
|
316
|
+
end
|
317
|
+
|
318
|
+
def assert_response_ok
|
319
|
+
assert_equal 200, last_response.status
|
320
|
+
end
|
321
|
+
|
322
|
+
def assert_content_type(expected_content_type, message=nil)
|
323
|
+
assert_equal expected_content_type, last_response.headers['content-type'], message
|
324
|
+
end
|
325
|
+
|
326
|
+
def assert_content_length(path)
|
327
|
+
assert_equal ::File.size(path).to_s, last_response.headers['content-length'], "Unexpected Content-Length header"
|
328
|
+
end
|
329
|
+
|
330
|
+
def assert_not_found(msg=nil)
|
331
|
+
assert_equal 404, last_response.status, msg
|
332
|
+
assert_equal 'Not Found', last_response.body, msg
|
333
|
+
end
|
5
334
|
|
6
|
-
def setup
|
7
|
-
ensure_correct_working_directory
|
8
|
-
::Rails.public_path = Pathname.new("#{Dir.pwd}/public")
|
9
|
-
::Rails.configuration.assets.compile = false
|
10
|
-
end
|
11
|
-
|
12
|
-
def teardown
|
13
|
-
revert_to_original_working_directory
|
14
|
-
end
|
15
|
-
|
16
|
-
def app
|
17
|
-
Rack::Zippy::AssetServer.new(create_rack_app)
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_request_for_non_asset_path_beginning_with_assets_dir_name_bypasses_middleware
|
21
|
-
get '/assets-are-great-but-im-not-one'
|
22
|
-
assert_underlying_app_responded
|
23
|
-
end
|
24
|
-
|
25
|
-
def test_request_for_subdir_of_assets_passed_onto_app
|
26
|
-
['/assets/blog', '/assets/blog/logos/'].each do |path|
|
27
|
-
local_path = "public#{path}"
|
28
|
-
assert File.directory?(local_path)
|
29
|
-
get path
|
30
|
-
assert_underlying_app_responded
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def test_request_for_non_existent_subdir_of_assets_passed_onto_app
|
35
|
-
['/assets/ghost', '/assets/does/not/exist', '/assets/nothing-here/with-trailing-slash/'].each do |path|
|
36
|
-
local_path = "public#{path}"
|
37
|
-
assert !File.exists?(local_path)
|
38
|
-
get path
|
39
|
-
assert_underlying_app_responded
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def test_request_for_assets_root_passed_onto_app
|
44
|
-
['/assets/', '/assets'].each do |assets_root|
|
45
|
-
get assets_root
|
46
|
-
assert_underlying_app_responded
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def test_cache_friendly_last_modified_is_not_set_for_files_outside_of_assets_subdir
|
51
|
-
get '/robots.txt'
|
52
|
-
assert_response_ok
|
53
|
-
assert_last_modified nil
|
54
|
-
end
|
55
|
-
|
56
|
-
def test_cache_friendly_last_modified_is_set_for_root_favicon_as_it_rarely_changes
|
57
|
-
get '/favicon.ico'
|
58
|
-
assert_response_ok
|
59
|
-
assert_cache_friendly_last_modified
|
60
|
-
end
|
61
|
-
|
62
|
-
def test_does_not_serve_assets_subdir_request_when_assets_compile_enabled
|
63
|
-
::Rails.configuration.assets.compile = true
|
64
|
-
get '/assets/application.css'
|
65
|
-
assert_underlying_app_responded
|
66
|
-
end
|
67
|
-
|
68
|
-
def test_serve_returns_true_if_request_has_static_extension_and_file_exists
|
69
|
-
assert app.send(:serve?, '/thanks.html')
|
70
|
-
end
|
71
|
-
|
72
|
-
def test_serve_returns_false_if_request_has_static_extension_and_file_does_not_exist
|
73
|
-
assert !app.send(:serve?, '/about.html')
|
74
|
-
end
|
75
|
-
|
76
|
-
def test_serve_returns_false_if_request_does_not_have_static_extension
|
77
|
-
assert !app.send(:serve?, '/about')
|
78
|
-
end
|
79
|
-
|
80
|
-
def test_serve_returns_true_for_assets_subdir_request_when_assets_compile_disabled
|
81
|
-
assert app.send(:serve?, '/assets/application.css')
|
82
|
-
end
|
83
|
-
|
84
|
-
def test_serve_returns_false_for_assets_subdir_request_when_assets_compile_enabled
|
85
|
-
::Rails.configuration.assets.compile = true
|
86
|
-
assert !app.send(:serve?, '/assets/application.css')
|
87
|
-
end
|
88
|
-
|
89
|
-
def test_block_asset_pipeline_from_generating_asset_returns_false_if_assets_compile_enabled
|
90
|
-
::Rails.configuration.assets.compile = true
|
91
|
-
assert ::Rails.configuration.assets.compile
|
92
|
-
assert !app.send(:block_asset_pipeline_from_generating_asset?)
|
93
|
-
end
|
94
|
-
|
95
|
-
def test_block_asset_pipeline_from_generating_asset_returns_true_if_assets_compile_disabled
|
96
|
-
assert !::Rails.configuration.assets.compile
|
97
|
-
assert app.send(:block_asset_pipeline_from_generating_asset?)
|
98
|
-
end
|
99
|
-
|
100
|
-
def test_responds_with_gzipped_css_to_gzip_capable_clients
|
101
|
-
params = {}
|
102
|
-
get '/assets/application.css', params, env_for_gzip_capable_client
|
103
|
-
assert_response_ok
|
104
|
-
assert_content_length 'public/assets/application.css.gz'
|
105
|
-
assert_content_type 'text/css'
|
106
|
-
assert_cache_max_age :year
|
107
|
-
assert_cache_friendly_last_modified
|
108
|
-
assert_equal 'gzip', last_response.headers['content-encoding']
|
109
|
-
assert_vary_accept_encoding_header
|
110
|
-
end
|
111
|
-
|
112
|
-
def test_responds_with_gzipped_js_to_gzip_capable_clients
|
113
|
-
params = {}
|
114
|
-
get '/assets/application.js', params, env_for_gzip_capable_client
|
115
|
-
assert_response_ok
|
116
|
-
assert_content_length 'public/assets/application.js.gz'
|
117
|
-
assert_content_type 'application/javascript'
|
118
|
-
assert_cache_max_age :year
|
119
|
-
assert_cache_friendly_last_modified
|
120
|
-
assert_equal 'gzip', last_response.headers['content-encoding']
|
121
|
-
assert_vary_accept_encoding_header
|
122
|
-
end
|
123
|
-
|
124
|
-
def test_responds_with_maximum_cache_headers_for_assets_subdir_requests
|
125
|
-
get '/assets/favicon.ico'
|
126
|
-
assert_response_ok
|
127
|
-
assert_cache_max_age :year
|
128
|
-
assert_cache_friendly_last_modified
|
129
|
-
end
|
130
|
-
|
131
|
-
def test_responds_with_month_long_cache_headers_for_root_favicon
|
132
|
-
get '/favicon.ico'
|
133
|
-
assert_response_ok
|
134
|
-
assert_cache_max_age :month
|
135
|
-
end
|
136
|
-
|
137
|
-
def test_responds_with_day_long_cache_headers_for_robots_txt
|
138
|
-
get '/robots.txt'
|
139
|
-
assert_response_ok
|
140
|
-
assert_cache_max_age :day
|
141
|
-
assert_last_modified nil
|
142
|
-
end
|
143
|
-
|
144
|
-
def test_responds_with_day_long_cache_headers_for_root_html_requests
|
145
|
-
get '/thanks.html'
|
146
|
-
assert_response_ok
|
147
|
-
assert_cache_max_age :day
|
148
|
-
assert_last_modified nil
|
149
|
-
end
|
150
|
-
|
151
|
-
def test_max_cache_and_vary_accept_encoding_headers_present_for_css_requests_by_non_gzip_clients
|
152
|
-
get '/assets/application.css'
|
153
|
-
assert_response_ok
|
154
|
-
assert_content_length 'public/assets/application.css'
|
155
|
-
assert_content_type 'text/css'
|
156
|
-
assert_cache_max_age :year
|
157
|
-
assert_cache_friendly_last_modified
|
158
|
-
assert_nil last_response.headers['content-encoding']
|
159
|
-
assert_vary_accept_encoding_header
|
160
|
-
end
|
161
|
-
|
162
|
-
def test_max_cache_and_vary_accept_encoding_headers_present_for_js_requests_by_non_gzip_clients
|
163
|
-
get '/assets/application.js'
|
164
|
-
assert_response_ok
|
165
|
-
assert_content_type 'application/javascript'
|
166
|
-
assert_content_length 'public/assets/application.js'
|
167
|
-
assert_cache_max_age :year
|
168
|
-
assert_cache_friendly_last_modified
|
169
|
-
assert_nil last_response.headers['content-encoding']
|
170
|
-
assert_vary_accept_encoding_header
|
171
|
-
end
|
172
|
-
|
173
|
-
def test_vary_header_not_present_if_gzipped_asset_unavailable
|
174
|
-
get '/assets/rails.png'
|
175
|
-
assert_response_ok
|
176
|
-
assert_nil last_response.headers['vary']
|
177
|
-
end
|
178
|
-
|
179
|
-
def assert_raises_illegal_path_error(path)
|
180
|
-
e = assert_raises SecurityError do
|
181
|
-
get path
|
182
|
-
end
|
183
|
-
assert_equal 'Illegal path requested', e.message
|
184
|
-
end
|
185
|
-
|
186
|
-
def test_throws_exception_if_path_contains_hidden_dir
|
187
|
-
paths = ['.confidential/secret-plans.pdf', '/.you-aint-seen-me/index.html', '/nothing/.to/see/here.jpg']
|
188
|
-
paths.each do |path|
|
189
|
-
assert_raises_illegal_path_error path
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
def test_throws_exception_if_path_ends_with_hidden_file
|
194
|
-
hidden_files = ['.htaccess', '/.top-secret', '/assets/.shhh']
|
195
|
-
hidden_files.each do |path|
|
196
|
-
assert_raises_illegal_path_error path
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def test_throws_exception_if_path_contains_consecutive_periods
|
201
|
-
assert_raises_illegal_path_error '/hello/../sensitive/file'
|
202
|
-
end
|
203
|
-
|
204
|
-
def test_serves_html
|
205
|
-
get '/thanks.html'
|
206
|
-
assert_response_ok
|
207
|
-
assert_content_type 'text/html'
|
208
|
-
assert_content_length 'public/thanks.html'
|
209
|
-
end
|
210
|
-
|
211
|
-
def test_serves_robots_txt
|
212
|
-
get '/robots.txt'
|
213
|
-
assert_response_ok
|
214
|
-
assert_content_type 'text/plain'
|
215
|
-
assert_content_length 'public/robots.txt'
|
216
|
-
end
|
217
|
-
|
218
|
-
def test_has_static_extension_handles_non_lowercase_chars
|
219
|
-
['pNG', 'JPEG', 'HTML', 'HtM', 'GIF', 'Ico'].each do |extension|
|
220
|
-
assert app.send(:has_static_extension?, "/some-asset.#{extension}")
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
def test_has_static_extension_returns_false_for_asset_paths_without_period
|
225
|
-
['/assets/somepng', '/indexhtml', '/assets/applicationcss'].each do |path|
|
226
|
-
assert !app.send(:has_static_extension?, path)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
def test_has_static_extension_returns_true_for_fonts
|
231
|
-
font_extensions = ['woff', 'woff2', 'ttf', 'eot', 'otf']
|
232
|
-
font_extensions.each do |extension|
|
233
|
-
assert app.send(:has_static_extension?, "/comic-sans.#{extension}"),
|
234
|
-
"'#{extension}' font extension not recognized"
|
235
335
|
end
|
236
336
|
end
|
237
|
-
|
238
|
-
def test_has_static_extension_returns_true_for_flash
|
239
|
-
assert app.send(:has_static_extension?, '/splash-page-like-its-1999.swf'),
|
240
|
-
"Should handle flash .swf files"
|
241
|
-
end
|
242
|
-
|
243
|
-
def test_passes_non_asset_requests_onto_app
|
244
|
-
get '/about'
|
245
|
-
assert_underlying_app_responded
|
246
|
-
end
|
247
|
-
|
248
|
-
def test_passes_not_found_asset_requests_onto_app
|
249
|
-
get '/index.html'
|
250
|
-
assert_underlying_app_responded
|
251
|
-
end
|
252
|
-
|
253
|
-
def test_responds_with_favicon_in_assets_dir
|
254
|
-
get '/assets/favicon.ico'
|
255
|
-
assert_response_ok
|
256
|
-
assert_content_type 'image/vnd.microsoft.icon'
|
257
|
-
assert_content_length 'public/assets/favicon.ico'
|
258
|
-
end
|
259
|
-
|
260
|
-
def test_responds_with_favicon_at_root
|
261
|
-
get '/favicon.ico'
|
262
|
-
assert_response_ok
|
263
|
-
assert_content_type 'image/vnd.microsoft.icon'
|
264
|
-
assert_content_length 'public/favicon.ico'
|
265
|
-
end
|
266
|
-
|
267
|
-
def test_request_for_non_existent_image_passed_onto_app
|
268
|
-
get '/assets/pot-of-gold.png'
|
269
|
-
assert_underlying_app_responded
|
270
|
-
end
|
271
|
-
|
272
|
-
def test_request_for_non_existent_css_passed_onto_app
|
273
|
-
get '/assets/unicorn.css'
|
274
|
-
assert_underlying_app_responded
|
275
|
-
end
|
276
|
-
|
277
|
-
def test_request_for_non_existent_js_passed_onto_app
|
278
|
-
get '/assets/dragon.js'
|
279
|
-
assert_underlying_app_responded
|
280
|
-
end
|
281
|
-
|
282
|
-
def test_asset_root_constructor_arg_accepts_string
|
283
|
-
asset_server = Rack::Zippy::AssetServer.new(create_rack_app, '/custom/asset/root')
|
284
|
-
assert_equal '/custom/asset/root', asset_server.instance_variable_get('@asset_root')
|
285
|
-
end
|
286
|
-
|
287
|
-
def test_default_asset_root_is_rails_public_path
|
288
|
-
Rails.public_path = '/unexpected/absolute/path/to/public'
|
289
|
-
asset_server = Rack::Zippy::AssetServer.new(create_rack_app)
|
290
|
-
assert_equal '/unexpected/absolute/path/to/public', asset_server.instance_variable_get('@asset_root')
|
291
|
-
end
|
292
|
-
|
293
|
-
private
|
294
|
-
|
295
|
-
DURATIONS_IN_SECS = {:year => 31536000, :month => 2678400, :day => 86400}.freeze
|
296
|
-
|
297
|
-
def create_rack_app
|
298
|
-
status = 200
|
299
|
-
headers = {}
|
300
|
-
response = 'Up above the streets and houses'
|
301
|
-
lambda { |env| [status, headers, response] }
|
302
|
-
end
|
303
|
-
|
304
|
-
def assert_last_modified(expected)
|
305
|
-
assert_equal expected, last_response.headers['last-modified']
|
306
|
-
end
|
307
|
-
|
308
|
-
def env_for_gzip_capable_client
|
309
|
-
{'HTTP_ACCEPT_ENCODING' => 'deflate,gzip,sdch'}
|
310
|
-
end
|
311
|
-
|
312
|
-
def assert_vary_accept_encoding_header
|
313
|
-
assert_equal 'Accept-Encoding', last_response.headers['vary']
|
314
|
-
end
|
315
|
-
|
316
|
-
def assert_cache_max_age(duration)
|
317
|
-
assert_equal "public, max-age=#{DURATIONS_IN_SECS[duration]}", last_response.headers['cache-control']
|
318
|
-
end
|
319
|
-
|
320
|
-
# Browser caching heuristics favour assets with older Last Modified dates IIRC
|
321
|
-
def assert_cache_friendly_last_modified
|
322
|
-
assert_last_modified 'Mon, 10 Jan 2005 10:00:00 GMT'
|
323
|
-
end
|
324
|
-
|
325
|
-
def assert_underlying_app_responded
|
326
|
-
assert_response_ok
|
327
|
-
assert_equal 'Up above the streets and houses', last_response.body
|
328
|
-
end
|
329
|
-
|
330
|
-
def assert_response_ok
|
331
|
-
assert_equal 200, last_response.status
|
332
|
-
end
|
333
|
-
|
334
|
-
def assert_content_type(expected_content_type)
|
335
|
-
assert_equal expected_content_type, last_response.headers['content-type']
|
336
|
-
end
|
337
|
-
|
338
|
-
def assert_content_length(path)
|
339
|
-
assert_equal ::File.size(path).to_s, last_response.headers['content-length']
|
340
|
-
end
|
341
|
-
|
342
|
-
def assert_not_found(msg=nil)
|
343
|
-
assert_equal 404, last_response.status, msg
|
344
|
-
assert_equal 'Not Found', last_response.body, msg
|
345
|
-
end
|
346
|
-
|
347
|
-
def ensure_correct_working_directory
|
348
|
-
is_project_root_working_directory = File.exists?('rack-zippy.gemspec')
|
349
|
-
if is_project_root_working_directory
|
350
|
-
@original_dir = Dir.pwd
|
351
|
-
Dir.chdir 'test'
|
352
|
-
end
|
353
|
-
end
|
354
|
-
|
355
|
-
def revert_to_original_working_directory
|
356
|
-
Dir.chdir @original_dir if @original_dir
|
357
|
-
end
|
358
|
-
|
359
337
|
end
|