right_publish 0.2.0

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.
@@ -0,0 +1,282 @@
1
+ require 'right_publish/repo'
2
+ require 'spec_helper'
3
+
4
+ describe RightPublish::YumRepo do
5
+
6
+ def pkg_parts(path)
7
+ result = {:name=>nil, :version=>nil, :arch=>nil}
8
+ if /([A-Za-z\-+]+)-([0-9][A-Za-z0-9\-\.]*)\.(#{RightPublish::YumRepo::PKG_TYPES.join('|')})\.rpm\Z/.match(path)
9
+ result[:name] = $1
10
+ result[:version] = $2
11
+ result[:arch] = $3
12
+ end
13
+ result
14
+ end
15
+
16
+
17
+ before(:each) do
18
+ @repo_subdir = 'fake/'
19
+ @repo = flexmock(RightPublish::RepoManager.get_repository(:yum_repo)) do |obj|
20
+ obj.should_receive(:fetch)
21
+ obj.should_receive(:store)
22
+ obj.should_receive(:do_in_subdir).and_return {|dir,block| block.call}
23
+ obj.should_receive(:yum_prune)
24
+ obj.should_receive(:pkg_parts).and_return { |pkg| pkg_parts(pkg) }
25
+ end
26
+
27
+ flexmock(RightPublish::Profile) do |obj|
28
+ obj.should_receive(:log).and_return { |str,dbg| }
29
+ end
30
+
31
+ flexmock(File) { |obj| obj.should_receive(:file?).and_return(true) }
32
+ end
33
+
34
+ [false, true].each do |should_sign|
35
+ context "#publish#{" and sign" if should_sign}" do
36
+
37
+ # Test the EPEL (not flat) repo profile type
38
+ context "EPEL layout" do
39
+
40
+ def build_expectations(pkgs, expected_dists)
41
+ pkgs = Array(pkgs)
42
+
43
+ pkgs.each do |pkg|
44
+ pkg_arch = pkg_parts(pkg)[:arch]
45
+ dest_archs = case pkg_arch
46
+ when RightPublish::YumRepo::SRC_ALL_ARCH
47
+ [RightPublish::YumRepo::SRC_ALL_PATH]
48
+ when RightPublish::YumRepo::BIN_ALL_ARCH
49
+ RightPublish::YumRepo::ARCHITECTURES
50
+ else
51
+ [pkg_arch]
52
+ end
53
+
54
+ expected_dists.each_pair do |dist,releases|
55
+ releases.each do |release|
56
+ dest_archs.each do |arch|
57
+ dist_path = File.join(@repo_subdir, @epel_version.to_s, dist.to_s, release, arch)
58
+ @install_expectations[dist_path] ||= []
59
+ @install_expectations[dist_path] << pkg
60
+ end
61
+ end
62
+ end
63
+ end
64
+ @repo.should_receive(:install_file).and_return do |pkg,dir|
65
+ @install_expectations.should be_has_key(dir)
66
+ @install_expectations[dir].delete(pkg)
67
+ @install_expectations.delete(dir) if @install_expectations[dir].empty?
68
+ end
69
+ system_calls = @install_expectations.size
70
+ system_sign_calls = (@profile.config[:yum_repo][:gpg_key_id]) ? 1 : 0
71
+ @repo.should_receive(:system).times(system_calls).and_return(0)
72
+ @repo.should_receive(:shellout_with_password).times(system_sign_calls).and_return(0)
73
+ end
74
+
75
+ before(:each) do
76
+ @epel_dists = {:el=>['5','6'],:fc=>['12','13']}
77
+ @epel_version = 1
78
+ @install_expectations = {}
79
+ @profile = flexmock(RightPublish::Profile) do |obj|
80
+ obj.should_receive(:config).and_return {
81
+ { :verbose=>false,
82
+ :remote_storage=>{:provider=>'mockremote'},
83
+ :local_storage=>{:provider=>'mocklocal'},
84
+ :yum_repo=>{
85
+ :epel=>@epel_version,
86
+ :subdir=>@repo_subdir,
87
+ :dists=>@epel_dists,
88
+ :gpg_key_id=>should_sign,
89
+ :gpg_password=>'fakepass'}
90
+ }
91
+ }
92
+ end
93
+ end
94
+
95
+ after(:each) do
96
+ @install_expectations.should be_empty
97
+ end
98
+
99
+ # Test for publish noarch packages (multicasting)
100
+ context "NoArch Packages" do
101
+ it "installs a single noarch rpm file to all distribution/architectures" do
102
+ rpm_file = 'foo-1.noarch.rpm'
103
+ build_expectations(rpm_file,@epel_dists)
104
+ @repo.publish(rpm_file, nil)
105
+ end
106
+
107
+ it "installs a single src rpm file to all dists" do
108
+ rpm_file = 'foo-1.src.rpm'
109
+ build_expectations(rpm_file,@epel_dists)
110
+ @repo.publish(rpm_file, nil)
111
+ end
112
+
113
+ it "installs a list of noarch rpm files to all distribution/architectures" do
114
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.noarch.rpm']
115
+ build_expectations(rpm_files,@epel_dists)
116
+ @repo.publish(rpm_files, nil)
117
+ end
118
+
119
+ it "globs and installs a directory of noarch rpm files to all distributions/architectures" do
120
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.src.rpm']
121
+ build_expectations(rpm_files,@epel_dists)
122
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
123
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return(rpm_files) }
124
+ @repo.publish('somedir/', nil)
125
+ end
126
+
127
+ it "fails with no files in a dir" do
128
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
129
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return([]) }
130
+ @repo.should_receive(:system).never
131
+ expect { @repo.publish('some_dir/', nil) }.to raise_error(RuntimeError)
132
+ end
133
+
134
+ it "fails with files missing a rpm extenstion" do
135
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.noarch.deb']
136
+ @repo.should_receive(:system).never
137
+ expect { @repo.publish(rpm_files, nil) }.to raise_error(RuntimeError)
138
+ end
139
+
140
+ it "fails with a mix of noarch and binary rpms" do
141
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.x86_64.rpm']
142
+ @repo.should_receive(:system).never
143
+ expect { @repo.publish(rpm_files, nil) }.to raise_error(RuntimeError)
144
+ end
145
+ end # end NoArch context
146
+
147
+ context "Architecture Dependant Packages" do
148
+ before(:each) do
149
+ @target_distribution_map = {:fc=>['12']}
150
+ @target_distribution_str = 'fc/12'
151
+ end
152
+
153
+ it "installs a single x86_64 rpm file to a dist" do
154
+ rpm_file = 'foo-1.x86_64.rpm'
155
+ build_expectations(rpm_file,@target_distribution_map)
156
+ @repo.publish(rpm_file, @target_distribution_str)
157
+ end
158
+
159
+ it "installs a list of x86_64 rpms to a dist" do
160
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.x86_64.rpm']
161
+ build_expectations(rpm_files,@target_distribution_map)
162
+ @repo.publish(rpm_files, @target_distribution_str)
163
+ end
164
+
165
+ it "installs a directory mixed of x86_64 and i386 rpms to a dist" do
166
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.i386.rpm']
167
+ build_expectations(rpm_files,@target_distribution_map)
168
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
169
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return(rpm_files) }
170
+ @repo.publish('some_dir/', @target_distribution_str)
171
+ end
172
+
173
+ it "fails with no files in a dir" do
174
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
175
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return([]) }
176
+ @repo.should_receive(:system).never
177
+ expect { @repo.publish('some_dir/', @target_distribution_str) }.to raise_error(RuntimeError)
178
+ end
179
+
180
+ it "fails with files missing a rpm extension" do
181
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.i386.deb']
182
+ @repo.should_receive(:system).never
183
+ expect { @repo.publish(rpm_files, @target_distribution_str) }.to raise_error(RuntimeError)
184
+ end
185
+
186
+ it "fails with unknown architectures" do
187
+ rpm_files = ['foo-1.ppc.rpm', 'bar-2.ppc.rpm']
188
+ @repo.should_receive(:system).never
189
+ expect { @repo.publish(rpm_files, @target_distribution_str) }.to raise_error(RuntimeError)
190
+ end
191
+
192
+ it "fails when not provided with a target" do
193
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.i386.rpm']
194
+ @repo.should_receive(:system).never
195
+ expect { @repo.publish(rpm_files, nil) }.to raise_error(RuntimeError)
196
+ end
197
+ end # end Architecture Dependant context
198
+ end # end EPEL context
199
+
200
+ # Test the Flat repo profile type
201
+ context "Flat layout" do
202
+ def build_expectations(pkgs)
203
+ @install_expectations = Array(pkgs)
204
+ @repo.should_receive(:install_file).and_return do |pkg,dir|
205
+ @install_expectations.delete(pkg)
206
+ end
207
+ system_calls = 1
208
+ system_sign_calls = (@profile.config[:yum_repo][:gpg_key_id]) ? 1 : 0
209
+ @repo.should_receive(:system).times(system_calls).and_return(0)
210
+ @repo.should_receive(:shellout_with_password).times(system_sign_calls).and_return(0)
211
+ end
212
+
213
+ before(:each) do
214
+ @epel_version = false
215
+ @install_expectations = []
216
+ @profile = flexmock(RightPublish::Profile) do |obj|
217
+ obj.should_receive(:config).and_return {
218
+ { :verbose=>false,
219
+ :remote_storage=>{:provider=>'mockremote'},
220
+ :local_storage=>{:provider=>'mocklocal'},
221
+ :yum_repo=>{
222
+ :epel=>@epel_version,
223
+ :subdir=>@repo_subdir,
224
+ :gpg_key_id=>should_sign,
225
+ :gpg_password=>'fakepass'}
226
+ }
227
+ }
228
+ end
229
+ end
230
+
231
+ after(:each) do
232
+ @install_expectations.should be_empty
233
+ end
234
+
235
+ it "installs a single rpm file to repo" do
236
+ rpm_file = 'foo-1.noarch.rpm'
237
+ build_expectations(rpm_file)
238
+ @repo.publish(rpm_file, nil)
239
+ end
240
+
241
+ it "installs a single src rpm file to repo" do
242
+ rpm_file = 'foo-1.src.rpm'
243
+ build_expectations(rpm_file)
244
+ @repo.publish(rpm_file, nil)
245
+ end
246
+
247
+ it "installs a list of rpm files to repo" do
248
+ rpm_files = ['foo-1.x86_64.rpm', 'bar-2.x86_64.rpm']
249
+ build_expectations(rpm_files)
250
+ @repo.publish(rpm_files, nil)
251
+ end
252
+
253
+ it "globs and installs a directory of rpm files to repo" do
254
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.src.rpm']
255
+ build_expectations(rpm_files)
256
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
257
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return(rpm_files) }
258
+ @repo.publish('somedir/', nil)
259
+ end
260
+
261
+ it "succeeds with a mix of noarch and binary rpms" do
262
+ rpm_files = ['foo-1.noarch.rpm', 'bar-2.x86_64.rpm']
263
+ build_expectations(rpm_files)
264
+ @repo.publish(rpm_files, nil)
265
+ end
266
+
267
+ it "fails with no files in a dir" do
268
+ flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
269
+ flexmock(Dir) { |obj| obj.should_receive(:glob).and_return([]) }
270
+ @repo.should_receive(:system).never
271
+ expect { @repo.publish('some_dir/', nil) }.to raise_error(RuntimeError)
272
+ end
273
+
274
+ it "fails with files missing a rpm extenstion" do
275
+ rpm_files = ['foo-1.i386.rpm', 'bar-2.noarch.deb']
276
+ @repo.should_receive(:system).never
277
+ expect { @repo.publish('some_dir/', nil) }.to raise_error(RuntimeError)
278
+ end
279
+ end # end Flat context
280
+ end # end #publish context
281
+ end
282
+ end
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2012- RightScale Inc
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require 'rubygems'
23
+ require 'bundler/setup'
24
+ require 'flexmock'
25
+ require 'ruby-debug'
26
+ require 'simplecov' unless RUBY_VERSION =~ /1\.8/
27
+
28
+ RSpec.configure do |config|
29
+ config.mock_with :flexmock
30
+ end
@@ -0,0 +1,71 @@
1
+ require 'right_publish/storage'
2
+ require 'spec_helper'
3
+
4
+ describe RightPublish::StorageManager do
5
+
6
+ def is_registered?(name, key)
7
+ @storage_types = RightPublish::StorageManager.storage_types
8
+ @storage_types.should have_key(name)
9
+ @storage_types[name].should equal(key)
10
+ RightPublish::StorageManager.get_storage(key)::STORAGE_KEY.should be_equal(key)
11
+ end
12
+
13
+ it "should provide a hash of known storage types" do
14
+ RightPublish::StorageManager.storage_types.should be_instance_of(Hash)
15
+ end
16
+
17
+ it "returns nil for unknown storage types" do
18
+ RightPublish::StorageManager.get_storage(:doesnotexist_storage).should be_equal(nil)
19
+ end
20
+
21
+ it "should know about the local storage" do
22
+ is_registered?('local', :local_storage)
23
+ end
24
+
25
+ it "should know about the s3 storage" do
26
+ is_registered?('s3', :s3_storage)
27
+ end
28
+
29
+ context ".register_storage" do
30
+ it "rejects storage without a STORAGE_KEY" do
31
+ class MissingKey
32
+ end
33
+ expect { RightPublish::StorageManager.register_storage(MissingKey) }.to raise_error(NameError)
34
+ end
35
+
36
+ it "rejects storage whose key does not end in _storage" do
37
+ class BadKey
38
+ STORAGE_KEY = :bad_key
39
+ end
40
+ expect { RightPublish::StorageManager.register_storage(BadKey) }.to raise_error(TypeError)
41
+ end
42
+
43
+ it "rejects storage who do not provide .get_directories" do
44
+ class NoDirectories
45
+ STORAGE_KEY = :nooptions_storage
46
+ end
47
+ expect { RightPublish::StorageManager.register_storage(NoDirectories) }.to raise_error(TypeError)
48
+ end
49
+
50
+ it "rejects storage who do not provide options" do
51
+ class NoOptions
52
+ STORAGE_KEY = :nooptions_storage
53
+ def self.get_directories()
54
+ end
55
+ end
56
+ expect { RightPublish::StorageManager.register_storage(NoOptions) }.to raise_error(NameError)
57
+ end
58
+
59
+ it "accpets storage who are properly defined" do
60
+ class GoodStorage
61
+ STORAGE_KEY = :good_storage
62
+ STORAGE_OPTIONS = {}
63
+ def self.get_directories()
64
+ end
65
+ end
66
+ RightPublish::StorageManager.register_storage(GoodStorage).should be_equal(nil)
67
+ is_registered?('good', :good_storage)
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,317 @@
1
+ require 'right_publish/storage'
2
+ require 'spec_helper'
3
+
4
+ describe RightPublish::Storage do
5
+
6
+ # Create a File entity.
7
+ def make_file(name, expects, copy, destroy)
8
+ file = flexmock("File:#{name}")
9
+
10
+ # We can set some expectations on it like if we believe we're
11
+ # going to be removed from Storage.
12
+ destroy_calls = 0
13
+ munge_body = false
14
+ if expects.instance_of? Hash
15
+ destroy_calls = (expects.has_key?(:destroy) && expects[:destroy] && 1) || 0
16
+ munge_body = (expects.has_key?(:munge_body) && expects[:munge_body]) || false
17
+ end
18
+
19
+ # For simplicity use the filename as the "contents" but provide a
20
+ # way to corrupt it to force testing content, not name matching.
21
+ file.should_receive(:body).and_return { (munge_body && 'data_mismatch') || name }
22
+
23
+ # Use the copy callback to insert a copy of ourselves at a different
24
+ # path (Remote duplication).
25
+ file.should_receive(:copy).and_return do |dir_key,dest|
26
+ new_file = make_file(dest, nil, copy, destroy)
27
+ copy.call(new_file)
28
+ end
29
+
30
+ # Use the destroy callback to remove outselves from the Files obj.
31
+ file.should_receive(:destroy).times(destroy_calls).and_return { destroy.call(name) }
32
+ file.should_receive(:key).and_return(name)
33
+ file
34
+ end
35
+
36
+ # Create a Fog Storage Mock
37
+ def make_storage(path, file_hash, expectations)
38
+ # List of File mocks bound to this storage instance
39
+ files = []
40
+
41
+ # These procedures are used as callbacks for the File mocks to copy
42
+ # or destroy themselves and have a side-effect on the about Array
43
+ create_proc = Proc.new { |file| files << file }
44
+ delete_proc = Proc.new { |file| files.delete_if { |f| f.key == file } }
45
+
46
+ # Create the File mocks and store in the Array
47
+ file_hash.each_pair do |name,expects|
48
+ files << make_file(name, expects, create_proc, delete_proc )
49
+ end
50
+
51
+ # Fog has a weird notion of a 'Files' and 'Directory' object being
52
+ # distinct. We care here because create is called on the 'Files'
53
+ # rather then on the 'Directory' and it is Enumerable.
54
+ file_list = flexmock(files)
55
+ file_list.should_receive(:create).
56
+ times(expectations[:create_call]).
57
+ and_return do |args|
58
+ new_file = make_file( args[:key], nil, create_proc, delete_proc )
59
+ files << new_file
60
+ new_file
61
+ end
62
+
63
+ # The only thing ever called on the Storage object is the
64
+ # #get_directories interface, or at least that's all we use.
65
+ obj = flexmock('Storage')
66
+ obj.should_receive(:get_directories).and_return do
67
+ # The Directory object is synonymous to an S3 bucket object
68
+ # and is responsible for providing the Files enumerable and
69
+ # calculating the MD5 hash of a given file.
70
+ f = flexmock('Directory')
71
+ f.should_receive(:compute_md5).and_return {|file| Digest::MD5.hexdigest(File.basename(file.body))}
72
+ f.should_receive(:files).and_return(file_list)
73
+ f.should_receive(:key).and_return(path)
74
+ f
75
+ end
76
+ obj
77
+ end
78
+
79
+ # In order to match we just make sure that the sizes of the Files
80
+ # objects match and that every File within A is also within B. We don't
81
+ # have to check the reverse since the sizes implies it.
82
+ def storage_match?(a,b)
83
+ a_files = a.get_directories.files
84
+ b_files = b.get_directories.files
85
+
86
+ return false if a_files.count != b_files.count
87
+ a_files.all? { |a_f|
88
+ name = a_f.key
89
+ b_files.any? { |b_f| name == b_f.key }
90
+ }
91
+ end
92
+
93
+ before(:each) do
94
+ flexmock(RightPublish::Profile) do |obj|
95
+ obj.should_receive(:log).and_return { |str,debug|
96
+ }
97
+ end
98
+ end
99
+
100
+ context ".sync_dirs" do
101
+ it "handles source AND destination storages that are empty" do
102
+ src_storage = make_storage(
103
+ 'foo',
104
+ {},
105
+ {:create_call=>0} )
106
+
107
+ # First copy of file1 will use create, second will be duped
108
+ dest_storage = make_storage(
109
+ 'bar',
110
+ {},
111
+ {:create_call=>0} )
112
+
113
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
114
+ storage_match?(src_storage, dest_storage).should be_true
115
+ end
116
+
117
+ it "does nothing with identical directories" do
118
+ src_storage = make_storage(
119
+ 'foo',
120
+ { 'dir1/file1'=>nil,
121
+ 'dir1/file2'=>nil,
122
+ 'file3'=>nil },
123
+ {:create_call=>0} )
124
+
125
+ dest_storage = make_storage(
126
+ 'bar',
127
+ { 'dir1/file1'=>nil,
128
+ 'dir1/file2'=>nil,
129
+ 'file3'=>nil },
130
+ {:create_call=>0} )
131
+
132
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage)
133
+ storage_match?(src_storage, dest_storage).should be_true
134
+ end
135
+
136
+ it "copies a new file to the destination" do
137
+ src_storage = make_storage(
138
+ 'foo',
139
+ { 'dir1/file1'=>nil,
140
+ 'dir1/file2'=>nil,
141
+ 'file3'=>nil,
142
+ 'file4'=>nil },
143
+ {:create_call=>0} )
144
+
145
+ dest_storage = make_storage(
146
+ 'bar',
147
+ { 'dir1/file1'=>nil,
148
+ 'dir1/file2'=>nil,
149
+ 'file3'=>nil },
150
+ {:create_call=>1} )
151
+
152
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage)
153
+ storage_match?(src_storage, dest_storage).should be_true
154
+ end
155
+
156
+ it "handles source storages that are empty" do
157
+ src_storage = make_storage(
158
+ 'foo',
159
+ {},
160
+ {:create_call=>0} )
161
+
162
+ # First copy of file1 will use create, second will be duped
163
+ dest_storage = make_storage(
164
+ 'bar',
165
+ { 'dir1/file1'=>{:destroy=>true} },
166
+ {:create_call=>0} )
167
+
168
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
169
+ storage_match?(src_storage, dest_storage).should be_true
170
+ end
171
+
172
+ it "handles destination storages that are empty" do
173
+ src_storage = make_storage(
174
+ 'foo',
175
+ { 'dir1/file1'=>nil },
176
+ {:create_call=>0} )
177
+
178
+ # First copy of file1 will use create, second will be duped
179
+ dest_storage = make_storage(
180
+ 'bar',
181
+ {},
182
+ {:create_call=>1} )
183
+
184
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
185
+ storage_match?(src_storage, dest_storage).should be_true
186
+ end
187
+
188
+ it "leaves an extraneous file in the destination w/o :sweep" do
189
+ src_storage = make_storage(
190
+ 'foo',
191
+ { 'dir1/file1'=>nil,
192
+ 'dir1/file2'=>nil,
193
+ 'file3'=>nil },
194
+ {:create_call=>0} )
195
+
196
+ dest_storage = make_storage(
197
+ 'bar',
198
+ { 'dir1/file1'=>nil,
199
+ 'dir1/file2'=>nil,
200
+ 'file3'=>nil,
201
+ 'file4'=>nil },
202
+ {:create_call=>0} )
203
+
204
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage)
205
+
206
+ # We should NOT match here. Not sure what the usecase of this
207
+ # is, but it's tested anyways.
208
+ storage_match?(src_storage, dest_storage).should be_false
209
+ end
210
+
211
+ it "destroys an extraneous file from the destination w/ :sweep" do
212
+ src_storage = make_storage(
213
+ 'foo',
214
+ { 'dir1/file1'=>nil,
215
+ 'dir1/file2'=>nil,
216
+ 'file3'=>nil },
217
+ {:create_call=>0} )
218
+
219
+ dest_storage = make_storage(
220
+ 'bar',
221
+ { 'dir1/file1'=>nil,
222
+ 'dir1/file2'=>nil,
223
+ 'file3'=>nil,
224
+ 'file4'=>{:destroy=>true} },
225
+ {:create_call=>0} )
226
+
227
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
228
+ storage_match?(src_storage, dest_storage).should be_true
229
+ end
230
+
231
+ it "destroys and copies files as needed to synchronize directories" do
232
+ src_storage = make_storage(
233
+ 'foo',
234
+ { 'dir1/file1'=>nil,
235
+ 'dir1/file2'=>nil,
236
+ 'file3'=>nil,
237
+ 'file4'=>nil,
238
+ 'file6'=>nil },
239
+ {:create_call=>0} )
240
+
241
+ dest_storage = make_storage(
242
+ 'bar',
243
+ { 'dir1/file1'=>nil,
244
+ 'dir1/file2'=>nil,
245
+ 'file3'=>nil,
246
+ 'file5'=>{:destroy=>true},
247
+ 'dir2/file2'=>{:destroy=>true} },
248
+ {:create_call=>2} )
249
+
250
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
251
+ storage_match?(src_storage, dest_storage).should be_true
252
+ end
253
+
254
+ it "uses the remote copy facilities when making duplicates" do
255
+ src_storage = make_storage(
256
+ 'foo',
257
+ { 'dir1/file1'=>nil,
258
+ 'dir1/file2'=>nil,
259
+ 'dir2/file1'=>nil,
260
+ 'file4'=>nil },
261
+ {:create_call=>0} )
262
+
263
+ # There are two duplicates, 'file1', in the local store. To save
264
+ # time/data we just call copy on the file that already exists in
265
+ # the remote, notice the '0' create_calls.
266
+ dest_storage = make_storage(
267
+ 'bar',
268
+ { 'dir1/file1'=>nil,
269
+ 'dir1/file2'=>nil,
270
+ 'file4'=>nil },
271
+ {:create_call=>0} )
272
+
273
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
274
+ storage_match?(src_storage, dest_storage).should be_true
275
+ end
276
+
277
+ it "uses the content of the file, not the name to determine equality" do
278
+ src_storage = make_storage(
279
+ 'foo',
280
+ { 'dir1/file1'=>nil,
281
+ 'dir1/file2'=>nil,
282
+ 'file4'=>nil },
283
+ {:create_call=>0} )
284
+
285
+ # :munge_body cause the MD5 sum to mismatch the file name
286
+ dest_storage = make_storage(
287
+ 'bar',
288
+ { 'dir1/file1'=>{:munge_body=>true,:destroy=>true},
289
+ 'dir1/file2'=>nil,
290
+ 'file4'=>nil },
291
+ {:create_call=>1} )
292
+
293
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
294
+ storage_match?(src_storage, dest_storage).should be_true
295
+ end
296
+
297
+ it "uses create AND duplicate routines with multiple new identical files" do
298
+ src_storage = make_storage(
299
+ 'foo',
300
+ { 'dir1/file1'=>nil,
301
+ 'dir1/file2'=>nil,
302
+ 'dir2/dir3/file2'=>nil,
303
+ 'file2'=>nil },
304
+ {:create_call=>0} )
305
+
306
+ # First copy of file1 will use create, the rest will be duped
307
+ dest_storage = make_storage(
308
+ 'bar',
309
+ { 'dir1/file1'=>nil },
310
+ {:create_call=>1} )
311
+
312
+ RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
313
+ storage_match?(src_storage, dest_storage).should be_true
314
+ end
315
+ end
316
+
317
+ end