zizia 1.0.1

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 (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