backup-agoddard 3.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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