backup 3.0.23 → 3.0.24

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 (197) hide show
  1. data/Gemfile.lock +42 -45
  2. data/Guardfile +7 -4
  3. data/README.md +10 -7
  4. data/backup.gemspec +2 -2
  5. data/lib/backup.rb +27 -97
  6. data/lib/backup/archive.rb +14 -6
  7. data/lib/backup/cli/helpers.rb +52 -49
  8. data/lib/backup/cli/utility.rb +9 -1
  9. data/lib/backup/compressor/base.rb +10 -4
  10. data/lib/backup/compressor/bzip2.rb +22 -26
  11. data/lib/backup/compressor/custom.rb +53 -0
  12. data/lib/backup/compressor/gzip.rb +22 -23
  13. data/lib/backup/compressor/lzma.rb +15 -13
  14. data/lib/backup/compressor/pbzip2.rb +20 -17
  15. data/lib/backup/config.rb +6 -3
  16. data/lib/backup/configuration.rb +33 -0
  17. data/lib/backup/configuration/helpers.rb +114 -28
  18. data/lib/backup/configuration/store.rb +24 -0
  19. data/lib/backup/database/base.rb +0 -6
  20. data/lib/backup/database/mongodb.rb +27 -11
  21. data/lib/backup/database/mysql.rb +19 -14
  22. data/lib/backup/database/postgresql.rb +16 -11
  23. data/lib/backup/database/redis.rb +7 -11
  24. data/lib/backup/database/riak.rb +3 -6
  25. data/lib/backup/dependency.rb +5 -11
  26. data/lib/backup/model.rb +14 -5
  27. data/lib/backup/notifier/campfire.rb +3 -16
  28. data/lib/backup/notifier/hipchat.rb +1 -7
  29. data/lib/backup/notifier/mail.rb +1 -1
  30. data/lib/backup/packager.rb +29 -19
  31. data/lib/backup/pipeline.rb +110 -0
  32. data/lib/backup/storage/dropbox.rb +4 -7
  33. data/lib/backup/syncer/base.rb +8 -4
  34. data/lib/backup/syncer/cloud/base.rb +247 -0
  35. data/lib/backup/syncer/cloud/cloud_files.rb +78 -0
  36. data/lib/backup/syncer/cloud/s3.rb +68 -0
  37. data/lib/backup/syncer/rsync/base.rb +1 -4
  38. data/lib/backup/syncer/rsync/local.rb +9 -5
  39. data/lib/backup/syncer/rsync/pull.rb +1 -1
  40. data/lib/backup/syncer/rsync/push.rb +10 -5
  41. data/lib/backup/version.rb +1 -1
  42. data/spec-live/.gitignore +6 -0
  43. data/spec-live/README +7 -0
  44. data/spec-live/backups/config.rb +153 -0
  45. data/spec-live/backups/config.yml.template +43 -0
  46. data/spec-live/compressor/custom_spec.rb +30 -0
  47. data/spec-live/compressor/gzip_spec.rb +30 -0
  48. data/spec-live/notifier/mail_spec.rb +85 -0
  49. data/spec-live/spec_helper.rb +85 -0
  50. data/spec-live/storage/dropbox_spec.rb +151 -0
  51. data/spec-live/storage/local_spec.rb +83 -0
  52. data/spec-live/storage/scp_spec.rb +193 -0
  53. data/spec-live/syncer/cloud/s3_spec.rb +124 -0
  54. data/spec/archive_spec.rb +86 -31
  55. data/spec/cleaner_spec.rb +8 -0
  56. data/spec/cli/helpers_spec.rb +200 -75
  57. data/spec/cli/utility_spec.rb +11 -3
  58. data/spec/compressor/base_spec.rb +31 -10
  59. data/spec/compressor/bzip2_spec.rb +212 -57
  60. data/spec/compressor/custom_spec.rb +106 -0
  61. data/spec/compressor/gzip_spec.rb +212 -57
  62. data/spec/compressor/lzma_spec.rb +75 -35
  63. data/spec/compressor/pbzip2_spec.rb +93 -52
  64. data/spec/configuration/helpers_spec.rb +406 -0
  65. data/spec/configuration/store_spec.rb +39 -0
  66. data/spec/configuration_spec.rb +62 -0
  67. data/spec/database/base_spec.rb +19 -10
  68. data/spec/database/mongodb_spec.rb +195 -70
  69. data/spec/database/mysql_spec.rb +183 -64
  70. data/spec/database/postgresql_spec.rb +167 -53
  71. data/spec/database/redis_spec.rb +121 -46
  72. data/spec/database/riak_spec.rb +96 -27
  73. data/spec/dependency_spec.rb +2 -0
  74. data/spec/encryptor/base_spec.rb +10 -0
  75. data/spec/encryptor/gpg_spec.rb +29 -13
  76. data/spec/encryptor/open_ssl_spec.rb +40 -21
  77. data/spec/logger_spec.rb +4 -0
  78. data/spec/model_spec.rb +19 -2
  79. data/spec/notifier/base_spec.rb +32 -17
  80. data/spec/notifier/campfire_spec.rb +63 -45
  81. data/spec/notifier/hipchat_spec.rb +79 -56
  82. data/spec/notifier/mail_spec.rb +82 -46
  83. data/spec/notifier/prowl_spec.rb +53 -32
  84. data/spec/notifier/twitter_spec.rb +62 -41
  85. data/spec/packager_spec.rb +95 -36
  86. data/spec/pipeline_spec.rb +259 -0
  87. data/spec/spec_helper.rb +6 -5
  88. data/spec/storage/base_spec.rb +61 -41
  89. data/spec/storage/cloudfiles_spec.rb +69 -45
  90. data/spec/storage/dropbox_spec.rb +158 -36
  91. data/spec/storage/ftp_spec.rb +69 -45
  92. data/spec/storage/local_spec.rb +47 -23
  93. data/spec/storage/ninefold_spec.rb +55 -31
  94. data/spec/storage/rsync_spec.rb +67 -50
  95. data/spec/storage/s3_spec.rb +65 -41
  96. data/spec/storage/scp_spec.rb +65 -41
  97. data/spec/storage/sftp_spec.rb +65 -41
  98. data/spec/syncer/base_spec.rb +91 -4
  99. data/spec/syncer/cloud/base_spec.rb +511 -0
  100. data/spec/syncer/cloud/cloud_files_spec.rb +181 -0
  101. data/spec/syncer/cloud/s3_spec.rb +174 -0
  102. data/spec/syncer/rsync/base_spec.rb +46 -66
  103. data/spec/syncer/rsync/local_spec.rb +55 -26
  104. data/spec/syncer/rsync/pull_spec.rb +15 -4
  105. data/spec/syncer/rsync/push_spec.rb +59 -52
  106. data/templates/cli/utility/compressor/bzip2 +1 -4
  107. data/templates/cli/utility/compressor/custom +11 -0
  108. data/templates/cli/utility/compressor/gzip +1 -4
  109. data/templates/cli/utility/compressor/lzma +3 -0
  110. data/templates/cli/utility/compressor/pbzip2 +3 -0
  111. data/templates/cli/utility/database/mysql +4 -1
  112. data/templates/cli/utility/syncer/cloud_files +17 -19
  113. data/templates/cli/utility/syncer/s3 +18 -20
  114. metadata +38 -92
  115. data/lib/backup/configuration/base.rb +0 -15
  116. data/lib/backup/configuration/compressor/base.rb +0 -9
  117. data/lib/backup/configuration/compressor/bzip2.rb +0 -23
  118. data/lib/backup/configuration/compressor/gzip.rb +0 -23
  119. data/lib/backup/configuration/compressor/lzma.rb +0 -23
  120. data/lib/backup/configuration/compressor/pbzip2.rb +0 -28
  121. data/lib/backup/configuration/database/base.rb +0 -19
  122. data/lib/backup/configuration/database/mongodb.rb +0 -49
  123. data/lib/backup/configuration/database/mysql.rb +0 -42
  124. data/lib/backup/configuration/database/postgresql.rb +0 -41
  125. data/lib/backup/configuration/database/redis.rb +0 -39
  126. data/lib/backup/configuration/database/riak.rb +0 -29
  127. data/lib/backup/configuration/encryptor/base.rb +0 -9
  128. data/lib/backup/configuration/encryptor/gpg.rb +0 -17
  129. data/lib/backup/configuration/encryptor/open_ssl.rb +0 -32
  130. data/lib/backup/configuration/notifier/base.rb +0 -28
  131. data/lib/backup/configuration/notifier/campfire.rb +0 -25
  132. data/lib/backup/configuration/notifier/hipchat.rb +0 -41
  133. data/lib/backup/configuration/notifier/mail.rb +0 -112
  134. data/lib/backup/configuration/notifier/presently.rb +0 -25
  135. data/lib/backup/configuration/notifier/prowl.rb +0 -23
  136. data/lib/backup/configuration/notifier/twitter.rb +0 -21
  137. data/lib/backup/configuration/storage/base.rb +0 -18
  138. data/lib/backup/configuration/storage/cloudfiles.rb +0 -25
  139. data/lib/backup/configuration/storage/dropbox.rb +0 -58
  140. data/lib/backup/configuration/storage/ftp.rb +0 -29
  141. data/lib/backup/configuration/storage/local.rb +0 -17
  142. data/lib/backup/configuration/storage/ninefold.rb +0 -20
  143. data/lib/backup/configuration/storage/rsync.rb +0 -29
  144. data/lib/backup/configuration/storage/s3.rb +0 -25
  145. data/lib/backup/configuration/storage/scp.rb +0 -25
  146. data/lib/backup/configuration/storage/sftp.rb +0 -25
  147. data/lib/backup/configuration/syncer/base.rb +0 -10
  148. data/lib/backup/configuration/syncer/cloud.rb +0 -23
  149. data/lib/backup/configuration/syncer/cloud_files.rb +0 -30
  150. data/lib/backup/configuration/syncer/rsync/base.rb +0 -28
  151. data/lib/backup/configuration/syncer/rsync/local.rb +0 -11
  152. data/lib/backup/configuration/syncer/rsync/pull.rb +0 -11
  153. data/lib/backup/configuration/syncer/rsync/push.rb +0 -31
  154. data/lib/backup/configuration/syncer/s3.rb +0 -23
  155. data/lib/backup/notifier/presently.rb +0 -88
  156. data/lib/backup/syncer/cloud.rb +0 -187
  157. data/lib/backup/syncer/cloud_files.rb +0 -56
  158. data/lib/backup/syncer/s3.rb +0 -47
  159. data/spec/configuration/base_spec.rb +0 -35
  160. data/spec/configuration/compressor/bzip2_spec.rb +0 -29
  161. data/spec/configuration/compressor/gzip_spec.rb +0 -29
  162. data/spec/configuration/compressor/lzma_spec.rb +0 -29
  163. data/spec/configuration/compressor/pbzip2_spec.rb +0 -32
  164. data/spec/configuration/database/base_spec.rb +0 -17
  165. data/spec/configuration/database/mongodb_spec.rb +0 -56
  166. data/spec/configuration/database/mysql_spec.rb +0 -53
  167. data/spec/configuration/database/postgresql_spec.rb +0 -53
  168. data/spec/configuration/database/redis_spec.rb +0 -50
  169. data/spec/configuration/database/riak_spec.rb +0 -35
  170. data/spec/configuration/encryptor/gpg_spec.rb +0 -26
  171. data/spec/configuration/encryptor/open_ssl_spec.rb +0 -35
  172. data/spec/configuration/notifier/base_spec.rb +0 -32
  173. data/spec/configuration/notifier/campfire_spec.rb +0 -32
  174. data/spec/configuration/notifier/hipchat_spec.rb +0 -44
  175. data/spec/configuration/notifier/mail_spec.rb +0 -71
  176. data/spec/configuration/notifier/presently_spec.rb +0 -35
  177. data/spec/configuration/notifier/prowl_spec.rb +0 -29
  178. data/spec/configuration/notifier/twitter_spec.rb +0 -35
  179. data/spec/configuration/storage/cloudfiles_spec.rb +0 -41
  180. data/spec/configuration/storage/dropbox_spec.rb +0 -38
  181. data/spec/configuration/storage/ftp_spec.rb +0 -44
  182. data/spec/configuration/storage/local_spec.rb +0 -29
  183. data/spec/configuration/storage/ninefold_spec.rb +0 -32
  184. data/spec/configuration/storage/rsync_spec.rb +0 -41
  185. data/spec/configuration/storage/s3_spec.rb +0 -38
  186. data/spec/configuration/storage/scp_spec.rb +0 -41
  187. data/spec/configuration/storage/sftp_spec.rb +0 -41
  188. data/spec/configuration/syncer/cloud_files_spec.rb +0 -44
  189. data/spec/configuration/syncer/rsync/base_spec.rb +0 -33
  190. data/spec/configuration/syncer/rsync/local_spec.rb +0 -10
  191. data/spec/configuration/syncer/rsync/pull_spec.rb +0 -10
  192. data/spec/configuration/syncer/rsync/push_spec.rb +0 -43
  193. data/spec/configuration/syncer/s3_spec.rb +0 -38
  194. data/spec/notifier/presently_spec.rb +0 -181
  195. data/spec/syncer/cloud_files_spec.rb +0 -192
  196. data/spec/syncer/s3_spec.rb +0 -192
  197. data/templates/cli/utility/notifier/presently +0 -13
@@ -0,0 +1,511 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../../../spec_helper.rb', __FILE__)
3
+
4
+ describe 'Backup::Syncer::Cloud::Base' do
5
+ let(:syncer) { Backup::Syncer::Cloud::Base.new }
6
+ let(:s) { sequence '' }
7
+
8
+ it 'should be a subclass of Syncer::Base' do
9
+ Backup::Syncer::Cloud::Base.
10
+ superclass.should == Backup::Syncer::Base
11
+ end
12
+
13
+ it 'should establish a class constant for a Mutex' do
14
+ Backup::Syncer::Cloud::Base::MUTEX.should be_an_instance_of Mutex
15
+ end
16
+
17
+ describe '#initialize' do
18
+ after { Backup::Syncer::Cloud::Base.clear_defaults! }
19
+
20
+ it 'should load pre-configured defaults through Syncer::Base' do
21
+ Backup::Syncer::Cloud::Base.any_instance.expects(:load_defaults!)
22
+ syncer
23
+ end
24
+
25
+ context 'when no pre-configured defaults have been set' do
26
+ it 'should use default values if none are given' do
27
+ syncer.path.should == 'backups'
28
+ syncer.mirror.should == false
29
+ syncer.concurrency_type.should == false
30
+ syncer.concurrency_level.should == 2
31
+ end
32
+ end # context 'when no pre-configured defaults have been set'
33
+
34
+ context 'when pre-configured defaults have been set' do
35
+ before do
36
+ Backup::Syncer::Cloud::Base.defaults do |cloud|
37
+ cloud.concurrency_type = 'default_concurrency_type'
38
+ cloud.concurrency_level = 'default_concurrency_level'
39
+ end
40
+ end
41
+
42
+ it 'should use pre-configured defaults' do
43
+ syncer.path.should == 'backups'
44
+ syncer.mirror.should == false
45
+ syncer.concurrency_type.should == 'default_concurrency_type'
46
+ syncer.concurrency_level.should == 'default_concurrency_level'
47
+ end
48
+ end # context 'when pre-configured defaults have been set'
49
+ end # describe '#initialize'
50
+
51
+ describe '#perform' do
52
+ let(:sync_context) { mock }
53
+
54
+ before do
55
+ syncer.stubs(:repository_object).returns(:a_repository_object)
56
+
57
+ Backup::Logger.expects(:message).with(
58
+ 'Syncer::Cloud::Base started the syncing process:'
59
+ )
60
+ Backup::Logger.expects(:message).with(
61
+ 'Syncer::Cloud::Base Syncing Complete!'
62
+ )
63
+ end
64
+
65
+ it 'should sync each directory' do
66
+ syncer.directories do
67
+ add '/dir/one'
68
+ add '/dir/two'
69
+ end
70
+
71
+ Backup::Syncer::Cloud::Base::SyncContext.expects(:new).in_sequence(s).with(
72
+ '/dir/one', :a_repository_object, 'backups'
73
+ ).returns(sync_context)
74
+ sync_context.expects(:sync!).in_sequence(s).with(
75
+ false, false, 2
76
+ )
77
+ Backup::Syncer::Cloud::Base::SyncContext.expects(:new).in_sequence(s).with(
78
+ '/dir/two', :a_repository_object, 'backups'
79
+ ).returns(sync_context)
80
+ sync_context.expects(:sync!).in_sequence(s).with(
81
+ false, false, 2
82
+ )
83
+
84
+ syncer.perform!
85
+ end
86
+
87
+ it 'should ensure each directory path is expanded with no trailing slash' do
88
+ syncer.directories do
89
+ add '/dir/one/'
90
+ add 'dir/two'
91
+ end
92
+
93
+ Backup::Syncer::Cloud::Base::SyncContext.expects(:new).with(
94
+ '/dir/one', :a_repository_object, 'backups'
95
+ ).returns(sync_context)
96
+
97
+ Backup::Syncer::Cloud::Base::SyncContext.expects(:new).with(
98
+ File.expand_path('dir/two'), :a_repository_object, 'backups'
99
+ ).returns(sync_context)
100
+
101
+ sync_context.stubs(:sync!)
102
+
103
+ syncer.perform!
104
+ end
105
+ end # describe '#perform'
106
+
107
+ describe 'Cloud::Base::SyncContext' do
108
+ let(:bucket) { mock }
109
+ let(:sync_context) do
110
+ Backup::Syncer::Cloud::Base::SyncContext.new(
111
+ '/dir/to/sync', bucket, 'backups'
112
+ )
113
+ end
114
+
115
+ describe '#initialize' do
116
+ it 'should set variables' do
117
+ sync_context.directory.should == '/dir/to/sync'
118
+ sync_context.bucket.should == bucket
119
+ sync_context.path.should == 'backups'
120
+ sync_context.remote_base.should == 'backups/sync'
121
+ end
122
+ end
123
+
124
+ describe '#sync!' do
125
+ let(:all_files_array) { mock }
126
+
127
+ before do
128
+ sync_context.stubs(:all_file_names).returns(all_files_array)
129
+ end
130
+
131
+ context 'when concurrency_type is set to `false`' do
132
+ it 'syncs files without concurrency' do
133
+ all_files_array.expects(:each).in_sequence(s).
134
+ multiple_yields('foo.file', 'foo_dir/foo.file')
135
+
136
+ sync_context.expects(:sync_file).in_sequence(s).
137
+ with('foo.file', :mirror)
138
+ sync_context.expects(:sync_file).in_sequence(s).
139
+ with('foo_dir/foo.file', :mirror)
140
+
141
+ sync_context.sync!(:mirror, false, :foo)
142
+ end
143
+ end
144
+
145
+ context 'when concurrency_type is set to `:threads`' do
146
+ it 'uses `concurrency_level` number of threads for concurrency' do
147
+ Parallel.expects(:each).in_sequence(s).with(
148
+ all_files_array, :in_threads => :num_of_threads
149
+ ).multiple_yields('foo.file', 'foo_dir/foo.file')
150
+
151
+ sync_context.expects(:sync_file).in_sequence(s).
152
+ with('foo.file', :mirror)
153
+ sync_context.expects(:sync_file).in_sequence(s).
154
+ with('foo_dir/foo.file', :mirror)
155
+
156
+ sync_context.sync!(:mirror, :threads, :num_of_threads)
157
+ end
158
+ end
159
+
160
+ context 'when concurrency_type is set to `:processes`' do
161
+ it 'uses `concurrency_level` number of processes for concurrency' do
162
+ Parallel.expects(:each).in_sequence(s).with(
163
+ all_files_array, :in_processes => :num_of_processes
164
+ ).multiple_yields('foo.file', 'foo_dir/foo.file')
165
+
166
+ sync_context.expects(:sync_file).in_sequence(s).
167
+ with('foo.file', :mirror)
168
+ sync_context.expects(:sync_file).in_sequence(s).
169
+ with('foo_dir/foo.file', :mirror)
170
+
171
+ sync_context.sync!(:mirror, :processes, :num_of_processes)
172
+ end
173
+ end
174
+
175
+ context 'when concurrency_type setting is invalid' do
176
+ it 'should raise an error' do
177
+ expect do
178
+ sync_context.sync!(:foo, 'unknown type', :foo)
179
+ end.to raise_error(
180
+ Backup::Errors::Syncer::Cloud::ConfigurationError,
181
+ 'Syncer::Cloud::ConfigurationError: ' +
182
+ "Unknown concurrency_type setting: \"unknown type\""
183
+ )
184
+ end
185
+ end
186
+ end # describe '#sync!'
187
+
188
+ describe '#all_file_names' do
189
+ let(:local_files_hash) do
190
+ { 'file_b' => :foo, 'file_a' => :foo, 'dir_a/file_b' => :foo }
191
+ end
192
+ let(:remote_files_hash) do
193
+ { 'file_c' => :foo, 'file_a' => :foo, 'dir_a/file_a' => :foo }
194
+ end
195
+ let(:local_remote_union_array) do
196
+ ['dir_a/file_a', 'dir_a/file_b', 'file_a', 'file_b', 'file_c']
197
+ end
198
+
199
+ it 'returns and caches a sorted union of local and remote file names' do
200
+ sync_context.expects(:local_files).once.returns(local_files_hash)
201
+ sync_context.expects(:remote_files).once.returns(remote_files_hash)
202
+
203
+ sync_context.send(:all_file_names).should == local_remote_union_array
204
+ sync_context.instance_variable_get(:@all_file_names).
205
+ should == local_remote_union_array
206
+ sync_context.send(:all_file_names).should == local_remote_union_array
207
+ end
208
+ end # describe '#all_file_names'
209
+
210
+ describe '#local_files' do
211
+ let(:local_file_class) { Backup::Syncer::Cloud::Base::LocalFile }
212
+ let(:local_hashes_data) { "line1\nline2\nbad\xFFline\nline3" }
213
+
214
+ let(:local_file_a) { stub(:relative_path => 'file_a') }
215
+ let(:local_file_b) { stub(:relative_path => 'file_b') }
216
+ let(:local_file_c) { stub(:relative_path => 'file_c') }
217
+ let(:local_files_hash) do
218
+ { 'file_a' => local_file_a,
219
+ 'file_b' => local_file_b,
220
+ 'file_c' => local_file_c }
221
+ end
222
+
223
+ it 'should return and caches a hash of LocalFile objects' do
224
+ sync_context.expects(:local_hashes).once.returns(local_hashes_data)
225
+
226
+ local_file_class.expects(:new).once.with('/dir/to/sync', "line1\n").
227
+ returns(local_file_a)
228
+ local_file_class.expects(:new).once.with('/dir/to/sync', "line2\n").
229
+ returns(local_file_b)
230
+ local_file_class.expects(:new).once.with('/dir/to/sync', "bad\xFFline\n").
231
+ returns(nil)
232
+ local_file_class.expects(:new).once.with('/dir/to/sync', "line3").
233
+ returns(local_file_c)
234
+
235
+ sync_context.send(:local_files).should == local_files_hash
236
+ sync_context.instance_variable_get(:@local_files).
237
+ should == local_files_hash
238
+ sync_context.send(:local_files).should == local_files_hash
239
+ end
240
+
241
+ # Note: don't use methods that validate encoding
242
+ it 'will raise an Exception if String#split is used',
243
+ :if => RUBY_VERSION >= '1.9' do
244
+ expect do
245
+ "line1\nbad\xFFline\nline3".split("\n")
246
+ end.to raise_error(ArgumentError, 'invalid byte sequence in UTF-8')
247
+ end
248
+ end # describe '#local_files'
249
+
250
+ describe '#local_hashes' do
251
+ it 'should collect file paths and MD5 checksums for @directory' do
252
+ Backup::Syncer::Cloud::Base::MUTEX.expects(:synchronize).yields
253
+ Backup::Logger.expects(:message).with(
254
+ "\s\sGenerating checksums for '/dir/to/sync'"
255
+ )
256
+ sync_context.expects(:`).with(
257
+ "find /dir/to/sync -print0 | xargs -0 openssl md5 2> /dev/null"
258
+ ).returns('MD5(tmp/foo)= 123abcdef')
259
+
260
+ sync_context.send(:local_hashes).should == 'MD5(tmp/foo)= 123abcdef'
261
+ end
262
+ end
263
+
264
+ describe '#remote_files' do
265
+ let(:repository_object) { mock }
266
+ let(:repository_files) { mock }
267
+ let(:file_objects) { mock }
268
+ let(:file_obj_a) { stub(:key => 'file_a') }
269
+ let(:file_obj_b) { stub(:key => 'file_b') }
270
+ let(:file_obj_c) { stub(:key => 'dir/file_c') }
271
+ let(:remote_files_hash) do
272
+ { 'file_a' => file_obj_a,
273
+ 'file_b' => file_obj_b,
274
+ 'dir/file_c' => file_obj_c }
275
+ end
276
+
277
+ before do
278
+ sync_context.instance_variable_set(:@bucket, repository_object)
279
+
280
+ repository_object.expects(:files).once.returns(repository_files)
281
+ repository_files.expects(:all).once.with(:prefix => 'backups/sync').
282
+ returns(file_objects)
283
+ file_objects.expects(:each).once.multiple_yields(
284
+ file_obj_a, file_obj_b, file_obj_c
285
+ )
286
+
287
+ # this is to avoid: unexpected invocation: #<Mock>.to_a()
288
+ # only 1.9.2 seems affected by this
289
+ if RUBY_VERSION == '1.9.2'
290
+ file_obj_a.stubs(:to_a)
291
+ file_obj_b.stubs(:to_a)
292
+ file_obj_c.stubs(:to_a)
293
+ end
294
+ end
295
+
296
+ context 'when it returns and caches a hash of repository file objects' do
297
+ it 'should remove the @remote_base from the path for the hash key' do
298
+ sync_context.send(:remote_files).should == remote_files_hash
299
+ sync_context.instance_variable_get(:@remote_files).
300
+ should == remote_files_hash
301
+ sync_context.send(:remote_files).should == remote_files_hash
302
+ end
303
+ end
304
+ end # describe '#remote_files'
305
+
306
+ describe '#sync_file' do
307
+ let(:local_file) do
308
+ stub(:path => '/dir/to/sync/sync.file', :md5 => 'abc123')
309
+ end
310
+ let(:remote_file) do
311
+ stub(:path => 'backups/sync/sync.file')
312
+ end
313
+ let(:file) { mock }
314
+ let(:repository_object) { mock }
315
+ let(:repository_files) { mock }
316
+
317
+ before do
318
+ sync_context.instance_variable_set(:@bucket, repository_object)
319
+ repository_object.stubs(:files).returns(repository_files)
320
+ end
321
+
322
+ context 'when the requested file to sync exists locally' do
323
+ before do
324
+ sync_context.stubs(:local_files).returns(
325
+ { 'sync.file' => local_file }
326
+ )
327
+ File.expects(:exist?).with('/dir/to/sync/sync.file').returns(true)
328
+ end
329
+
330
+ context 'when the MD5 checksum matches the remote file' do
331
+ before do
332
+ remote_file.stubs(:etag).returns('abc123')
333
+ sync_context.stubs(:remote_files).returns(
334
+ { 'sync.file' => remote_file }
335
+ )
336
+ end
337
+
338
+ it 'should skip the file' do
339
+ File.expects(:open).never
340
+ Backup::Syncer::Cloud::Base::MUTEX.expects(:synchronize).yields
341
+ Backup::Logger.expects(:message).with(
342
+ "\s\s[skipping] 'backups/sync/sync.file'"
343
+ )
344
+
345
+ sync_context.send(:sync_file, 'sync.file', :foo)
346
+ end
347
+ end
348
+
349
+ context 'when the MD5 checksum does not match the remote file' do
350
+ before do
351
+ remote_file.stubs(:etag).returns('dfg456')
352
+ sync_context.stubs(:remote_files).returns(
353
+ { 'sync.file' => remote_file }
354
+ )
355
+ end
356
+
357
+ it 'should upload the file' do
358
+ Backup::Syncer::Cloud::Base::MUTEX.expects(:synchronize).yields
359
+ Backup::Logger.expects(:message).with(
360
+ "\s\s[transferring] 'backups/sync/sync.file'"
361
+ )
362
+
363
+ File.expects(:open).with('/dir/to/sync/sync.file', 'r').yields(file)
364
+ repository_files.expects(:create).with(
365
+ :key => 'backups/sync/sync.file',
366
+ :body => file
367
+ )
368
+
369
+ sync_context.send(:sync_file, 'sync.file', :foo)
370
+ end
371
+ end
372
+
373
+ context 'when the requested file does not exist on the remote' do
374
+ before do
375
+ sync_context.stubs(:remote_files).returns({})
376
+ end
377
+
378
+ it 'should upload the file' do
379
+ Backup::Syncer::Cloud::Base::MUTEX.expects(:synchronize).yields
380
+ Backup::Logger.expects(:message).with(
381
+ "\s\s[transferring] 'backups/sync/sync.file'"
382
+ )
383
+
384
+ File.expects(:open).with('/dir/to/sync/sync.file', 'r').yields(file)
385
+ repository_files.expects(:create).with(
386
+ :key => 'backups/sync/sync.file',
387
+ :body => file
388
+ )
389
+
390
+ sync_context.send(:sync_file, 'sync.file', :foo)
391
+ end
392
+ end
393
+ end
394
+
395
+ context 'when the requested file does not exist locally' do
396
+ before do
397
+ sync_context.stubs(:remote_files).returns(
398
+ { 'sync.file' => remote_file }
399
+ )
400
+ sync_context.stubs(:local_files).returns({})
401
+ end
402
+
403
+ context 'when the `mirror` option is set to true' do
404
+ it 'should remove the file from the remote' do
405
+ Backup::Syncer::Cloud::Base::MUTEX.expects(:synchronize).yields
406
+ Backup::Logger.expects(:message).with(
407
+ "\s\s[removing] 'backups/sync/sync.file'"
408
+ )
409
+
410
+ remote_file.expects(:destroy)
411
+
412
+ sync_context.send(:sync_file, 'sync.file', true)
413
+ end
414
+ end
415
+
416
+ context 'when the `mirror` option is set to false' do
417
+ it 'should leave the file on the remote' do
418
+ Backup::Syncer::Cloud::Base::MUTEX.expects(:synchronize).yields
419
+ Backup::Logger.expects(:message).with(
420
+ "\s\s[leaving] 'backups/sync/sync.file'"
421
+ )
422
+
423
+ remote_file.expects(:destroy).never
424
+
425
+ sync_context.send(:sync_file, 'sync.file', false)
426
+ end
427
+ end
428
+ end
429
+ end # describe '#sync_file'
430
+ end # describe 'Cloud::Base::SyncContext'
431
+
432
+ describe 'Cloud::Base::LocalFile' do
433
+ let(:local_file_class) { Backup::Syncer::Cloud::Base::LocalFile }
434
+
435
+ describe '#new' do
436
+ describe 'wrapping #initialize and using #sanitize to validate objects' do
437
+ context 'when the path is valid UTF-8' do
438
+ let(:local_file) do
439
+ local_file_class.new('foo', 'MD5(foo)= foo')
440
+ end
441
+
442
+ it 'should return the new object' do
443
+ Backup::Syncer::Cloud::Base::MUTEX.expects(:synchronize).never
444
+ Backup::Logger.expects(:warn).never
445
+
446
+ local_file.should be_an_instance_of local_file_class
447
+ end
448
+ end
449
+
450
+ context 'when the path contains invalid UTF-8' do
451
+ let(:local_file) do
452
+ local_file_class.new(
453
+ "/bad/pa\xFFth", "MD5(/bad/pa\xFFth/to/file)= foo"
454
+ )
455
+ end
456
+ it 'should return nil and log a warning' do
457
+ Backup::Syncer::Cloud::Base::MUTEX.expects(:synchronize).yields
458
+ Backup::Logger.expects(:warn).with(
459
+ "\s\s[skipping] /bad/pa\xEF\xBF\xBDth/to/file\n" +
460
+ "\s\sPath Contains Invalid UTF-8 byte sequences"
461
+ )
462
+
463
+ local_file.should be_nil
464
+ end
465
+ end
466
+ end
467
+ end # describe '#new'
468
+
469
+ describe '#initialize' do
470
+ let(:local_file) do
471
+ local_file_class.new(:directory, :line)
472
+ end
473
+
474
+ before do
475
+ local_file_class.any_instance.expects(:sanitize).with(:directory).
476
+ returns('/dir/to/sync')
477
+ local_file_class.any_instance.expects(:sanitize).with(:line).
478
+ returns("MD5(/dir/to/sync/subdir/sync.file)= 123abcdef\n")
479
+ end
480
+
481
+ it 'should determine @path, @relative_path and @md5' do
482
+ local_file.path.should == '/dir/to/sync/subdir/sync.file'
483
+ local_file.relative_path.should == 'subdir/sync.file'
484
+ local_file.md5.should == '123abcdef'
485
+ end
486
+
487
+ it 'should return nil if the object is invalid' do
488
+ local_file_class.any_instance.expects(:invalid?).returns(true)
489
+ Backup::Syncer::Cloud::Base::MUTEX.expects(:synchronize).yields
490
+ Backup::Logger.expects(:warn)
491
+ local_file.should be_nil
492
+ end
493
+ end # describe '#initialize'
494
+
495
+ describe '#sanitize' do
496
+ let(:local_file) do
497
+ local_file_class.new('foo', 'MD5(foo)= foo')
498
+ end
499
+
500
+ it 'should replace any invalid UTF-8 characters' do
501
+ local_file.send(:sanitize, "/path/to/d\xFFir/subdir/sync\xFFfile").
502
+ should == "/path/to/d\xEF\xBF\xBDir/subdir/sync\xEF\xBF\xBDfile"
503
+ end
504
+
505
+ it 'should flag the LocalFile object as invalid' do
506
+ local_file.send(:sanitize, "/path/to/d\xFFir/subdir/sync\xFFfile")
507
+ local_file.invalid?.should be_true
508
+ end
509
+ end # describe '#sanitize'
510
+ end # describe 'Cloud::Base::LocalFile'
511
+ end