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.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +65 -0
- data/.rubocop_todo.yml +21 -0
- data/.solr_wrapper +8 -0
- data/.travis.yml +11 -0
- data/Gemfile +12 -0
- data/README.md +77 -0
- data/Rakefile +34 -0
- data/docs/_config.yml +1 -0
- data/docs/index.md +98 -0
- data/lib/zizia/always_invalid_validator.rb +17 -0
- data/lib/zizia/hash_mapper.rb +44 -0
- data/lib/zizia/hyrax_basic_metadata_mapper.rb +149 -0
- data/lib/zizia/hyrax_record_importer.rb +261 -0
- data/lib/zizia/importer.rb +61 -0
- data/lib/zizia/input_record.rb +65 -0
- data/lib/zizia/log_stream.rb +43 -0
- data/lib/zizia/metadata_mapper.rb +83 -0
- data/lib/zizia/metadata_only_stack.rb +70 -0
- data/lib/zizia/parser.rb +132 -0
- data/lib/zizia/parsers/csv_parser.rb +45 -0
- data/lib/zizia/record_importer.rb +57 -0
- data/lib/zizia/spec/fakes/fake_parser.rb +22 -0
- data/lib/zizia/spec/shared_examples/a_mapper.rb +32 -0
- data/lib/zizia/spec/shared_examples/a_message_stream.rb +11 -0
- data/lib/zizia/spec/shared_examples/a_parser.rb +73 -0
- data/lib/zizia/spec/shared_examples/a_validator.rb +46 -0
- data/lib/zizia/spec.rb +15 -0
- data/lib/zizia/streams/formatted_message_stream.rb +70 -0
- data/lib/zizia/validator.rb +117 -0
- data/lib/zizia/validators/csv_format_validator.rb +26 -0
- data/lib/zizia/validators/title_validator.rb +30 -0
- data/lib/zizia/version.rb +5 -0
- data/lib/zizia.rb +73 -0
- data/log/.keep +0 -0
- data/spec/fixtures/bad_example.csv +2 -0
- data/spec/fixtures/example.csv +4 -0
- data/spec/fixtures/hyrax/example.csv +3 -0
- data/spec/fixtures/images/animals/cat.png +0 -0
- data/spec/fixtures/images/zizia.png +0 -0
- data/spec/fixtures/zizia.png +0 -0
- data/spec/integration/import_csv_spec.rb +28 -0
- data/spec/integration/import_hyrax_csv.rb +71 -0
- data/spec/spec_helper.rb +18 -0
- data/spec/stdout_stream_spec.rb +9 -0
- data/spec/support/hyrax/basic_metadata.rb +30 -0
- data/spec/support/hyrax/core_metadata.rb +15 -0
- data/spec/support/shared_contexts/with_work_type.rb +101 -0
- data/spec/zizia/csv_format_validator_spec.rb +38 -0
- data/spec/zizia/csv_parser_spec.rb +73 -0
- data/spec/zizia/formatted_message_stream_spec.rb +35 -0
- data/spec/zizia/hash_mapper_spec.rb +8 -0
- data/spec/zizia/hyrax_basic_metadata_mapper_spec.rb +190 -0
- data/spec/zizia/hyrax_record_importer_spec.rb +178 -0
- data/spec/zizia/importer_spec.rb +46 -0
- data/spec/zizia/input_record_spec.rb +71 -0
- data/spec/zizia/parser_spec.rb +47 -0
- data/spec/zizia/record_importer_spec.rb +70 -0
- data/spec/zizia/title_validator_spec.rb +23 -0
- data/spec/zizia/validator_spec.rb +9 -0
- data/spec/zizia/version_spec.rb +7 -0
- data/spec/zizia_spec.rb +19 -0
- data/zizia.gemspec +34 -0
- 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
|
data/spec/zizia_spec.rb
ADDED
@@ -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
|