backup-agoddard 3.0.27

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of backup-agoddard might be problematic. Click here for more details.

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