backup 3.0.26 → 3.0.27

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.
@@ -374,19 +374,18 @@ describe Backup::Database::MySQL do
374
374
  end
375
375
 
376
376
  describe 'deprecations' do
377
- after do
378
- Backup::Database::MySQL.clear_defaults!
379
- end
380
-
381
377
  describe '#utility_path' do
382
378
  before do
383
379
  Backup::Database::MySQL.any_instance.stubs(:utility)
384
- Backup::Logger.expects(:warn).with(
385
- instance_of(Backup::Errors::ConfigurationError)
386
- )
387
- Backup::Logger.expects(:warn).with(
388
- "Backup::Database::MySQL.mysqldump_utility is being set to 'foo'"
389
- )
380
+ Backup::Logger.expects(:warn).with {|err|
381
+ err.should be_an_instance_of Backup::Errors::ConfigurationError
382
+ err.message.should match(
383
+ /Use MySQL#mysqldump_utility instead/
384
+ )
385
+ }
386
+ end
387
+ after do
388
+ Backup::Database::MySQL.clear_defaults!
390
389
  end
391
390
 
392
391
  context 'when set directly' do
@@ -316,19 +316,18 @@ describe Backup::Database::PostgreSQL do
316
316
  end
317
317
 
318
318
  describe 'deprecations' do
319
- after do
320
- Backup::Database::PostgreSQL.clear_defaults!
321
- end
322
-
323
319
  describe '#utility_path' do
324
320
  before do
325
321
  Backup::Database::PostgreSQL.any_instance.stubs(:utility)
326
- Backup::Logger.expects(:warn).with(
327
- instance_of(Backup::Errors::ConfigurationError)
328
- )
329
- Backup::Logger.expects(:warn).with(
330
- "Backup::Database::PostgreSQL.pg_dump_utility is being set to 'foo'"
331
- )
322
+ Backup::Logger.expects(:warn).with {|err|
323
+ err.should be_an_instance_of Backup::Errors::ConfigurationError
324
+ err.message.should match(
325
+ /Use PostgreSQL#pg_dump_utility instead/
326
+ )
327
+ }
328
+ end
329
+ after do
330
+ Backup::Database::PostgreSQL.clear_defaults!
332
331
  end
333
332
 
334
333
  context 'when set directly' do
@@ -297,19 +297,18 @@ describe Backup::Database::Redis do
297
297
  end
298
298
 
299
299
  describe 'deprecations' do
300
- after do
301
- Backup::Database::Redis.clear_defaults!
302
- end
303
-
304
300
  describe '#utility_path' do
305
301
  before do
306
302
  Backup::Database::Redis.any_instance.stubs(:utility)
307
- Backup::Logger.expects(:warn).with(
308
- instance_of(Backup::Errors::ConfigurationError)
309
- )
310
- Backup::Logger.expects(:warn).with(
311
- "Backup::Database::Redis.redis_cli_utility is being set to 'foo'"
312
- )
303
+ Backup::Logger.expects(:warn).with {|err|
304
+ err.should be_an_instance_of Backup::Errors::ConfigurationError
305
+ err.message.should match(
306
+ /Use Redis#redis_cli_utility instead/
307
+ )
308
+ }
309
+ end
310
+ after do
311
+ Backup::Database::Redis.clear_defaults!
313
312
  end
314
313
 
315
314
  context 'when set directly' do
@@ -139,19 +139,18 @@ describe Backup::Database::Riak do
139
139
  end
140
140
 
141
141
  describe 'deprecations' do
142
- after do
143
- Backup::Database::Riak.clear_defaults!
144
- end
145
-
146
142
  describe '#utility_path' do
147
143
  before do
148
144
  Backup::Database::Riak.any_instance.stubs(:utility)
149
- Backup::Logger.expects(:warn).with(
150
- instance_of(Backup::Errors::ConfigurationError)
151
- )
152
- Backup::Logger.expects(:warn).with(
153
- "Backup::Database::Riak.riak_admin_utility is being set to 'foo'"
154
- )
145
+ Backup::Logger.expects(:warn).with {|err|
146
+ err.should be_an_instance_of Backup::Errors::ConfigurationError
147
+ err.message.should match(
148
+ /Use Riak#riak_admin_utility instead/
149
+ )
150
+ }
151
+ end
152
+ after do
153
+ Backup::Database::Riak.clear_defaults!
155
154
  end
156
155
 
157
156
  context 'when set directly' do
@@ -5,7 +5,8 @@ require File.expand_path('../../spec_helper.rb', __FILE__)
5
5
  describe Backup::Encryptor::GPG do
6
6
  let(:encryptor) do
7
7
  Backup::Encryptor::GPG.new do |e|
8
- e.key = 'gpg_key'
8
+ e.mode = :symmetric
9
+ e.passphrase = 'test secret'
9
10
  end
10
11
  end
11
12
 
@@ -14,6 +15,30 @@ describe Backup::Encryptor::GPG do
14
15
  superclass.should == Backup::Encryptor::Base
15
16
  end
16
17
 
18
+ it 'supports three modes of operation' do
19
+ Backup::Encryptor::GPG::MODES.should == [:asymmetric, :symmetric, :both]
20
+ end
21
+
22
+ describe '#mode=' do
23
+ it 'should accept valid modes' do
24
+ mode = Backup::Encryptor::GPG::MODES.shuffle.first
25
+ encryptor.mode = mode
26
+ encryptor.mode.should == mode
27
+ end
28
+
29
+ it 'should convert string input to a symbol' do
30
+ mode = Backup::Encryptor::GPG::MODES.shuffle.first
31
+ encryptor.mode = mode.to_s
32
+ encryptor.mode.should == mode
33
+ end
34
+
35
+ it 'should raise an error for invalid modes' do
36
+ expect do
37
+ encryptor.mode = 'foo'
38
+ end.to raise_error(Backup::Errors::Encryptor::GPG::InvalidModeError)
39
+ end
40
+ end # describe '#mode='
41
+
17
42
  describe '#initialize' do
18
43
  after { Backup::Encryptor::GPG.clear_defaults! }
19
44
 
@@ -24,127 +49,861 @@ describe Backup::Encryptor::GPG do
24
49
 
25
50
  context 'when no pre-configured defaults have been set' do
26
51
  it 'should use the values given' do
27
- encryptor.key.should == 'gpg_key'
52
+ encryptor.mode.should == :symmetric
53
+ encryptor.passphrase.should == 'test secret'
28
54
  end
29
55
 
30
56
  it 'should use default values if none are given' do
31
57
  encryptor = Backup::Encryptor::GPG.new
32
- encryptor.key.should be_nil
58
+ encryptor.mode.should == :asymmetric
59
+ encryptor.keys.should be_nil
60
+ encryptor.recipients.should be_nil
61
+ encryptor.passphrase.should be_nil
62
+ encryptor.passphrase_file.should be_nil
63
+ encryptor.gpg_config.should be_nil
64
+ encryptor.gpg_homedir.should be_nil
33
65
  end
34
66
  end # context 'when no pre-configured defaults have been set'
35
67
 
36
68
  context 'when pre-configured defaults have been set' do
37
69
  before do
38
70
  Backup::Encryptor::GPG.defaults do |e|
39
- e.key = 'default_key'
71
+ e.mode = :both
72
+ e.keys = { 'test_key' => 'test public key' }
73
+ e.recipients = 'test_key'
74
+ e.passphrase_file = 'my/pass/file'
40
75
  end
41
76
  end
42
77
 
43
78
  it 'should use pre-configured defaults' do
44
79
  encryptor = Backup::Encryptor::GPG.new
45
- encryptor.key.should == 'default_key'
80
+ encryptor.mode.should == :both
81
+ encryptor.keys.should == { 'test_key' => 'test public key' }
82
+ encryptor.recipients.should == 'test_key'
83
+ encryptor.passphrase_file.should == 'my/pass/file'
46
84
  end
47
85
 
48
86
  it 'should override pre-configured defaults' do
49
- encryptor.key.should == 'gpg_key'
87
+ encryptor.mode.should == :symmetric
88
+ encryptor.keys.should == { 'test_key' => 'test public key' }
89
+ encryptor.recipients.should == 'test_key'
90
+ encryptor.passphrase.should == 'test secret'
91
+ encryptor.passphrase_file.should == 'my/pass/file'
50
92
  end
51
93
  end # context 'when pre-configured defaults have been set'
52
94
  end # describe '#initialize'
53
95
 
54
96
  describe '#encrypt_with' do
55
- it 'should yield the encryption command and extension' do
97
+ before do
56
98
  encryptor.expects(:log!)
57
- encryptor.expects(:extract_encryption_key_email!)
58
- encryptor.expects(:utility).with(:gpg).returns('gpg')
59
- encryptor.expects(:options).returns('command options')
99
+ encryptor.expects(:prepare)
100
+ encryptor.expects(:cleanup) # ensure call
101
+ end
60
102
 
61
- encryptor.encrypt_with do |command, ext|
62
- command.should == 'gpg command options'
63
- ext.should == '.gpg'
103
+ context 'when encryption can be performed' do
104
+ it 'should yield the encryption command and extension' do
105
+ encryptor.expects(:mode_options).twice.returns('mode_options')
106
+ encryptor.expects(:base_options).returns('base_options')
107
+ encryptor.expects(:utility).with(:gpg).returns('gpg')
108
+
109
+ encryptor.encrypt_with do |command, ext|
110
+ command.should == 'gpg base_options mode_options'
111
+ ext.should == '.gpg'
112
+ end
64
113
  end
65
114
  end
66
- end
67
115
 
68
- describe '#extract_encryption_key_email!' do
69
- it 'should extract the encryption_key_email' do
70
- encryptor.expects(:utility).with(:gpg).returns('gpg')
71
- encryptor.expects(:with_tmp_key_file).yields('/path/to/tmpfile')
72
- encryptor.expects(:run).with("gpg --import '/path/to/tmpfile' 2>&1").
73
- returns('gpg: key A1B2C3D4: "User Name (Comment) <user@host>" not changed')
116
+ context 'when encryption can not be performed' do
117
+ it 'should raise an error when no mode_options are returned' do
118
+ encryptor.expects(:mode_options).returns([])
119
+
120
+ expect do
121
+ encryptor.encrypt_with
122
+ end.to raise_error(Backup::Errors::Encryptor::GPG::EncryptionError)
123
+ end
124
+ end
125
+ end # describe '#encrypt_with'
74
126
 
75
- encryptor.send(:extract_encryption_key_email!)
76
- encryptor.instance_variable_get(:@encryption_key_email).should == 'user@host'
127
+ describe '#prepare and #cleanup' do
128
+ it 'should setup required variables' do
129
+ encryptor.instance_variable_set(:@tempdirs, nil)
130
+ FileUtils.expects(:rm_rf).never
131
+ encryptor.send(:prepare)
132
+ encryptor.instance_variable_get(:@tempdirs).should == []
77
133
  end
78
134
 
79
- it 'should use the cached key email if already extracted' do
80
- encryptor.instance_variable_set(:@encryption_key_email, 'foo@host')
81
- encryptor.expects(:utility).never
82
- encryptor.expects(:with_tmp_key_file).never
83
- encryptor.expects(:run).never
135
+ it 'should remove any tempdirs and clear all variables' do
136
+ encryptor.instance_variable_set(:@tempdirs, ['a', 'b'])
137
+ FileUtils.expects(:rm_rf).with(['a', 'b'], {:secure => true})
84
138
 
85
- encryptor.send(:extract_encryption_key_email!)
139
+ encryptor.instance_variable_set(:@base_options, true)
140
+ encryptor.instance_variable_set(:@mode_options, true)
141
+ encryptor.instance_variable_set(:@user_recipients, true)
142
+ encryptor.instance_variable_set(:@user_keys, true)
143
+ encryptor.instance_variable_set(:@system_identifiers, true)
144
+
145
+ encryptor.send(:cleanup)
146
+
147
+ encryptor.instance_variable_get(:@tempdirs).should == []
148
+ encryptor.instance_variable_get(:@base_options).should be_nil
149
+ encryptor.instance_variable_get(:@mode_options).should be_nil
150
+ encryptor.instance_variable_get(:@user_recipients).should be_nil
151
+ encryptor.instance_variable_get(:@user_keys).should be_nil
152
+ encryptor.instance_variable_get(:@system_identifiers).should be_nil
86
153
  end
87
- end
154
+ end # describe '#prepare and #cleanup'
155
+
156
+ describe '#base_options' do
157
+ context 'while caching the return value in @base_options' do
158
+ before { encryptor.instance_variable_set(:@base_options, nil) }
159
+
160
+ context 'when #gpg_homedir is given' do
161
+ it 'should return the proper options' do
162
+ encryptor.expects(:setup_gpg_homedir).once.returns('/a/dir')
163
+ encryptor.expects(:setup_gpg_config).once.returns(false)
164
+
165
+ ret = "--no-tty --homedir '/a/dir'"
166
+ encryptor.send(:base_options).should == ret
167
+ encryptor.send(:base_options).should == ret
168
+ encryptor.instance_variable_get(:@base_options).should == ret
169
+ end
170
+ end
171
+
172
+ context 'when #gpg_config is given' do
173
+ it 'should return the proper options' do
174
+ encryptor.expects(:setup_gpg_homedir).once.returns(false)
175
+ encryptor.expects(:setup_gpg_config).once.returns('/a/file')
88
176
 
89
- describe '#options' do
90
- it 'should return the option string for the gpg command' do
91
- encryptor.instance_variable_set(:@encryption_key_email, 'user@host')
92
- encryptor.send(:options).should == "-e --trust-model always -r 'user@host'"
177
+ ret = "--no-tty --options '/a/file'"
178
+ encryptor.send(:base_options).should == ret
179
+ encryptor.send(:base_options).should == ret
180
+ encryptor.instance_variable_get(:@base_options).should == ret
181
+ end
182
+ end
183
+
184
+ context 'when #gpg_homedir and #gpg_config is given' do
185
+ it 'should return the proper options' do
186
+ encryptor.expects(:setup_gpg_homedir).once.returns('/a/dir')
187
+ encryptor.expects(:setup_gpg_config).once.returns('/a/file')
188
+
189
+ ret = "--no-tty --homedir '/a/dir' --options '/a/file'"
190
+ encryptor.send(:base_options).should == ret
191
+ encryptor.send(:base_options).should == ret
192
+ encryptor.instance_variable_get(:@base_options).should == ret
193
+ end
194
+ end
195
+
196
+ context 'when neither #gpg_homedir and #gpg_config is given' do
197
+ it 'should return the proper options' do
198
+ encryptor.expects(:setup_gpg_homedir).once.returns(false)
199
+ encryptor.expects(:setup_gpg_config).once.returns(false)
200
+
201
+ ret = '--no-tty'
202
+ encryptor.send(:base_options).should == ret
203
+ encryptor.send(:base_options).should == ret
204
+ encryptor.instance_variable_get(:@base_options).should == ret
205
+ end
206
+ end
207
+ end
208
+ end # describe '#base_options'
209
+
210
+ describe '#setup_gpg_homedir' do
211
+ context 'when #gpg_homedir is not set' do
212
+ it 'should return false' do
213
+ encryptor.gpg_homedir = nil
214
+ encryptor.send(:setup_gpg_homedir).should be_false
215
+ end
216
+ end
217
+
218
+ context 'when #gpg_homedir is set' do
219
+ let(:path) { 'some/path' }
220
+ let(:expanded_path) { File.expand_path(path) }
221
+
222
+ before do
223
+ encryptor.gpg_homedir = path
224
+ Backup::Config.stubs(:user).returns('a_user')
225
+ end
226
+
227
+ context 'and no errors occur' do
228
+ before do
229
+ FileUtils.expects(:mkdir_p).with(expanded_path)
230
+ FileUtils.expects(:chown).with('a_user', nil, expanded_path)
231
+ FileUtils.expects(:chmod).with(0700, expanded_path)
232
+ end
233
+
234
+ context 'and the gpg_homedir files exist' do
235
+ before do
236
+ %w{ pubring.gpg secring.gpg trustdb.gpg }.each do |file|
237
+ File.expects(:exist?).with(
238
+ File.join(expanded_path, file)
239
+ ).returns(true)
240
+ end
241
+ end
242
+
243
+ it 'should ensure permissions and return the path' do
244
+ encryptor.expects(:utility).never
245
+ encryptor.send(:setup_gpg_homedir).should == expanded_path
246
+ end
247
+ end
248
+
249
+ context 'and the gpg_homedir files do not exist' do
250
+ before do
251
+ File.stubs(:exist?).returns(false)
252
+ end
253
+
254
+ it 'should call gpg to initialize the files' do
255
+ encryptor.expects(:utility).with(:gpg).returns('gpg')
256
+ encryptor.expects(:run).with(
257
+ "gpg --homedir '#{ expanded_path }' -K 2>&1 >/dev/null"
258
+ )
259
+ encryptor.send(:setup_gpg_homedir).should == expanded_path
260
+ end
261
+ end
262
+ end
263
+
264
+ context 'and errors occur' do
265
+ it 'should wrap and raise the error' do
266
+ File.expects(:expand_path).raises('error message')
267
+
268
+ expect do
269
+ encryptor.send(:setup_gpg_homedir)
270
+ end.to raise_error {|err|
271
+ err.should
272
+ be_an_instance_of Backup::Errors::Encryptor::GPG::HomedirError
273
+ err.message.should
274
+ match(/Reason: RuntimeError\n error message/)
275
+ }
276
+ end
277
+ end
278
+ end
279
+ end # describe '#setup_gpg_homedir'
280
+
281
+ describe '#setup_gpg_config' do
282
+ context 'when #gpg_config is not set' do
283
+ it 'should return false' do
284
+ encryptor.gpg_config = nil
285
+ encryptor.send(:setup_gpg_config).should be_false
286
+ end
93
287
  end
94
- end
95
288
 
96
- describe '#with_tmp_key_file' do
97
- let(:tmp_file) { mock }
98
- let(:s) { sequence '' }
289
+ context 'when #gpg_config is set' do
290
+ before do
291
+ encryptor.gpg_config = <<-EOF
292
+ # a comment
293
+ text which will be
294
+
295
+ \tthe content of a gpg.conf file
296
+ EOF
297
+ Backup::Config.stubs(:tmp_path).returns('/Backup/tmp')
298
+ encryptor.instance_variable_set(:@tempdirs, [])
299
+ end
300
+
301
+ context 'when no errors occur' do
302
+ let(:tempdir) { mock }
303
+ let(:tempfile) { mock }
304
+ let(:tempfile_path) { mock }
305
+ let(:path) { mock }
306
+
307
+ before do
308
+ encryptor.expects(:cleanup).never
309
+ tempfile.stubs(:path).returns(tempfile_path)
310
+ end
311
+
312
+ it 'should create and return the file path' do
313
+ # create temporary directory and convert to a Pathname object
314
+ Dir.expects(:mktmpdir).with(
315
+ 'backup-gpg_config', '/Backup/tmp'
316
+ ).returns(tempdir)
317
+
318
+ # create temporary file within the temporary directory
319
+ Tempfile.expects(:open).with(
320
+ 'backup-gpg_config', tempdir
321
+ ).returns(tempfile)
322
+
323
+ # write the gpg_config, stripping leading tabs/spaces
324
+ tempfile.expects(:write).with(
325
+ "# a comment\n" +
326
+ "text which will be\n" +
327
+ "\n" +
328
+ "the content of a gpg.conf file\n"
329
+ )
330
+ # close the file
331
+ tempfile.expects(:close)
332
+
333
+ # check the config file
334
+ encryptor.expects(:check_gpg_config).with(tempfile_path)
335
+
336
+ # method returns the tempfile's path
337
+ encryptor.send(:setup_gpg_config).should == tempfile_path
338
+
339
+ # tempdir added to @tempdirs
340
+ encryptor.instance_variable_get(:@tempdirs)[0].should == tempdir
341
+ end
342
+ end
343
+
344
+ context 'when errors occur' do
345
+ before do
346
+ encryptor.expects(:cleanup) # run before the error is raised
347
+ end
348
+
349
+ it 'should wrap and raise the error' do
350
+ Dir.expects(:mktmpdir).raises('an error')
351
+
352
+ expect do
353
+ encryptor.send(:setup_gpg_config)
354
+ end.to raise_error {|err|
355
+ err.should be_an_instance_of(
356
+ Backup::Errors::Encryptor::GPG::GPGConfigError
357
+ )
358
+ err.message.should match(
359
+ /Error creating temporary file for #gpg_config/
360
+ )
361
+ err.message.should match(
362
+ /Reason: RuntimeError\n an error/
363
+ )
364
+ }
365
+ end
366
+ end
367
+ end
368
+ end # describe '#setup_gpg_config'
369
+
370
+ describe '#check_gpg_config' do
371
+ let(:cmd_ret) { mock }
372
+ let(:file_path) { '/path/to/tempfile' }
99
373
 
100
374
  before do
101
- tmp_file.stubs(:path).returns('/path/to/tmp_file')
102
- encryptor.stubs(:encryption_key).returns('provided key')
375
+ encryptor.expects(:utility).with(:gpg).returns('gpg')
376
+ encryptor.expects(:run).with(
377
+ "gpg --options '#{ file_path }' --gpgconf-test 2>&1"
378
+ ).returns(cmd_ret)
103
379
  end
104
380
 
105
- it 'should provide a tempfile with the provided key' do
106
- Tempfile.expects(:new).in_sequence(s).
107
- with('backup.pub').
108
- returns(tmp_file)
109
- FileUtils.expects(:chown).in_sequence(s).
110
- with(Backup::Config.user, nil, '/path/to/tmp_file')
111
- FileUtils.expects(:chmod).in_sequence(s).
112
- with(0600, '/path/to/tmp_file')
113
- tmp_file.expects(:write).in_sequence(s).
114
- with('provided key')
115
- tmp_file.expects(:close).in_sequence(s)
116
- tmp_file.expects(:delete).in_sequence(s)
381
+ context 'when no errors are reported' do
382
+ before { cmd_ret.expects(:chomp).returns('') }
117
383
 
118
- encryptor.send(:with_tmp_key_file) do |tmp_file|
119
- tmp_file.should == '/path/to/tmp_file'
384
+ it 'should do nothing' do
385
+ encryptor.send(:check_gpg_config, file_path).should be_nil
120
386
  end
121
387
  end
122
- end
123
388
 
124
- describe '#encryption_key' do
125
- it 'should strip leading whitespace from the given key' do
126
- encryptor.key = <<-KEY
389
+ context 'when errors are reported' do
390
+ let(:error_message) { "gpg: /path/to/tempfile:1: invalid option" }
391
+ before { cmd_ret.expects(:chomp).returns(error_message) }
392
+
393
+ it 'should raise the error message reported' do
394
+ expect do
395
+ encryptor.send(:check_gpg_config, file_path)
396
+ end.to raise_error(RuntimeError, error_message)
397
+ end
398
+ end
399
+ end # describe '#check_gpg_config'
400
+
401
+ describe '#mode_options' do
402
+ let(:s_opts) { "-c --passphrase_file '/some/file'" }
403
+ let(:a_opts) { "-e --trust-model always -r 'identifier'" }
404
+
405
+ context 'while caching the return value in @mode_options' do
406
+ before { encryptor.instance_variable_set(:@mode_options, nil) }
407
+
408
+ context 'when #mode is :symmetric' do
409
+ it 'should return symmetric encryption options' do
410
+ encryptor.expects(:symmetric_options).once.returns(s_opts)
411
+ encryptor.expects(:asymmetric_options).never
412
+
413
+ encryptor.mode = :symmetric
414
+ encryptor.send(:mode_options).should == s_opts
415
+ encryptor.send(:mode_options).should == s_opts
416
+ encryptor.instance_variable_get(:@mode_options).should == s_opts
417
+ end
418
+ end
419
+
420
+ context 'when #mode is :asymmetric' do
421
+ it 'should return asymmetric encryption options' do
422
+ encryptor.expects(:symmetric_options).never
423
+ encryptor.expects(:asymmetric_options).once.returns(a_opts)
424
+
425
+ encryptor.mode = :asymmetric
426
+ encryptor.send(:mode_options).should == a_opts
427
+ encryptor.send(:mode_options).should == a_opts
428
+ encryptor.instance_variable_get(:@mode_options).should == a_opts
429
+ end
430
+ end
431
+
432
+ context 'when #mode is :both' do
433
+ it 'should return both symmetric and asymmetric encryption options' do
434
+ encryptor.expects(:symmetric_options).once.returns(s_opts)
435
+ encryptor.expects(:asymmetric_options).once.returns(a_opts)
436
+
437
+ encryptor.mode = :both
438
+ opts = "#{ s_opts } #{ a_opts }"
439
+
440
+ encryptor.send(:mode_options).should == opts
441
+ encryptor.send(:mode_options).should == opts
442
+ encryptor.instance_variable_get(:@mode_options).should == opts
443
+ end
444
+ end
445
+ end
446
+ end # describe '#mode_options'
447
+
448
+ describe '#symmetric_options' do
449
+ let(:path) { '/path/to/passphrase/file' }
450
+ let(:s_opts) { "-c --passphrase-file '#{ path }'" }
451
+
452
+ context 'when setup_passphrase_file returns a path' do
453
+ it 'should return the options' do
454
+ encryptor.expects(:setup_passphrase_file).returns(path)
455
+ File.expects(:exist?).with(path).returns(true)
456
+
457
+ encryptor.send(:symmetric_options).should == s_opts
458
+ end
459
+ end
460
+
461
+ context 'when setup_passphrase_file returns false' do
462
+ before do
463
+ encryptor.expects(:setup_passphrase_file).returns(false)
464
+ end
465
+
466
+ context 'and no :passphrase_file is set' do
467
+ it 'should return nil and log a warning' do
468
+ encryptor.expects(:passphrase_file).returns(nil)
469
+ Backup::Logger.expects(:warn)
470
+
471
+ encryptor.send(:symmetric_options).should be_nil
472
+ end
473
+ end
474
+
475
+ context 'and a :passphrase_file is set' do
476
+ before do
477
+ encryptor.expects(:passphrase_file).twice.returns(path)
478
+ File.expects(:expand_path).with(path).returns(path)
479
+ end
480
+
481
+ context 'when :passphrase_file exists' do
482
+ it 'should return the options' do
483
+ File.expects(:exist?).with(path).returns(true)
484
+ encryptor.send(:symmetric_options).should == s_opts
485
+ end
486
+ end
487
+
488
+ context 'when :passphrase_file is no valid' do
489
+ it 'should return nil and log a warning' do
490
+ File.expects(:exist?).with(path).returns(false)
491
+ Backup::Logger.expects(:warn)
492
+ encryptor.send(:symmetric_options).should be_nil
493
+ end
494
+ end
495
+ end
496
+ end
497
+ end # describe '#symmetric_options'
498
+
499
+ describe '#setup_passphrase_file' do
500
+ context 'when :passphrase is not set' do
501
+ it 'should return false' do
502
+ encryptor.expects(:passphrase).returns(nil)
503
+ encryptor.send(:setup_passphrase_file).should be_false
504
+ end
505
+ end
506
+
507
+ context 'when :passphrase is set' do
508
+ let(:tempdir) { mock }
509
+ let(:tempfile) { mock }
510
+ let(:tempfile_path) { mock }
511
+
512
+ before do
513
+ encryptor.instance_variable_set(:@tempdirs, [])
514
+ Backup::Config.stubs(:tmp_path).returns('/Backup/tmp')
515
+ encryptor.stubs(:passphrase).returns('a secret')
516
+ tempfile.stubs(:path).returns(tempfile_path)
517
+ end
518
+
519
+ context 'and no errors occur' do
520
+ it 'should return the path for the temp file' do
521
+ # creates temporary directory in Config.tmp_path
522
+ Dir.expects(:mktmpdir).
523
+ with('backup-gpg_passphrase', '/Backup/tmp').
524
+ returns(tempdir)
525
+
526
+ # create the temporary file in that temporary directory
527
+ Tempfile.expects(:open).
528
+ with('backup-gpg_passphrase', tempdir).
529
+ returns(tempfile)
530
+ tempfile.expects(:write).with('a secret')
531
+ tempfile.expects(:close)
532
+
533
+ encryptor.send(:setup_passphrase_file).should == tempfile_path
534
+
535
+ # adds the temporary directory to @tempdirs
536
+ encryptor.instance_variable_get(:@tempdirs)[0].should == tempdir
537
+ end
538
+ end
539
+
540
+ context 'and an error occurs' do
541
+ it 'should return false and log a warning' do
542
+ Dir.expects(:mktmpdir).raises('an error')
543
+ Backup::Logger.expects(:warn).with do |err|
544
+ err.should be_an_instance_of(
545
+ Backup::Errors::Encryptor::GPG::PassphraseError
546
+ )
547
+ err.message.should match(
548
+ /Reason: RuntimeError\n an error/
549
+ )
550
+ end
551
+ encryptor.send(:setup_passphrase_file).should be_false
552
+ end
553
+ end
554
+
555
+ end
556
+ end # describe '#setup_passphrase_file'
557
+
558
+ describe '#asymmetric_options' do
559
+ context 'when recipients are found' do
560
+ it 'should return the options' do
561
+ encryptor.stubs(:user_recipients).returns(['keyid1', 'keyid2'])
562
+ encryptor.send(:asymmetric_options).should ==
563
+ "-e --trust-model always -r 'keyid1' -r 'keyid2'"
564
+ end
565
+ end
566
+
567
+ context 'when no recipients are found' do
568
+ it 'should return nil log a warning' do
569
+ encryptor.expects(:user_recipients).returns([])
570
+ Backup::Logger.expects(:warn)
571
+ encryptor.send(:asymmetric_options).should be_nil
572
+ end
573
+ end
574
+ end # describe '#asymmetric_options'
575
+
576
+ describe '#user_recipients' do
577
+ context 'when an Array of :recipients are given' do
578
+ it 'should return the recipient list and cache the result' do
579
+ encryptor.expects(:recipients).returns(
580
+ ['key_id1', 'key_id2', 'key_id3', 'key_id4']
581
+ )
582
+ encryptor.expects(:clean_identifier).with('key_id1').returns('key_id1')
583
+ encryptor.expects(:clean_identifier).with('key_id2').returns('key_id2')
584
+ encryptor.expects(:clean_identifier).with('key_id3').returns('key_id3')
585
+ encryptor.expects(:clean_identifier).with('key_id4').returns('key_id4')
586
+
587
+ # key_id1 and key_id3 will be found in the system
588
+ encryptor.stubs(:system_identifiers).returns(['key_id1', 'key_id3'])
589
+
590
+ # key_id2 will be imported (key_id returned)
591
+ encryptor.stubs(:user_keys).returns({ 'key_id2' => 'a public key' })
592
+ encryptor.expects(:import_key).
593
+ with('key_id2', 'a public key').
594
+ returns('key_id2')
595
+
596
+ # key_id4 will not be found in user_keys, so a warning will be logged.
597
+ # This will return nil into the array, which will be compacted out.
598
+ Backup::Logger.expects(:warn).with do |msg|
599
+ msg.should match(/'key_id4'/)
600
+ end
601
+
602
+ encryptor.instance_variable_set(:@user_recipients, nil)
603
+ recipient_list = ['key_id1', 'key_id2', 'key_id3']
604
+ encryptor.send(:user_recipients).should == recipient_list
605
+ # results are cached (expectations would fail if called twice)
606
+ encryptor.send(:user_recipients).should == recipient_list
607
+ encryptor.instance_variable_get(:@user_recipients).should == recipient_list
608
+ end
609
+ end
610
+
611
+ context 'when :recipients is a single recipient, given as a String' do
612
+ it 'should return the cleaned identifier in an Array' do
613
+ encryptor.expects(:recipients).returns('key_id')
614
+ # the key will be found in system_identifiers
615
+ encryptor.stubs(:system_identifiers).returns(['key_id'])
616
+ encryptor.expects(:clean_identifier).with('key_id').returns('key_id')
617
+
618
+ encryptor.send(:user_recipients).should == ['key_id']
619
+ end
620
+ end
621
+
622
+ context 'when :recipients is not set' do
623
+ it 'should return an empty Array' do
624
+ encryptor.expects(:recipients).returns(nil)
625
+ encryptor.send(:user_recipients).should == []
626
+ end
627
+ end
628
+ end # describe '#user_recipients'
629
+
630
+ describe '#user_keys' do
631
+ context 'when :keys has been set' do
632
+ before do
633
+ encryptor.expects(:keys).returns(
634
+ { 'key1' => :foo, 'key2' => :foo, 'key3' => :foo }
635
+ )
636
+ encryptor.instance_variable_set(:@user_keys, nil)
637
+ end
638
+
639
+ it 'should return a new Hash of #keys with cleaned identifiers' do
640
+ encryptor.expects(:clean_identifier).with('key1').returns('clean_key1')
641
+ encryptor.expects(:clean_identifier).with('key2').returns('clean_key2')
642
+ encryptor.expects(:clean_identifier).with('key3').returns('clean_key3')
643
+
644
+ Backup::Logger.expects(:warn).never
645
+
646
+ cleaned_hash = {
647
+ 'clean_key1' => :foo, 'clean_key2' => :foo, 'clean_key3' => :foo
648
+ }
649
+ encryptor.send(:user_keys).should == cleaned_hash
650
+ # results are cached (expectations would fail if called twice)
651
+ encryptor.send(:user_keys).should == cleaned_hash
652
+ encryptor.instance_variable_get(:@user_keys).should == cleaned_hash
653
+ end
654
+
655
+ it 'should log a warning if cleaning results in a duplicate identifier' do
656
+ encryptor.expects(:clean_identifier).with('key1').returns('clean_key1')
657
+ encryptor.expects(:clean_identifier).with('key2').returns('clean_key2')
658
+ # return a duplicate key
659
+ encryptor.expects(:clean_identifier).with('key3').returns('clean_key2')
660
+
661
+ Backup::Logger.expects(:warn)
662
+
663
+ cleaned_hash = {
664
+ 'clean_key1' => :foo, 'clean_key2' => :foo
665
+ }
666
+ encryptor.send(:user_keys).should == cleaned_hash
667
+ # results are cached (expectations would fail if called twice)
668
+ encryptor.send(:user_keys).should == cleaned_hash
669
+ encryptor.instance_variable_get(:@user_keys).should == cleaned_hash
670
+ end
671
+ end
672
+
673
+ context 'when :keys has not be set' do
674
+ before do
675
+ encryptor.expects(:keys).returns(nil)
676
+ encryptor.instance_variable_set(:@user_keys, nil)
677
+ end
678
+
679
+ it 'should return an empty hash' do
680
+ encryptor.send(:user_keys).should == {}
681
+ end
682
+ end
683
+ end # describe '#user_keys'
684
+
685
+ describe '#clean_identifier' do
686
+ it 'should remove all spaces and upcase non-email identifiers' do
687
+ encryptor.send(:clean_identifier, ' 9d66 6290 c5f7 ee0f ').
688
+ should == '9D666290C5F7EE0F'
689
+ end
690
+
691
+ # Even though spaces in an email are technically possible,
692
+ # GPG won't allow anything but /[A-Za-z0-9_\-.]/
693
+ it 'should remove all spaces and wrap email addresses in <>' do
694
+ emails = [
695
+ "\t Foo.Bar@example.com ",
696
+ " < Foo-Bar@example.com\t > ",
697
+ "< <Foo_Bar @\texample.com> >"
698
+ ]
699
+ cleaned = [
700
+ '<Foo.Bar@example.com>',
701
+ '<Foo-Bar@example.com>',
702
+ '<Foo_Bar@example.com>'
703
+ ]
704
+
705
+ emails.map {|email|
706
+ encryptor.send(:clean_identifier, email)
707
+ }.should == cleaned
708
+ end
709
+ end # describe '#clean_identifier'
710
+
711
+ describe '#import_key' do
712
+ let(:gpg_return_ok) {
713
+ <<-EOS.gsub(/^ +/, '')
714
+ gpg: keyring `/tmp/.gnupg/secring.gpg' created
715
+ gpg: keyring `/tmp/.gnupg/pubring.gpg' created
716
+ gpg: /tmp/.gnupg/trustdb.gpg: trustdb created
717
+ gpg: key 0x9D666290C5F7EE0F: public key "Backup Test <backup01@foo.com>" imported
718
+ gpg: Total number processed: 1
719
+ gpg: imported: 1 (RSA: 1)
720
+ EOS
721
+ }
722
+ let(:gpg_return_failed) {
723
+ <<-EOS.gsub(/^ +/, '')
724
+ gpg: no valid OpenPGP data found.
725
+ gpg: Total number processed: 0
726
+ EOS
727
+ }
728
+ let(:gpg_key) {
729
+ <<-EOS
127
730
  -----BEGIN PGP PUBLIC KEY BLOCK-----
128
- \tVersion: GnuPG v1.4.11 (Darwin)
731
+ Version: GnuPG v1.4.12 (GNU/Linux)
129
732
 
130
- mQENBE12G/8BCAC4mnlSMYMBwBYTHe5zURcnYYNCORPWOr0iXGiLWuKxYtrDQyLm
131
- X2Nws44Iz7Wp7AuJRAjkitf1cRBgXyDu8wuogXO7JqPmtsUdBCABz9w5NH6IQjgR
132
- WNa3g2n0nokA7Zr5FA4GXoEaYivfbvGiyNpd6P4okH+//G2p+3FIryu5xz+89D1b
133
- =Yvhg
733
+ mI0EUAmiNwEEAKpNP4GVKcjJrTtAh0XKk0NQsId6h/1pzEok2bExkNvD6eSjYRFL
734
+ gXY+pNqaEE6cHrg+uQatVQITX8EoVJhQ9Z1mYJB+g62zqOQPe10Spb381O9y4dN/
735
+ /ge/yL+/+R2CUrKeNF9nSA24+V4mTSqgo7sTnevDzGj4Srzs76MmkpU=
736
+ =TU/B
134
737
  -----END PGP PUBLIC KEY BLOCK-----
135
- KEY
738
+ EOS
739
+ }
740
+ let(:tempfile) { mock }
136
741
 
137
- encryptor.send(:encryption_key).should == <<-KEY
742
+ before do
743
+ Backup::Config.stubs(:tmp_path).returns('/tmp/path')
744
+ encryptor.stubs(:base_options).returns("--some 'base options'")
745
+ encryptor.stubs(:utility).returns('gpg')
746
+ tempfile.stubs(:path).returns('/tmp/file/path')
747
+ end
748
+
749
+ context 'when the import is successful' do
750
+ it 'should return the long key ID' do
751
+ Tempfile.expects(:open).with(
752
+ 'backup-gpg_import', '/tmp/path'
753
+ ).returns(tempfile)
754
+ tempfile.expects(:write).with(<<-EOS)
138
755
  -----BEGIN PGP PUBLIC KEY BLOCK-----
139
- Version: GnuPG v1.4.11 (Darwin)
756
+ Version: GnuPG v1.4.12 (GNU/Linux)
140
757
 
141
- mQENBE12G/8BCAC4mnlSMYMBwBYTHe5zURcnYYNCORPWOr0iXGiLWuKxYtrDQyLm
142
- X2Nws44Iz7Wp7AuJRAjkitf1cRBgXyDu8wuogXO7JqPmtsUdBCABz9w5NH6IQjgR
143
- WNa3g2n0nokA7Zr5FA4GXoEaYivfbvGiyNpd6P4okH+//G2p+3FIryu5xz+89D1b
144
- =Yvhg
758
+ mI0EUAmiNwEEAKpNP4GVKcjJrTtAh0XKk0NQsId6h/1pzEok2bExkNvD6eSjYRFL
759
+ gXY+pNqaEE6cHrg+uQatVQITX8EoVJhQ9Z1mYJB+g62zqOQPe10Spb381O9y4dN/
760
+ /ge/yL+/+R2CUrKeNF9nSA24+V4mTSqgo7sTnevDzGj4Srzs76MmkpU=
761
+ =TU/B
145
762
  -----END PGP PUBLIC KEY BLOCK-----
146
- KEY
763
+ EOS
764
+
765
+ tempfile.expects(:close)
766
+
767
+ encryptor.expects(:run).with(
768
+ "gpg --some 'base options' --keyid-format 0xlong " +
769
+ "--import '/tmp/file/path' 2>&1"
770
+ ).returns(gpg_return_ok)
771
+
772
+ tempfile.expects(:delete)
773
+
774
+ Backup::Logger.expects(:warn).never
775
+
776
+ encryptor.send(:import_key, 'some_identifier', gpg_key).
777
+ should == '9D666290C5F7EE0F'
778
+ end
147
779
  end
148
- end
149
780
 
781
+ context 'when the import is unsuccessful' do
782
+ it 'should return nil and log a warning' do
783
+ Tempfile.expects(:open).raises('an error')
784
+ Backup::Logger.expects(:warn).with {|err|
785
+ err.should be_an_instance_of(
786
+ Backup::Errors::Encryptor::GPG::KeyImportError
787
+ )
788
+ err.message.should match(
789
+ /Public key import failed for 'some_identifier'/
790
+ )
791
+ err.message.should match(
792
+ /Reason: RuntimeError\n an error/
793
+ )
794
+ }
795
+
796
+ encryptor.send(:import_key, 'some_identifier', 'foo').should be_nil
797
+ end
798
+ end
799
+ end # describe '#import_key'
800
+
801
+ describe '#system_identifiers' do
802
+ let(:gpg_output) {
803
+ <<-EOS.gsub(/^ +/, '')
804
+ tru::1:1343402941:0:3:1:5
805
+ pub:-:1024:1:5EFD157FFF9CFEA6:1342808803:::-:::scESC:
806
+ fpr:::::::::72E56E48E362BB402B3344045EFD157FFF9CFEA6:
807
+ uid:-::::1342808803::3BED8A0A5100FE9028BEB53610247518594B60A8::Backup Test (No Email):
808
+ sub:-:1024:1:E6CF1DC860A82E07:1342808803::::::e:
809
+ pub:-:1024:1:570CE9221E3DA3E8:1342808841:::-:::scESC:
810
+ fpr:::::::::616BBC8409C1AED791F8E6F8570CE9221E3DA3E8:
811
+ uid:-::::1342808875::ECFF419EFE4BD3C7CBCCD58FACAD283A9E98FECD::Backup Test <backup04@foo.com>:
812
+ uid:-::::1342808841::DDFD072C193BB45587EBA9D19A7DA1BB0E5E8A22::Backup Test <backup03@foo.com>:
813
+ sub:-:1024:1:B65C0ADEB804268D:1342808841::::::e:
814
+ pub:-:1024:1:54F81C93A7641A16:1342809011:::-:::scESC:
815
+ fpr:::::::::71335B9B960CF3A3071535F454F81C93A7641A16:
816
+ uid:-::::1342809011::2E5801E9C064C2A165B61EE35D50A5F9B64BF345::Backup Test (other email is <backup06@foo.com>) <backup05@foo.com>:
817
+ sub:-:1024:1:5B57BC34628252C7:1342809011::::::e:
818
+ pub:-:1024:1:0A5B6CC9581A88CF:1342809049:::-:::scESC:
819
+ fpr:::::::::E8C459082544924B8AEA06280A5B6CC9581A88CF:
820
+ uid:-::::1342809470::4A404F9ED6780E7E0E02A7F7607828E648789058::Backup Test <backup08@foo.com>:
821
+ uid:-::::::9785ADEBBBCE94CE0FF25774F610F2B11C839E9B::Backup Test <backup07@foo.com>:
822
+ uid:r::::::4AD074B1857819EFA105DFB6C464600AA451BF18::Backup Test <backup09@foo.com>:
823
+ sub:e:1024:1:60A420E39B979B06:1342809049:1342895611:::::e:
824
+ sub:-:1024:1:A05786E7AD5B8352:1342809166::::::e:
825
+ pub:i:1024:1:4A83569F4E5E8D8A:1342810132:::-:::esca:
826
+ fpr:::::::::FFEAD1DB201FB214873E73994A83569F4E5E8D8A:
827
+ uid:-::::::3D41A10AF2437C8C5BF6050FA80FE20CE30769BF::Backup Test <backup10@foo.com>:
828
+ sub:i:1024:1:662F18DB92C8DFD8:1342810132::::::e:
829
+ pub:r:1024:1:15ECEF9ECA136FFF:1342810387:::-:::sc:
830
+ fpr:::::::::3D1CBF3FEFCE5ABB728922F615ECEF9ECA136FFF:
831
+ uid:r::::1342810387::296434E1662AE0B2FF8E93EC3BF3AFE24514D0E0::Backup Test <backup11@foo.com>:
832
+ sub:r:1024:1:097A79EB1F7D4619:1342810387::::::e:
833
+ sub:r:1024:1:39093E8E9057625E:1342810404::::::e:
834
+ pub:e:1024:1:31920687A8A7941B:1342810629:1342897029::-:::sc:
835
+ fpr:::::::::03B399CBC2F4B61019D14BCD31920687A8A7941B:
836
+ uid:e::::1342810629::ED8151565B25281CB92DD1E534701E660126CB0C::Backup Test <backup12@foo.com>:
837
+ sub:e:1024:1:AEF89BEE95042A0F:1342810629:1342897029:::::e:
838
+ pub:-:1024:1:E3DBAEC3FEEA03E2:1342810728:::-:::scSC:
839
+ fpr:::::::::444B0870D985CF70BBB7F4DCE3DBAEC3FEEA03E2:
840
+ uid:-::::1342810796::4D1B8CC29335BF79232CA71210F75CF80318B06A::Backup Test <backup13@foo.com>:
841
+ uid:-::::1342810728::F1422363E8DC1EC3076906505CE66855BB44CAB7::Backup Test <backup14@foo.com>:
842
+ sub:e:1024:1:C95DED316504D17C:1342810728:1342897218:::::e:
843
+ pub:u:1024:1:027B83DB8A82B9CB:1343402840:::u:::scESC:
844
+ fpr:::::::::A20D90150CE4E5F851AD3A9D027B83DB8A82B9CB:
845
+ uid:u::::1343402840::307F1E025E8BEB7DABCADC353291184AD493A28E::Backup Test <backup01@foo.com>:
846
+ sub:u:1024:1:EF31D36414FD8B2B:1343402840::::::e:
847
+ pub:u:1024:1:4CEA6442A4A57A76:1343402867:::u:::scESC:
848
+ fpr:::::::::5742EAFB4CF38014B474671E4CEA6442A4A57A76:
849
+ uid:u::::1343402932::C220D9FF5C9652AA31D3CE0487D88EFF291FA1ED::Backup Test:
850
+ uid:u::::1343402922::E89778553F703C26517AD8321C17C81F3213A782::Backup Test <backup02@foo.com>:
851
+ sub:u:1024:1:140DDC2E97DA3567:1343402867::::::e:
852
+ EOS
853
+ }
854
+
855
+ let(:valid_identifiers) {
856
+ %w{ FF9CFEA6 5EFD157FFF9CFEA6 72E56E48E362BB402B3344045EFD157FFF9CFEA6
857
+ 1E3DA3E8 570CE9221E3DA3E8 616BBC8409C1AED791F8E6F8570CE9221E3DA3E8
858
+ <backup04@foo.com> <backup03@foo.com>
859
+ A7641A16 54F81C93A7641A16 71335B9B960CF3A3071535F454F81C93A7641A16
860
+ <backup05@foo.com>
861
+ 581A88CF 0A5B6CC9581A88CF E8C459082544924B8AEA06280A5B6CC9581A88CF
862
+ <backup08@foo.com> <backup07@foo.com>
863
+ FEEA03E2 E3DBAEC3FEEA03E2 444B0870D985CF70BBB7F4DCE3DBAEC3FEEA03E2
864
+ <backup13@foo.com> <backup14@foo.com>
865
+ 8A82B9CB 027B83DB8A82B9CB A20D90150CE4E5F851AD3A9D027B83DB8A82B9CB
866
+ <backup01@foo.com>
867
+ A4A57A76 4CEA6442A4A57A76 5742EAFB4CF38014B474671E4CEA6442A4A57A76
868
+ <backup02@foo.com> }
869
+ }
870
+ it 'should return an array of all valid identifiers' do
871
+ encryptor.instance_variable_set(:@system_identifiers, nil)
872
+
873
+ encryptor.expects(:utility).with(:gpg).returns('gpg')
874
+ encryptor.expects(:base_options).returns("--base 'options'")
875
+ encryptor.expects(:run).with(
876
+ "gpg --base 'options' --with-colons --fixed-list-mode --fingerprint"
877
+ ).returns(gpg_output)
878
+
879
+ encryptor.send(:system_identifiers).should == valid_identifiers
880
+ # results cached
881
+ encryptor.send(:system_identifiers).should == valid_identifiers
882
+ encryptor.instance_variable_get(:@system_identifiers).
883
+ should == valid_identifiers
884
+ end
885
+ end # describe '#system_identifiers'
886
+
887
+ describe 'deprecations' do
888
+ describe '#key' do
889
+ it 'should import #key, use identifier for #recipients and log a warning' do
890
+ encryptor.expects(:import_key).
891
+ with('deprecated :key', 'a public key').
892
+ returns('an_identifier')
893
+
894
+ Backup::Logger.expects(:warn).with {|err|
895
+ err.should be_an_instance_of(Backup::Errors::ConfigurationError)
896
+ err.message.should match(
897
+ "GPG#key has been deprecated as of backup v.3.0.26"
898
+ )
899
+ err.message.should match(
900
+ "replaced with #keys and #recipients"
901
+ )
902
+ }
903
+
904
+ encryptor.key = 'a public key'
905
+ encryptor.recipients.should == 'an_identifier'
906
+ end
907
+ end # describe '#key'
908
+ end # describe 'deprecations'
150
909
  end