moneta 0.6.0 → 0.7.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.
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