zizia 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rubocop.yml +65 -0
  4. data/.rubocop_todo.yml +21 -0
  5. data/.solr_wrapper +8 -0
  6. data/.travis.yml +11 -0
  7. data/Gemfile +12 -0
  8. data/README.md +77 -0
  9. data/Rakefile +34 -0
  10. data/docs/_config.yml +1 -0
  11. data/docs/index.md +98 -0
  12. data/lib/zizia/always_invalid_validator.rb +17 -0
  13. data/lib/zizia/hash_mapper.rb +44 -0
  14. data/lib/zizia/hyrax_basic_metadata_mapper.rb +149 -0
  15. data/lib/zizia/hyrax_record_importer.rb +261 -0
  16. data/lib/zizia/importer.rb +61 -0
  17. data/lib/zizia/input_record.rb +65 -0
  18. data/lib/zizia/log_stream.rb +43 -0
  19. data/lib/zizia/metadata_mapper.rb +83 -0
  20. data/lib/zizia/metadata_only_stack.rb +70 -0
  21. data/lib/zizia/parser.rb +132 -0
  22. data/lib/zizia/parsers/csv_parser.rb +45 -0
  23. data/lib/zizia/record_importer.rb +57 -0
  24. data/lib/zizia/spec/fakes/fake_parser.rb +22 -0
  25. data/lib/zizia/spec/shared_examples/a_mapper.rb +32 -0
  26. data/lib/zizia/spec/shared_examples/a_message_stream.rb +11 -0
  27. data/lib/zizia/spec/shared_examples/a_parser.rb +73 -0
  28. data/lib/zizia/spec/shared_examples/a_validator.rb +46 -0
  29. data/lib/zizia/spec.rb +15 -0
  30. data/lib/zizia/streams/formatted_message_stream.rb +70 -0
  31. data/lib/zizia/validator.rb +117 -0
  32. data/lib/zizia/validators/csv_format_validator.rb +26 -0
  33. data/lib/zizia/validators/title_validator.rb +30 -0
  34. data/lib/zizia/version.rb +5 -0
  35. data/lib/zizia.rb +73 -0
  36. data/log/.keep +0 -0
  37. data/spec/fixtures/bad_example.csv +2 -0
  38. data/spec/fixtures/example.csv +4 -0
  39. data/spec/fixtures/hyrax/example.csv +3 -0
  40. data/spec/fixtures/images/animals/cat.png +0 -0
  41. data/spec/fixtures/images/zizia.png +0 -0
  42. data/spec/fixtures/zizia.png +0 -0
  43. data/spec/integration/import_csv_spec.rb +28 -0
  44. data/spec/integration/import_hyrax_csv.rb +71 -0
  45. data/spec/spec_helper.rb +18 -0
  46. data/spec/stdout_stream_spec.rb +9 -0
  47. data/spec/support/hyrax/basic_metadata.rb +30 -0
  48. data/spec/support/hyrax/core_metadata.rb +15 -0
  49. data/spec/support/shared_contexts/with_work_type.rb +101 -0
  50. data/spec/zizia/csv_format_validator_spec.rb +38 -0
  51. data/spec/zizia/csv_parser_spec.rb +73 -0
  52. data/spec/zizia/formatted_message_stream_spec.rb +35 -0
  53. data/spec/zizia/hash_mapper_spec.rb +8 -0
  54. data/spec/zizia/hyrax_basic_metadata_mapper_spec.rb +190 -0
  55. data/spec/zizia/hyrax_record_importer_spec.rb +178 -0
  56. data/spec/zizia/importer_spec.rb +46 -0
  57. data/spec/zizia/input_record_spec.rb +71 -0
  58. data/spec/zizia/parser_spec.rb +47 -0
  59. data/spec/zizia/record_importer_spec.rb +70 -0
  60. data/spec/zizia/title_validator_spec.rb +23 -0
  61. data/spec/zizia/validator_spec.rb +9 -0
  62. data/spec/zizia/version_spec.rb +7 -0
  63. data/spec/zizia_spec.rb +19 -0
  64. data/zizia.gemspec +34 -0
  65. metadata +246 -0
@@ -0,0 +1,190 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+
4
+ describe Zizia::HyraxBasicMetadataMapper do
5
+ let(:mapper) { described_class.new }
6
+
7
+ # Properties defined in Hyrax::CoreMetadata
8
+ let(:core_fields) do
9
+ [:depositor, :title, :date_modified]
10
+ end
11
+
12
+ # Properties defined in Hyrax::BasicMetadata
13
+ let(:basic_fields) do
14
+ [:label, :relative_path, :import_url,
15
+ :resource_type, :creator, :contributor,
16
+ :description, :keyword, :license,
17
+ :rights_statement, :publisher, :date_created,
18
+ :subject, :language, :identifier, :based_near,
19
+ :related_url, :bibliographic_citation, :source]
20
+ end
21
+
22
+ let(:tenejo_fields) do
23
+ [:visibility]
24
+ end
25
+
26
+ it_behaves_like 'a Zizia::Mapper' do
27
+ let(:metadata) do
28
+ { title: ['A Title for a Record'],
29
+ my_custom_field: ['This gets ignored'] }
30
+ end
31
+ let(:expected_fields) { core_fields + basic_fields + tenejo_fields }
32
+ end
33
+
34
+ context 'with metadata, but some missing fields' do
35
+ before { mapper.metadata = metadata }
36
+ let(:metadata) do
37
+ { 'depositor' => 'someone@example.org',
38
+ 'title' => 'A Title',
39
+ 'language' => 'English' }
40
+ end
41
+
42
+ it 'provides methods for the fields, even fields that aren\'t included in the metadata' do
43
+ expect(metadata).to include('title')
44
+ expect(mapper).to respond_to(:title)
45
+
46
+ expect(metadata).not_to include('label')
47
+ expect(mapper).to respond_to(:label)
48
+ end
49
+
50
+ it 'returns single values for single-value fields' do
51
+ expect(mapper.depositor).to eq 'someone@example.org'
52
+ expect(mapper.date_modified).to eq nil
53
+ expect(mapper.label).to eq nil
54
+ expect(mapper.relative_path).to eq nil
55
+ expect(mapper.import_url).to eq nil
56
+ end
57
+
58
+ it 'returns array values for multi-value fields' do
59
+ expect(mapper.title).to eq ['A Title']
60
+ expect(mapper.language).to eq ['English']
61
+ expect(mapper.keyword).to eq []
62
+ expect(mapper.subject).to eq []
63
+ end
64
+ end
65
+
66
+ context 'fields with multiple values' do
67
+ before { mapper.metadata = metadata }
68
+ let(:metadata) do
69
+ { 'title' => 'A Title',
70
+ 'language' => 'English|~|French|~|Japanese' }
71
+ end
72
+
73
+ it 'splits the values using the delimiter' do
74
+ expect(mapper.title).to eq ['A Title']
75
+ expect(mapper.language).to eq ['English', 'French', 'Japanese']
76
+ expect(mapper.keyword).to eq []
77
+ end
78
+
79
+ it 'can set a different delimiter' do
80
+ expect(mapper.delimiter).to eq '|~|'
81
+ mapper.delimiter = 'ಠ_ಠ'
82
+ expect(mapper.delimiter).to eq 'ಠ_ಠ'
83
+ end
84
+ end
85
+
86
+ describe 'lenient headers' do
87
+ context 'headers with capital letters' do
88
+ before { mapper.metadata = metadata }
89
+ let(:metadata) do
90
+ { 'Title' => 'A Title',
91
+ 'Related URL' => 'http://example.com',
92
+ 'Abstract or Summary' => 'desc1|~|desc2',
93
+ 'visiBILITY' => 'open',
94
+ 'Depositor' => 'someone@example.org',
95
+ 'DATE_modified' => 'mod date',
96
+ 'laBel' => 'label',
97
+ 'relative_PATH' => 'rel path',
98
+ 'import_URL' => 'imp url' }
99
+ end
100
+
101
+ it 'matches the correct fields' do
102
+ expect(mapper.title).to eq ['A Title']
103
+ expect(mapper.related_url).to eq ['http://example.com']
104
+ expect(mapper.description).to eq ['desc1', 'desc2']
105
+ expect(mapper.creator).to eq []
106
+ expect(mapper.visibility).to eq 'open'
107
+ expect(mapper.depositor).to eq 'someone@example.org'
108
+ expect(mapper.date_modified).to eq 'mod date'
109
+ expect(mapper.label).to eq 'label'
110
+ expect(mapper.relative_path).to eq 'rel path'
111
+ expect(mapper.import_url).to eq 'imp url'
112
+ end
113
+ end
114
+
115
+ context 'headers with sloppy whitespace' do
116
+ before { mapper.metadata = metadata }
117
+ let(:metadata) do
118
+ { ' Title ' => 'A Title',
119
+ " Related URL \n " => 'http://example.com',
120
+ ' visiBILITY ' => 'open' }
121
+ end
122
+
123
+ it 'matches the correct fields' do
124
+ expect(mapper.title).to eq ['A Title']
125
+ expect(mapper.related_url).to eq ['http://example.com']
126
+ expect(mapper.visibility).to eq 'open'
127
+ end
128
+ end
129
+
130
+ context 'Visibility values in the CSV should match the Edit UI' do
131
+ load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__)
132
+ include_context 'with a work type'
133
+ context 'public is a synonym for open' do
134
+ before { mapper.metadata = metadata }
135
+ let(:metadata) do
136
+ { ' Title ' => 'A Title',
137
+ " Related URL \n " => 'http://example.com',
138
+ ' visiBILITY ' => 'PubLIC' }
139
+ end
140
+
141
+ it 'transforms public to open regardless of capitalization' do
142
+ expect(mapper.title).to eq ['A Title']
143
+ expect(mapper.related_url).to eq ['http://example.com']
144
+ expect(mapper.visibility).to eq 'open'
145
+ end
146
+ end
147
+ context 'institution name is a synonym for authenticated' do
148
+ before { mapper.metadata = metadata }
149
+ let(:metadata) do
150
+ { ' Title ' => 'A Title',
151
+ " Related URL \n " => 'http://example.com',
152
+ ' visiBILITY ' => 'my_institution' }
153
+ end
154
+
155
+ it 'transforms institution name to authenticated regardless of capitalization' do
156
+ expect(mapper.title).to eq ['A Title']
157
+ expect(mapper.related_url).to eq ['http://example.com']
158
+ expect(mapper.visibility).to eq 'authenticated'
159
+ end
160
+ end
161
+ context 'full institution name is a synonym for authenticated' do
162
+ before { mapper.metadata = metadata }
163
+ let(:metadata) do
164
+ { ' Title ' => 'A Title',
165
+ " Related URL \n " => 'http://example.com',
166
+ ' visiBILITY ' => 'my full institution name' }
167
+ end
168
+
169
+ it 'transforms full institution name to authenticated regardless of capitalization' do
170
+ expect(mapper.title).to eq ['A Title']
171
+ expect(mapper.related_url).to eq ['http://example.com']
172
+ expect(mapper.visibility).to eq 'authenticated'
173
+ end
174
+ end
175
+ end
176
+
177
+ # When someone accidentally has too many commas in the CSV rows
178
+ context 'headers with a nil' do
179
+ before { mapper.metadata = metadata }
180
+ let(:metadata) do
181
+ { ' Title ' => 'A Title',
182
+ nil => nil }
183
+ end
184
+
185
+ it 'doesn\'t raise an error for missing fields' do
186
+ expect(mapper.depositor).to eq nil
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+
4
+ describe Zizia::HyraxRecordImporter, :clean do
5
+ subject(:importer) do
6
+ described_class.new(error_stream: error_stream, info_stream: info_stream)
7
+ end
8
+
9
+ load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__)
10
+ include_context 'with a work type'
11
+
12
+ let(:error_stream) { [] }
13
+ let(:info_stream) { [] }
14
+ let(:record) { Zizia::InputRecord.from(metadata: metadata) }
15
+
16
+ context 'collection id' do
17
+ subject(:importer) do
18
+ described_class.new(attributes: { collection_id: collection_id })
19
+ end
20
+ let(:collection_id) { '123' }
21
+
22
+ it 'can have a collection id' do
23
+ expect(importer.collection_id).to eq collection_id
24
+ end
25
+ end
26
+
27
+ context 'with no attached files' do
28
+ let(:metadata) do
29
+ {
30
+ 'title' => 'A Title',
31
+ 'language' => 'English',
32
+ 'visibility' => 'open'
33
+ }
34
+ end
35
+
36
+ it 'creates a work for record' do
37
+ expect { importer.import(record: record) }
38
+ .to change { Work.count }
39
+ .by 1
40
+ end
41
+ end
42
+
43
+ # Instead of having a files field in the mapper, which will create a
44
+ # Hyrax::UploadedFile for each file before attaching it, some importers will
45
+ # use a remote_files strategy and instead treat each file as a remote file and
46
+ # fetch it at object creation time. This might be faster, and we might eventually
47
+ # want to adopt it as our default. For now, do not raise an error if there is no
48
+ # `files` field in the mapper being used.
49
+ context 'with no files filed in the mapper' do
50
+ let(:metadata) do
51
+ {
52
+ 'title' => 'A Title',
53
+ 'language' => 'English',
54
+ 'visibility' => 'open'
55
+ }
56
+ end
57
+ let(:record) { Zizia::InputRecord.from(metadata: metadata, mapper: Zizia::MetadataMapper.new) }
58
+
59
+ it 'creates a work for record' do
60
+ expect { importer.import(record: record) }
61
+ .to change { Work.count }
62
+ .by 1
63
+ end
64
+ end
65
+
66
+ context 'with attached files' do
67
+ before do
68
+ ENV['IMPORT_PATH'] = File.expand_path('../fixtures/images', File.dirname(__FILE__))
69
+ end
70
+ let(:metadata) do
71
+ {
72
+ 'title' => 'A Title',
73
+ 'language' => 'English',
74
+ 'visibility' => 'open',
75
+ 'files' => 'zizia.png|~|cat.png'
76
+ }
77
+ end
78
+ it 'finds a file even if it is in a subdirectory' do
79
+ expect(importer.find_file_path('cat.png')).to eq "#{ENV['IMPORT_PATH']}/animals/cat.png"
80
+ end
81
+ it 'creates a work for record' do
82
+ expect { importer.import(record: record) }
83
+ .to change { Work.count }
84
+ .by 1
85
+ end
86
+ it 'makes an uploaded file object for each file attachment' do
87
+ expect { importer.import(record: record) }
88
+ .to change { Hyrax::UploadedFile.count }
89
+ .by 2
90
+ end
91
+ end
92
+
93
+ context 'with attached files, alternate capitalization and whitespace in "files" header' do
94
+ before do
95
+ ENV['IMPORT_PATH'] = File.expand_path('../fixtures/images', File.dirname(__FILE__))
96
+ end
97
+ let(:metadata) do
98
+ {
99
+ 'title' => 'A Title',
100
+ 'visibility' => 'open',
101
+ ' Files' => 'zizia.png|~|cat.png'
102
+ }
103
+ end
104
+
105
+ it 'makes an uploaded file object for each file attachment' do
106
+ expect { importer.import(record: record) }
107
+ .to change { Hyrax::UploadedFile.count }
108
+ .by 2
109
+ end
110
+ end
111
+
112
+ context 'with missing files' do
113
+ before do
114
+ ENV['IMPORT_PATH'] = File.expand_path('../fixtures/images', File.dirname(__FILE__))
115
+ end
116
+ it 'raises an exception' do
117
+ expect { importer.find_file_path('foo.png') }.to raise_exception(RuntimeError)
118
+ end
119
+ end
120
+
121
+ describe '#set_depositor' do
122
+ let(:metadata) { { 'title' => 'A Title' } }
123
+
124
+ context 'when no depositor is set' do
125
+ it 'sets the Hyrax default batch user' do
126
+ expect(importer.depositor.user_key).to eq 'batchuser@example.com'
127
+ end
128
+ end
129
+
130
+ context 'when depositor is passed to initializer' do
131
+ subject(:importer) { described_class.new(error_stream: error_stream, info_stream: info_stream, attributes: { depositor_id: user.user_key }) }
132
+
133
+ let(:user) { ::User.new(id: '123', user_key: 'special_user@example.com') }
134
+ before { allow(::User).to receive(:find).and_return(user) }
135
+
136
+ it 'sets it to the passed-in depositor' do
137
+ expect(importer.depositor.user_key).to eq 'special_user@example.com'
138
+ end
139
+ end
140
+
141
+ context 'when depositor is set in metadata' do
142
+ let(:metadata) do
143
+ { 'title' => 'A Title',
144
+ 'Depositor' => 'metadata_user@example.com' }
145
+ end
146
+
147
+ it 'sets the Hyrax default batch user' do
148
+ expect(importer.depositor.user_key).to eq 'batchuser@example.com'
149
+ # TODO: expect(importer.depositor.user_key).to eq 'metadata_user@example.com'
150
+ # The metadata depositor should probably override any passed-in or default depositor.
151
+ end
152
+ end
153
+ end
154
+ # When submitting location data (a.k.a., the "based near" attribute) via the UI,
155
+ # Hyrax expects to receive a `based_near_attributes` hash in a specific format.
156
+ # We need to take geonames urls as provided by the customer and transform them to
157
+ # mimic what the Hyrax UI would ordinarily produce. These will get turned into
158
+ # Hyrax::ControlledVocabularies::Location objects upon ingest.
159
+ context 'with location uris' do
160
+ let(:based_near) { ['http://www.geonames.org/5667009/montana.html', 'http://www.geonames.org/6252001/united-states.html'] }
161
+ let(:expected_bn_hash) do
162
+ {
163
+ "0" => {
164
+ "id" => "http://sws.geonames.org/5667009/", "_destroy" => ""
165
+ },
166
+ "1" => {
167
+ "id" => "http://sws.geonames.org/6252001/", "_destroy" => ""
168
+ }
169
+ }
170
+ end
171
+ it "gets a sws uri from a geonames uri" do
172
+ expect(importer.uri_to_sws("http://www.geonames.org/6252001/united-states.html")).to eq "http://sws.geonames.org/6252001/"
173
+ end
174
+ it 'transforms an array of geonames uris into the expected based_near_attributes hash' do
175
+ expect(importer.based_near_attributes(based_near)).to eq expected_bn_hash
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Zizia::Importer do
6
+ load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__)
7
+ include_context 'with a work type'
8
+
9
+ subject(:importer) { described_class.new(parser: parser) }
10
+ let(:parser) { FakeParser.new(file: input) }
11
+ let(:input) { [{ 'title' => '1' }, { 'title' => '2' }, { 'title' => '3' }] }
12
+
13
+ let(:fake_record_importer) do
14
+ Class.new do
15
+ attr_accessor :batch_id, :success_count, :failure_count
16
+
17
+ def import(record:)
18
+ records << record.attributes
19
+ end
20
+
21
+ def records
22
+ @records ||= []
23
+ end
24
+ end
25
+ end
26
+
27
+ describe '#records' do
28
+ it 'reflects the parsed records' do
29
+ expect(importer.records.map(&:attributes))
30
+ .to contain_exactly(*parser.records.map(&:attributes))
31
+ end
32
+ end
33
+
34
+ describe '#import' do
35
+ let(:record_importer) { fake_record_importer.new }
36
+
37
+ before { importer.record_importer = record_importer }
38
+
39
+ it 'sends records to the record importer' do
40
+ expect { importer.import }
41
+ .to change { record_importer.records }
42
+ .from(be_empty)
43
+ .to a_collection_containing_exactly(*importer.records.map(&:attributes))
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Zizia::InputRecord do
6
+ load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__)
7
+ include_context 'with a work type'
8
+ subject(:record) { described_class.from(metadata: metadata) }
9
+
10
+ let(:metadata) do
11
+ { 'title' => 'Comet in Moominland',
12
+ 'abstract or summary' => 'A book about moomins.' }
13
+ end
14
+
15
+ it 'defaults to a Hyrax Mapper' do
16
+ expect(described_class.new).to have_attributes(mapper: an_instance_of(Zizia::HyraxBasicMetadataMapper))
17
+ end
18
+
19
+ it 'has metadata and a mapper' do
20
+ is_expected
21
+ .to have_attributes(mapper: an_instance_of(Zizia::HyraxBasicMetadataMapper))
22
+ end
23
+
24
+ describe '#attributes' do
25
+ it 'handles basic text fields' do
26
+ expect(record.attributes).to include(:title, :description)
27
+ end
28
+
29
+ it 'does not include representative_file' do
30
+ expect(record.attributes).not_to include(:representative_file)
31
+ end
32
+ end
33
+
34
+ describe '#representative_file' do
35
+ it 'is nil if mapper does not provide a representative file' do
36
+ expect(record.representative_file).to be_nil
37
+ end
38
+
39
+ context 'when mapper provides representative_file' do
40
+ let(:representative_file) { :A_DUMMY_FILE }
41
+
42
+ before do
43
+ allow(record.mapper)
44
+ .to receive(:representative_file)
45
+ .and_return(representative_file)
46
+ end
47
+
48
+ it 'is the file from the mapper' do
49
+ expect(record.representative_file).to eql representative_file
50
+ end
51
+ end
52
+ end
53
+
54
+ describe 'mapped fields' do
55
+ it 'has methods for metadata fields' do
56
+ expect(record.title).to contain_exactly metadata['title']
57
+ end
58
+
59
+ it 'has methods for additional mapped metadata fields' do
60
+ expect(record.description).to contain_exactly metadata['abstract or summary']
61
+ end
62
+
63
+ it 'knows it responds to methods for metadata fields' do
64
+ expect(record).to respond_to :title
65
+ end
66
+
67
+ it 'knows it responds to methods for additional metadata fields' do
68
+ expect(record).to respond_to :description
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Zizia::Parser do
6
+ subject(:parser) { described_class.new(file: file) }
7
+ let(:file) { :fake_file }
8
+
9
+ it_behaves_like 'a Zizia::Parser'
10
+
11
+ describe '.for' do
12
+ it 'raises an error' do
13
+ expect { described_class.for(file: file) }.to raise_error TypeError
14
+ end
15
+
16
+ context 'with a matching parser subclass' do
17
+ before(:context) do
18
+ ##
19
+ # An importer that matches all types
20
+ class MyFakeParser < described_class
21
+ class << self
22
+ def match?(**_opts)
23
+ true
24
+ end
25
+ end
26
+ end
27
+
28
+ class NestedParser < MyFakeParser; end
29
+ end
30
+
31
+ after(:context) do
32
+ Object.send(:remove_const, :MyFakeParser)
33
+ Object.send(:remove_const, :NestedParser)
34
+ end
35
+
36
+ it 'returns an importer instance' do
37
+ expect(described_class.for(file: file)).to be_a NestedParser
38
+ end
39
+ end
40
+ end
41
+
42
+ describe '#records' do
43
+ it 'raises NotImplementedError' do
44
+ expect { parser.records }.to raise_error NotImplementedError
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Zizia::RecordImporter, :clean do
6
+ subject(:importer) do
7
+ described_class.new(error_stream: error_stream, info_stream: info_stream)
8
+ end
9
+
10
+ let(:error_stream) { [] }
11
+ let(:info_stream) { [] }
12
+ let(:record) { Zizia::InputRecord.from(metadata: metadata) }
13
+ let(:metadata) do
14
+ {
15
+ 'title' => 'A Title',
16
+ 'language' => 'English',
17
+ 'visibility' => 'open'
18
+ }
19
+ end
20
+
21
+ it 'raises an error when no work type exists' do
22
+ expect { importer.import(record: record) }
23
+ .to raise_error 'No curation_concern found for import'
24
+ end
25
+
26
+ context 'with a registered work type' do
27
+ load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__)
28
+ include_context 'with a work type'
29
+
30
+ it 'creates a work for record' do
31
+ expect { importer.import(record: record) }
32
+ .to change { Work.count }
33
+ .by 1
34
+ end
35
+
36
+ it 'writes to the info stream before and after create' do
37
+ expect { importer.import(record: record) }
38
+ .to change { info_stream }
39
+ .to contain_exactly(/^Creating record/, /^Record created/)
40
+ end
41
+
42
+ context 'when input record errors with LDP errors' do
43
+ let(:ldp_error) { Ldp::PreconditionFailed }
44
+
45
+ before { allow(record).to receive(:attributes).and_raise(ldp_error) }
46
+
47
+ it 'writes errors to the error stream (no reraise!)' do
48
+ expect { importer.import(record: record) }
49
+ .to change { error_stream }
50
+ .to contain_exactly(an_instance_of(ldp_error))
51
+ end
52
+ end
53
+
54
+ context 'when input record errors unexpectedly' do
55
+ let(:custom_error) { Class.new(RuntimeError) }
56
+
57
+ before { allow(record).to receive(:attributes).and_raise(custom_error) }
58
+
59
+ it 'writes errors to the error stream' do
60
+ expect { begin; importer.import(record: record); rescue; end }
61
+ .to change { error_stream }
62
+ .to contain_exactly(an_instance_of(custom_error))
63
+ end
64
+
65
+ it 'reraises error' do
66
+ expect { importer.import(record: record) }.to raise_error(custom_error)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Zizia::TitleValidator do
6
+ subject(:validator) { described_class.new(error_stream: []) }
7
+
8
+ let(:invalid_parser) do
9
+ FakeParser.new(file: [{ 'title' => 'moomin' }, {}, {}])
10
+ end
11
+
12
+ it_behaves_like 'a Zizia::Validator' do
13
+ let(:valid_parser) { FakeParser.new(file: [{ 'title' => 'moomin' }]) }
14
+ end
15
+
16
+ describe '#validate' do
17
+ it 'populates errors for records with missing titles' do
18
+ expect(validator.validate(parser: invalid_parser))
19
+ .to contain_exactly(an_instance_of(described_class::Error),
20
+ an_instance_of(described_class::Error))
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Zizia::Validator do
6
+ it_behaves_like 'a Zizia::Validator' do
7
+ let(:valid_parser) { :any }
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Zizia::VERSION do
6
+ it { is_expected.to be_a String }
7
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Zizia do
6
+ describe '#config' do
7
+ it 'can set a default error stream' do
8
+ expect { described_class.config { |c| c.default_error_stream = STDOUT } }
9
+ .to change { described_class.config.default_error_stream }
10
+ .to(STDOUT)
11
+ end
12
+
13
+ it 'can set a default info stream' do
14
+ expect { described_class.config { |c| c.default_info_stream = STDOUT } }
15
+ .to change { described_class.config.default_info_stream }
16
+ .to(STDOUT)
17
+ end
18
+ end
19
+ end