zizia 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|