rack-page_caching 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.
@@ -0,0 +1,272 @@
1
+ require 'action_controller'
2
+ require "rack/page_caching/action_controller"
3
+ require 'test_helper'
4
+ require './test/support/file_helper'
5
+
6
+ class TestController < ActionController::Base
7
+ caches_page :cache, if: Proc.new { |c| !c.request.format.json? }
8
+ caches_page :just_head
9
+ caches_page :redirect_somewhere
10
+ caches_page :no_gzip, gzip: false
11
+ caches_page :gzip_level, gzip: :best_speed
12
+ caches_page :accept_xml
13
+
14
+ def cache
15
+ render text: 'foo bar'
16
+ end
17
+
18
+ def no_gzip
19
+ render text: 'no gzip'
20
+ end
21
+
22
+ def just_head
23
+ head :ok
24
+ end
25
+
26
+ def redirect_somewhere
27
+ redirect_to '/just_head'
28
+ end
29
+
30
+ def custom_caching
31
+ render text: 'custom'
32
+ cache_page('Cache rules everything around me', 'wootang.html')
33
+ end
34
+
35
+ def expire_custom_caching
36
+ expire_page 'wootang.html'
37
+ head :ok
38
+ end
39
+
40
+ def custom_caching_with_starting_slash
41
+ render text: 'custom'
42
+ cache_page('Starting slash', '/slash.html')
43
+ end
44
+
45
+ def expire_starting_slash
46
+ expire_page '/slash.html'
47
+ head :ok
48
+ end
49
+
50
+ def without_extension
51
+ render text: 'without extension'
52
+ cache_page('Path without extension', 'without_extension')
53
+ end
54
+
55
+ def with_trailing_slash
56
+ render text: 'trailing slash'
57
+ cache_page('trailing slash', 'hello/world/')
58
+ end
59
+
60
+ def expire_trailing_slash
61
+ expire_page 'hello/world.html'
62
+ head :ok
63
+ end
64
+
65
+ def gzip_level
66
+ render text: 'level up'
67
+ end
68
+
69
+ def accept_xml
70
+ respond_to do |format|
71
+ format.html { render text: 'I am html' }
72
+ format.xml { render text: 'I am xml' }
73
+ end
74
+ end
75
+ end
76
+
77
+ describe Rack::PageCaching::ActionController do
78
+ include Rack::Test::Methods
79
+ include FileHelper
80
+
81
+ def app
82
+ options = {
83
+ page_cache_directory: cache_path,
84
+ include_hostname: false
85
+ }
86
+ Rack::Builder.new {
87
+ map '/with-domain' do
88
+ use Rack::PageCaching, options.merge(include_hostname: true)
89
+ run TestController.action(:cache)
90
+ end
91
+ map '/' do
92
+ use Rack::PageCaching, options
93
+ run TestController.action(:cache)
94
+ end
95
+ [:cache, :just_head, :redirect_somewhere, :custom_caching,
96
+ :no_gzip, :gzip_level, :without_extension, :accept_xml,
97
+ :custom_caching_with_starting_slash, :expire_custom_caching,
98
+ :expire_starting_slash, :with_trailing_slash, :expire_trailing_slash
99
+ ].each do |action|
100
+ map "/#{action}" do
101
+ use Rack::PageCaching, options
102
+ run TestController.action(action)
103
+ end
104
+ end
105
+ }.to_app
106
+ end
107
+
108
+ def set_path(*path)
109
+ @cache_path = [cache_path].concat path
110
+ end
111
+
112
+ let(:cache_file) { File.join(*@cache_path) }
113
+
114
+ it 'caches the requested page and creates gzipped file by default' do
115
+ get '/cache'
116
+ set_path 'cache.html'
117
+ assert File.exist?(cache_file), 'cache.html should exist'
118
+ File.read(cache_file).must_equal 'foo bar'
119
+ assert File.exist?(File.join(cache_path, 'cache.html.gz')),
120
+ 'gzipped cache.html file should exist'
121
+ assert last_request.env['rack.page_caching.compression'] == Zlib::BEST_COMPRESSION,
122
+ 'compression level must use config gzip by default'
123
+ end
124
+
125
+ it 'saves to a file with the domain as a folder' do
126
+ get 'http://www.test.org/with-domain'
127
+ set_path 'www.test.org', 'with-domain.html'
128
+ assert File.exist?(cache_file), 'with-domain.html should exist'
129
+ File.read(cache_file).must_equal 'foo bar'
130
+ end
131
+
132
+ it 'respects conditionals' do
133
+ get '/cache', format: :json
134
+ assert_cache_folder_is_empty
135
+ end
136
+
137
+ it 'caches to index.html when caching on /' do
138
+ get '/'
139
+ set_path 'index.html'
140
+ assert File.exist?(cache_file),
141
+ 'index.html file should exist'
142
+ end
143
+
144
+ it 'caches head request' do
145
+ head '/just_head'
146
+ set_path 'just_head.html'
147
+ assert File.exist?(cache_file), 'head response should have been cached'
148
+ end
149
+
150
+ it 'will not cache when http status is not 200' do
151
+ get '/redirect_somewhere'
152
+ assert_cache_folder_is_empty
153
+ end
154
+
155
+ it 'caches custom text to a custom path' do
156
+ get '/custom_caching'
157
+ set_path 'wootang.html'
158
+ assert File.exist?(cache_file), 'wootang.html should exist'
159
+ File.read(cache_file).must_equal 'Cache rules everything around me'
160
+ end
161
+
162
+ def assert_file_deleted_after_expiry(path, expire_path, file)
163
+ get path
164
+ set_path file
165
+ assert File.exist?(cache_file), "#{file} should exist"
166
+ assert File.exist?(cache_file + '.gz'), "#{file}.gz should exist"
167
+
168
+ get expire_path
169
+ refute File.exist?(cache_file), "#{file} should be deleted"
170
+ refute File.exist?(cache_file + '.gz'), "#{file}.gz should be deleted"
171
+ end
172
+
173
+ it 'expires page at custom path' do
174
+ assert_file_deleted_after_expiry '/custom_caching', '/expire_custom_caching',
175
+ 'wootang.html'
176
+ end
177
+
178
+ it 'caches custom text whose path starts with a slash' do
179
+ get '/custom_caching_with_starting_slash'
180
+ set_path 'slash.html'
181
+ assert File.exist?(cache_file), '/slash.html should exist'
182
+ File.read(cache_file).must_equal 'Starting slash'
183
+ end
184
+
185
+ it 'expires page with path that starts with slash' do
186
+ assert_file_deleted_after_expiry '/custom_caching_with_starting_slash',
187
+ '/expire_starting_slash', 'slash.html'
188
+ end
189
+
190
+ it 'caches path that does not have extensions' do
191
+ get '/without_extension'
192
+ set_path 'without_extension.html'
193
+ assert File.exist?(cache_file), 'without_extension.html should exist'
194
+ end
195
+
196
+ it 'caches path with trailing slash' do
197
+ get '/with_trailing_slash'
198
+ set_path 'hello/world.html'
199
+ assert File.exist?(cache_file), 'hello/world.html should exist'
200
+ end
201
+
202
+ it 'expires path with trailing slash' do
203
+ assert_file_deleted_after_expiry '/with_trailing_slash',
204
+ '/expire_trailing_slash', 'hello/world.html'
205
+ end
206
+
207
+ it 'does not create a gzip file when gzip argument is false' do
208
+ get '/no_gzip'
209
+ set_path 'no_gzip.html'
210
+ assert File.exist?(cache_file), 'no_gzip.html should exist'
211
+ refute File.exist?(File.join(cache_path, 'no_gzip.html.gz')),
212
+ 'gzipped no_gzip.html file should not exist'
213
+ end
214
+
215
+ it 'allows overriding of gzip level' do
216
+ get '/gzip_level'
217
+ assert last_request.env['rack.page_caching.compression'] == Zlib::BEST_SPEED,
218
+ 'compression level must be set to the requested level'
219
+ end
220
+
221
+ it 'caches according to requested format' do
222
+ get '/accept_xml', { format: :xml }
223
+ set_path 'accept_xml.xml'
224
+ assert File.exist?(cache_file), 'accept_xml.xml should exist'
225
+ end
226
+
227
+ describe 'notifications' do
228
+ class Counter
229
+ def self.incr
230
+ @counter += 1
231
+ end
232
+
233
+ def self.reset
234
+ @counter = 0
235
+ end
236
+
237
+ def self.check
238
+ @counter
239
+ end
240
+ end
241
+
242
+ let(:subscribe) do
243
+ Counter.reset
244
+ Counter.check.must_equal 0
245
+ @subscriber = ActiveSupport::Notifications.subscribe(@event) do |name, start, finish, id, payload|
246
+ payload[:path].must_equal cache_file
247
+ Counter.incr
248
+ end
249
+ end
250
+
251
+ after do
252
+ ActiveSupport::Notifications.unsubscribe @subscriber
253
+ end
254
+
255
+ it 'notifies subscribers after writing' do
256
+ @event = 'write_page.action_controller'
257
+ set_path 'cache.html'
258
+ subscribe
259
+ get '/cache'
260
+ Counter.check.must_equal 1
261
+ end
262
+
263
+ it 'notifies subscribers after deleting' do
264
+ @event = 'expire_page.action_controller'
265
+ set_path 'wootang.html'
266
+ subscribe
267
+ get '/custom_caching'
268
+ get '/expire_custom_caching'
269
+ Counter.check.must_equal 1
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,187 @@
1
+ require 'test_helper'
2
+ require './test/support/file_helper'
3
+
4
+ describe Rack::PageCaching::Cache do
5
+ let(:options) { {} }
6
+
7
+ let(:config) { Rack::PageCaching::Environment.new(options) }
8
+
9
+ let(:content_type) { 'text/html' }
10
+
11
+ let(:path) { '/hotels/singapore' }
12
+
13
+ let(:env) do
14
+ {
15
+ 'PATH_INFO' => path,
16
+ 'REQUEST_METHOD' => 'GET',
17
+ 'HTTP_HOST' => 'www.example.org',
18
+ 'rack.page_caching.perform_caching' => true
19
+ }
20
+ end
21
+
22
+ let(:response) do
23
+ Rack::PageCaching::Response.new(
24
+ [
25
+ 200,
26
+ { 'Content-Type' => content_type },
27
+ ['Foo Bar']
28
+ ],
29
+ env
30
+ )
31
+ end
32
+
33
+ let(:cache) { Rack::PageCaching::Cache.new(response) }
34
+
35
+ before { Rack::PageCaching.environment = config }
36
+
37
+ describe '#gzip_level' do
38
+ subject { cache.gzip_level }
39
+
40
+ describe 'from config' do
41
+ let(:options) { { page_cache_compression: :best_compression } }
42
+
43
+ it 'returns the gzip_level from config if gzip level is not specified in response' do
44
+ subject.must_equal Zlib::BEST_COMPRESSION
45
+ end
46
+ end
47
+
48
+ describe 'from response' do
49
+ before do
50
+ env.merge! 'rack.page_caching.compression' => Rack::PageCaching::Utils.gzip_level(:best_speed)
51
+ options.merge! page_cache_compression: :best_compression
52
+ end
53
+
54
+ it 'returns the gzip_level from response' do
55
+ subject.must_equal Zlib::BEST_SPEED
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#page_cache_path' do
61
+ subject { cache.page_cache_path }
62
+
63
+ it 'returns the correct page_cache_path' do
64
+ subject.must_equal '/hotels/singapore.html'
65
+ end
66
+
67
+ describe 'uses correct extension' do
68
+ let(:content_type) { 'application/xml' }
69
+ it 'returns the correct extension for the cache path' do
70
+ subject.must_equal '/hotels/singapore.xml'
71
+ end
72
+ end
73
+
74
+ describe 'uses index.html' do
75
+ let(:path) { '/' }
76
+
77
+ it 'appends index.html when path is only a slash' do
78
+ subject.must_equal '/index.html'
79
+ end
80
+ end
81
+
82
+ describe 'uses hostname' do
83
+ before { options.merge!(include_hostname: true) }
84
+
85
+ it 'includes hostname in cache path' do
86
+ subject.must_equal '/www.example.org/hotels/singapore.html'
87
+ end
88
+ end
89
+
90
+ describe 'skips extension' do
91
+ let(:path) { '/index.json' }
92
+ let(:content_type) { 'application/json' }
93
+
94
+ it 'does not add extension again if path already has extension' do
95
+ subject.must_equal '/index.json'
96
+ end
97
+ end
98
+
99
+ describe 'escapes path' do
100
+ let(:path) { '/path%20with%20spaces' }
101
+
102
+ it 'escapes the path' do
103
+ subject.must_equal '/path with spaces.html'
104
+ end
105
+ end
106
+ end
107
+
108
+ describe 'saving to disk' do
109
+ include FileHelper
110
+
111
+ let(:options) { { page_cache_directory: cache_path } }
112
+ let(:cache_file) { File.join(cache_path, 'hotels', 'singapore.html') }
113
+ let(:cache_content) { File.read(cache_file) }
114
+
115
+ describe '#store' do
116
+ it 'stores the response to disk' do
117
+ cache.store
118
+ cache_content.must_equal 'Foo Bar'
119
+ end
120
+
121
+ it 'overwrites the file for the same call' do
122
+ cache.store
123
+ cache.store
124
+ cache_content.must_equal 'Foo Bar'
125
+ end
126
+
127
+ describe 'compression' do
128
+ let(:options) do
129
+ {
130
+ page_cache_compression: :best_speed,
131
+ page_cache_directory: cache_path,
132
+ }
133
+ end
134
+
135
+ let(:zipped_path) { File.join(cache_path, 'hotels', 'singapore.html.gz') }
136
+
137
+ it 'creates a compressed file' do
138
+ cache.store
139
+ File.exist?(zipped_path).must_equal true
140
+ end
141
+
142
+ it 'zips the content' do
143
+ cache.store
144
+ Zlib::GzipReader.open(zipped_path) do |gz|
145
+ gz.read.must_equal 'Foo Bar'
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ describe '.store' do
152
+ it 'stores the response to disk' do
153
+ Rack::PageCaching::Cache.store(response)
154
+ cache_content.must_equal 'Foo Bar'
155
+ end
156
+ end
157
+
158
+ describe '.delete' do
159
+ it 'stores the file to disk' do
160
+ Rack::PageCaching::Cache.store(response)
161
+ File.exist?(cache_file).must_equal true
162
+ Rack::PageCaching::Cache.delete(File.join('hotels', 'singapore.html'))
163
+ refute File.exist?(cache_file)
164
+ end
165
+
166
+ describe 'globs' do
167
+ let(:options) do
168
+ {
169
+ page_cache_directory: cache_path,
170
+ include_hostname: true
171
+ }
172
+ end
173
+
174
+ let(:cache_file) do
175
+ File.join(cache_path, 'www.example.org', 'hotels', 'singapore.html')
176
+ end
177
+
178
+ it 'accepts globs in path' do
179
+ Rack::PageCaching::Cache.store(response)
180
+ File.exist?(cache_file).must_equal true
181
+ Rack::PageCaching::Cache.delete(File.join('**', 'hotels', 'singapore.html'))
182
+ refute File.exist?(cache_file)
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,82 @@
1
+ require 'test_helper'
2
+ require 'pathname'
3
+
4
+ describe Rack::PageCaching::Environment do
5
+ let(:env) { Rack::PageCaching::Environment.new(options) }
6
+
7
+ describe 'defaults' do
8
+ let(:options) { {} }
9
+
10
+ it 'is not enabled when a page_cache_directory is not set' do
11
+ refute env.enabled?
12
+ end
13
+
14
+ it 'defaults page_cache_compression to false' do
15
+ refute env.page_cache_compression
16
+ end
17
+
18
+ it 'defaults include_hostname to false' do
19
+ refute env.include_hostname?
20
+ end
21
+ end
22
+
23
+ describe 'set options' do
24
+ let(:options) {
25
+ {
26
+ include_hostname: true,
27
+ page_cache_compression: :best_compression,
28
+ page_cache_directory: '/var/tmp'
29
+ }
30
+ }
31
+
32
+ it 'includes hostname' do
33
+ env.include_hostname?.must_equal true
34
+ end
35
+
36
+ it 'is enabled' do
37
+ env.enabled?.must_equal true
38
+ end
39
+
40
+ it 'returns the correct zlib compression constant' do
41
+ env.page_cache_compression.must_equal Zlib::BEST_COMPRESSION
42
+ end
43
+
44
+ it 'sets the correct page cache directory' do
45
+ env.page_cache_directory.must_equal '/var/tmp'
46
+ end
47
+
48
+ describe 'page_cache_directory' do
49
+ before { options.merge! page_cache_directory: Pathname.new('/var/tmp') }
50
+
51
+ it 'accepts a Pathname as page cache directory' do
52
+ env.page_cache_directory.must_equal '/var/tmp'
53
+ end
54
+ end
55
+ end
56
+
57
+ describe 'in Rails' do
58
+ before do
59
+ controller = Object.new
60
+ controller.define_singleton_method(:perform_caching) { true }
61
+ config = Object.new
62
+ config.define_singleton_method(:action_controller) { controller }
63
+ application = Object.new
64
+ application.define_singleton_method(:config) { config }
65
+ root = Object.new
66
+ root.define_singleton_method(:join) do |folder|
67
+ Pathname.new('/var/www/app').join(folder)
68
+ end
69
+ Rails = Class.new
70
+ Rails.define_singleton_method(:application) { application }
71
+ Rails.define_singleton_method(:root) { root }
72
+ end
73
+
74
+ let(:options) { {} }
75
+
76
+ it 'sets the page cache path to public folder relative to rails root' do
77
+ env.page_cache_directory.must_equal '/var/www/app/public'
78
+ end
79
+
80
+ after { Object.send(:remove_const, :Rails) }
81
+ end
82
+ end
@@ -0,0 +1,24 @@
1
+ require 'test_helper'
2
+
3
+ describe Rack::PageCaching::MimeTypes do
4
+ let(:mime_types) { Rack::PageCaching::MimeTypes }
5
+
6
+ describe '.extension_for' do
7
+ {
8
+ 'text/html' => '.html',
9
+ 'application/json' => '.json',
10
+ 'text/css' => '.css'
11
+ }.each do |content_type, extension|
12
+ it "returns #{extension} for content type #{content_type}" do
13
+ mime_types.extension_for(content_type).must_equal extension
14
+ end
15
+ end
16
+ end
17
+
18
+ describe '.register' do
19
+ it 'allows registration of custom content_type' do
20
+ mime_types.register 'text/ing', '.ing'
21
+ mime_types.extension_for('text/ing').must_equal '.ing'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,72 @@
1
+ require 'test_helper'
2
+
3
+ describe Rack::PageCaching::Response do
4
+ let(:rack_response) do
5
+ [
6
+ 200,
7
+ { 'Content-Type' => 'text/html; charset=utf-8' },
8
+ ['Foo Bar']
9
+ ]
10
+ end
11
+
12
+ let(:env) do
13
+ {
14
+ 'PATH_INFO' => '/hotels/singapore',
15
+ 'REQUEST_METHOD' => 'GET',
16
+ 'HTTP_HOST' => 'www.example.org',
17
+ 'rack.page_caching.perform_caching' => true
18
+ }
19
+ end
20
+
21
+ let(:response) { Rack::PageCaching::Response.new(rack_response, env) }
22
+
23
+ it 'is cacheable for successful html responses' do
24
+ response.cacheable?.must_equal true
25
+ end
26
+
27
+ it 'returns the path' do
28
+ response.path.must_equal '/hotels/singapore'
29
+ end
30
+
31
+ it 'returns the body' do
32
+ response.body.must_equal ['Foo Bar']
33
+ end
34
+
35
+ it 'returns the content type' do
36
+ response.content_type.must_equal 'text/html'
37
+ end
38
+
39
+ it 'returns the host' do
40
+ response.host.must_equal 'www.example.org'
41
+ end
42
+
43
+ describe 'no caching' do
44
+ describe 'no directive provided' do
45
+ before { env.delete 'rack.page_caching.perform_caching' }
46
+ it 'is not cacheable by default' do
47
+ response.cacheable?.must_equal false
48
+ end
49
+ end
50
+
51
+ describe 'directive is false' do
52
+ before { env.merge! 'rack.page_caching.perform_caching' => false }
53
+ it 'is not cacheable if perform_caching env is set to false' do
54
+ response.cacheable?.must_equal false
55
+ end
56
+ end
57
+
58
+ describe 'response not cacheable' do
59
+ before { env.merge! 'REQUEST_METHOD' => 'POST' }
60
+ it 'does not cache post requests' do
61
+ response.cacheable?.must_equal false
62
+ end
63
+ end
64
+
65
+ describe 'non-200 response' do
66
+ before { rack_response[0] = 404 }
67
+ it 'does not cache resposes where status is not 200' do
68
+ response.cacheable?.must_equal false
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,18 @@
1
+ require 'test_helper'
2
+
3
+ describe Rack::PageCaching::Utils do
4
+ let(:utils) { Rack::PageCaching::Utils }
5
+ describe '.gzip_level' do
6
+ it 'accepts a number and returns it' do
7
+ utils.gzip_level(1).must_equal 1
8
+ end
9
+
10
+ it 'accepts a symbol and returns the corresponding zlib constant' do
11
+ utils.gzip_level(:best_speed).must_equal Zlib::BEST_SPEED
12
+ end
13
+
14
+ it 'returns false for unrecognized input' do
15
+ utils.gzip_level('best_speed').must_equal false
16
+ end
17
+ end
18
+ end