backup 3.0.20 → 3.0.21

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 (178) hide show
  1. data/Gemfile +1 -5
  2. data/Gemfile.lock +46 -50
  3. data/README.md +54 -27
  4. data/lib/backup.rb +16 -39
  5. data/lib/backup/archive.rb +42 -18
  6. data/lib/backup/cleaner.rb +110 -25
  7. data/lib/backup/cli/helpers.rb +17 -32
  8. data/lib/backup/cli/utility.rb +46 -107
  9. data/lib/backup/compressor/base.rb +14 -2
  10. data/lib/backup/compressor/bzip2.rb +10 -24
  11. data/lib/backup/compressor/gzip.rb +10 -24
  12. data/lib/backup/compressor/lzma.rb +10 -23
  13. data/lib/backup/compressor/pbzip2.rb +12 -32
  14. data/lib/backup/config.rb +171 -0
  15. data/lib/backup/configuration/compressor/base.rb +1 -2
  16. data/lib/backup/configuration/compressor/pbzip2.rb +4 -4
  17. data/lib/backup/configuration/database/base.rb +2 -1
  18. data/lib/backup/configuration/database/mongodb.rb +8 -0
  19. data/lib/backup/configuration/database/mysql.rb +4 -0
  20. data/lib/backup/configuration/database/postgresql.rb +4 -0
  21. data/lib/backup/configuration/database/redis.rb +4 -0
  22. data/lib/backup/configuration/database/riak.rb +5 -1
  23. data/lib/backup/configuration/encryptor/base.rb +1 -2
  24. data/lib/backup/configuration/encryptor/open_ssl.rb +1 -1
  25. data/lib/backup/configuration/helpers.rb +7 -2
  26. data/lib/backup/configuration/notifier/base.rb +4 -28
  27. data/lib/backup/configuration/storage/base.rb +1 -1
  28. data/lib/backup/configuration/storage/dropbox.rb +14 -4
  29. data/lib/backup/configuration/syncer/base.rb +10 -0
  30. data/lib/backup/configuration/syncer/rsync/base.rb +28 -0
  31. data/lib/backup/configuration/syncer/rsync/local.rb +11 -0
  32. data/lib/backup/configuration/syncer/rsync/pull.rb +11 -0
  33. data/lib/backup/configuration/syncer/rsync/push.rb +31 -0
  34. data/lib/backup/configuration/syncer/s3.rb +0 -4
  35. data/lib/backup/database/base.rb +25 -7
  36. data/lib/backup/database/mongodb.rb +112 -75
  37. data/lib/backup/database/mysql.rb +54 -29
  38. data/lib/backup/database/postgresql.rb +60 -42
  39. data/lib/backup/database/redis.rb +61 -39
  40. data/lib/backup/database/riak.rb +35 -11
  41. data/lib/backup/dependency.rb +4 -5
  42. data/lib/backup/encryptor/base.rb +13 -1
  43. data/lib/backup/encryptor/gpg.rb +39 -39
  44. data/lib/backup/encryptor/open_ssl.rb +28 -38
  45. data/lib/backup/logger.rb +20 -11
  46. data/lib/backup/model.rb +206 -163
  47. data/lib/backup/notifier/base.rb +27 -25
  48. data/lib/backup/notifier/campfire.rb +7 -13
  49. data/lib/backup/notifier/hipchat.rb +28 -28
  50. data/lib/backup/notifier/mail.rb +24 -26
  51. data/lib/backup/notifier/presently.rb +10 -18
  52. data/lib/backup/notifier/prowl.rb +9 -17
  53. data/lib/backup/notifier/twitter.rb +11 -18
  54. data/lib/backup/package.rb +47 -0
  55. data/lib/backup/packager.rb +81 -16
  56. data/lib/backup/splitter.rb +48 -35
  57. data/lib/backup/storage/base.rb +44 -172
  58. data/lib/backup/storage/cloudfiles.rb +31 -46
  59. data/lib/backup/storage/cycler.rb +117 -0
  60. data/lib/backup/storage/dropbox.rb +92 -76
  61. data/lib/backup/storage/ftp.rb +30 -40
  62. data/lib/backup/storage/local.rb +44 -45
  63. data/lib/backup/storage/ninefold.rb +55 -49
  64. data/lib/backup/storage/rsync.rb +49 -56
  65. data/lib/backup/storage/s3.rb +33 -44
  66. data/lib/backup/storage/scp.rb +21 -48
  67. data/lib/backup/storage/sftp.rb +26 -40
  68. data/lib/backup/syncer/base.rb +7 -0
  69. data/lib/backup/syncer/rsync/base.rb +78 -0
  70. data/lib/backup/syncer/rsync/local.rb +53 -0
  71. data/lib/backup/syncer/rsync/pull.rb +38 -0
  72. data/lib/backup/syncer/rsync/push.rb +113 -0
  73. data/lib/backup/syncer/s3.rb +42 -32
  74. data/lib/backup/version.rb +1 -1
  75. data/spec/archive_spec.rb +235 -69
  76. data/spec/cleaner_spec.rb +304 -0
  77. data/spec/cli/helpers_spec.rb +142 -1
  78. data/spec/cli/utility_spec.rb +338 -13
  79. data/spec/compressor/base_spec.rb +31 -0
  80. data/spec/compressor/bzip2_spec.rb +60 -35
  81. data/spec/compressor/gzip_spec.rb +60 -35
  82. data/spec/compressor/lzma_spec.rb +60 -35
  83. data/spec/compressor/pbzip2_spec.rb +98 -37
  84. data/spec/config_spec.rb +321 -0
  85. data/spec/configuration/base_spec.rb +4 -4
  86. data/spec/configuration/compressor/bzip2_spec.rb +1 -0
  87. data/spec/configuration/compressor/gzip_spec.rb +1 -0
  88. data/spec/configuration/compressor/lzma_spec.rb +1 -0
  89. data/spec/configuration/compressor/pbzip2_spec.rb +32 -0
  90. data/spec/configuration/database/base_spec.rb +2 -1
  91. data/spec/configuration/database/mongodb_spec.rb +26 -16
  92. data/spec/configuration/database/mysql_spec.rb +4 -0
  93. data/spec/configuration/database/postgresql_spec.rb +4 -0
  94. data/spec/configuration/database/redis_spec.rb +4 -0
  95. data/spec/configuration/database/riak_spec.rb +4 -0
  96. data/spec/configuration/encryptor/gpg_spec.rb +1 -0
  97. data/spec/configuration/encryptor/open_ssl_spec.rb +1 -0
  98. data/spec/configuration/notifier/base_spec.rb +32 -0
  99. data/spec/configuration/notifier/campfire_spec.rb +1 -0
  100. data/spec/configuration/notifier/hipchat_spec.rb +1 -0
  101. data/spec/configuration/notifier/mail_spec.rb +1 -0
  102. data/spec/configuration/notifier/presently_spec.rb +1 -0
  103. data/spec/configuration/notifier/prowl_spec.rb +1 -0
  104. data/spec/configuration/notifier/twitter_spec.rb +1 -0
  105. data/spec/configuration/storage/cloudfiles_spec.rb +1 -0
  106. data/spec/configuration/storage/dropbox_spec.rb +4 -3
  107. data/spec/configuration/storage/ftp_spec.rb +1 -0
  108. data/spec/configuration/storage/local_spec.rb +1 -0
  109. data/spec/configuration/storage/ninefold_spec.rb +1 -0
  110. data/spec/configuration/storage/rsync_spec.rb +3 -1
  111. data/spec/configuration/storage/s3_spec.rb +1 -0
  112. data/spec/configuration/storage/scp_spec.rb +1 -0
  113. data/spec/configuration/storage/sftp_spec.rb +1 -0
  114. data/spec/configuration/syncer/rsync/base_spec.rb +33 -0
  115. data/spec/configuration/syncer/rsync/local_spec.rb +10 -0
  116. data/spec/configuration/syncer/rsync/pull_spec.rb +10 -0
  117. data/spec/configuration/syncer/{rsync_spec.rb → rsync/push_spec.rb} +12 -15
  118. data/spec/configuration/syncer/s3_spec.rb +2 -3
  119. data/spec/database/base_spec.rb +35 -20
  120. data/spec/database/mongodb_spec.rb +298 -119
  121. data/spec/database/mysql_spec.rb +147 -72
  122. data/spec/database/postgresql_spec.rb +155 -100
  123. data/spec/database/redis_spec.rb +200 -97
  124. data/spec/database/riak_spec.rb +82 -24
  125. data/spec/dependency_spec.rb +49 -0
  126. data/spec/encryptor/base_spec.rb +30 -0
  127. data/spec/encryptor/gpg_spec.rb +105 -28
  128. data/spec/encryptor/open_ssl_spec.rb +85 -114
  129. data/spec/logger_spec.rb +74 -8
  130. data/spec/model_spec.rb +528 -220
  131. data/spec/notifier/base_spec.rb +89 -0
  132. data/spec/notifier/campfire_spec.rb +147 -119
  133. data/spec/notifier/hipchat_spec.rb +140 -145
  134. data/spec/notifier/mail_spec.rb +190 -248
  135. data/spec/notifier/presently_spec.rb +147 -282
  136. data/spec/notifier/prowl_spec.rb +79 -111
  137. data/spec/notifier/twitter_spec.rb +87 -106
  138. data/spec/package_spec.rb +61 -0
  139. data/spec/packager_spec.rb +154 -0
  140. data/spec/spec_helper.rb +36 -13
  141. data/spec/splitter_spec.rb +90 -41
  142. data/spec/storage/base_spec.rb +95 -239
  143. data/spec/storage/cloudfiles_spec.rb +185 -75
  144. data/spec/storage/cycler_spec.rb +239 -0
  145. data/spec/storage/dropbox_spec.rb +318 -87
  146. data/spec/storage/ftp_spec.rb +165 -152
  147. data/spec/storage/local_spec.rb +206 -54
  148. data/spec/storage/ninefold_spec.rb +264 -128
  149. data/spec/storage/rsync_spec.rb +244 -163
  150. data/spec/storage/s3_spec.rb +175 -64
  151. data/spec/storage/scp_spec.rb +156 -150
  152. data/spec/storage/sftp_spec.rb +153 -135
  153. data/spec/syncer/base_spec.rb +22 -0
  154. data/spec/syncer/rsync/base_spec.rb +118 -0
  155. data/spec/syncer/rsync/local_spec.rb +121 -0
  156. data/spec/syncer/rsync/pull_spec.rb +90 -0
  157. data/spec/syncer/rsync/push_spec.rb +327 -0
  158. data/spec/syncer/s3_spec.rb +180 -91
  159. data/templates/cli/utility/config +1 -1
  160. data/templates/cli/utility/database/mongodb +4 -0
  161. data/templates/cli/utility/database/mysql +3 -0
  162. data/templates/cli/utility/database/postgresql +3 -0
  163. data/templates/cli/utility/database/redis +3 -0
  164. data/templates/cli/utility/database/riak +3 -0
  165. data/templates/cli/utility/storage/dropbox +4 -1
  166. data/templates/cli/utility/syncer/rsync_local +12 -0
  167. data/templates/cli/utility/syncer/{rsync → rsync_pull} +2 -2
  168. data/templates/cli/utility/syncer/rsync_push +17 -0
  169. data/templates/storage/dropbox/authorization_url.erb +1 -1
  170. metadata +42 -17
  171. data/lib/backup/configuration/syncer/rsync.rb +0 -45
  172. data/lib/backup/finder.rb +0 -87
  173. data/lib/backup/storage/object.rb +0 -47
  174. data/lib/backup/syncer/rsync.rb +0 -152
  175. data/spec/backup_spec.rb +0 -11
  176. data/spec/finder_spec.rb +0 -91
  177. data/spec/storage/object_spec.rb +0 -74
  178. data/spec/syncer/rsync_spec.rb +0 -195
@@ -1,11 +1,10 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require File.expand_path('../spec_helper.rb', __FILE__)
4
- require 'timecop'
5
4
 
6
5
  describe Backup::Logger do
7
6
  let(:logger_time) { Time.now.strftime("%Y/%m/%d %H:%M:%S") }
8
- let(:logfile_path) { File.join(Backup::LOG_PATH, 'backup.log') }
7
+ let(:logfile_path) { File.join(Backup::Config.log_path, 'backup.log') }
9
8
  let(:logfile_mock) { mock }
10
9
  let(:s) { sequence '' }
11
10
 
@@ -17,7 +16,7 @@ describe Backup::Logger do
17
16
  subject.unstub(message_type)
18
17
  end
19
18
 
20
- subject.send(:remove_const, :QUIET) rescue nil
19
+ subject.quiet = nil
21
20
  subject.send(:remove_instance_variable, :@messages) rescue nil
22
21
  subject.send(:remove_instance_variable, :@has_warnings) rescue nil
23
22
  end
@@ -145,6 +144,66 @@ describe Backup::Logger do
145
144
  end
146
145
  end # describe '#clear!'
147
146
 
147
+ describe '#truncate!' do
148
+ context 'when log file is <= max_bytes' do
149
+ it 'should do nothing' do
150
+ stat = mock
151
+ [1, 2].each do |size|
152
+ File.expects(:stat).with(
153
+ File.join(Backup::Config.log_path, 'backup.log')
154
+ ).returns(stat)
155
+ stat.expects(:size).returns(size)
156
+
157
+ FileUtils.expects(:mv).never
158
+ File.expects(:open).never
159
+ FileUtils.expects(:rm_f).never
160
+
161
+ subject.truncate!(2)
162
+ end
163
+ end
164
+ end
165
+
166
+ context 'when log file is > max_bytes' do
167
+ before do
168
+ FileUtils.unstub(:mkdir_p)
169
+ FileUtils.unstub(:mv)
170
+ FileUtils.unstub(:rm)
171
+ FileUtils.unstub(:rm_f)
172
+ end
173
+
174
+ it 'should truncate the file, removing older lines' do
175
+ max_bytes = 5_000
176
+ line_len = 120
177
+ Dir.mktmpdir do |dir|
178
+ Backup::Config.update(:root_path => dir)
179
+ FileUtils.mkdir_p(Backup::Config.log_path)
180
+ log_file = File.join(Backup::Config.log_path, 'backup.log')
181
+
182
+ lineno = 0
183
+ over_max = (max_bytes * 1.25).to_i
184
+ File.open(log_file, 'w') do |f|
185
+ until f.pos > over_max
186
+ f.puts "#{ lineno += 1 }: ".ljust(line_len, 'x')
187
+ end
188
+ end
189
+ File.stat(log_file).size.
190
+ should be_within(line_len + 2).of(over_max)
191
+
192
+ subject.truncate!(max_bytes)
193
+
194
+ File.stat(log_file).size.
195
+ should be_within(line_len + 2).of(max_bytes)
196
+
197
+ File.readlines(log_file).last.chomp.
198
+ should == "#{ lineno }: ".ljust(line_len, 'x')
199
+
200
+ File.exist?(log_file + '~').should be_false
201
+ end
202
+ end
203
+ end
204
+
205
+ end # describe '#truncate!'
206
+
148
207
  describe '#loggify' do
149
208
 
150
209
  it 'returns an array of strings split on newline separators' do
@@ -224,7 +283,7 @@ describe Backup::Logger do
224
283
  end
225
284
 
226
285
  it 'returns nil if quiet? is true' do
227
- subject.send(:const_set, :QUIET, true)
286
+ subject.quiet = true
228
287
  subject.expects(:puts).never
229
288
  subject.send(:to_console, 'a string')
230
289
  end
@@ -277,10 +336,17 @@ describe Backup::Logger do
277
336
 
278
337
  end # color methods
279
338
 
280
- describe '#quiet?' do
281
- it 'reports if the QUIET constant has been set' do
282
- expect { subject.send(:const_set, :QUIET, true) }.to
283
- change{ subject.send(:quiet?) }.from(false).to(true)
339
+ describe '#quiet' do
340
+ it 'should be nil by default' do
341
+ # of course, 'before' is setting it to nil ;)
342
+ subject.quiet.should be_nil
343
+ end
344
+
345
+ it 'can be set true/false' do
346
+ subject.quiet = true
347
+ subject.quiet.should be_true
348
+ subject.quiet = false
349
+ subject.quiet.should be_false
284
350
  end
285
351
  end
286
352
 
@@ -2,308 +2,432 @@
2
2
 
3
3
  require File.expand_path('../spec_helper.rb', __FILE__)
4
4
 
5
- describe Backup::Model do
5
+ describe 'Backup::Model' do
6
+ let(:model) { Backup::Model.new(:test_trigger, 'test label') }
7
+ let(:s) { sequence '' }
6
8
 
7
9
  before do
8
- # stub out the creation of an archive, for this spec's purpose
9
- Backup::Archive.stubs(:new).returns(true)
10
+ Backup::Model.all.clear
11
+ end
10
12
 
11
- # create mockup classes for testing the behavior of Backup::Model
12
- class Backup::Database::TestDatabase
13
- def initialize(&block); end
14
- end
15
- class Backup::Storage::TestStorage
16
- def initialize(storage_id = nil, &block); end
17
- end
18
- class Backup::Compressor::TestGzip
19
- def initialize(&block); end
20
- end
21
- class Backup::Compressor::TestSevenZip
22
- def initialize(&block); end
23
- end
24
- class Backup::Encryptor::TestOpenSSL
25
- def initialize(&block); end
26
- end
27
- class Backup::Encryptor::TestGPG
28
- def initialize(&block); end
13
+ describe '.all' do
14
+ it 'should be an empty array by default' do
15
+ Backup::Model.all.should == []
29
16
  end
30
- class Backup::Notifier::TestMail
31
- def initialize(&block); end
32
- end
33
-
34
17
  end
35
18
 
36
- let(:model) { Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') {} }
19
+ describe 'finder methods' do
20
+ before do
21
+ [:a, :b, :c, :b, :d].each_with_index do |sym, i|
22
+ Backup::Model.new("trigger_#{sym}", "label#{i}")
23
+ end
24
+ end
37
25
 
38
- it do
39
- Backup::Model.extension.should == 'tar'
40
- end
26
+ describe '.find' do
27
+ it 'should return the first matching model' do
28
+ Backup::Model.find('trigger_b').label.should == 'label1'
29
+ end
41
30
 
42
- it do
43
- Backup::Model.new('foo', 'bar') {}
44
- Backup::Model.extension.should == 'tar'
45
- end
31
+ it 'should accept symbols' do
32
+ Backup::Model.find(:trigger_b).label.should == 'label1'
33
+ end
46
34
 
47
- before do
48
- Backup::Model.extension = 'tar'
49
- end
35
+ it 'should raise an error if trigger is not found' do
36
+ expect do
37
+ Backup::Model.find(:f)
38
+ end.to raise_error(
39
+ Backup::Errors::Model::MissingTriggerError,
40
+ "Model::MissingTriggerError: Could not find trigger 'f'."
41
+ )
42
+ end
43
+ end
50
44
 
51
- it do
52
- Backup::Model.new('blah', 'blah') {}
53
- Backup::Model.extension.should == 'tar'
54
- end
45
+ describe '.find_matching' do
46
+ it 'should find all triggers matching a wildcard' do
47
+ Backup::Model.find_matching('tri*_b').count.should be(2)
48
+ Backup::Model.find_matching('trigg*').count.should be(5)
49
+ end
55
50
 
56
- it do
57
- Backup::Model.new('blah', 'blah') {}
58
- Backup::Model.file.should == "#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") }"
59
- end
51
+ it 'should return an empty array if no matches are found' do
52
+ Backup::Model.find_matching('foo*').should == []
53
+ end
54
+ end
60
55
 
61
- it do
62
- Backup::Model.new('blah', 'blah') {}
63
- File.basename(Backup::Model.file).should == "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar"
64
- end
56
+ end # describe 'finder methods'
65
57
 
66
- it do
67
- Backup::Model.new('blah', 'blah') {}
68
- Backup::Model.tmp_path.should == File.join(Backup::TMP_PATH, Backup::TRIGGER)
69
- end
58
+ describe '#initialize' do
70
59
 
71
- it 'should create a new model with a trigger and label' do
72
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') {}
73
- model.trigger.should == 'mysql-s3'
74
- model.label.should == 'MySQL S3 Backup for MyApp'
75
- end
60
+ it 'should convert trigger to a string' do
61
+ Backup::Model.new(:foo, :bar).trigger.should == 'foo'
62
+ end
76
63
 
77
- it 'should have the time logged in the object' do
78
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') {}
79
- model.time.should == Backup::TIME
80
- end
64
+ it 'should convert label to a string' do
65
+ Backup::Model.new(:foo, :bar).label.should == 'bar'
66
+ end
81
67
 
82
- describe '#extension' do
83
- it 'should start out with just .tar before compression occurs' do
84
- Backup::Model.extension.should == 'tar'
68
+ it 'should set all procedure variables to an empty array' do
69
+ model.send(:procedure_instance_variables).each do |var|
70
+ model.instance_variable_get(var).should == []
71
+ end
85
72
  end
86
- end
87
73
 
88
- describe 'databases' do
89
- it 'should add the mysql adapter to the array of databases to invoke' do
90
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
91
- database('TestDatabase')
74
+ it 'should accept and instance_eval a block' do
75
+ block = lambda {|model| throw(:instance, model) }
76
+ caught = catch(:instance) do
77
+ Backup::Model.new('gotcha', '', &block)
92
78
  end
79
+ caught.trigger.should == 'gotcha'
80
+ end
93
81
 
94
- model.databases.count.should == 1
82
+ it 'should add itself to Model.all' do
83
+ Backup::Model.all.should == [model]
95
84
  end
96
85
 
97
- it 'should add 2 mysql adapters to the array of adapters to invoke' do
98
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
99
- database('TestDatabase')
100
- database('TestDatabase')
86
+ end # describe '#initialize'
87
+
88
+ describe 'DSL Methods' do
89
+
90
+ module Fake
91
+ module NoArg
92
+ class Base
93
+ attr_accessor :block_arg
94
+ def initialize(&block)
95
+ instance_eval(&block) if block_given?
96
+ end
97
+ end
101
98
  end
99
+ module OneArg
100
+ class Base
101
+ attr_accessor :arg1, :block_arg
102
+ def initialize(arg1, &block)
103
+ @arg1 = arg1
104
+ instance_eval(&block) if block_given?
105
+ end
106
+ end
107
+ end
108
+ module TwoArgs
109
+ class Base
110
+ attr_accessor :arg1, :arg2, :block_arg
111
+ def initialize(arg1, arg2, &block)
112
+ @arg1 = arg1
113
+ @arg2 = arg2
114
+ instance_eval(&block) if block_given?
115
+ end
116
+ end
117
+ end
118
+ end
102
119
 
103
- model.databases.count.should == 2
120
+ # Set +const+ to +replacement+ for the calling block
121
+ def using_fake(const, replacement)
122
+ orig = Backup.const_get(const)
123
+ Backup.send(:remove_const, const)
124
+ Backup.const_set(const, replacement)
125
+ yield
126
+ Backup.send(:remove_const, const)
127
+ Backup.const_set(const, orig)
104
128
  end
105
- end
106
129
 
107
- describe 'storages' do
108
- it 'should add a storage to the array of storages to use' do
109
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
110
- store_with('TestStorage')
130
+ describe '#archive' do
131
+ it 'should add archives' do
132
+ using_fake('Archive', Fake::TwoArgs::Base) do
133
+ model.archive('foo') {|a| a.block_arg = :foo }
134
+ model.archive('bar') {|a| a.block_arg = :bar }
135
+ model.archives.count.should == 2
136
+ a1, a2 = model.archives
137
+ a1.arg1.should be(model)
138
+ a1.arg2.should == 'foo'
139
+ a1.block_arg.should == :foo
140
+ a2.arg1.should be(model)
141
+ a2.arg2.should == 'bar'
142
+ a2.block_arg.should == :bar
143
+ end
111
144
  end
112
-
113
- model.storages.count.should == 1
114
145
  end
115
146
 
116
- it 'should add a storage to the array of storages to use' do
117
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
118
- store_with('TestStorage')
119
- store_with('TestStorage')
147
+ describe '#database' do
148
+ it 'should add databases' do
149
+ using_fake('Database', Fake::OneArg) do
150
+ model.database('Base') {|a| a.block_arg = :foo }
151
+ model.database('Base') {|a| a.block_arg = :bar }
152
+ model.databases.count.should be(2)
153
+ d1, d2 = model.databases
154
+ d1.arg1.should be(model)
155
+ d1.block_arg.should == :foo
156
+ d2.arg1.should be(model)
157
+ d2.block_arg.should == :bar
158
+ end
120
159
  end
121
160
 
122
- model.storages.count.should == 2
161
+ it 'should accept a nested class name' do
162
+ using_fake('Database', Fake) do
163
+ model.database('OneArg::Base')
164
+ model.databases.first.should be_an_instance_of Fake::OneArg::Base
165
+ end
166
+ end
123
167
  end
124
168
 
125
- it 'should accept an optional storage_id parameter' do
126
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
127
- store_with('TestStorage', 'test storage_id')
169
+ describe '#store_with' do
170
+ it 'should add storages' do
171
+ using_fake('Storage', Fake::TwoArgs) do
172
+ model.store_with('Base', 'foo') {|a| a.block_arg = :foo }
173
+ model.store_with('Base', 'bar') {|a| a.block_arg = :bar }
174
+ model.storages.count.should be(2)
175
+ s1, s2 = model.storages
176
+ s1.arg1.should be(model)
177
+ s1.arg2.should == 'foo'
178
+ s1.block_arg.should == :foo
179
+ s2.arg1.should be(model)
180
+ s2.arg2.should == 'bar'
181
+ s2.block_arg.should == :bar
182
+ end
128
183
  end
129
184
 
130
- model.storages.count.should == 1
185
+ it 'should accept a nested class name' do
186
+ using_fake('Storage', Fake) do
187
+ model.store_with('TwoArgs::Base')
188
+ model.storages.first.should be_an_instance_of Fake::TwoArgs::Base
189
+ end
190
+ end
131
191
  end
132
192
 
133
- end
193
+ describe '#sync_with' do
194
+ it 'should add syncers' do
195
+ using_fake('Syncer', Fake::NoArg) do
196
+ model.sync_with('Base') {|a| a.block_arg = :foo }
197
+ model.sync_with('Base') {|a| a.block_arg = :bar }
198
+ model.syncers.count.should be(2)
199
+ s1, s2 = model.syncers
200
+ s1.block_arg.should == :foo
201
+ s2.block_arg.should == :bar
202
+ end
203
+ end
134
204
 
135
- describe 'archives' do
136
- it 'should add an archive to the array of archives to use' do
137
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
138
- archive('my_archive')
205
+ it 'should accept a nested class name' do
206
+ using_fake('Syncer', Fake) do
207
+ model.sync_with('NoArg::Base')
208
+ model.syncers.first.should be_an_instance_of Fake::NoArg::Base
209
+ end
139
210
  end
140
211
 
141
- model.archives.count.should == 1
212
+ it 'should warn user of change from RSync to RSync::Local' do
213
+ Backup::Logger.expects(:warn)
214
+ model.sync_with('Backup::Config::RSync')
215
+ model.syncers.first.should be_an_instance_of Backup::Syncer::RSync::Local
216
+ end
142
217
  end
143
218
 
144
- it 'should add a storage to the array of storages to use' do
145
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
146
- archive('TestStorage')
147
- archive('TestStorage')
219
+ describe '#notify_by' do
220
+ it 'should add notifiers' do
221
+ using_fake('Notifier', Fake::OneArg) do
222
+ model.notify_by('Base') {|a| a.block_arg = :foo }
223
+ model.notify_by('Base') {|a| a.block_arg = :bar }
224
+ model.notifiers.count.should be(2)
225
+ n1, n2 = model.notifiers
226
+ n1.arg1.should be(model)
227
+ n1.block_arg.should == :foo
228
+ n2.arg1.should be(model)
229
+ n2.block_arg.should == :bar
230
+ end
148
231
  end
149
232
 
150
- model.archives.count.should == 2
233
+ it 'should accept a nested class name' do
234
+ using_fake('Notifier', Fake) do
235
+ model.notify_by('OneArg::Base')
236
+ model.notifiers.first.should be_an_instance_of Fake::OneArg::Base
237
+ end
238
+ end
151
239
  end
152
- end
153
240
 
154
- describe '#compress_with' do
155
- it 'should add a compressor to the array of compressors to use' do
156
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
157
- compress_with('TestGzip')
241
+ describe '#encrypt_with' do
242
+ it 'should add an encryptor' do
243
+ using_fake('Encryptor', Fake::NoArg) do
244
+ model.encrypt_with('Base') {|a| a.block_arg = :foo }
245
+ model.encryptor.should be_an_instance_of Fake::NoArg::Base
246
+ model.encryptor.block_arg.should == :foo
247
+ end
158
248
  end
159
249
 
160
- model.compressors.count.should == 1
250
+ it 'should accept a nested class name' do
251
+ using_fake('Encryptor', Fake) do
252
+ model.encrypt_with('NoArg::Base')
253
+ model.encryptor.should be_an_instance_of Fake::NoArg::Base
254
+ end
255
+ end
161
256
  end
162
257
 
163
- it 'should add a compressor to the array of compressors to use' do
164
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
165
- compress_with('TestGzip')
166
- compress_with('TestSevenZip')
258
+ describe '#compress_with' do
259
+ it 'should add a compressor' do
260
+ using_fake('Compressor', Fake::NoArg) do
261
+ model.compress_with('Base') {|a| a.block_arg = :foo }
262
+ model.compressor.should be_an_instance_of Fake::NoArg::Base
263
+ model.compressor.block_arg.should == :foo
264
+ end
167
265
  end
168
266
 
169
- model.compressors.count.should == 2
267
+ it 'should accept a nested class name' do
268
+ using_fake('Compressor', Fake) do
269
+ model.compress_with('NoArg::Base')
270
+ model.compressor.should be_an_instance_of Fake::NoArg::Base
271
+ end
272
+ end
170
273
  end
171
- end
172
274
 
173
- describe '#encrypt_with' do
174
- it 'should add a encryptor to the array of encryptors to use' do
175
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
176
- encrypt_with('TestOpenSSL')
275
+ describe '#split_into_chunks_of' do
276
+ it 'should add a splitter' do
277
+ using_fake('Splitter', Fake::TwoArgs::Base) do
278
+ model.split_into_chunks_of(123)
279
+ model.splitter.should be_an_instance_of Fake::TwoArgs::Base
280
+ model.splitter.arg1.should be(model)
281
+ model.splitter.arg2.should == 123
282
+ end
177
283
  end
178
284
 
179
- model.encryptors.count.should == 1
285
+ it 'should raise an error if chunk_size is not an Integer' do
286
+ expect do
287
+ model.split_into_chunks_of('345')
288
+ end.to raise_error {|err|
289
+ err.should be_an_instance_of Backup::Errors::Model::ConfigurationError
290
+ err.message.should match(/must be an Integer/)
291
+ }
292
+ end
180
293
  end
181
294
 
182
- it 'should add a encryptor to the array of encryptors to use' do
183
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
184
- encrypt_with('TestOpenSSL')
185
- encrypt_with('TestGPG')
186
- end
295
+ end # describe 'DSL Methods'
296
+
297
+ describe '#prepare!' do
298
+ it 'should prepare for the backup' do
299
+ FileUtils.expects(:mkdir_p).with(
300
+ File.join(Backup::Config.data_path, 'test_trigger')
301
+ )
302
+ Backup::Cleaner.expects(:prepare).with(model)
187
303
 
188
- model.encryptors.count.should == 2
304
+ model.prepare!
189
305
  end
190
306
  end
191
307
 
192
- describe '#notify_by' do
193
- it 'should add a notifier to the array of notifiers to use' do
194
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
195
- notify_by('TestMail')
196
- end
197
-
198
- model.notifiers.count.should == 1
308
+ describe '#perform!' do
309
+ let(:procedure_a) { lambda {} }
310
+ let(:procedure_b) { mock }
311
+ let(:procedure_c) { mock }
312
+ let(:procedure_d) { lambda {} }
313
+ let(:procedure_e) { lambda {} }
314
+ let(:procedure_f) { mock }
315
+ let(:procedures) do
316
+ [ procedure_a, [procedure_b, procedure_c],
317
+ procedure_d, procedure_e, [procedure_f] ]
318
+ end
319
+ let(:syncer_a) { mock }
320
+ let(:syncer_b) { mock }
321
+ let(:syncers) { [syncer_a, syncer_b] }
322
+ let(:notifier_a) { mock }
323
+ let(:notifier_b) { mock }
324
+ let(:notifiers) { [notifier_a, notifier_b] }
325
+
326
+ it 'should set the @time and @started_at variables' do
327
+ Timecop.freeze(Time.now)
328
+ started_at = Time.now
329
+ time = started_at.strftime("%Y.%m.%d.%H.%M.%S")
330
+
331
+ model.perform!
332
+ model.time.should == time
333
+ model.instance_variable_get(:@started_at).should == started_at
199
334
  end
200
335
 
201
- it 'should add a notifier to the array of notifiers to use' do
202
- model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
203
- notify_by('TestMail')
204
- notify_by('TestMail')
336
+ context 'when no errors occur' do
337
+ before do
338
+ model.expects(:procedures).returns(procedures)
339
+ model.expects(:syncers).returns(syncers)
340
+ model.expects(:notifiers).returns(notifiers)
205
341
  end
206
342
 
207
- model.notifiers.count.should == 2
208
- end
209
- end
343
+ context 'when databases are configured' do
344
+ before do
345
+ model.instance_variable_set(:@databases, [true])
346
+ end
210
347
 
211
- describe '#package!' do
212
- let(:packager) { Backup::Packager.new(model) }
348
+ it 'should perform all procedures' do
349
+ model.expects(:log!).in_sequence(s).with(:started)
213
350
 
214
- before do
215
- [:utility, :run].each { |method| model.stubs(method) }
216
- end
351
+ procedure_a.expects(:call).in_sequence(s)
352
+ procedure_b.expects(:perform!).in_sequence(s)
353
+ procedure_c.expects(:perform!).in_sequence(s)
354
+ procedure_d.expects(:call).in_sequence(s)
355
+ procedure_e.expects(:call).in_sequence(s)
356
+ procedure_f.expects(:perform!).in_sequence(s)
217
357
 
218
- it 'should package the folder' do
219
- packager.expects(:utility).with(:tar).returns(:tar)
220
- packager.expects(:run).with(%|tar -c -f '#{ File.join( Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar" ) }' -C '#{ Backup::TMP_PATH }' '#{ Backup::TRIGGER }'|)
221
- Backup::Logger.expects(:message).with("Backup::Packager started packaging the backup files.")
222
- packager.package!
223
- end
224
- end
358
+ syncer_a.expects(:perform!).in_sequence(s)
359
+ syncer_b.expects(:perform!).in_sequence(s)
225
360
 
226
- describe '#clean!' do
227
- let(:cleaner) { Backup::Cleaner.new(model) }
361
+ notifier_a.expects(:perform!).in_sequence(s)
362
+ notifier_b.expects(:perform!).in_sequence(s)
228
363
 
229
- before do
230
- [:utility, :run, :rm].each { |method| model.stubs(method) }
231
- end
364
+ model.expects(:log!).in_sequence(s).with(:finished)
232
365
 
233
- context 'when the backup archive is not chunked' do
234
- it 'should remove the temporary files and folders that were created' do
235
- cleaner.expects(:utility).with(:rm).returns(:rm)
236
- Backup::Model.chunk_suffixes = []
237
- cleaner.expects(:run).with "rm -rf '#{ File.join(Backup::TMP_PATH, Backup::TRIGGER) }' " +
238
- "'#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") }'"
239
- cleaner.clean!
366
+ model.perform!
367
+ end
240
368
  end
241
- end
242
369
 
243
- context 'when the backup archive is chunked' do
244
- it 'should remove the temporary files and folders that were created' do
245
- cleaner.expects(:utility).with(:rm).returns(:rm)
246
- Backup::Model.chunk_suffixes = ["aa", "ab", "ac"]
247
- cleaner.expects(:run).with "rm -rf '#{ File.join(Backup::TMP_PATH, Backup::TRIGGER) }' " +
248
- "'#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar") }' " +
249
- "'#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar-aa") }' " +
250
- "'#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar-ab") }' " +
251
- "'#{ File.join(Backup::TMP_PATH, "#{ Backup::TIME }.#{ Backup::TRIGGER }.tar-ac") }'"
252
- cleaner.clean!
253
- end
254
- end
370
+ context 'when archives are configured' do
371
+ before do
372
+ model.instance_variable_set(:@archives, [true])
373
+ end
255
374
 
256
- it do
257
- Backup::Logger.expects(:message).with("Backup::Cleaner started cleaning up the temporary files.")
258
- model.send(:clean!)
259
- end
260
- end
375
+ it 'should perform all procedures' do
376
+ model.expects(:log!).in_sequence(s).with(:started)
261
377
 
262
- describe '#split_into_chunks_of' do
263
- it do
264
- model.should respond_to(:split_into_chunks_of)
265
- end
378
+ procedure_a.expects(:call).in_sequence(s)
379
+ procedure_b.expects(:perform!).in_sequence(s)
380
+ procedure_c.expects(:perform!).in_sequence(s)
381
+ procedure_d.expects(:call).in_sequence(s)
382
+ procedure_e.expects(:call).in_sequence(s)
383
+ procedure_f.expects(:perform!).in_sequence(s)
266
384
 
267
- it do
268
- model.split_into_chunks_of(500)
269
- model.chunk_size.should == 500
270
- end
385
+ syncer_a.expects(:perform!).in_sequence(s)
386
+ syncer_b.expects(:perform!).in_sequence(s)
271
387
 
272
- it do
273
- model.chunk_size.should == nil
274
- end
275
- end
388
+ notifier_a.expects(:perform!).in_sequence(s)
389
+ notifier_b.expects(:perform!).in_sequence(s)
276
390
 
277
- describe '#perform!' do
391
+ model.expects(:log!).in_sequence(s).with(:finished)
392
+
393
+ model.perform!
394
+ end
395
+ end
396
+
397
+ end # context 'when no errors occur'
278
398
 
279
399
  # for the purposes of testing the error handling, we're just going to
280
400
  # stub the first thing this method calls and raise an error
281
- describe 'when errors occur' do
282
- let(:model) { Backup::Model.new('foo', 'foo') {} }
401
+ context 'when errors occur' do
402
+ let(:error_a) { mock }
403
+ let(:error_b) { mock }
404
+ let(:notifier) { mock }
283
405
 
284
406
  before do
285
- # method ensures that #clean! is always run before exiting
286
- model.expects(:clean!)
407
+ error_a.stubs(:backtrace).returns(['many', 'backtrace', 'lines'])
287
408
  end
288
409
 
289
410
  it 'logs, notifies and continues if a StandardError is rescued' do
290
- model.stubs(:databases).raises(StandardError, 'non-fatal error')
291
-
292
- Backup::Logger.expects(:error).twice
293
- Backup::Logger.expects(:message).once
411
+ Time.stubs(:now).raises(StandardError, 'non-fatal error')
294
412
 
295
- Backup::Errors::ModelError.expects(:wrap).with do |err, msg|
413
+ Backup::Errors::ModelError.expects(:wrap).in_sequence(s).with do |err, msg|
296
414
  err.message.should == 'non-fatal error'
297
- msg.should match(/Backup for foo \(foo\) Failed!/)
298
- end
415
+ msg.should match(/Backup for test label \(test_trigger\) Failed!/)
416
+ end.returns(error_a)
417
+ Backup::Logger.expects(:error).in_sequence(s).with(error_a)
418
+ Backup::Logger.expects(:error).in_sequence(s).with(
419
+ "\nBacktrace:\n\s\smany\n\s\sbacktrace\n\s\slines\n\n"
420
+ )
421
+
422
+ Backup::Cleaner.expects(:warnings).in_sequence(s).with(model)
299
423
 
300
- Backup::Errors::ModelError.expects(:new).with do |msg|
424
+ Backup::Errors::ModelError.expects(:new).in_sequence(s).with do |msg|
301
425
  msg.should match(/Backup will now attempt to continue/)
302
- end
426
+ end.returns(error_b)
427
+ Backup::Logger.expects(:message).in_sequence(s).with(error_b)
303
428
 
304
429
  # notifiers called, but any Exception is ignored
305
- notifier = mock
306
- notifier.expects(:perform!).raises(Exception)
430
+ notifier.expects(:perform!).with(true).raises(Exception)
307
431
  model.expects(:notifiers).returns([notifier])
308
432
 
309
433
  # returns to allow next trigger to run
@@ -311,31 +435,215 @@ describe Backup::Model do
311
435
  end
312
436
 
313
437
  it 'logs, notifies and exits if an Exception is rescued' do
314
- model.stubs(:databases).raises(Exception, 'fatal error')
438
+ Time.stubs(:now).raises(Exception, 'fatal error')
315
439
 
316
- Backup::Logger.expects(:error).times(3)
317
- Backup::Logger.expects(:message).never
318
-
319
- Backup::Errors::ModelError.expects(:wrap).with do |err, msg|
440
+ Backup::Errors::ModelError.expects(:wrap).in_sequence(s).with do |err, msg|
320
441
  err.message.should == 'fatal error'
321
- msg.should match(/Backup for foo \(foo\) Failed!/)
322
- end
442
+ msg.should match(/Backup for test label \(test_trigger\) Failed!/)
443
+ end.returns(error_a)
444
+ Backup::Logger.expects(:error).in_sequence(s).with(error_a)
445
+ Backup::Logger.expects(:error).in_sequence(s).with(
446
+ "\nBacktrace:\n\s\smany\n\s\sbacktrace\n\s\slines\n\n"
447
+ )
448
+
449
+ Backup::Cleaner.expects(:warnings).in_sequence(s).with(model)
323
450
 
324
- Backup::Errors::ModelError.expects(:new).with do |msg|
451
+ Backup::Errors::ModelError.expects(:new).in_sequence(s).with do |msg|
325
452
  msg.should match(/Backup will now exit/)
326
- end
453
+ end.returns(error_b)
454
+ Backup::Logger.expects(:error).in_sequence(s).with(error_b)
327
455
 
328
456
  expect do
329
457
  # notifiers called, but any Exception is ignored
330
458
  notifier = mock
331
- notifier.expects(:perform!).raises(Exception)
459
+ notifier.expects(:perform!).with(true).raises(Exception)
332
460
  model.expects(:notifiers).returns([notifier])
333
461
  end.not_to raise_error
334
462
 
335
- expect { model.perform! }.to raise_error(SystemExit)
463
+ expect do
464
+ model.perform!
465
+ end.to raise_error(SystemExit) {|exit| exit.status.should be(1) }
336
466
  end
337
467
 
338
468
  end # context 'when errors occur'
339
469
 
340
470
  end # describe '#perform!'
471
+
472
+ describe '#package!' do
473
+ it 'should package the backup' do
474
+ Backup::Packager.expects(:package!).in_sequence(s).with(model)
475
+ Backup::Cleaner.expects(:remove_packaging).in_sequence(s).with(model)
476
+
477
+ model.send(:package!)
478
+ model.package.should be_an_instance_of Backup::Package
479
+ end
480
+ end
481
+
482
+ describe '#clean' do
483
+ it 'should remove the final packaged files' do
484
+ package = mock
485
+ model.instance_variable_set(:@package, package)
486
+ Backup::Cleaner.expects(:remove_package).with(package)
487
+
488
+ model.send(:clean!)
489
+ end
490
+ end
491
+
492
+ describe '#procedures' do
493
+ it 'should return an array of specific, ordered procedures' do
494
+ model.stubs(:databases).returns(:databases)
495
+ model.stubs(:archives).returns(:archives)
496
+ model.stubs(:package!).returns(:package)
497
+ model.stubs(:storages).returns(:storages)
498
+ model.stubs(:clean!).returns(:clean)
499
+
500
+ one, two, three, four, five = model.send(:procedures)
501
+ one.should == :databases
502
+ two.should == :archives
503
+ three.call.should == :package
504
+ four.should == :storages
505
+ five.call.should == :clean
506
+ end
507
+ end
508
+
509
+ describe '#procedure_instance_variables' do
510
+ # these are all set to an empty Array in #initialize
511
+ it 'should return an array of Array holding instance variables' do
512
+ model.send(:procedure_instance_variables).should ==
513
+ [:@databases, :@archives, :@storages, :@notifiers, :@syncers]
514
+ end
515
+ end
516
+
517
+ describe '#get_class_from_scope' do
518
+
519
+ module Fake
520
+ module TestScope
521
+ class TestKlass; end
522
+ end
523
+ end
524
+ module TestScope
525
+ module TestKlass; end
526
+ end
527
+
528
+ context 'when name is given as a string' do
529
+ it 'should return the constant for the given scope and name' do
530
+ model.send(
531
+ :get_class_from_scope,
532
+ Fake,
533
+ 'TestScope'
534
+ ).should == Fake::TestScope
535
+ end
536
+
537
+ it 'should accept a nested class name' do
538
+ model.send(
539
+ :get_class_from_scope,
540
+ Fake,
541
+ 'TestScope::TestKlass'
542
+ ).should == Fake::TestScope::TestKlass
543
+ end
544
+ end
545
+
546
+ context 'when name is given as a module' do
547
+ it 'should return the constant for the given scope and name' do
548
+ model.send(
549
+ :get_class_from_scope,
550
+ Fake,
551
+ TestScope
552
+ ).should == Fake::TestScope
553
+ end
554
+
555
+ it 'should accept a nested class name' do
556
+ model.send(
557
+ :get_class_from_scope,
558
+ Fake,
559
+ TestScope::TestKlass
560
+ ).should == Fake::TestScope::TestKlass
561
+ end
562
+ end
563
+
564
+ context 'when name is given as a module defined under Backup::Config' do
565
+ # this is necessary since the specs in spec/config_spec.rb
566
+ # remove all the constants from Backup::Config as part of those tests.
567
+ before(:all) do
568
+ module Backup::Config
569
+ module TestScope
570
+ module TestKlass; end
571
+ end
572
+ end
573
+ end
574
+
575
+ it 'should return the constant for the given scope and name' do
576
+ model.send(
577
+ :get_class_from_scope,
578
+ Fake,
579
+ Backup::Config::TestScope
580
+ ).should == Fake::TestScope
581
+ end
582
+
583
+ it 'should accept a nested class name' do
584
+ model.send(
585
+ :get_class_from_scope,
586
+ Fake,
587
+ Backup::Config::TestScope::TestKlass
588
+ ).should == Fake::TestScope::TestKlass
589
+ end
590
+ end
591
+
592
+ end # describe '#get_class_from_scope'
593
+
594
+ describe '#log!' do
595
+ context 'when action is :started' do
596
+ it 'should log that the backup has started with the version' do
597
+ Backup::Logger.expects(:message).with(
598
+ "Performing Backup for 'test label (test_trigger)'!\n" +
599
+ "[ backup #{ Backup::Version.current } : #{ RUBY_DESCRIPTION } ]"
600
+ )
601
+ model.send(:log!, :started)
602
+ end
603
+ end
604
+
605
+ context 'when action is :finished' do
606
+ before { model.expects(:elapsed_time).returns('01:02:03') }
607
+ context 'when warnings were issued' do
608
+ before { Backup::Logger.expects(:has_warnings?).returns(true) }
609
+ it 'should log a warning that the backup has finished with warnings' do
610
+ Backup::Logger.expects(:warn).with(
611
+ "Backup for 'test label (test_trigger)' " +
612
+ "Completed Successfully (with Warnings) in 01:02:03"
613
+ )
614
+ model.send(:log!, :finished)
615
+ end
616
+ end
617
+
618
+ context 'when no warnings were issued' do
619
+ it 'should log that the backup has finished with the elapsed time' do
620
+ Backup::Logger.expects(:message).with(
621
+ "Backup for 'test label (test_trigger)' " +
622
+ "Completed Successfully in 01:02:03"
623
+ )
624
+ model.send(:log!, :finished)
625
+ end
626
+ end
627
+ end
628
+ end # describe '#log!'
629
+
630
+ describe '#elapsed_time' do
631
+ it 'should return a string representing the elapsed time' do
632
+ Timecop.freeze(Time.now)
633
+ { 0 => '00:00:00', 1 => '00:00:01', 59 => '00:00:59',
634
+ 60 => '00:01:00', 61 => '00:01:01', 119 => '00:01:59',
635
+ 3540 => '00:59:00', 3541 => '00:59:01', 3599 => '00:59:59',
636
+ 3600 => '01:00:00', 3601 => '01:00:01', 3659 => '01:00:59',
637
+ 3660 => '01:01:00', 3661 => '01:01:01', 3719 => '01:01:59',
638
+ 7140 => '01:59:00', 7141 => '01:59:01', 7199 => '01:59:59',
639
+ 212400 => '59:00:00', 212401 => '59:00:01', 212459 => '59:00:59',
640
+ 212460 => '59:01:00', 212461 => '59:01:01', 212519 => '59:01:59',
641
+ 215940 => '59:59:00', 215941 => '59:59:01', 215999 => '59:59:59'
642
+ }.each do |duration, expected|
643
+ model.instance_variable_set(:@started_at, Time.now - duration)
644
+ model.send(:elapsed_time).should == expected
645
+ end
646
+ end
647
+ end
648
+
341
649
  end