moneta 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +39 -0
  3. data/Gemfile +61 -0
  4. data/LICENSE +2 -2
  5. data/README.md +450 -0
  6. data/Rakefile +29 -51
  7. data/SPEC.md +75 -0
  8. data/benchmarks/run.rb +195 -0
  9. data/lib/action_dispatch/middleware/session/moneta_store.rb +11 -0
  10. data/lib/active_support/cache/moneta_store.rb +55 -0
  11. data/lib/moneta.rb +121 -67
  12. data/lib/moneta/adapters/activerecord.rb +87 -0
  13. data/lib/moneta/adapters/cassandra.rb +91 -0
  14. data/lib/moneta/adapters/client.rb +69 -0
  15. data/lib/moneta/adapters/cookie.rb +35 -0
  16. data/lib/moneta/adapters/couch.rb +57 -0
  17. data/lib/moneta/adapters/datamapper.rb +75 -0
  18. data/lib/moneta/adapters/dbm.rb +25 -0
  19. data/lib/moneta/adapters/file.rb +79 -0
  20. data/lib/moneta/adapters/fog.rb +51 -0
  21. data/lib/moneta/adapters/gdbm.rb +25 -0
  22. data/lib/moneta/adapters/hbase.rb +101 -0
  23. data/lib/moneta/adapters/leveldb.rb +35 -0
  24. data/lib/moneta/adapters/localmemcache.rb +28 -0
  25. data/lib/moneta/adapters/lruhash.rb +85 -0
  26. data/lib/moneta/adapters/memcached.rb +11 -0
  27. data/lib/moneta/adapters/memcached_dalli.rb +69 -0
  28. data/lib/moneta/adapters/memcached_native.rb +70 -0
  29. data/lib/moneta/adapters/memory.rb +10 -0
  30. data/lib/moneta/adapters/mongo.rb +50 -0
  31. data/lib/moneta/adapters/null.rb +30 -0
  32. data/lib/moneta/adapters/pstore.rb +69 -0
  33. data/lib/moneta/adapters/redis.rb +68 -0
  34. data/lib/moneta/adapters/riak.rb +57 -0
  35. data/lib/moneta/adapters/sdbm.rb +35 -0
  36. data/lib/moneta/adapters/sequel.rb +79 -0
  37. data/lib/moneta/adapters/sqlite.rb +65 -0
  38. data/lib/moneta/adapters/tokyocabinet.rb +41 -0
  39. data/lib/moneta/adapters/yaml.rb +15 -0
  40. data/lib/moneta/base.rb +78 -0
  41. data/lib/moneta/builder.rb +39 -0
  42. data/lib/moneta/cache.rb +84 -0
  43. data/lib/moneta/expires.rb +71 -0
  44. data/lib/moneta/lock.rb +25 -0
  45. data/lib/moneta/logger.rb +61 -0
  46. data/lib/moneta/mixins.rb +65 -0
  47. data/lib/moneta/net.rb +18 -0
  48. data/lib/moneta/optionmerger.rb +39 -0
  49. data/lib/moneta/proxy.rb +86 -0
  50. data/lib/moneta/server.rb +81 -0
  51. data/lib/moneta/shared.rb +60 -0
  52. data/lib/moneta/stack.rb +78 -0
  53. data/lib/moneta/transformer.rb +159 -0
  54. data/lib/moneta/transformer/config.rb +42 -0
  55. data/lib/moneta/transformer/helper.rb +37 -0
  56. data/lib/moneta/version.rb +5 -0
  57. data/lib/moneta/wrapper.rb +33 -0
  58. data/lib/rack/cache/moneta.rb +93 -0
  59. data/lib/rack/moneta_cookies.rb +64 -0
  60. data/lib/rack/session/moneta.rb +63 -0
  61. data/moneta.gemspec +19 -0
  62. data/spec/action_dispatch/fixtures/session_autoload_test/foo.rb +10 -0
  63. data/spec/action_dispatch/session_moneta_store_spec.rb +196 -0
  64. data/spec/active_support/cache_moneta_store_spec.rb +197 -0
  65. data/spec/generate.rb +1489 -0
  66. data/spec/helper.rb +91 -0
  67. data/spec/moneta/adapter_activerecord_spec.rb +32 -0
  68. data/spec/moneta/adapter_cassandra_spec.rb +30 -0
  69. data/spec/moneta/adapter_client_spec.rb +19 -0
  70. data/spec/moneta/adapter_cookie_spec.rb +18 -0
  71. data/spec/moneta/adapter_couch_spec.rb +18 -0
  72. data/spec/moneta/adapter_datamapper_spec.rb +49 -0
  73. data/spec/moneta/adapter_dbm_spec.rb +18 -0
  74. data/spec/moneta/adapter_file_spec.rb +18 -0
  75. data/spec/moneta/adapter_fog_spec.rb +23 -0
  76. data/spec/moneta/adapter_gdbm_spec.rb +18 -0
  77. data/spec/moneta/adapter_hbase_spec.rb +18 -0
  78. data/spec/moneta/adapter_leveldb_spec.rb +18 -0
  79. data/spec/moneta/adapter_localmemcache_spec.rb +18 -0
  80. data/spec/moneta/adapter_lruhash_spec.rb +31 -0
  81. data/spec/moneta/adapter_memcached_dalli_spec.rb +30 -0
  82. data/spec/moneta/adapter_memcached_native_spec.rb +31 -0
  83. data/spec/moneta/adapter_memcached_spec.rb +30 -0
  84. data/spec/moneta/adapter_memory_spec.rb +39 -0
  85. data/spec/moneta/adapter_mongo_spec.rb +18 -0
  86. data/spec/moneta/adapter_pstore_spec.rb +21 -0
  87. data/spec/moneta/adapter_redis_spec.rb +30 -0
  88. data/spec/moneta/adapter_riak_spec.rb +22 -0
  89. data/spec/moneta/adapter_sdbm_spec.rb +18 -0
  90. data/spec/moneta/adapter_sequel_spec.rb +18 -0
  91. data/spec/moneta/adapter_sqlite_spec.rb +18 -0
  92. data/spec/moneta/adapter_tokyocabinet_bdb_spec.rb +18 -0
  93. data/spec/moneta/adapter_tokyocabinet_hdb_spec.rb +18 -0
  94. data/spec/moneta/adapter_yaml_spec.rb +21 -0
  95. data/spec/moneta/cache_file_memory_spec.rb +34 -0
  96. data/spec/moneta/cache_memory_null_spec.rb +23 -0
  97. data/spec/moneta/expires_file_spec.rb +76 -0
  98. data/spec/moneta/expires_memory_spec.rb +65 -0
  99. data/spec/moneta/lock_spec.rb +42 -0
  100. data/spec/moneta/null_adapter_spec.rb +26 -0
  101. data/spec/moneta/optionmerger_spec.rb +92 -0
  102. data/spec/moneta/proxy_expires_memory_spec.rb +55 -0
  103. data/spec/moneta/proxy_redis_spec.rb +23 -0
  104. data/spec/moneta/shared_spec.rb +30 -0
  105. data/spec/moneta/simple_activerecord_spec.rb +51 -0
  106. data/spec/moneta/simple_activerecord_with_expires_spec.rb +52 -0
  107. data/spec/moneta/simple_cassandra_spec.rb +52 -0
  108. data/spec/moneta/simple_client_tcp_spec.rb +67 -0
  109. data/spec/moneta/simple_client_unix_spec.rb +53 -0
  110. data/spec/moneta/simple_couch_spec.rb +51 -0
  111. data/spec/moneta/simple_couch_with_expires_spec.rb +52 -0
  112. data/spec/moneta/simple_datamapper_spec.rb +53 -0
  113. data/spec/moneta/simple_datamapper_with_expires_spec.rb +54 -0
  114. data/spec/moneta/simple_datamapper_with_repository_spec.rb +53 -0
  115. data/spec/moneta/simple_dbm_spec.rb +51 -0
  116. data/spec/moneta/simple_dbm_with_expires_spec.rb +52 -0
  117. data/spec/moneta/simple_file_spec.rb +51 -0
  118. data/spec/moneta/simple_file_with_expires_spec.rb +52 -0
  119. data/spec/moneta/simple_fog_spec.rb +56 -0
  120. data/spec/moneta/simple_fog_with_expires_spec.rb +58 -0
  121. data/spec/moneta/simple_gdbm_spec.rb +51 -0
  122. data/spec/moneta/simple_gdbm_with_expires_spec.rb +52 -0
  123. data/spec/moneta/simple_hashfile_spec.rb +51 -0
  124. data/spec/moneta/simple_hashfile_with_expires_spec.rb +52 -0
  125. data/spec/moneta/simple_hbase_spec.rb +51 -0
  126. data/spec/moneta/simple_hbase_with_expires_spec.rb +52 -0
  127. data/spec/moneta/simple_leveldb_spec.rb +51 -0
  128. data/spec/moneta/simple_leveldb_with_expires_spec.rb +52 -0
  129. data/spec/moneta/simple_localmemcache_spec.rb +51 -0
  130. data/spec/moneta/simple_localmemcache_with_expires_spec.rb +52 -0
  131. data/spec/moneta/simple_lruhash_spec.rb +51 -0
  132. data/spec/moneta/simple_lruhash_with_expires_spec.rb +52 -0
  133. data/spec/moneta/simple_memcached_dalli_spec.rb +52 -0
  134. data/spec/moneta/simple_memcached_native_spec.rb +52 -0
  135. data/spec/moneta/simple_memcached_spec.rb +52 -0
  136. data/spec/moneta/simple_memory_spec.rb +51 -0
  137. data/spec/moneta/simple_memory_with_compress_spec.rb +51 -0
  138. data/spec/moneta/simple_memory_with_expires_spec.rb +52 -0
  139. data/spec/moneta/simple_memory_with_json_key_serializer_spec.rb +37 -0
  140. data/spec/moneta/simple_memory_with_json_serializer_spec.rb +28 -0
  141. data/spec/moneta/simple_memory_with_json_value_serializer_spec.rb +35 -0
  142. data/spec/moneta/simple_memory_with_prefix_spec.rb +51 -0
  143. data/spec/moneta/simple_memory_with_snappy_compress_spec.rb +51 -0
  144. data/spec/moneta/simple_mongo_spec.rb +51 -0
  145. data/spec/moneta/simple_mongo_with_expires_spec.rb +52 -0
  146. data/spec/moneta/simple_null_spec.rb +36 -0
  147. data/spec/moneta/simple_pstore_spec.rb +51 -0
  148. data/spec/moneta/simple_pstore_with_expires_spec.rb +52 -0
  149. data/spec/moneta/simple_redis_spec.rb +52 -0
  150. data/spec/moneta/simple_riak_spec.rb +55 -0
  151. data/spec/moneta/simple_riak_with_expires_spec.rb +56 -0
  152. data/spec/moneta/simple_sdbm_spec.rb +51 -0
  153. data/spec/moneta/simple_sdbm_with_expires_spec.rb +52 -0
  154. data/spec/moneta/simple_sequel_spec.rb +51 -0
  155. data/spec/moneta/simple_sequel_with_expires_spec.rb +52 -0
  156. data/spec/moneta/simple_sqlite_spec.rb +51 -0
  157. data/spec/moneta/simple_sqlite_with_expires_spec.rb +52 -0
  158. data/spec/moneta/simple_tokyocabinet_spec.rb +51 -0
  159. data/spec/moneta/simple_tokyocabinet_with_expires_spec.rb +52 -0
  160. data/spec/moneta/simple_yaml_spec.rb +50 -0
  161. data/spec/moneta/simple_yaml_with_expires_spec.rb +51 -0
  162. data/spec/moneta/stack_file_memory_spec.rb +25 -0
  163. data/spec/moneta/stack_memory_file_spec.rb +24 -0
  164. data/spec/moneta/transformer_bencode_spec.rb +30 -0
  165. data/spec/moneta/transformer_bert_spec.rb +30 -0
  166. data/spec/moneta/transformer_bson_spec.rb +30 -0
  167. data/spec/moneta/transformer_bzip2_spec.rb +27 -0
  168. data/spec/moneta/transformer_json_spec.rb +30 -0
  169. data/spec/moneta/transformer_lzma_spec.rb +27 -0
  170. data/spec/moneta/transformer_lzo_spec.rb +27 -0
  171. data/spec/moneta/transformer_marshal_base64_spec.rb +54 -0
  172. data/spec/moneta/transformer_marshal_escape_spec.rb +54 -0
  173. data/spec/moneta/transformer_marshal_hmac_spec.rb +54 -0
  174. data/spec/moneta/transformer_marshal_md5_spec.rb +54 -0
  175. data/spec/moneta/transformer_marshal_md5_spread_spec.rb +54 -0
  176. data/spec/moneta/transformer_marshal_prefix_spec.rb +54 -0
  177. data/spec/moneta/transformer_marshal_rmd160_spec.rb +54 -0
  178. data/spec/moneta/transformer_marshal_sha1_spec.rb +54 -0
  179. data/spec/moneta/transformer_marshal_sha256_spec.rb +54 -0
  180. data/spec/moneta/transformer_marshal_sha384_spec.rb +54 -0
  181. data/spec/moneta/transformer_marshal_sha512_spec.rb +54 -0
  182. data/spec/moneta/transformer_marshal_truncate_spec.rb +54 -0
  183. data/spec/moneta/transformer_marshal_uuencode_spec.rb +54 -0
  184. data/spec/moneta/transformer_msgpack_spec.rb +30 -0
  185. data/spec/moneta/transformer_ox_spec.rb +51 -0
  186. data/spec/moneta/transformer_quicklz_spec.rb +27 -0
  187. data/spec/moneta/transformer_snappy_spec.rb +27 -0
  188. data/spec/moneta/transformer_tnet_spec.rb +30 -0
  189. data/spec/moneta/transformer_yaml_spec.rb +51 -0
  190. data/spec/moneta/transformer_zlib_spec.rb +27 -0
  191. data/spec/monetaspecs.rb +2663 -0
  192. data/spec/rack/cache_moneta_spec.rb +355 -0
  193. data/spec/rack/moneta_cookies_spec.rb +81 -0
  194. data/spec/rack/session_moneta_spec.rb +305 -0
  195. metadata +359 -56
  196. data/README +0 -51
  197. data/TODO +0 -4
  198. data/lib/moneta/basic_file.rb +0 -111
  199. data/lib/moneta/berkeley.rb +0 -53
  200. data/lib/moneta/couch.rb +0 -63
  201. data/lib/moneta/datamapper.rb +0 -117
  202. data/lib/moneta/file.rb +0 -91
  203. data/lib/moneta/lmc.rb +0 -52
  204. data/lib/moneta/memcache.rb +0 -52
  205. data/lib/moneta/memory.rb +0 -11
  206. data/lib/moneta/mongodb.rb +0 -58
  207. data/lib/moneta/redis.rb +0 -49
  208. data/lib/moneta/rufus.rb +0 -41
  209. data/lib/moneta/s3.rb +0 -162
  210. data/lib/moneta/sdbm.rb +0 -33
  211. data/lib/moneta/tyrant.rb +0 -58
  212. data/lib/moneta/xattr.rb +0 -58
@@ -0,0 +1,355 @@
1
+ require 'rack/cache/moneta'
2
+ require 'rack/mock'
3
+ require 'rack/cache'
4
+
5
+ class Object
6
+ def sha_like?
7
+ length == 40 && self =~ /^[0-9a-z]+$/
8
+ end
9
+ end
10
+
11
+ describe Rack::Cache::MetaStore::Moneta do
12
+ before do
13
+ Rack::Cache::Moneta['meta'] = Moneta.new(:Memory, :expires => true)
14
+ Rack::Cache::Moneta['entity'] = Moneta.new(:Memory, :expires => true)
15
+ @store = Rack::Cache::MetaStore::Moneta.resolve uri('moneta://entity')
16
+ @entity_store = Rack::Cache::EntityStore::Moneta.resolve uri('moneta://meta')
17
+ @request = mock_request('/', {})
18
+ @response = mock_response(200, {}, ['hello world'])
19
+ end
20
+
21
+ after do
22
+ Rack::Cache::Moneta['meta'].clear
23
+ Rack::Cache::Moneta['entity'].clear
24
+ end
25
+
26
+ it "has the class referenced by homonym constant" do
27
+ Rack::Cache::MetaStore::MONETA.should == Rack::Cache::MetaStore::Moneta
28
+ end
29
+
30
+ it "instantiates the store" do
31
+ @store.should be_kind_of(Rack::Cache::MetaStore::Moneta)
32
+ end
33
+
34
+ it "resolves the connection uri" do
35
+ Rack::Cache::MetaStore::Moneta.resolve(uri('moneta://Memory?expires=true')).should be_kind_of(Rack::Cache::MetaStore::Moneta)
36
+ end
37
+
38
+ # Low-level implementation methods ===========================================
39
+
40
+ it 'writes a list of negotation tuples with #write' do
41
+ # lambda {
42
+ @store.write('/test', [[{}, {}]])
43
+ # }.should_not raise Exception
44
+ end
45
+
46
+ it 'reads a list of negotation tuples with #read' do
47
+ @store.write('/test', [[{},{}],[{},{}]])
48
+ tuples = @store.read('/test')
49
+ tuples.should == [ [{},{}], [{},{}] ]
50
+ end
51
+
52
+ it 'reads an empty list with #read when nothing cached at key' do
53
+ @store.read('/nothing').should be_empty
54
+ end
55
+
56
+ it 'removes entries for key with #purge' do
57
+ @store.write('/test', [[{},{}]])
58
+ @store.read('/test').should_not be_empty
59
+
60
+ @store.purge('/test')
61
+ @store.read('/test').should be_empty
62
+ end
63
+
64
+ it 'succeeds when purging non-existing entries' do
65
+ @store.read('/test').should be_empty
66
+ @store.purge('/test')
67
+ end
68
+
69
+ it 'returns nil from #purge' do
70
+ @store.write('/test', [[{},{}]])
71
+ @store.purge('/test').should be_nil
72
+ @store.read('/test').should == []
73
+ end
74
+
75
+ %w[/test http://example.com:8080/ /test?x=y /test?x=y&p=q].each do |key|
76
+ it "can read and write key: '#{key}'" do
77
+ # lambda {
78
+ @store.write(key, [[{},{}]])
79
+ # }.should_not raise Exception
80
+ @store.read(key).should == [[{},{}]]
81
+ end
82
+ end
83
+
84
+ it "can read and write fairly large keys" do
85
+ key = "b" * 4096
86
+ # lambda {
87
+ @store.write(key, [[{},{}]])
88
+ # }.should_not raise Exception
89
+ @store.read(key).should == [[{},{}]]
90
+ end
91
+
92
+ it "allows custom cache keys from block" do
93
+ request = mock_request('/test', {})
94
+ request.env['rack-cache.cache_key'] =
95
+ lambda { |request| request.path_info.reverse }
96
+ @store.cache_key(request).should == 'tset/'
97
+ end
98
+
99
+ it "allows custom cache keys from class" do
100
+ request = mock_request('/test', {})
101
+ request.env['rack-cache.cache_key'] = Class.new do
102
+ def self.call(request); request.path_info.reverse end
103
+ end
104
+ @store.cache_key(request).should == 'tset/'
105
+ end
106
+
107
+ it 'does not blow up when given a non-marhsalable object with an ALL_CAPS key' do
108
+ store_simple_entry('/bad', { 'SOME_THING' => Proc.new {} })
109
+ end
110
+
111
+ # Abstract methods ===========================================================
112
+
113
+ it 'stores a cache entry' do
114
+ cache_key = store_simple_entry
115
+ @store.read(cache_key).should_not be_empty
116
+ end
117
+
118
+ it 'sets the X-Content-Digest response header before storing' do
119
+ cache_key = store_simple_entry
120
+ req, res = @store.read(cache_key).first
121
+ res['X-Content-Digest'].should == 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'
122
+ end
123
+
124
+ it 'finds a stored entry with #lookup' do
125
+ store_simple_entry
126
+ response = @store.lookup(@request, @entity_store)
127
+ response.should_not be_nil
128
+ response.should be_kind_of(Rack::Cache::Response)
129
+ end
130
+
131
+ it 'does not find an entry with #lookup when none exists' do
132
+ req = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
133
+ @store.lookup(req, @entity_store).should be_nil
134
+ end
135
+
136
+ it "canonizes urls for cache keys" do
137
+ store_simple_entry(path='/test?x=y&p=q')
138
+
139
+ hits_req = mock_request(path, {})
140
+ miss_req = mock_request('/test?p=x', {})
141
+
142
+ @store.lookup(hits_req, @entity_store).should_not be_nil
143
+ @store.lookup(miss_req, @entity_store).should be_nil
144
+ end
145
+
146
+ it 'does not find an entry with #lookup when the body does not exist' do
147
+ store_simple_entry
148
+ @response.headers['X-Content-Digest'].should_not be_nil
149
+ @entity_store.purge(@response.headers['X-Content-Digest'])
150
+ @store.lookup(@request, @entity_store).should be_nil
151
+ end
152
+
153
+ it 'restores response headers properly with #lookup' do
154
+ store_simple_entry
155
+ response = @store.lookup(@request, @entity_store)
156
+ response.headers.should == @response.headers.merge('Content-Length' => '4')
157
+ end
158
+
159
+ it 'restores response body from entity store with #lookup' do
160
+ store_simple_entry
161
+ response = @store.lookup(@request, @entity_store)
162
+ body = '' ; response.body.each {|p| body << p}
163
+ body.should == 'test'
164
+ end
165
+
166
+ it 'invalidates meta and entity store entries with #invalidate' do
167
+ store_simple_entry
168
+ @store.invalidate(@request, @entity_store)
169
+ response = @store.lookup(@request, @entity_store)
170
+ response.should be_kind_of(Rack::Cache::Response)
171
+ response.should_not be :fresh?
172
+ end
173
+
174
+ it 'succeeds quietly when #invalidate called with no matching entries' do
175
+ req = mock_request('/test', {})
176
+ @store.invalidate(req, @entity_store)
177
+ @store.lookup(@request, @entity_store).should be_nil
178
+ end
179
+
180
+ # Vary =======================================================================
181
+
182
+ it 'does not return entries that Vary with #lookup' do
183
+ req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
184
+ req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
185
+ res = mock_response(200, {'Vary' => 'Foo Bar'}, ['test'])
186
+ @store.store(req1, res, @entity_store)
187
+
188
+ @store.lookup(req2, @entity_store).should be_nil
189
+ end
190
+
191
+ it 'stores multiple responses for each Vary combination' do
192
+ req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
193
+ res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
194
+ key = @store.store(req1, res1, @entity_store)
195
+
196
+ req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
197
+ res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
198
+ @store.store(req2, res2, @entity_store)
199
+
200
+ req3 = mock_request('/test', {'HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom'})
201
+ res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
202
+ @store.store(req3, res3, @entity_store)
203
+
204
+ slurp(@store.lookup(req3, @entity_store).body).should == 'test 3'
205
+ slurp(@store.lookup(req1, @entity_store).body).should == 'test 1'
206
+ slurp(@store.lookup(req2, @entity_store).body).should == 'test 2'
207
+
208
+ @store.read(key).length.should == 3
209
+ end
210
+
211
+ it 'overwrites non-varying responses with #store' do
212
+ req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
213
+ res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
214
+ key = @store.store(req1, res1, @entity_store)
215
+ slurp(@store.lookup(req1, @entity_store).body).should == 'test 1'
216
+
217
+ req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
218
+ res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
219
+ @store.store(req2, res2, @entity_store)
220
+ slurp(@store.lookup(req2, @entity_store).body).should == 'test 2'
221
+
222
+ req3 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
223
+ res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
224
+ @store.store(req3, res3, @entity_store)
225
+ slurp(@store.lookup(req1, @entity_store).body).should == 'test 3'
226
+
227
+ @store.read(key).length.should == 2
228
+ end
229
+
230
+ private
231
+ def mock_request(uri, opts)
232
+ env = Rack::MockRequest.env_for(uri, opts || {})
233
+ Rack::Cache::Request.new(env)
234
+ end
235
+
236
+ def mock_response(status, headers, body)
237
+ headers ||= {}
238
+ body = Array(body).compact
239
+ Rack::Cache::Response.new(status, headers, body)
240
+ end
241
+
242
+ def slurp(body)
243
+ buf = ''
244
+ body.each { |part| buf << part }
245
+ buf
246
+ end
247
+
248
+ # Stores an entry for the given request args, returns a url encoded cache key
249
+ # for the request.
250
+ def store_simple_entry(*request_args)
251
+ path, headers = request_args
252
+ @request = mock_request(path || '/test', headers || {})
253
+ @response = mock_response(200, {'Cache-Control' => 'max-age=420'}, ['test'])
254
+ body = @response.body
255
+ cache_key = @store.store(@request, @response, @entity_store)
256
+ @response.body.should == body
257
+ cache_key
258
+ end
259
+
260
+ def uri(uri)
261
+ URI.parse uri
262
+ end
263
+ end
264
+
265
+ describe Rack::Cache::EntityStore::Moneta do
266
+ before do
267
+ @store = Rack::Cache::EntityStore::Moneta.resolve(uri('moneta://Memory?expires=true'))
268
+ end
269
+
270
+ it 'has the class referenced by homonym constant' do
271
+ Rack::Cache::EntityStore::MONETA.should == Rack::Cache::EntityStore::Moneta
272
+ end
273
+
274
+ it 'resolves the connection uri' do
275
+ Rack::Cache::EntityStore::Moneta.resolve(uri('moneta://Memory?expires=true')).should be_kind_of(Rack::Cache::EntityStore::Moneta)
276
+ end
277
+
278
+ it 'responds to all required messages' do
279
+ %w[read open write exist?].each do |message|
280
+ @store.should respond_to message
281
+ end
282
+ end
283
+
284
+ it 'stores bodies with #write' do
285
+ key, size = @store.write(['My wild love went riding,'])
286
+ key.should_not be_nil
287
+ key.should be_sha_like
288
+
289
+ data = @store.read(key)
290
+ data.should == 'My wild love went riding,'
291
+ end
292
+
293
+ it 'takes a ttl parameter for #write' do
294
+ key, size = @store.write(['My wild love went riding,'], 0)
295
+ key.should_not be_nil
296
+ key.should be_sha_like
297
+
298
+ data = @store.read(key)
299
+ data.should == 'My wild love went riding,'
300
+ end
301
+
302
+ it 'correctly determines whether cached body exists for key with #exist?' do
303
+ key, size = @store.write(['She rode to the devil,'])
304
+ @store.exist?(key).should be_true
305
+ @store.exist?('938jasddj83jasdh4438021ksdfjsdfjsdsf').should be_false
306
+ end
307
+
308
+ it 'can read data written with #write' do
309
+ key, size = @store.write(['And asked him to pay.'])
310
+ data = @store.read(key)
311
+ data.should == 'And asked him to pay.'
312
+ end
313
+
314
+ it 'gives a 40 character SHA1 hex digest from #write' do
315
+ key, size = @store.write(['she rode to the sea;'])
316
+ key.should_not be_nil
317
+ key.length.should == 40
318
+ key.should match(/^[0-9a-z]+$/)
319
+ key.should == '90a4c84d51a277f3dafc34693ca264531b9f51b6'
320
+ end
321
+
322
+ it 'returns the entire body as a String from #read' do
323
+ key, size = @store.write(['She gathered together'])
324
+ @store.read(key).should == 'She gathered together'
325
+ end
326
+
327
+ it 'returns nil from #read when key does not exist' do
328
+ @store.read('87fe0a1ae82a518592f6b12b0183e950b4541c62').should be_nil
329
+ end
330
+
331
+ it 'returns a Rack compatible body from #open' do
332
+ key, size = @store.write(['Some shells for her hair.'])
333
+ body = @store.open(key)
334
+ body.should respond_to :each
335
+ buf = ''
336
+ body.each { |part| buf << part }
337
+ buf.should == 'Some shells for her hair.'
338
+ end
339
+
340
+ it 'returns nil from #open when key does not exist' do
341
+ @store.open('87fe0a1ae82a518592f6b12b0183e950b4541c62').should be_nil
342
+ end
343
+
344
+ it 'deletes stored entries with #purge' do
345
+ key, size = @store.write(['My wild love went riding,'])
346
+ @store.purge(key).should be_nil
347
+ @store.read(key).should be_nil
348
+ end
349
+
350
+ private
351
+
352
+ def uri(uri)
353
+ URI.parse uri
354
+ end
355
+ end
@@ -0,0 +1,81 @@
1
+ require 'helper'
2
+ require 'rack/mock'
3
+ require 'rack/moneta_cookies'
4
+
5
+ describe Rack::MonetaCookies do
6
+ def config(options={},&block)
7
+ @options = options
8
+ @block = block
9
+ end
10
+
11
+ def app(&block)
12
+ @app_block ||= block
13
+ end
14
+
15
+ def backend
16
+ Rack::MockRequest.new(Rack::MonetaCookies.new(lambda{|env|
17
+ @store = env['rack.request.cookie_hash']
18
+ app.call(env) if app
19
+ [200,{},[]]
20
+ }, @options || {}, &@block))
21
+ end
22
+
23
+ def get(cookies = {}, &block)
24
+ app(&block)
25
+ @response = backend.get('/','HTTP_COOKIE' => Rack::Utils.build_query(cookies))
26
+ end
27
+
28
+ it 'should be able to read a simple key' do
29
+ get 'key' => 'value' do
30
+ expect( @store['key'] ).to eql('value')
31
+ end
32
+ end
33
+
34
+ it 'should be able to set a simple key' do
35
+ get do
36
+ @store['key'] = 'value'
37
+ end
38
+ expect( @response['Set-Cookie'] ).to eql('key=value')
39
+ end
40
+
41
+ it 'should be able to remove a simple key' do
42
+ get 'key' => 'value' do
43
+ @store.delete('key')
44
+ end
45
+ expect( @response['Set-Cookie'] ).to eql('key=; expires=Thu, 01-Jan-1970 00:00:00 GMT')
46
+ end
47
+
48
+ it 'should accept a config block' do
49
+ config do
50
+ use :Transformer, :key => :prefix, :prefix => 'moneta.'
51
+ adapter :Cookie
52
+ end
53
+ get 'moneta.key' => 'right', 'key' => 'wrong' do
54
+ expect( @store['key'] ).to eql('right')
55
+ end
56
+ end
57
+
58
+ it 'should accept a :domain option' do
59
+ config :domain => 'example.com'
60
+ get do
61
+ @store['key'] = 'value'
62
+ end
63
+ expect(@response['Set-Cookie']).to eql('key=value; domain=example.com')
64
+ end
65
+
66
+ it 'should accept a :path option' do
67
+ config :path => '/path'
68
+ get do
69
+ @store['key'] = 'value'
70
+ end
71
+ expect(@response['Set-Cookie']).to eql('key=value; path=/path')
72
+ end
73
+
74
+ it 'should be accessible via Rack::Request' do
75
+ get 'key' => 'value' do |env|
76
+ req = Rack::Request.new(env)
77
+ expect(req.cookies['key']).to eql('value')
78
+ end
79
+ end
80
+
81
+ end
@@ -0,0 +1,305 @@
1
+ require 'rack/session/moneta'
2
+ require 'rack/lint'
3
+ require 'rack/mock'
4
+ require 'thread'
5
+
6
+ describe Rack::Session::Moneta do
7
+ session_key = Rack::Session::Moneta::DEFAULT_OPTIONS[:key]
8
+ session_match = /#{session_key}=([0-9a-fA-F]+);/
9
+ incrementor = lambda do |env|
10
+ env["rack.session"]["counter"] ||= 0
11
+ env["rack.session"]["counter"] += 1
12
+ Rack::Response.new(env["rack.session"].inspect).to_a
13
+ end
14
+ drop_session = Rack::Lint.new(proc do |env|
15
+ env['rack.session.options'][:drop] = true
16
+ incrementor.call(env)
17
+ end)
18
+ renew_session = Rack::Lint.new(proc do |env|
19
+ env['rack.session.options'][:renew] = true
20
+ incrementor.call(env)
21
+ end)
22
+ defer_session = Rack::Lint.new(proc do |env|
23
+ env['rack.session.options'][:defer] = true
24
+ incrementor.call(env)
25
+ end)
26
+ skip_session = Rack::Lint.new(proc do |env|
27
+ env['rack.session.options'][:skip] = true
28
+ incrementor.call(env)
29
+ end)
30
+ incrementor = Rack::Lint.new(incrementor)
31
+
32
+ it 'supports different constructors' do
33
+ Rack::Session::Moneta.new(incrementor, :store => :Memory)
34
+ Rack::Session::Moneta.new(incrementor, :store => Moneta.new(:Memory, :expires => true))
35
+ Rack::Session::Moneta.new(incrementor) do
36
+ use :Expires
37
+ adapter :Memory
38
+ end
39
+ end
40
+
41
+ it "creates a new cookie" do
42
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
43
+ res = Rack::MockRequest.new(pool).get("/")
44
+ res["Set-Cookie"].should include("#{session_key}=")
45
+ res.body.should == '{"counter"=>1}'
46
+ end
47
+
48
+ it "determines session from a cookie" do
49
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
50
+ req = Rack::MockRequest.new(pool)
51
+ res = req.get("/")
52
+ cookie = res["Set-Cookie"]
53
+ req.get("/", "HTTP_COOKIE" => cookie).
54
+ body.should == '{"counter"=>2}'
55
+ req.get("/", "HTTP_COOKIE" => cookie).
56
+ body.should == '{"counter"=>3}'
57
+ end
58
+
59
+ it "determines session only from a cookie by default" do
60
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
61
+ req = Rack::MockRequest.new(pool)
62
+ res = req.get("/")
63
+ sid = res["Set-Cookie"][session_match, 1]
64
+ req.get("/?rack.session=#{sid}").
65
+ body.should == '{"counter"=>1}'
66
+ req.get("/?rack.session=#{sid}").
67
+ body.should == '{"counter"=>1}'
68
+ end
69
+
70
+ it "determines session from params" do
71
+ pool = Rack::Session::Moneta.new(incrementor, :cookie_only => false, :store => :Memory)
72
+ req = Rack::MockRequest.new(pool)
73
+ res = req.get("/")
74
+ sid = res["Set-Cookie"][session_match, 1]
75
+ req.get("/?rack.session=#{sid}").
76
+ body.should == '{"counter"=>2}'
77
+ req.get("/?rack.session=#{sid}").
78
+ body.should == '{"counter"=>3}'
79
+ end
80
+
81
+ it "survives nonexistant cookies" do
82
+ bad_cookie = "rack.session=blarghfasel"
83
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
84
+ res = Rack::MockRequest.new(pool).
85
+ get("/", "HTTP_COOKIE" => bad_cookie)
86
+ res.body.should == '{"counter"=>1}'
87
+ cookie = res["Set-Cookie"][session_match]
88
+ cookie.should_not match(/#{bad_cookie}/)
89
+ end
90
+
91
+ it "maintains freshness" do
92
+ pool = Rack::Session::Moneta.new(incrementor, :expire_after => 3, :store => :Memory)
93
+ res = Rack::MockRequest.new(pool).get('/')
94
+ res.body.should include '"counter"=>1'
95
+ cookie = res["Set-Cookie"]
96
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
97
+ res["Set-Cookie"].should == cookie
98
+ res.body.should include '"counter"=>2'
99
+ puts 'Sleeping to expire session' if $DEBUG
100
+ sleep 4
101
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
102
+ res["Set-Cookie"].should_not == cookie
103
+ res.body.should include '"counter"=>1'
104
+ end
105
+
106
+ it "does not send the same session id if it did not change" do
107
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
108
+ req = Rack::MockRequest.new(pool)
109
+
110
+ res0 = req.get("/")
111
+ cookie = res0["Set-Cookie"][session_match]
112
+ res0.body.should == '{"counter"=>1}'
113
+
114
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
115
+ res1["Set-Cookie"].should be_nil
116
+ res1.body.should == '{"counter"=>2}'
117
+
118
+ res2 = req.get("/", "HTTP_COOKIE" => cookie)
119
+ res2["Set-Cookie"].should be_nil
120
+ res2.body.should == '{"counter"=>3}'
121
+ end
122
+
123
+ it "deletes cookies with :drop option" do
124
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
125
+ req = Rack::MockRequest.new(pool)
126
+ drop = Rack::Utils::Context.new(pool, drop_session)
127
+ dreq = Rack::MockRequest.new(drop)
128
+
129
+ res1 = req.get("/")
130
+ session = (cookie = res1["Set-Cookie"])[session_match]
131
+ res1.body.should == '{"counter"=>1}'
132
+
133
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
134
+ res2["Set-Cookie"].should == nil
135
+ res2.body.should == '{"counter"=>2}'
136
+
137
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
138
+ res3["Set-Cookie"][session_match].should_not == session
139
+ res3.body.should == '{"counter"=>1}'
140
+ end
141
+
142
+ it "provides new session id with :renew option" do
143
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
144
+ req = Rack::MockRequest.new(pool)
145
+ renew = Rack::Utils::Context.new(pool, renew_session)
146
+ rreq = Rack::MockRequest.new(renew)
147
+
148
+ res1 = req.get("/")
149
+ session = (cookie = res1["Set-Cookie"])[session_match]
150
+ res1.body.should == '{"counter"=>1}'
151
+
152
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
153
+ new_cookie = res2["Set-Cookie"]
154
+ new_session = new_cookie[session_match]
155
+ new_session.should_not == session
156
+ res2.body.should == '{"counter"=>2}'
157
+
158
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
159
+ res3.body.should == '{"counter"=>3}'
160
+
161
+ # Old cookie was deleted
162
+ res4 = req.get("/", "HTTP_COOKIE" => cookie)
163
+ res4.body.should == '{"counter"=>1}'
164
+ end
165
+
166
+ it "omits cookie with :defer option but still updates the state" do
167
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
168
+ count = Rack::Utils::Context.new(pool, incrementor)
169
+ defer = Rack::Utils::Context.new(pool, defer_session)
170
+ dreq = Rack::MockRequest.new(defer)
171
+ creq = Rack::MockRequest.new(count)
172
+
173
+ res0 = dreq.get("/")
174
+ res0["Set-Cookie"].should == nil
175
+ res0.body.should == '{"counter"=>1}'
176
+
177
+ res0 = creq.get("/")
178
+ res1 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
179
+ res1.body.should == '{"counter"=>2}'
180
+ res2 = dreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
181
+ res2.body.should == '{"counter"=>3}'
182
+ end
183
+
184
+ it "omits cookie and state update with :skip option" do
185
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
186
+ count = Rack::Utils::Context.new(pool, incrementor)
187
+ skip = Rack::Utils::Context.new(pool, skip_session)
188
+ sreq = Rack::MockRequest.new(skip)
189
+ creq = Rack::MockRequest.new(count)
190
+
191
+ res0 = sreq.get("/")
192
+ res0["Set-Cookie"].should == nil
193
+ res0.body.should == '{"counter"=>1}'
194
+
195
+ res0 = creq.get("/")
196
+ res1 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
197
+ res1.body.should == '{"counter"=>2}'
198
+ res2 = sreq.get("/", "HTTP_COOKIE" => res0["Set-Cookie"])
199
+ res2.body.should == '{"counter"=>2}'
200
+ end
201
+
202
+ it "updates deep hashes correctly" do
203
+ hash_check = proc do |env|
204
+ session = env['rack.session']
205
+ unless session.include? 'test'
206
+ session.update :a => :b, :c => { :d => :e },
207
+ :f => { :g => { :h => :i} }, 'test' => true
208
+ else
209
+ session[:f][:g][:h] = :j
210
+ end
211
+ [200, {}, [session.inspect]]
212
+ end
213
+ pool = Rack::Session::Moneta.new(hash_check, :store => :Memory)
214
+ req = Rack::MockRequest.new(pool)
215
+
216
+ res0 = req.get("/")
217
+ session_id = (cookie = res0["Set-Cookie"])[session_match, 1]
218
+ ses0 = pool.pool[session_id]
219
+
220
+ req.get("/", "HTTP_COOKIE" => cookie)
221
+ ses1 = pool.pool[session_id]
222
+
223
+ ses1.should_not == ses0
224
+ end
225
+
226
+ # anyone know how to do this better?
227
+ it "cleanly merges sessions when multithreaded" do
228
+ unless $DEBUG
229
+ 1.should == 1 # fake assertion to appease the mighty bacon
230
+ next
231
+ end
232
+ warn 'Running multithread test for Session::Memcache'
233
+ pool = Rack::Session::Moneta.new(incrementor, :store => :Memory)
234
+ req = Rack::MockRequest.new(pool)
235
+
236
+ res = req.get('/')
237
+ res.body.should == '{"counter"=>1}'
238
+ cookie = res["Set-Cookie"]
239
+ session_id = cookie[session_match, 1]
240
+
241
+ delta_incrementor = lambda do |env|
242
+ # emulate disconjoinment of threading
243
+ env['rack.session'] = env['rack.session'].dup
244
+ Thread.stop
245
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
246
+ incrementor.call(env)
247
+ end
248
+ tses = Rack::Utils::Context.new pool, delta_incrementor
249
+ treq = Rack::MockRequest.new(tses)
250
+ tnum = rand(7).to_i+5
251
+ r = Array.new(tnum) do
252
+ Thread.new(treq) do |run|
253
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
254
+ end
255
+ end.reverse.map{|t| t.run.join.value }
256
+ r.each do |request|
257
+ request['Set-Cookie'].should == cookie
258
+ request.body.should include '"counter"=>2'
259
+ end
260
+
261
+ session = pool.pool[session_id]
262
+ session.size.should == tnum+1 # counter
263
+ session['counter'].should == 2 # meeeh
264
+
265
+ tnum = rand(7).to_i+5
266
+ r = Array.new(tnum) do |i|
267
+ app = Rack::Utils::Context.new pool, time_delta
268
+ req = Rack::MockRequest.new app
269
+ Thread.new(req) do |run|
270
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
271
+ end
272
+ end.reverse.map{|t| t.run.join.value }
273
+ r.each do |request|
274
+ request['Set-Cookie'].should == cookie
275
+ request.body.should include '"counter"=>3'
276
+ end
277
+
278
+ session = pool.pool[session_id]
279
+ session.size.should.be tnum+1
280
+ session['counter'].should.be 3
281
+
282
+ drop_counter = proc do |env|
283
+ env['rack.session'].delete 'counter'
284
+ env['rack.session']['foo'] = 'bar'
285
+ [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
286
+ end
287
+ tses = Rack::Utils::Context.new pool, drop_counter
288
+ treq = Rack::MockRequest.new(tses)
289
+ tnum = rand(7).to_i+5
290
+ r = Array.new(tnum) do
291
+ Thread.new(treq) do |run|
292
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
293
+ end
294
+ end.reverse.map{|t| t.run.join.value }
295
+ r.each do |request|
296
+ request['Set-Cookie'].should == cookie
297
+ request.body.should include '"foo"=>"bar"'
298
+ end
299
+
300
+ session = pool.pool[session_id]
301
+ session.size.should.be r.size+1
302
+ session['counter'].should.be.nil?
303
+ session['foo'].should == 'bar'
304
+ end
305
+ end