druid-tools 0.4.1 → 1.0.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.
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