hoodoo 1.13.0 → 1.14.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 (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...'