backup 3.0.26 → 3.0.27

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,287 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../../spec_helper.rb', __FILE__)
4
+
5
+ describe 'Encryptor::GPG',
6
+ :if => Backup::SpecLive::CONFIG['encryptor']['gpg']['specs_enabled'] do
7
+
8
+ def archive_file_for(model)
9
+ File.join(
10
+ Backup::SpecLive::TMP_PATH,
11
+ "#{model.trigger}", model.time, "#{model.trigger}.tar.gpg"
12
+ )
13
+ end
14
+
15
+ # clear out, then load the encryptor.gpg_homedir with the keys for the
16
+ # given key_type (:public/:private) for the given identifiers.
17
+ #
18
+ def load_gpg_homedir(encryptor, key_type, identifiers)
19
+ # Clear out any files if the directory exists
20
+ dir = File.expand_path(encryptor.gpg_homedir)
21
+ FileUtils.rm(Dir[File.join(dir, '*')]) if File.exists?(dir)
22
+
23
+ # Make sure the directory exists with proper permissions.
24
+ # This will also initialize the keyring files, so this method can be
25
+ # called with no identifiers to simply reset the directory without
26
+ # importing any keys.
27
+ encryptor.send(:setup_gpg_homedir)
28
+
29
+ # Import the keys, making sure each import is successful.
30
+ # #import_key will log a warning for the identifier if the
31
+ # import fails, so we'll just abort if we get a failure here.
32
+ [identifiers].flatten.compact.each do |identifier|
33
+ ret_id = encryptor.send(:import_key,
34
+ identifier, Backup::SpecLive::GPGKeys[identifier][key_type]
35
+ )
36
+ unless ret_id == Backup::SpecLive::GPGKeys[identifier][:long_id]
37
+ abort("load_gpg_homedir failed")
38
+ end
39
+ end
40
+ end
41
+
42
+ # make sure the archive can be decrypted
43
+ def can_decrypt?(model, passphrase = nil)
44
+ enc = model.encryptor
45
+ archive = archive_file_for(model)
46
+ outfile = File.join(File.dirname(archive), 'outfile')
47
+
48
+ pass_opt = "--passphrase '#{ passphrase }'" if passphrase
49
+ enc.send(:run,
50
+ "#{ enc.send(:utility, :gpg) } #{ enc.send(:base_options) } " +
51
+ "#{ pass_opt } -o '#{ outfile }' -d '#{ archive }' 2>&1"
52
+ )
53
+
54
+ if File.exist?(outfile)
55
+ File.delete(outfile)
56
+ true
57
+ else
58
+ false
59
+ end
60
+ end
61
+
62
+ context 'using :asymmetric mode with some existing keys' do
63
+ let(:model) { h_set_trigger('encryptor_gpg_asymmetric') }
64
+
65
+ it 'should encrypt the archive' do
66
+ recipients = [:backup01, :backup02, :backup03, :backup04]
67
+ # Preload keys for backup01 and backup02.
68
+ # Keys for backup03 and backup04 are configured in :keys in the model
69
+ # and will be imported when the model is performed.
70
+ # The Model specifies all 4 as :recipients.
71
+ load_gpg_homedir(model.encryptor, :public, recipients[0..1])
72
+
73
+ model.perform!
74
+
75
+ Backup::Logger.has_warnings?.should be_false
76
+
77
+ File.exist?(archive_file_for(model)).should be_true
78
+
79
+ # make sure all 4 recipients can decrypt the archive
80
+ recipients.each do |recipient|
81
+ load_gpg_homedir(model.encryptor, :private, recipient)
82
+ can_decrypt?(model).should be_true
83
+ end
84
+ end
85
+ end # context 'using :asymmetric mode with some existing keys'
86
+
87
+ context 'using :asymmetric mode with a missing public key' do
88
+ let(:model) { h_set_trigger('encryptor_gpg_asymmetric_missing') }
89
+
90
+ # backup01 will be preloaded.
91
+ # backup02 will be imported from :keys
92
+ # backupfoo will be a missing recipient
93
+ it 'should encrypt the archive' do
94
+ load_gpg_homedir(model.encryptor, :public, :backup01)
95
+
96
+ model.perform!
97
+
98
+ Backup::Logger.has_warnings?.should be_true
99
+ Backup::Logger.messages.any? {|msg|
100
+ msg =~ /No public key was found in #keys for '<backupfoo@foo.com>'/
101
+ }.should be_true
102
+
103
+ File.exist?(archive_file_for(model)).should be_true
104
+
105
+ [:backup01, :backup02].each do |recipient|
106
+ load_gpg_homedir(model.encryptor, :private, recipient)
107
+ can_decrypt?(model).should be_true
108
+ end
109
+ end
110
+ end # context 'using :asymmetric mode with a missing public key'
111
+
112
+ context 'using :asymmetric mode with no valid public keys' do
113
+ let(:model) { h_set_trigger('encryptor_gpg_asymmetric_fail') }
114
+
115
+ it 'should abort the backup' do
116
+ model.perform!
117
+
118
+ # issues warnings about the missing keys
119
+ Backup::Logger.has_warnings?.should be_true
120
+ Backup::Logger.messages.any? {|msg|
121
+ msg =~ /No public key was found in #keys for '<backupfoo@foo.com>'/
122
+ }.should be_true
123
+ Backup::Logger.messages.any? {|msg|
124
+ msg =~ /No public key was found in #keys for '<backupfoo2@foo.com>'/
125
+ }.should be_true
126
+
127
+ # issues warning about not being able to perform asymmetric encryption
128
+ Backup::Logger.messages.any? {|msg|
129
+ msg =~ /No recipients available for asymmetric encryption/
130
+ }.should be_true
131
+
132
+ # Since there are no other options for encryption,
133
+ # the backup failes with an error.
134
+ Backup::Logger.messages.any? {|msg|
135
+ msg =~ /\[error\]\s+ModelError/
136
+ }.should be_true
137
+ Backup::Logger.messages.any? {|msg|
138
+ msg =~ /\[error\]\s+Reason: Encryptor::GPG::EncryptionError/
139
+ }.should be_true
140
+ Backup::Logger.messages.any? {|msg|
141
+ msg =~ /\[error\]\s+Encryption could not be performed for mode 'asymmetric'/
142
+ }.should be_true
143
+
144
+ # Although, any further backup models would be run, as this error
145
+ # is rescued in Backup::Model#perform
146
+ Backup::Logger.messages.any? {|msg|
147
+ msg =~ /Backup will now attempt to continue/
148
+ }.should be_true
149
+
150
+ File.exist?(archive_file_for(model)).should be_false
151
+ end
152
+ end # context 'using :asymmetric mode with no valid public keys'
153
+
154
+ context 'using :symmetric mode' do
155
+ let(:model) { h_set_trigger('encryptor_gpg_symmetric') }
156
+
157
+ it 'should encrypt the archive' do
158
+ model.perform!
159
+
160
+ Backup::Logger.has_warnings?.should be_false
161
+
162
+ File.exist?(archive_file_for(model)).should be_true
163
+
164
+ can_decrypt?(model, 'a secret').should be_true
165
+
166
+ # note that without specifying any preferences, the default
167
+ # algorithm used is CAST5
168
+ Backup::Logger.messages.any? {|msg|
169
+ msg =~ /gpg: CAST5 encrypted data/
170
+ }.should be_true
171
+ end
172
+ end # context 'using :symmetric mode'
173
+
174
+ # The #gpg_config preferences should also be able to override the algorithm
175
+ # preferences in the recipients' public keys, but the gpg output doesn't
176
+ # give us an easy way to check this. You'd have to inspect the leading bytes
177
+ # of the encrypted file per RFC4880, and I'm not going that far :)
178
+ context 'using :symmetric mode with given gpg_config' do
179
+ let(:model) { h_set_trigger('encryptor_gpg_symmetric_with_config') }
180
+
181
+ it 'should encrypt the archive using the proper algorithm preference' do
182
+ model.perform!
183
+
184
+ Backup::Logger.has_warnings?.should be_false
185
+
186
+ File.exist?(archive_file_for(model)).should be_true
187
+
188
+ can_decrypt?(model, 'a secret').should be_true
189
+
190
+ # preferences set in #gpg_config specified using AES256 before CAST5
191
+ Backup::Logger.messages.any? {|msg|
192
+ msg =~ /gpg: AES256 encrypted data/
193
+ }.should be_true
194
+ end
195
+ end # context 'using :symmetric mode with given gpg_config'
196
+
197
+ context 'using :both mode' do
198
+ let(:model) { h_set_trigger('encryptor_gpg_both') }
199
+
200
+ it 'should encrypt the archive' do
201
+ # Preload key for backup01.
202
+ # Key for backup03 will be imported when the model is performed.
203
+ load_gpg_homedir(model.encryptor, :public, :backup01)
204
+
205
+ model.perform!
206
+
207
+ Backup::Logger.has_warnings?.should be_false
208
+
209
+ File.exist?(archive_file_for(model)).should be_true
210
+
211
+ # make sure both recipients can decrypt the archive
212
+ [:backup01, :backup03].each do |recipient|
213
+ load_gpg_homedir(model.encryptor, :private, recipient)
214
+ can_decrypt?(model).should be_true
215
+ end
216
+
217
+ # with no private keys in the keyring,
218
+ # archive can be decrypted using the passphrase.
219
+ load_gpg_homedir(model.encryptor, :private, nil)
220
+ can_decrypt?(model, 'a secret').should be_true
221
+ end
222
+ end # context 'using :both mode'
223
+
224
+ context 'using :both mode with no valid asymmetric recipients' do
225
+ let(:model) { h_set_trigger('encryptor_gpg_both_no_asymmetric') }
226
+
227
+ it 'should encrypt the archive using only symmetric encryption' do
228
+ # we'll load backup02, but this isn't one of the :recipients
229
+ load_gpg_homedir(model.encryptor, :public, :backup02)
230
+
231
+ model.perform!
232
+
233
+ # issues warnings about the missing keys
234
+ Backup::Logger.has_warnings?.should be_true
235
+ Backup::Logger.messages.any? {|msg|
236
+ msg =~ /No public key was found in #keys for '16325C61'/
237
+ }.should be_true
238
+ Backup::Logger.messages.any? {|msg|
239
+ msg =~ /No public key was found in #keys for '<backup03@foo.com>'/
240
+ }.should be_true
241
+
242
+ # issues warning about not being able to perform asymmetric encryption
243
+ Backup::Logger.messages.any? {|msg|
244
+ msg =~ /No recipients available for asymmetric encryption/
245
+ }.should be_true
246
+
247
+ # backup proceeded, since symmetric encryption could still be performed
248
+ File.exist?(archive_file_for(model)).should be_true
249
+
250
+ # with no private keys in the keyring,
251
+ # archive can be decrypted using the passphrase.
252
+ load_gpg_homedir(model.encryptor, :private, nil)
253
+ can_decrypt?(model, 'a secret').should be_true
254
+ end
255
+ end # context 'using :both mode with no valid asymmetric recipients'
256
+
257
+ context 'when using the deprecated #key accessor' do
258
+ let(:model) {
259
+ # See notes in spec-live/spec_helper.rb
260
+ h_set_single_model do
261
+ Backup::Model.new(:encryptor_gpg_deprecate_key, 'test_label') do
262
+ archive :test_archive, &Backup::SpecLive::ARCHIVE_JOB
263
+ encrypt_with 'GPG' do |e|
264
+ e.key = Backup::SpecLive::GPGKeys[:backup03][:public]
265
+ end
266
+ store_with 'Local'
267
+ end
268
+ end
269
+ }
270
+
271
+ it 'should log a warning and store an encrypted archive' do
272
+ model.perform!
273
+
274
+ Backup::Logger.has_warnings?.should be_true
275
+ Backup::Logger.messages.any? {|msg|
276
+ msg =~ /GPG#key has been deprecated/
277
+ }.should be_true
278
+
279
+ File.exist?(archive_file_for(model)).should be_true
280
+
281
+ load_gpg_homedir(model.encryptor, :private, :backup03)
282
+
283
+ can_decrypt?(model).should be_true
284
+ end
285
+ end # context 'when using the deprecated #key accessor'
286
+
287
+ end
@@ -9,33 +9,49 @@ describe 'Notifier::Mail',
9
9
 
10
10
  it 'should send a success email' do
11
11
  model = h_set_trigger(trigger)
12
- expect do
13
- model.perform!
14
- end.not_to raise_error
12
+ model.perform!
13
+
14
+ Backup::Logger.has_warnings?.should be_false
15
+ Backup::Logger.messages.any? {|msg| msg =~ /\[error\]/ }.should be_false
15
16
  end
16
17
 
17
18
  it 'should send a warning email' do
18
19
  model = h_set_trigger(trigger)
19
20
  Backup::Logger.warn 'You have been warned!'
20
- expect do
21
- model.perform!
22
- end.not_to raise_error
21
+ model.perform!
22
+
23
+ Backup::Logger.has_warnings?.should be_true
24
+ Backup::Logger.messages.any? {|msg| msg =~ /\[error\]/ }.should be_false
23
25
  end
24
26
 
25
27
  it 'should send a failure email for non-fatal errors' do
26
28
  model = h_set_trigger(trigger)
27
29
  model.stubs(:databases).raises('A successful failure?')
28
- expect do
29
- model.perform!
30
- end.not_to raise_error
30
+ model.perform!
31
+
32
+ Backup::Logger.has_warnings?.should be_false
33
+ Backup::Logger.messages.any? {|msg|
34
+ msg =~ /\[error\]\s+A successful failure/
35
+ }.should be_true
36
+ Backup::Logger.messages.any? {|msg|
37
+ msg =~ /Backup will now attempt to continue/
38
+ }.should be_true
31
39
  end
32
40
 
33
- it 'should send a failure email fatal errors' do
41
+ it 'should send a failure email for fatal errors' do
34
42
  model = h_set_trigger(trigger)
35
43
  model.stubs(:databases).raises(NoMemoryError, 'with increasing frequency...')
36
44
  expect do
37
45
  model.perform!
38
- end.to raise_error
46
+ end.to raise_error(SystemExit)
47
+
48
+ Backup::Logger.has_warnings?.should be_false
49
+ Backup::Logger.messages.any? {|msg|
50
+ msg =~ /with increasing frequency/
51
+ }.should be_true
52
+ Backup::Logger.messages.any? {|msg|
53
+ msg =~ /Backup will now exit/
54
+ }.should be_true
39
55
  end
40
56
  end # describe 'Notifier::Mail :smtp'
41
57
 
@@ -45,9 +61,11 @@ describe 'Notifier::Mail',
45
61
 
46
62
  it 'should send a success email' do
47
63
  model = h_set_trigger(trigger)
48
- expect do
49
- model.perform!
50
- end.not_to raise_error
64
+ model.perform!
65
+
66
+ Backup::Logger.has_warnings?.should be_false
67
+ Backup::Logger.messages.any? {|msg| msg =~ /\[error\]/ }.should be_false
68
+
51
69
  File.exist?(test_email).should be_true
52
70
  File.read(test_email).should match(/without any errors/)
53
71
  end
@@ -55,9 +73,11 @@ describe 'Notifier::Mail',
55
73
  it 'should send a warning email' do
56
74
  model = h_set_trigger(trigger)
57
75
  Backup::Logger.warn 'You have been warned!'
58
- expect do
59
- model.perform!
60
- end.not_to raise_error
76
+ model.perform!
77
+
78
+ Backup::Logger.has_warnings?.should be_true
79
+ Backup::Logger.messages.any? {|msg| msg =~ /\[error\]/ }.should be_false
80
+
61
81
  File.exist?(test_email).should be_true
62
82
  File.read(test_email).should match(/You have been warned/)
63
83
  end
@@ -65,19 +85,35 @@ describe 'Notifier::Mail',
65
85
  it 'should send a failure email for non-fatal errors' do
66
86
  model = h_set_trigger(trigger)
67
87
  model.stubs(:databases).raises('A successful failure?')
68
- expect do
69
- model.perform!
70
- end.not_to raise_error
88
+ model.perform!
89
+
90
+ Backup::Logger.has_warnings?.should be_false
91
+ Backup::Logger.messages.any? {|msg|
92
+ msg =~ /\[error\]\s+A successful failure/
93
+ }.should be_true
94
+ Backup::Logger.messages.any? {|msg|
95
+ msg =~ /Backup will now attempt to continue/
96
+ }.should be_true
97
+
71
98
  File.exist?(test_email).should be_true
72
99
  File.read(test_email).should match(/successful failure/)
73
100
  end
74
101
 
75
- it 'should send a failure email fatal errors' do
102
+ it 'should send a failure email for fatal errors' do
76
103
  model = h_set_trigger(trigger)
77
104
  model.stubs(:databases).raises(NoMemoryError, 'with increasing frequency...')
78
105
  expect do
79
106
  model.perform!
80
- end.to raise_error
107
+ end.to raise_error(SystemExit)
108
+
109
+ Backup::Logger.has_warnings?.should be_false
110
+ Backup::Logger.messages.any? {|msg|
111
+ msg =~ /with increasing frequency/
112
+ }.should be_true
113
+ Backup::Logger.messages.any? {|msg|
114
+ msg =~ /Backup will now exit/
115
+ }.should be_true
116
+
81
117
  File.exist?(test_email).should be_true
82
118
  File.read(test_email).should match(/with increasing frequency/)
83
119
  end
@@ -8,6 +8,11 @@ require 'bundler/setup'
8
8
  # Load Backup
9
9
  require 'backup'
10
10
 
11
+ # Backup::SpecLive::GPGKeys
12
+ # Loaded here so these are available in backups/models.rb
13
+ # as well as within encryptor/gpg_spec.rb
14
+ require File.expand_path('../encryptor/gpg_keys.rb', __FILE__)
15
+
11
16
  module Backup
12
17
  module SpecLive
13
18
  PATH = File.expand_path('..', __FILE__)
@@ -15,6 +20,11 @@ module Backup
15
20
  TMP_PATH = PATH + '/tmp'
16
21
  SYNC_PATH = PATH + '/sync'
17
22
 
23
+ ARCHIVE_JOB = lambda do |archive|
24
+ archive.add File.expand_path('../../lib/backup', __FILE__)
25
+ archive.exclude File.expand_path('../../lib/backup/storage', __FILE__)
26
+ end
27
+
18
28
  config = PATH + '/backups/config.yml'
19
29
  if File.exist?(config)
20
30
  CONFIG = YAML.load_file(config)
@@ -24,14 +34,57 @@ module Backup
24
34
  exit!
25
35
  end
26
36
 
37
+ class << self
38
+ attr_accessor :load_models
39
+ end
40
+
27
41
  module ExampleHelpers
28
42
 
43
+ # This method loads all defaults in config.rb and all the Models
44
+ # in models.rb, then returns the Model for the given trigger.
29
45
  def h_set_trigger(trigger)
46
+ Backup::SpecLive.load_models = true
47
+ Backup::Logger.clear!
48
+ Backup::Model.all.clear
49
+ Backup::Config.load_config!
50
+ model = Backup::Model.find(trigger)
51
+ model.prepare!
52
+ model
53
+ end
54
+
55
+ # This method can be used to setup a test where you need to setup
56
+ # and perform a single Model that can not be setup in models.rb.
57
+ # This is primarily for Models used to test deprecations, since
58
+ # those warnings will be output when the Model is instantiated
59
+ # and will pollute the output of all other tests.
60
+ #
61
+ # Usage:
62
+ # model = h_set_single_model do
63
+ # Backup::Model.new(:test_trigger, 'test label') do
64
+ # ...setup model...
65
+ # end
66
+ # end
67
+ #
68
+ # The block doesn't have to return the model, as it will be retrieved
69
+ # from Model.all (since it will be the only one).
70
+ #
71
+ # Remember when defining the model that the DSL constants won't be
72
+ # available, as the block is not being evaluated in the context of
73
+ # the Backup::Config module. So, just use strings instead.
74
+ # e.g. store_with 'Local' vs store_with Local
75
+ #
76
+ # Note this will still load any defaults setup in config.rb, so don't
77
+ # do anything in config.rb that would generate a deprecation warning :)
78
+ #
79
+ def h_set_single_model(&block)
80
+ Backup::SpecLive.load_models = false
30
81
  Backup::Logger.clear!
31
82
  Backup::Model.all.clear
32
83
  Backup::Config.load_config!
33
- FileUtils.mkdir_p(File.join(Backup::Config.data_path, trigger))
34
- Backup::Model.find(trigger)
84
+ block.call
85
+ model = Backup::Model.all.first
86
+ model.prepare!
87
+ model
35
88
  end
36
89
 
37
90
  def h_clean_data_paths!
@@ -82,4 +135,17 @@ RSpec.configure do |config|
82
135
  end
83
136
  end
84
137
 
85
- puts "\n\nRuby version: #{RUBY_DESCRIPTION}\n\n"
138
+ puts "\nRuby version: #{ RUBY_DESCRIPTION }\n"
139
+
140
+ unless ENV['VERBOSE']
141
+ puts <<-EOS
142
+
143
+ Some of these tests can be slow, so be patient.
144
+ It's recommended you run these with:
145
+ $ VERBOSE=1 rspec spec-live/
146
+ For some tests, [error] and [warning] messages are normal.
147
+ Some could pass, but still have problems.
148
+ So, pay attention to the messages :)
149
+
150
+ EOS
151
+ end