hoodoo 1.13.0 → 1.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/lib/hoodoo.rb +1 -0
  3. data/lib/hoodoo/services/services/session.rb +8 -104
  4. data/lib/hoodoo/transient_store.rb +19 -0
  5. data/lib/hoodoo/transient_store/mocks/dalli_client.rb +148 -0
  6. data/lib/hoodoo/transient_store/mocks/redis.rb +138 -0
  7. data/lib/hoodoo/transient_store/transient_store.rb +344 -0
  8. data/lib/hoodoo/transient_store/transient_store/base.rb +81 -0
  9. data/lib/hoodoo/transient_store/transient_store/memcached.rb +116 -0
  10. data/lib/hoodoo/transient_store/transient_store/memcached_redis_mirror.rb +181 -0
  11. data/lib/hoodoo/transient_store/transient_store/redis.rb +126 -0
  12. data/lib/hoodoo/version.rb +1 -1
  13. data/spec/active/active_record/support_spec.rb +3 -9
  14. data/spec/active/active_record/translated_spec.rb +2 -5
  15. data/spec/logger/writers/file_writer_spec.rb +1 -4
  16. data/spec/logger/writers/stream_writer_spec.rb +2 -9
  17. data/spec/services/middleware/middleware_logging_spec.rb +1 -4
  18. data/spec/services/middleware/middleware_permissions_spec.rb +2 -2
  19. data/spec/services/services/interface_spec.rb +2 -2
  20. data/spec/services/services/session_spec.rb +26 -19
  21. data/spec/transient_store/transient_store/base_spec.rb +52 -0
  22. data/spec/transient_store/transient_store/memcached_redis_mirror_spec.rb +380 -0
  23. data/spec/transient_store/transient_store/memcached_spec.rb +244 -0
  24. data/spec/transient_store/transient_store/mocks/dalli_client_spec.rb +44 -0
  25. data/spec/transient_store/transient_store/mocks/redis_spec.rb +28 -0
  26. data/spec/transient_store/transient_store/redis_spec.rb +242 -0
  27. data/spec/transient_store/transient_store_spec.rb +448 -0
  28. metadata +31 -9
@@ -3,7 +3,14 @@ require 'spec_helper'
3
3
  describe Hoodoo::Services::Session do
4
4
 
5
5
  before :each do
6
- Hoodoo::Services::Session::MockDalliClient.reset()
6
+ Hoodoo::TransientStore::Mocks::DalliClient.reset()
7
+ end
8
+
9
+ it 'includes a legacy alias to the TransientStore mock back-end' do
10
+ expect( Hoodoo::Services::Session::MockDalliClient == Hoodoo::TransientStore::Mocks::DalliClient ).to eql( true )
11
+ expect {
12
+ Hoodoo::TransientStore::Mocks::DalliClient.reset()
13
+ }.to_not raise_error
7
14
  end
8
15
 
9
16
  it 'initialises with default options' do
@@ -109,7 +116,7 @@ describe Hoodoo::Services::Session do
109
116
 
110
117
  expect( s1.save_to_memcached ).to eq( :ok )
111
118
 
112
- store = Hoodoo::Services::Session::MockDalliClient.store()
119
+ store = Hoodoo::TransientStore::Mocks::DalliClient.store()
113
120
  expect( store[ '1234' ] ).to_not be_nil
114
121
  expect( store[ '0987' ] ).to eq( { :expires_at => nil, :value => { 'version' => 2 } } )
115
122
 
@@ -135,7 +142,7 @@ describe Hoodoo::Services::Session do
135
142
  end
136
143
 
137
144
  it 'refuses to save if a newer caller version is present' do
138
- expect( described_class ).to receive( :connect_to_memcached ).twice.and_return( Hoodoo::Services::Session::MockDalliClient.new )
145
+ expect( described_class ).to receive( :connect_to_memcached ).twice.and_return( Hoodoo::TransientStore::Mocks::DalliClient.new )
139
146
 
140
147
  # Save a session with a high caller version
141
148
 
@@ -162,7 +169,7 @@ describe Hoodoo::Services::Session do
162
169
  end
163
170
 
164
171
  it 'invalidates a session if the client ID advances during its lifetime' do
165
- expect( described_class ).to receive( :connect_to_memcached ).exactly( 4 ).times.and_return( Hoodoo::Services::Session::MockDalliClient.new )
172
+ expect( described_class ).to receive( :connect_to_memcached ).exactly( 4 ).times.and_return( Hoodoo::TransientStore::Mocks::DalliClient.new )
166
173
  loader = described_class.new
167
174
 
168
175
  # Save a session with a low caller version.
@@ -194,7 +201,7 @@ describe Hoodoo::Services::Session do
194
201
  end
195
202
 
196
203
  it 'refuses to load if the caller version is outdated' do
197
- expect( described_class ).to receive( :connect_to_memcached ).exactly( 5 ).times.and_return( Hoodoo::Services::Session::MockDalliClient.new )
204
+ expect( described_class ).to receive( :connect_to_memcached ).exactly( 5 ).times.and_return( Hoodoo::TransientStore::Mocks::DalliClient.new )
198
205
  loader = described_class.new
199
206
 
200
207
  # Save a session with a low caller version
@@ -233,7 +240,7 @@ describe Hoodoo::Services::Session do
233
240
  end
234
241
 
235
242
  it 'refuses to load if expired' do
236
- expect( described_class ).to receive( :connect_to_memcached ).twice.and_return( Hoodoo::Services::Session::MockDalliClient.new )
243
+ expect( described_class ).to receive( :connect_to_memcached ).twice.and_return( Hoodoo::TransientStore::Mocks::DalliClient.new )
237
244
  loader = described_class.new
238
245
 
239
246
  # Save a session with a high caller version
@@ -263,18 +270,18 @@ describe Hoodoo::Services::Session do
263
270
  :caller_version => 1
264
271
  )
265
272
 
266
- expect( described_class ).to receive( :connect_to_memcached ).once.and_return( Hoodoo::Services::Session::MockDalliClient.new )
273
+ expect( described_class ).to receive( :connect_to_memcached ).once.and_return( Hoodoo::TransientStore::Mocks::DalliClient.new )
267
274
 
268
275
  expect( s.update_caller_version_in_memcached( '9944', 23 ) ).to eq( :ok )
269
- expect( s.update_caller_version_in_memcached( 'abcd', 2, Hoodoo::Services::Session::MockDalliClient.new ) ).to eq( :ok )
276
+ expect( s.update_caller_version_in_memcached( 'abcd', 2, Hoodoo::TransientStore::Mocks::DalliClient.new ) ).to eq( :ok )
270
277
 
271
- store = Hoodoo::Services::Session::MockDalliClient.store()
278
+ store = Hoodoo::TransientStore::Mocks::DalliClient.store()
272
279
  expect( store[ '9944' ] ).to eq( { :expires_at => nil, :value => { 'version' => 23 } } )
273
280
  expect( store[ 'abcd' ] ).to eq( { :expires_at => nil, :value => { 'version' => 2 } } )
274
281
  end
275
282
 
276
283
  it 'handles invalid session IDs when loading' do
277
- expect( described_class ).to receive( :connect_to_memcached ).once.and_return( Hoodoo::Services::Session::MockDalliClient.new )
284
+ expect( described_class ).to receive( :connect_to_memcached ).once.and_return( Hoodoo::TransientStore::Mocks::DalliClient.new )
278
285
  loader = described_class.new
279
286
  expect( loader.load_from_memcached!( '1234' ) ).to eq( :not_found )
280
287
  end
@@ -289,7 +296,7 @@ describe Hoodoo::Services::Session do
289
296
  it 'logs Memcached exceptions when loading' do
290
297
  loader = described_class.new
291
298
 
292
- expect_any_instance_of( Hoodoo::Services::Session::MockDalliClient ).to receive( :get ).once do
299
+ expect_any_instance_of( Hoodoo::TransientStore::Mocks::DalliClient ).to receive( :get ).once do
293
300
  raise 'Mock Memcached connection failure'
294
301
  end
295
302
 
@@ -312,7 +319,7 @@ describe Hoodoo::Services::Session do
312
319
  # The first 'set' call is an attempt to update the caller version before
313
320
  # the updated session is saved.
314
321
 
315
- expect_any_instance_of( Hoodoo::Services::Session::MockDalliClient ).to receive( :set ).once do
322
+ expect_any_instance_of( Hoodoo::TransientStore::Mocks::DalliClient ).to receive( :set ).once do
316
323
  raise 'Mock Memcached connection failure'
317
324
  end
318
325
 
@@ -332,8 +339,8 @@ describe Hoodoo::Services::Session do
332
339
  :caller_version => 1
333
340
  )
334
341
 
335
- expect_any_instance_of( Hoodoo::Services::Session::MockDalliClient ).to receive( :set ).once.and_call_original
336
- expect_any_instance_of( Hoodoo::Services::Session::MockDalliClient ).to receive( :set ).once do
342
+ expect_any_instance_of( Hoodoo::TransientStore::Mocks::DalliClient ).to receive( :set ).once.and_call_original
343
+ expect_any_instance_of( Hoodoo::TransientStore::Mocks::DalliClient ).to receive( :set ).once do
337
344
  raise 'Mock Memcached connection failure'
338
345
  end
339
346
 
@@ -372,7 +379,7 @@ describe Hoodoo::Services::Session do
372
379
  end
373
380
 
374
381
  it 'logs and reports deletion exceptions' do
375
- fdc = Hoodoo::Services::Session::MockDalliClient.new
382
+ fdc = Hoodoo::TransientStore::Mocks::DalliClient.new
376
383
  s = described_class.new(
377
384
  :session_id => '1234',
378
385
  :memcached_host => 'abcd',
@@ -397,11 +404,11 @@ describe Hoodoo::Services::Session do
397
404
  before :example do
398
405
  # Clear the connection cache for each test
399
406
  Hoodoo::Services::Session.class_variable_set( '@@dalli_clients', nil ) # Hack for test!
400
- Hoodoo::Services::Session::MockDalliClient.bypass( true )
407
+ Hoodoo::TransientStore::Mocks::DalliClient.bypass( true )
401
408
  end
402
409
 
403
410
  after :example do
404
- Hoodoo::Services::Session::MockDalliClient.bypass( false )
411
+ Hoodoo::TransientStore::Mocks::DalliClient.bypass( false )
405
412
  end
406
413
 
407
414
  it 'complains about a missing host' do
@@ -437,13 +444,13 @@ describe Hoodoo::Services::Session do
437
444
  end
438
445
 
439
446
  it 'only initialises once for one given host' do
440
- expect( Dalli::Client ).to receive( :new ).once.and_return( Hoodoo::Services::Session::MockDalliClient.new )
447
+ expect( Dalli::Client ).to receive( :new ).once.and_return( Hoodoo::TransientStore::Mocks::DalliClient.new )
441
448
 
442
449
  1.upto( 3 ) do
443
450
  described_class.connect_to_memcached( 'one' )
444
451
  end
445
452
 
446
- expect( Dalli::Client ).to receive( :new ).once.and_return( Hoodoo::Services::Session::MockDalliClient.new )
453
+ expect( Dalli::Client ).to receive( :new ).once.and_return( Hoodoo::TransientStore::Mocks::DalliClient.new )
447
454
 
448
455
  1.upto( 3 ) do
449
456
  described_class.connect_to_memcached( 'two' )
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+
3
+ describe Hoodoo::TransientStore::Base do
4
+ context 'creation' do
5
+ it 'initialises' do
6
+ base = Hoodoo::TransientStore::Base.new(
7
+ storage_host_uri: 'localhost',
8
+ namespace: 'foo'
9
+ )
10
+
11
+ expect( base ).to_not be_nil
12
+
13
+ # Subclasses may rightly rely on these, so they should be tested.
14
+ #
15
+ expect( base.instance_variable_get( '@storage_host_uri' ) ).to eq( 'localhost' )
16
+ expect( base.instance_variable_get( '@namespace' ) ).to eq( 'foo' )
17
+ end
18
+ end
19
+
20
+ context 'subclass author warning' do
21
+ before :each do
22
+ @base = Hoodoo::TransientStore::Base.new(
23
+ storage_host_uri: 'localhost',
24
+ namespace: 'foo'
25
+ )
26
+ end
27
+
28
+ it 'is generated for #get' do
29
+ expect {
30
+ @base.set( key: 'foo', payload: {}, maximum_lifespan: 5 )
31
+ }.to raise_error( RuntimeError, 'Subclasses must implement Hoodoo::TransientStore::Base#set' )
32
+ end
33
+
34
+ it 'is generated for #set' do
35
+ expect {
36
+ @base.get( key: 'foo' )
37
+ }.to raise_error( RuntimeError, 'Subclasses must implement Hoodoo::TransientStore::Base#get' )
38
+ end
39
+
40
+ it 'is generated for #delete' do
41
+ expect {
42
+ @base.delete( key: 'foo' )
43
+ }.to raise_error( RuntimeError, 'Subclasses must implement Hoodoo::TransientStore::Base#delete' )
44
+ end
45
+
46
+ it 'is generated for #close' do
47
+ expect {
48
+ @base.close()
49
+ }.to raise_error( RuntimeError, 'Subclasses must implement Hoodoo::TransientStore::Base#close' )
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,380 @@
1
+ require 'spec_helper'
2
+
3
+ require 'hoodoo/transient_store/mocks/dalli_client'
4
+ require 'hoodoo/transient_store/mocks/redis'
5
+
6
+ # These tests make sure that the mirror class calls down to the Memcached and
7
+ # Redis abstractions, but assumes those abstractions are thoroughly tested by
8
+ # their own unit tests. So it makes sure it gets expected call and result
9
+ # behaviour, but doesn't worry about mock or real Redis and so-on.
10
+ #
11
+ describe Hoodoo::TransientStore::MemcachedRedisMirror do
12
+
13
+ it 'registers itself' do
14
+ expect( Hoodoo::TransientStore.supported_storage_engines() ).to include( :memcached_redis_mirror )
15
+ end
16
+
17
+ before :each do
18
+ @memcached_uri = 'localhost:11211'
19
+ @redis_uri = 'redis://localhost:6379'
20
+ @namespace = Hoodoo::UUID.generate()
21
+ @storage_engine_uri = {
22
+ :memcached => @memcached_uri,
23
+ :redis => @redis_uri
24
+ }
25
+
26
+ # Use pure mock back-ends behind the Memcached and Redis abstraction
27
+ # layers; real back-end tests are done for them in their unit tests.
28
+
29
+ Hoodoo::TransientStore::Mocks::DalliClient.reset()
30
+ Hoodoo::TransientStore::Mocks::Redis.reset()
31
+
32
+ allow( Dalli::Client ).to(
33
+ receive( :new ).
34
+ and_return( Hoodoo::TransientStore::Mocks::DalliClient.new )
35
+ )
36
+
37
+ allow( Redis ).to(
38
+ receive( :new ).
39
+ and_return( Hoodoo::TransientStore::Mocks::Redis.new )
40
+ )
41
+
42
+ @instance = Hoodoo::TransientStore::MemcachedRedisMirror.new(
43
+ storage_host_uri: @storage_engine_uri,
44
+ namespace: @namespace
45
+ )
46
+ end
47
+
48
+ # ==========================================================================
49
+
50
+ context '#initialize' do
51
+ it 'initialises' do
52
+ expect( @instance ).to be_a( Hoodoo::TransientStore::MemcachedRedisMirror )
53
+ end
54
+
55
+ it 'complains about bad parameters' do
56
+ expect {
57
+ Hoodoo::TransientStore::MemcachedRedisMirror.new(
58
+ storage_host_uri: 'some string',
59
+ namespace: @namespace
60
+ )
61
+ }.to raise_error( RuntimeError, 'Hoodoo::TransientStore::MemcachedRedisMirror: Bad storage host URI data passed to constructor' )
62
+
63
+ expect {
64
+ Hoodoo::TransientStore::MemcachedRedisMirror.new(
65
+ storage_host_uri: { :hash => 'without required keys' },
66
+ namespace: @namespace
67
+ )
68
+ }.to raise_error( RuntimeError, 'Hoodoo::TransientStore::MemcachedRedisMirror: Bad storage host URI data passed to constructor' )
69
+ end
70
+
71
+ it 'creates Memcached and Redis instances' do
72
+ expect( Hoodoo::TransientStore::Memcached ).to receive( :new )
73
+ expect( Hoodoo::TransientStore::Redis ).to receive( :new )
74
+
75
+ Hoodoo::TransientStore::MemcachedRedisMirror.new(
76
+ storage_host_uri: @storage_engine_uri,
77
+ namespace: @namespace
78
+ )
79
+ end
80
+ end
81
+
82
+ # ==========================================================================
83
+
84
+ context 'when initialised' do
85
+ before :each do
86
+ @key = Hoodoo::UUID.generate()
87
+ @payload = { 'bar' => 'baz' }
88
+ @ttl = 120
89
+ end
90
+
91
+ context '#set' do
92
+ it 'sets' do
93
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :set ).with( key: @key, payload: @payload, maximum_lifespan: @ttl ).and_call_original()
94
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :set ).with( key: @key, payload: @payload, maximum_lifespan: @ttl ).and_call_original()
95
+
96
+ result = @instance.set(
97
+ key: @key,
98
+ payload: @payload,
99
+ maximum_lifespan: @ttl
100
+ )
101
+
102
+ expect( result ).to eq( true )
103
+ end
104
+
105
+ it 'allows Memcached exceptions to propagate' do
106
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :set ).and_raise( 'Hello world' )
107
+
108
+ expect {
109
+ @instance.set(
110
+ key: @key,
111
+ payload: @payload,
112
+ maximum_lifespan: @ttl
113
+ )
114
+ }.to raise_error( RuntimeError, "Hello world" )
115
+ end
116
+
117
+ it 'allows Redis exceptions to propagate' do
118
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :set ).and_raise( 'Hello world' )
119
+
120
+ expect {
121
+ @instance.set(
122
+ key: @key,
123
+ payload: @payload,
124
+ maximum_lifespan: @ttl
125
+ )
126
+ }.to raise_error( RuntimeError, "Hello world" )
127
+ end
128
+ end
129
+
130
+ context '#get' do
131
+ before :each do
132
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :set ).with( key: @key, payload: @payload, maximum_lifespan: @ttl ).and_call_original()
133
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :set ).with( key: @key, payload: @payload, maximum_lifespan: @ttl ).and_call_original()
134
+
135
+ @instance.set(
136
+ key: @key,
137
+ payload: @payload,
138
+ maximum_lifespan: @ttl
139
+ )
140
+ end
141
+
142
+ it 'gets known keys' do
143
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).with( key: @key ).and_call_original()
144
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).with( key: @key ).and_call_original()
145
+
146
+ expect( @instance.get( key: @key ) ).to eql( @payload )
147
+ end
148
+
149
+ it 'returns "nil" for unknown keys' do
150
+ expect( @instance.get( key: Hoodoo::UUID.generate() ) ).to be_nil
151
+ end
152
+
153
+ it 'returns "nil" if Memcached is missing the data' do
154
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).with( key: @key ).and_return( nil )
155
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).with( key: @key ).and_call_original()
156
+
157
+ expect( @instance.get( key: @key ) ).to be_nil
158
+ end
159
+
160
+ it 'returns "nil" if Redis is missing the data' do
161
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).with( key: @key ).and_call_original()
162
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).with( key: @key ).and_return( nil )
163
+
164
+ expect( @instance.get( key: @key ) ).to be_nil
165
+ end
166
+
167
+ it 'allows Memcached exceptions to propagate' do
168
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).and_raise( 'Hello world' )
169
+
170
+ expect {
171
+ @instance.get( key: @key )
172
+ }.to raise_error( RuntimeError, "Hello world" )
173
+ end
174
+
175
+ it 'allows Redis exceptions to propagate' do
176
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).and_raise( 'Hello world' )
177
+
178
+ expect {
179
+ @instance.get( key: @key )
180
+ }.to raise_error( RuntimeError, "Hello world" )
181
+ end
182
+
183
+ context 'with migration selector of' do
184
+ context ':both' do
185
+ it 'is the default' do
186
+ expect( @instance.get_keys_from ).to eql( :both )
187
+ end
188
+
189
+ # This is covered elsewhere too but belt-and-braces checks don't hurt
190
+ # and the test list looks cleaner when this one section covers all
191
+ # allowed values of the selector.
192
+
193
+ it 'returns data if the key is in both engines' do
194
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).with( key: @key ).and_call_original()
195
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).with( key: @key ).and_call_original()
196
+
197
+ expect( @instance.get( key: @key ) ).to eql( @payload )
198
+ end
199
+
200
+ it 'returns "nil" if the key is only in Memcached' do
201
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).with( key: @key ).and_call_original()
202
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).with( key: @key ).and_return( nil )
203
+
204
+ expect( @instance.get( key: @key ) ).to be_nil
205
+ end
206
+
207
+ it 'returns "nil" if the key is only in Redis' do
208
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).with( key: @key ).and_return( nil )
209
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).with( key: @key ).and_call_original()
210
+
211
+ expect( @instance.get( key: @key ) ).to be_nil
212
+ end
213
+ end
214
+
215
+ context ':memcached' do
216
+ before( :each ) { @instance.get_keys_from = :memcached }
217
+ after( :each ) { @instance.get_keys_from = :both }
218
+
219
+ it 'returns data if the key is in both engines' do
220
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).with( key: @key ).and_call_original()
221
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to_not receive( :get )
222
+
223
+ expect( @instance.get( key: @key ) ).to eql( @payload )
224
+ end
225
+
226
+ it 'returns data if the key is only in Memcached' do
227
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).with( key: @key ).and_call_original()
228
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to_not receive( :get )
229
+
230
+ expect( @instance.get( key: @key ) ).to eql( @payload )
231
+ end
232
+
233
+ it 'returns "nil" if the key is only in Redis' do
234
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :get ).with( key: @key ).and_return( nil )
235
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to_not receive( :get )
236
+
237
+ expect( @instance.get( key: @key ) ).to be_nil
238
+ end
239
+ end
240
+
241
+ context ':redis' do
242
+ before( :each ) { @instance.get_keys_from = :redis }
243
+ after( :each ) { @instance.get_keys_from = :both }
244
+
245
+ it 'returns data if the key is in both engines' do
246
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to_not receive( :get )
247
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).with( key: @key ).and_call_original()
248
+
249
+ expect( @instance.get( key: @key ) ).to eql( @payload )
250
+ end
251
+
252
+ it 'returns "nil" if the key is only in Memcached' do
253
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to_not receive( :get )
254
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).with( key: @key ).and_return( nil )
255
+
256
+ expect( @instance.get( key: @key ) ).to be_nil
257
+ end
258
+
259
+ it 'returns data if the key is only in Redis' do
260
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to_not receive( :get )
261
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :get ).with( key: @key ).and_call_original()
262
+
263
+ expect( @instance.get( key: @key ) ).to eql( @payload )
264
+ end
265
+ end
266
+
267
+ context 'an unrecognised value' do
268
+ before( :each ) { @instance.get_keys_from = :foo }
269
+ after( :each ) { @instance.get_keys_from = :both }
270
+
271
+ it 'causes complaint' do
272
+ expect {
273
+ @instance.get( key: @key )
274
+ }.to raise_error( RuntimeError, "Hoodoo::TransientStore::Base\#get: Invalid prior value given in \#get_keys_from= of ':foo' - only ':both', ':memcached' or ':redis' are allowed" )
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ context '#delete' do
281
+ before :each do
282
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :set ).with( key: @key, payload: @payload, maximum_lifespan: @ttl ).and_call_original()
283
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :set ).with( key: @key, payload: @payload, maximum_lifespan: @ttl ).and_call_original()
284
+
285
+ @instance.set(
286
+ key: @key,
287
+ payload: @payload,
288
+ maximum_lifespan: @ttl
289
+ )
290
+ end
291
+
292
+ it 'deletes known keys' do
293
+ expect( @instance.get( key: @key ) ).to eql( @payload )
294
+
295
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :delete ).with( key: @key ).and_call_original()
296
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :delete ).with( key: @key ).and_call_original()
297
+
298
+ result = @instance.delete( key: @key )
299
+
300
+ expect( result ).to eq( true )
301
+ expect( @instance.get( key: @key ) ).to eql( nil )
302
+ end
303
+
304
+ it 'ignores unknown keys' do
305
+ expect( @instance.delete( key: Hoodoo::UUID.generate() ) ).to eql( true )
306
+ end
307
+
308
+ it 'allows Memcached exceptions to propagate but still calls Redis' do
309
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :delete ).and_raise( 'Hello world' )
310
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :delete ).with( key: @key ).and_call_original()
311
+
312
+ expect {
313
+ @instance.delete( key: @key )
314
+ }.to raise_error( RuntimeError, "Hello world" )
315
+ end
316
+
317
+ it 'allows Redis exceptions to propagate but still calls Memcached' do
318
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :delete ).with( key: @key ).and_call_original()
319
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :delete ).and_raise( 'Hello world' )
320
+
321
+ expect {
322
+ @instance.delete( key: @key )
323
+ }.to raise_error( RuntimeError, "Hello world" )
324
+ end
325
+
326
+ it 'deletes from Memcached even if the Redis data is missing' do
327
+ expect( @instance.get( key: @key ) ).to eql( @payload )
328
+
329
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :delete ).with( key: @key ).and_call_original()
330
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :delete ).with( key: @key ).and_call_original()
331
+
332
+ Hoodoo::TransientStore::Mocks::Redis.reset()
333
+
334
+ result = @instance.delete( key: @key )
335
+
336
+ expect( result ).to eq( true )
337
+ expect( @instance.get( key: @key ) ).to eql( nil )
338
+ end
339
+
340
+ it 'deletes from Redis even if the Memcached data is missing' do
341
+ expect( @instance.get( key: @key ) ).to eql( @payload )
342
+
343
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :delete ).with( key: @key ).and_call_original()
344
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :delete ).with( key: @key ).and_call_original()
345
+
346
+ Hoodoo::TransientStore::Mocks::DalliClient.reset()
347
+
348
+ result = @instance.delete( key: @key )
349
+
350
+ expect( result ).to eq( true )
351
+ expect( @instance.get( key: @key ) ).to eql( nil )
352
+ end
353
+ end
354
+
355
+ context '#close' do
356
+ it 'closes normally' do
357
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :close ).and_call_original()
358
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :close ).and_call_original()
359
+
360
+ @instance.close()
361
+ end
362
+
363
+ it 'still closes Redis if Memcached raises an exception' do
364
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :close ).and_raise( "Hello world" )
365
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :close ).and_call_original()
366
+
367
+ @instance.close() rescue nil
368
+ end
369
+
370
+ it 'still closes Memcached if Redis raises an exception' do
371
+ expect_any_instance_of( Hoodoo::TransientStore::Memcached ).to receive( :close ).and_call_original()
372
+ expect_any_instance_of( Hoodoo::TransientStore::Redis ).to receive( :close ).and_raise( "Hello world" )
373
+
374
+ @instance.close() rescue nil
375
+ end
376
+ end
377
+
378
+ end # 'context "when initialised (#{ backend })" do'
379
+
380
+ end # 'describe...'