druid-tools 0.4.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rubocop.yml +41 -0
  4. data/.rubocop_todo.yml +372 -0
  5. data/.travis.yml +12 -2
  6. data/README.md +36 -16
  7. data/Rakefile +4 -1
  8. data/VERSION +1 -1
  9. data/druid-tools.gemspec +4 -1
  10. data/lib/druid_tools/druid.rb +19 -6
  11. data/spec/druid_spec.rb +470 -0
  12. data/spec/spec_helper.rb +9 -0
  13. metadata +49 -36
  14. data/doc/classes/DruidTools.html +0 -132
  15. data/doc/classes/DruidTools/DifferentContentExistsError.html +0 -111
  16. data/doc/classes/DruidTools/Druid.html +0 -352
  17. data/doc/classes/DruidTools/Druid.src/M000001.html +0 -18
  18. data/doc/classes/DruidTools/Druid.src/M000002.html +0 -18
  19. data/doc/classes/DruidTools/Druid.src/M000003.html +0 -18
  20. data/doc/classes/DruidTools/Druid.src/M000004.html +0 -24
  21. data/doc/classes/DruidTools/Druid.src/M000005.html +0 -18
  22. data/doc/classes/DruidTools/Druid.src/M000006.html +0 -18
  23. data/doc/classes/DruidTools/Druid.src/M000007.html +0 -20
  24. data/doc/classes/DruidTools/Druid.src/M000008.html +0 -25
  25. data/doc/classes/DruidTools/Druid.src/M000009.html +0 -20
  26. data/doc/classes/DruidTools/Druid.src/M000010.html +0 -27
  27. data/doc/classes/DruidTools/Druid.src/M000011.html +0 -24
  28. data/doc/classes/DruidTools/Druid.src/M000012.html +0 -29
  29. data/doc/classes/DruidTools/InvalidDruidError.html +0 -111
  30. data/doc/classes/DruidTools/SameContentExistsError.html +0 -111
  31. data/doc/created.rid +0 -1
  32. data/doc/files/lib/druid-tools_rb.html +0 -108
  33. data/doc/files/lib/druid_tools/druid_rb.html +0 -108
  34. data/doc/files/lib/druid_tools/exceptions_rb.html +0 -101
  35. data/doc/files/lib/druid_tools/version_rb.html +0 -101
  36. data/doc/files/lib/druid_tools_rb.html +0 -110
  37. data/doc/files/spec/druid_tools_spec_rb.html +0 -115
  38. data/doc/files/spec/spec_helper_rb.html +0 -114
  39. data/doc/fr_class_index.html +0 -28
  40. data/doc/fr_file_index.html +0 -30
  41. data/doc/fr_method_index.html +0 -38
  42. data/doc/index.html +0 -24
  43. data/doc/rdoc-style.css +0 -208
  44. data/spec/druid_tools_spec.rb +0 -426
data/README.md CHANGED
@@ -1,9 +1,35 @@
1
+ [![Build Status](https://travis-ci.org/sul-dlss/druid-tools.svg?branch=delete-records)](https://travis-ci.org/sul-dlss/druid-tools)
2
+ [![Coverage Status](https://coveralls.io/repos/github/sul-dlss/druid-tools/badge.svg?branch=master)](https://coveralls.io/github/sul-dlss/druid-tools?branch=master)
3
+ [![Dependency Status](https://gemnasium.com/badges/github.com/sul-dlss/druid-tools.svg)](https://gemnasium.com/github.com/sul-dlss/druid-tools)
4
+ [![Gem Version](https://badge.fury.io/rb/druid-tools.svg)](https://badge.fury.io/rb/druid-tools)
5
+
1
6
  # Druid::Tools
2
7
 
3
8
  Tools to manipulate DRUID trees and content directories
4
9
 
10
+ Note that druid syntax is defined in consul (and druids are issued by the SURI service). See https://consul.stanford.edu/pages/viewpage.action?title=SURI+2.0+Specification&spaceKey=chimera
11
+
12
+ Druid format:
13
+
14
+ bbdddbbdddd (two letters two digits two letters 4 digits)
15
+
16
+ Letters must be lowercase, and must not include A, E, I, O, U or L. (capitals for easier distinction here)
17
+ We often use vowels in our test data, and this code base has allowed vowels historically (though not
18
+ uppercase). We now recommend setting the strict argument to true whenever using this code to build
19
+ DruidTools::Druid objects or to validate druid identifier strings.
20
+
5
21
  ## Usage
6
22
 
23
+ ### with strict argument
24
+
25
+ ```ruby
26
+ d = DruidTools::Druid.new('druid:ab123cd4567', '/dor/workspace', true) # no aeioul
27
+ => ArgumentError: Invalid DRUID: 'druid:ab123cd4567'
28
+ d = DruidTools::Druid.new('druid:bb123cd4567', '/dor/workspace', true)
29
+ d.druid
30
+ => "druid:bb123cd4567"
31
+ ```
32
+
7
33
  ### Get attributes and paths
8
34
 
9
35
  ```ruby
@@ -27,6 +53,13 @@ d = DruidTools::Druid.valid?('druid:ab123cd4567')
27
53
  => true
28
54
  d = DruidTools::Druid.valid?('blah')
29
55
  => false
56
+ d = DruidTools::Druid.valid?('druid:ab123cd4567', true) # strict validation: no aeioul
57
+ => false
58
+ d = DruidTools::Druid.valid?('druid:ab123cd4567', false)
59
+ => true
60
+ d = DruidTools::Druid.valid?('druid:bb123cd4567', true)
61
+ => true
62
+
30
63
  ```
31
64
 
32
65
  ### Manipulate directories and symlinks
@@ -80,9 +113,9 @@ d.find_content('this/file/does/not/exist.jpg')
80
113
  ### Pruning: removes leaves of tree up to non-empty branches
81
114
 
82
115
  ```ruby
83
- d1 = DruidTools::Druid.new 'druid:cd456ef7890', '/workspace'
116
+ d1 = DruidTools::Druid.new('druid:cd456ef7890', '/workspace')
84
117
  d1.mkdir
85
- d2 = DruidTools::Druid.new 'druid:cd456gh1234', '/workspace'
118
+ d2 = DruidTools::Druid.new('druid:cd456gh1234', '/workspace')
86
119
  d2.mkdir
87
120
 
88
121
  # /workspace/cd/456/gh/1234/cd456gh1234 pruned down to /workspace/cd/456
@@ -93,22 +126,9 @@ d2.prune!
93
126
  ### Stacks and Purl compatible Druid. All files at the leaf directories
94
127
 
95
128
  ```ruby
96
- pd = DruidTools::PurlDruid.new 'druid:ab123cd4567', '/purl'
129
+ pd = DruidTools::PurlDruid.new('druid:ab123cd4567', '/purl')
97
130
  pd.path
98
131
  => "/purl/ab/123/cd/4567"
99
132
  pd.content_dir
100
133
  => "/purl/ab/123/cd/4567"
101
134
  ```
102
-
103
- ### History
104
-
105
- - <b>0.3.0</b> - Added #prune method. Added AccessDruid for stacks and purl access
106
- - <b>0.2.6</b> - Fixed VERSION warning message, and documentation cleanup
107
- - <b>0.2.5</b> - Added glob pattern as DruidTools::Druid.glob
108
- - <b>0.2.4</b> - Allow non-String as .new parameter and added InvalidDruidError
109
- - <b>0.2.3</b> - Fine tune behavior of find_filelist_parent
110
- - <b>0.2.2</b> - Added find_filelist_parent method allowing search for a set of files
111
- - <b>0.2.1</b> - Do not error out during symlink creation if it already exists
112
- - <b>0.2.0</b> - Added DruidTools::Druid.valid?
113
- - <b>0.1.0</b> - Additional support for alternate content locations
114
- - <b>0.0.1</b> - Initial Release
data/Rakefile CHANGED
@@ -4,4 +4,7 @@ require "bundler/gem_tasks"
4
4
  require 'rspec/core/rake_task'
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
 
7
- task :default => :spec
7
+ require 'rubocop/rake_task'
8
+ RuboCop::RakeTask.new
9
+
10
+ task :default => [:spec, :rubocop]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.1
1
+ 1.0.0
@@ -15,7 +15,10 @@ Gem::Specification.new do |gem|
15
15
  gem.name = 'druid-tools'
16
16
  gem.require_paths = ['lib']
17
17
  gem.version = File.read('VERSION').strip
18
-
18
+
19
19
  gem.add_development_dependency 'rake', '>= 10.1.0'
20
20
  gem.add_development_dependency 'rspec', '~> 3.0'
21
+ gem.add_development_dependency 'coveralls'
22
+ gem.add_development_dependency 'rubocop', '~> 0.50.0' # avoid code churn due to rubocop changes
23
+ gem.add_development_dependency 'rubocop-rspec', '~> 1.18.0' # avoid code churn due to rubocop-rspec changes
21
24
  end
@@ -6,11 +6,17 @@ module DruidTools
6
6
  @@deletes_directory_name = '.deletes'
7
7
  attr_accessor :druid, :base
8
8
 
9
+ # See https://consul.stanford.edu/pages/viewpage.action?title=SURI+2.0+Specification&spaceKey=chimera
10
+ # character class matching allowed letters in a druid suitable for use in regex (no aeioul)
11
+ STRICT_LET = '[b-df-hjkmnp-tv-z]'.freeze
12
+
9
13
  class << self
10
14
  attr_accessor :prefix
11
15
 
16
+ # @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
12
17
  # @return [Regexp] matches druid:aa111aa1111 or aa111aa1111
13
- def pattern
18
+ def pattern(strict=false)
19
+ return /\A(?:#{self.prefix}:)?(#{STRICT_LET}{2})(\d{3})(#{STRICT_LET}{2})(\d{4})\z/ if strict
14
20
  /\A(?:#{self.prefix}:)?([a-z]{2})(\d{3})([a-z]{2})(\d{4})\z/
15
21
  end
16
22
 
@@ -19,10 +25,16 @@ module DruidTools
19
25
  "{#{self.prefix}:,}[a-z][a-z][0-9][0-9][0-9][a-z][a-z][0-9][0-9][0-9][0-9]"
20
26
  end
21
27
 
28
+ # @return [String] suitable for use in [Dir#glob]
29
+ def strict_glob
30
+ "{#{self.prefix}:,}#{STRICT_LET}#{STRICT_LET}[0-9][0-9][0-9]#{STRICT_LET}#{STRICT_LET}[0-9][0-9][0-9][0-9]"
31
+ end
32
+
22
33
  # @param [String] druid id
34
+ # @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
23
35
  # @return [Boolean] true if druid matches pattern; otherwise false
24
- def valid?(druid)
25
- return druid =~ pattern ? true : false
36
+ def valid?(druid, strict=false)
37
+ druid =~ pattern(strict) ? true : false
26
38
  end
27
39
 
28
40
  end
@@ -41,13 +53,14 @@ module DruidTools
41
53
  end
42
54
 
43
55
  # @param druid [String] A valid druid
56
+ # @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
44
57
  # @param base [String] The directory used by #path
45
- def initialize(druid, base='.')
58
+ def initialize(druid, base='.', strict=false)
46
59
  druid = druid.to_s unless druid.is_a? String
47
- unless self.class.valid?(druid)
60
+ unless self.class.valid?(druid, strict)
48
61
  raise ArgumentError, "Invalid DRUID: '#{druid}'"
49
62
  end
50
- druid = [self.class.prefix,druid].join(':') unless druid =~ /^#{self.class.prefix}:/
63
+ druid = [self.class.prefix, druid].join(':') unless druid =~ /^#{self.class.prefix}:/
51
64
  @base = base
52
65
  @druid = druid
53
66
  end
@@ -0,0 +1,470 @@
1
+ describe DruidTools::Druid do
2
+ let(:fixture_dir) { File.expand_path("../fixtures", __FILE__) }
3
+ let(:druid_str) { 'druid:cd456ef7890' }
4
+ let(:tree_1) { File.join(fixture_dir, 'cd/456/ef/7890/cd456ef7890') }
5
+ let(:strictly_valid_druid_str) { 'druid:cd456gh1234' }
6
+ let(:tree_2) { File.join(fixture_dir, 'cd/456/gh/1234/cd456gh1234') }
7
+
8
+ after(:each) do
9
+ FileUtils.rm_rf(File.join(fixture_dir, 'cd'))
10
+ end
11
+
12
+ context '.valid?' do
13
+ # also tests .pattern
14
+ it "correctly validates druid strings" do
15
+ tests = [
16
+ # Expected Input druid
17
+ [true, 'druid:aa000bb0001'],
18
+ [true, 'aa000bb0001'],
19
+ [false, 'Aa000bb0001'],
20
+ [false, "xxx\naa000bb0001"],
21
+ [false, 'aaa000bb0001'],
22
+ [false, 'druidX:aa000bb0001'],
23
+ [false, ':aa000bb0001'],
24
+ [true, 'aa123bb1234'],
25
+ [false, 'aa12bb1234'],
26
+ [false, 'aa1234bb1234'],
27
+ [false, 'aa123bb123'],
28
+ [false, 'aa123bb12345'],
29
+ [false, 'a123bb1234'],
30
+ [false, 'aaa123bb1234'],
31
+ [false, 'aa123b1234'],
32
+ [false, 'aa123bbb1234'],
33
+ [false, 'druid:az918AZ9381'.upcase],
34
+ [true, 'druid:az918AZ9381'.downcase],
35
+ [true, 'druid:zz943vx1492']
36
+ ]
37
+ tests.each do |exp, dru|
38
+ expect(DruidTools::Druid.valid?(dru)).to eq(exp)
39
+ expect(DruidTools::Druid.valid?(dru, false)).to eq(exp)
40
+ end
41
+ end
42
+ context 'strict' do
43
+ it "correctly validates druid strings" do
44
+ tests = [
45
+ # Expected Input druid
46
+ [false, 'aa000aa0000'],
47
+ [false, 'ee000ee0000'],
48
+ [false, 'ii000ii0000'],
49
+ [false, 'oo000oo0000'],
50
+ [false, 'uu000uu0000'],
51
+ [false, 'll000ll0000'],
52
+ [false, 'aa000bb0001'],
53
+ [true, 'druid:dd000bb0001'],
54
+ [false, 'druid:aa000bb0001'],
55
+ [true, 'dd000bb0001'],
56
+ [false, 'Dd000bb0001'],
57
+ [false, "xxx\ndd000bb0001"],
58
+ [false, 'ddd000bb0001'],
59
+ [false, 'druidX:dd000bb0001'],
60
+ [false, ':dd000bb0001'],
61
+ [true, 'cc123bb1234'],
62
+ [false, 'aa123bb1234'],
63
+ [false, 'dd12bb1234'],
64
+ [false, 'dd1234bb1234'],
65
+ [false, 'dd123bb123'],
66
+ [false, 'dd123bb12345'],
67
+ [false, 'd123bb1234'],
68
+ [false, 'ddd123bb1234'],
69
+ [false, 'dd123b1234'],
70
+ [false, 'dd123bbb1234'],
71
+ [false, 'druid:bz918BZ9381'.upcase],
72
+ [true, 'druid:bz918BZ9381'.downcase],
73
+ [false, 'druid:az918AZ9381'.downcase],
74
+ [true, 'druid:zz943vx1492']
75
+ ]
76
+ tests.each do |exp, dru|
77
+ expect(DruidTools::Druid.valid?(dru, true)).to eq(exp)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ it "#druid provides the full druid including the prefix" do
84
+ expect(DruidTools::Druid.new('druid:cd456ef7890', fixture_dir).druid).to eq('druid:cd456ef7890')
85
+ expect(DruidTools::Druid.new('cd456ef7890', fixture_dir).druid).to eq('druid:cd456ef7890')
86
+ end
87
+
88
+ it "#id extracts the ID from the stem" do
89
+ expect(DruidTools::Druid.new('druid:cd456ef7890', fixture_dir).id).to eq('cd456ef7890')
90
+ expect(DruidTools::Druid.new('cd456ef7890', fixture_dir).id).to eq('cd456ef7890')
91
+ end
92
+
93
+ context '#new' do
94
+ it "raises exception if the druid is invalid" do
95
+ expect { DruidTools::Druid.new('nondruid:cd456ef7890', fixture_dir) }.to raise_error(ArgumentError)
96
+ expect { DruidTools::Druid.new('druid:cd4567ef890', fixture_dir) }.to raise_error(ArgumentError)
97
+ end
98
+ it "takes strict argument" do
99
+ DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir, true)
100
+ expect { DruidTools::Druid.new(druid_str, fixture_dir, true) }.to raise_error(ArgumentError)
101
+ end
102
+ end
103
+
104
+ it "#tree builds a druid tree from a druid" do
105
+ druid = DruidTools::Druid.new(druid_str, fixture_dir)
106
+ expect(druid.tree).to eq(['cd', '456', 'ef', '7890', 'cd456ef7890'])
107
+ expect(druid.path).to eq(tree_1)
108
+ end
109
+
110
+ it "#mkdir, #rmdir create and destroy druid directories" do
111
+ expect(File.exists?(tree_1)).to eq false
112
+ expect(File.exists?(tree_2)).to eq false
113
+
114
+ druid_1 = DruidTools::Druid.new(druid_str, fixture_dir)
115
+ druid_2 = DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir)
116
+
117
+ druid_1.mkdir
118
+ expect(File.exists?(tree_1)).to eq true
119
+ expect(File.exists?(tree_2)).to eq false
120
+
121
+ druid_2.mkdir
122
+ expect(File.exists?(tree_1)).to eq true
123
+ expect(File.exists?(tree_2)).to eq true
124
+
125
+ druid_2.rmdir
126
+ expect(File.exists?(tree_1)).to eq true
127
+ expect(File.exists?(tree_2)).to eq false
128
+
129
+ druid_1.rmdir
130
+ expect(File.exists?(tree_1)).to eq false
131
+ expect(File.exists?(tree_2)).to eq false
132
+ expect(File.exists?(File.join(fixture_dir, 'cd'))).to eq false
133
+ end
134
+
135
+ describe "alternate prefixes" do
136
+ before :all do
137
+ DruidTools::Druid.prefix = 'sulair'
138
+ end
139
+
140
+ after :all do
141
+ DruidTools::Druid.prefix = 'druid'
142
+ end
143
+
144
+ it "handles alternate prefixes" do
145
+ expect { DruidTools::Druid.new('druid:cd456ef7890', fixture_dir) }.to raise_error(ArgumentError)
146
+ expect(DruidTools::Druid.new('sulair:cd456ef7890', fixture_dir).id).to eq('cd456ef7890')
147
+ expect(DruidTools::Druid.new('cd456ef7890', fixture_dir).druid).to eq('sulair:cd456ef7890')
148
+ end
149
+ end
150
+
151
+ describe "content directories" do
152
+ it "knows where its content goes" do
153
+ druid = DruidTools::Druid.new(druid_str, fixture_dir)
154
+ expect(druid.content_dir(false)).to eq(File.join(tree_1, 'content'))
155
+ expect(druid.metadata_dir(false)).to eq(File.join(tree_1, 'metadata'))
156
+ expect(druid.temp_dir(false)).to eq(File.join(tree_1, 'temp'))
157
+
158
+ expect(File.exists?(File.join(tree_1, 'content'))).to eq false
159
+ expect(File.exists?(File.join(tree_1, 'metadata'))).to eq false
160
+ expect(File.exists?(File.join(tree_1, 'temp'))).to eq false
161
+ end
162
+
163
+ it "creates its content directories on the fly" do
164
+ druid = DruidTools::Druid.new(druid_str, fixture_dir)
165
+ expect(druid.content_dir).to eq(File.join(tree_1, 'content'))
166
+ expect(druid.metadata_dir).to eq(File.join(tree_1, 'metadata'))
167
+ expect(druid.temp_dir).to eq(File.join(tree_1, 'temp'))
168
+
169
+ expect(File.exists?(File.join(tree_1, 'content'))).to eq true
170
+ expect(File.exists?(File.join(tree_1, 'metadata'))).to eq true
171
+ expect(File.exists?(File.join(tree_1, 'temp'))).to eq true
172
+ end
173
+
174
+ it "matches glob" do
175
+ druid = DruidTools::Druid.new(druid_str, fixture_dir)
176
+ druid.mkdir
177
+ expect(Dir.glob(File.join(File.dirname(druid.path), DruidTools::Druid::glob)).size).to eq(1)
178
+ end
179
+ it "matches strict_glob" do
180
+ druid = DruidTools::Druid.new(druid_str, fixture_dir)
181
+ druid.mkdir
182
+ expect(Dir.glob(File.join(File.dirname(druid.path), DruidTools::Druid::strict_glob)).size).to eq(0)
183
+ druid = DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir)
184
+ druid.mkdir
185
+ expect(Dir.glob(File.join(File.dirname(druid.path), DruidTools::Druid::strict_glob)).size).to eq(1)
186
+ end
187
+ end
188
+
189
+ describe "content discovery" do
190
+ let(:druid) { DruidTools::Druid.new(druid_str, fixture_dir) }
191
+ let(:filelist) { %w(1 2 3 4).collect { |num| "someFile#{num}" } }
192
+
193
+ it "finds content in content directories" do
194
+ location = druid.content_dir
195
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
196
+ expect(druid.find_content('someContent')).to eq(File.join(location, 'someContent'))
197
+ end
198
+
199
+ it "finds content in the root directory" do
200
+ location = druid.path(nil, true)
201
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
202
+ expect(druid.find_content('someContent')).to eq(File.join(location, 'someContent'))
203
+ end
204
+
205
+ it "finds content in the leaf directory" do
206
+ location = File.expand_path('..', druid.path(nil, true))
207
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
208
+ expect(druid.find_content('someContent')).to eq(File.join(location, 'someContent'))
209
+ end
210
+
211
+ it "does not find content in the wrong content directory" do
212
+ location = druid.metadata_dir
213
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
214
+ expect(druid.find_content('someContent')).to be_nil
215
+ end
216
+
217
+ it "does not find content in a higher-up directory" do
218
+ location = File.expand_path('../..', druid.path(nil, true))
219
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
220
+ expect(druid.find_content('someContent')).to be_nil
221
+ end
222
+
223
+ it "finds a filelist in the content directory" do
224
+ location = Pathname(druid.content_dir)
225
+ filelist.each do |filename|
226
+ location.join(filename).open('w') { |f| f.write "This is #{filename}" }
227
+ end
228
+ expect(druid.find_filelist_parent('content', filelist)).to eq(location)
229
+ end
230
+
231
+ it "finds a filelist in the root directory" do
232
+ location = Pathname(druid.path(nil, true))
233
+ filelist.each do |filename|
234
+ location.join(filename).open('w') { |f| f.write "This is #{filename}" }
235
+ end
236
+ expect(druid.find_filelist_parent('content', filelist)).to eq(location)
237
+ end
238
+
239
+ it "finds a filelist in the leaf directory" do
240
+ location = Pathname(File.expand_path('..', druid.path(nil, true)))
241
+ filelist.each do |filename|
242
+ location.join(filename).open('w') { |f| f.write "This is #{filename}" }
243
+ end
244
+ expect(druid.find_filelist_parent('content', filelist)).to eq(location)
245
+ end
246
+
247
+ it "raises an exception if the first file in the filelist is not found" do
248
+ location = Pathname(druid.content_dir)
249
+ expect{druid.find_filelist_parent('content', filelist)}.to raise_exception(/content dir not found for 'someFile1' when searching/)
250
+ end
251
+
252
+ it "raises an exception if any other file in the filelist is not found" do
253
+ location = Pathname(druid.content_dir)
254
+ location.join(filelist.first).open('w') { |f| f.write "This is #{filelist.first}" }
255
+ expect{druid.find_filelist_parent('content', filelist)}.to raise_exception(/File 'someFile2' not found/)
256
+ end
257
+ end
258
+
259
+ describe "#mkdir error handling" do
260
+ it "raises SameContentExistsError if the directory already exists" do
261
+ druid_obj = DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir)
262
+ druid_obj.mkdir
263
+ expect { druid_obj.mkdir }.to raise_error(DruidTools::SameContentExistsError)
264
+ end
265
+
266
+ it "raises DifferentContentExistsError if a link already exists in the workspace for this druid" do
267
+ source_dir = '/tmp/content_dir'
268
+ FileUtils.mkdir_p(source_dir)
269
+ dr = DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir)
270
+ dr.mkdir_with_final_link(source_dir)
271
+ expect { dr.mkdir }.to raise_error(DruidTools::DifferentContentExistsError)
272
+ end
273
+ end
274
+
275
+ describe "#mkdir_with_final_link" do
276
+ let(:source_dir) { '/tmp/content_dir' }
277
+ let(:druid_obj){ DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir) }
278
+
279
+ before(:each) do
280
+ FileUtils.mkdir_p(source_dir)
281
+ end
282
+
283
+ it "creates a druid tree in the workspace with the final directory being a link to the passed in source" do
284
+ druid_obj.mkdir_with_final_link(source_dir)
285
+ expect(File).to be_symlink(druid_obj.path)
286
+ expect(File.readlink(tree_2)).to eq(source_dir)
287
+ end
288
+
289
+ it "does not error out if the link to source already exists" do
290
+ druid_obj.mkdir_with_final_link(source_dir)
291
+ expect(File).to be_symlink(druid_obj.path)
292
+ expect(File.readlink(tree_2)).to eq(source_dir)
293
+ end
294
+
295
+ it "raises DifferentContentExistsError if a directory already exists in the workspace for this druid" do
296
+ druid_obj.mkdir(fixture_dir)
297
+ expect { druid_obj.mkdir_with_final_link(source_dir) }.to raise_error(DruidTools::DifferentContentExistsError)
298
+ end
299
+ end
300
+
301
+ describe "#prune!" do
302
+ let(:workspace) { Dir.mktmpdir }
303
+ let(:dr1) { DruidTools::Druid.new(druid_str, workspace) }
304
+ let(:dr2) { DruidTools::Druid.new(strictly_valid_druid_str, workspace) }
305
+ let(:pathname1) { dr1.pathname }
306
+
307
+ after(:each) do
308
+ FileUtils.remove_entry workspace
309
+ end
310
+
311
+ it "throws error on misconfig when base dir cannot be created" do
312
+ dir = '/some/dir/that/does/not/exist' # we don't have permissions to create
313
+ dr0 = DruidTools::Druid.new(druid_str, dir)
314
+ expect {dr0.prune!}.to raise_error(StandardError)
315
+ expect(File).to_not exist(dir)
316
+ end
317
+
318
+ it "does not throw error when base can be created" do
319
+ subdir = File.join(Dir.mktmpdir, 'some', 'nonexistant', 'subdir') # but this one *can* be created
320
+ dr2 = DruidTools::Druid.new(strictly_valid_druid_str, subdir)
321
+ expect {dr2.prune!}.not_to raise_error()
322
+ expect(File).to exist(subdir)
323
+ end
324
+
325
+ context "shared ancestor" do
326
+
327
+ before(:each) do
328
+ #Nil the create records for this context because we're in a known read only one
329
+ dr1.mkdir
330
+ dr2.mkdir
331
+ dr1.prune!
332
+ end
333
+
334
+ it "deletes the outermost directory" do
335
+ expect(File).to_not exist(dr1.path)
336
+ end
337
+
338
+ it "deletes empty ancestor directories" do
339
+ expect(File).to_not exist(pathname1.parent)
340
+ expect(File).to_not exist(pathname1.parent.parent)
341
+ end
342
+
343
+ it "stops at ancestor directories that have children" do
344
+ # 'cd/456' should still exist because of druid2
345
+ shared_ancestor = pathname1.parent.parent.parent
346
+ expect(shared_ancestor.to_s).to match(/cd\/456$/)
347
+ expect(File).to exist(shared_ancestor)
348
+ end
349
+ end
350
+
351
+ it "removes all directories up to the base path when there are no common ancestors" do
352
+ #Make sure a delete record is not present
353
+ expect(dr1.deletes_record_exists?).to be_falsey
354
+
355
+ #Nil the create records for this test
356
+ dr1.mkdir
357
+ dr1.prune!
358
+ expect(File).to_not exist(File.join(workspace, 'cd'))
359
+ expect(File).to exist(workspace)
360
+
361
+ #Make sure a delete record was created
362
+ expect(dr1.deletes_dir_exists?).to be_truthy
363
+ expect(dr1.deletes_record_exists?).to be_truthy
364
+ end
365
+
366
+ it "removes directories with symlinks" do
367
+ #Make sure a delete record is not present
368
+ expect(dr2.deletes_record_exists?).to be_falsey
369
+
370
+ #Nil the create records for this test
371
+ source_dir = File.join workspace, 'src_dir'
372
+ FileUtils.mkdir_p(source_dir)
373
+ dr2.mkdir_with_final_link(source_dir)
374
+ dr2.prune!
375
+ expect(File).to_not exist(dr2.path)
376
+ expect(File).to_not exist(File.join(workspace, 'cd'))
377
+
378
+ #Make sure a delete record was created
379
+ expect(dr2.deletes_dir_exists?).to be_truthy
380
+ expect(dr2.deletes_record_exists?).to be_truthy
381
+ end
382
+
383
+ describe "logging deleted druids" do
384
+
385
+ #Purge any paths or delete records created in the test
386
+ after :each do
387
+ #Remove the .deletes dir to clean up
388
+ dr2.deletes_delete_record if dr2.deletes_record_exists?
389
+ FileUtils.rm_rf dr2.deletes_dir_pathname
390
+ end
391
+
392
+ it "returns the path to the .deletes directory as a Pathname" do
393
+ expect(dr2.deletes_dir_pathname.class).to eq(Pathname)
394
+ end
395
+
396
+ it "returns the path to the delete record for a druid as a Pathname" do
397
+ expect(dr2.deletes_record_pathname.class).to eq(Pathname)
398
+ end
399
+
400
+ it "returns the path to the delete record for a druid as top_level/.deletes/druid" do
401
+ expect(dr2.deletes_record_pathname.to_s).to eq("#{dr2.base}/.deletes/#{dr2.id}")
402
+ end
403
+
404
+ it "returns false when the .deletes dir is not present on the file system" do
405
+ expect(dr2.deletes_dir_exists?).to be_falsey
406
+ end
407
+
408
+ it "creates the .deletes dir and detect it exists" do
409
+
410
+ #Clean the .deletes dir if present
411
+ FileUtils.rm_rf dr2.deletes_dir_pathname
412
+
413
+ #Test for exists? and create
414
+ expect(dr2.deletes_dir_exists?).to be_falsey
415
+ dr2.create_deletes_dir
416
+ expect(dr2.deletes_dir_exists?).to be_truthy
417
+ end
418
+
419
+ it "returns false when the .deletes dir does not have a deleted record for a druid" do
420
+ expect(dr2.deletes_record_exists?).to be_falsey
421
+ end
422
+
423
+ it "creates a deleted record with a parent directory that has no .deletes directory and no deleted for the file and successfully create a delete record there" do
424
+ #Expect there not to be a .deletes dir or file (the file expectation is redundant I know)
425
+ expect(dr2.deletes_dir_exists?).to be_falsey
426
+ expect(dr2.deletes_record_exists?).to be_falsey
427
+
428
+ #Create the delete record
429
+ dr2.creates_delete_record
430
+
431
+ #Check to ensure items were created
432
+ expect(dr2.deletes_dir_exists?).to be_truthy
433
+ expect(dr2.deletes_record_exists?).to be_truthy
434
+ end
435
+
436
+ it "creates a delete record with a parent directory that has a .deletes directory that does not contain a delete record for this druid" do
437
+ #Expect there not to be a .deletes dir or file (the file expectation is redundant I know)
438
+ expect(dr2.deletes_dir_exists?).to be_falsey
439
+ expect(dr2.deletes_record_exists?).to be_falsey
440
+
441
+ #Creates the deletes dir and check
442
+ dr2.create_deletes_dir
443
+ expect(dr2.deletes_dir_exists?).to be_truthy
444
+ expect(dr2.deletes_record_exists?).to be_falsey
445
+
446
+ #Create the delete record
447
+ dr2.creates_delete_record
448
+
449
+ #Check to ensure items were created
450
+ expect(dr2.deletes_dir_exists?).to be_truthy
451
+ expect(dr2.deletes_record_exists?).to be_truthy
452
+ end
453
+
454
+ it "creates a delete record with a parent directory that does not have a .deletes directory and contains an older delete record" do
455
+ #Expect there not to be a .deletes dir or file (the file expectation is redundant I know)
456
+ expect(dr2.deletes_dir_exists?).to be_falsey
457
+ expect(dr2.deletes_record_exists?).to be_falsey
458
+
459
+ dr2.creates_delete_record
460
+ time = Time.now
461
+ expect(File.mtime(dr2.deletes_record_pathname)).to be <= time
462
+ sleep(1) #force a one second pause in case the machine is fast, since mtime only goes down to the second
463
+
464
+ dr2.creates_delete_record
465
+ #Should have a new newer deleted record
466
+ expect(File.mtime(dr2.deletes_record_pathname)).to be > time
467
+ end
468
+ end
469
+ end
470
+ end