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
data/lib/zizia.rb ADDED
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Bulk object import for Samvera.
5
+ #
6
+ # == Importers
7
+ #
8
+ # {Importer} is the core class for importing records using {Zizia}.
9
+ # Importers accept a {Parser} and (optionally) a custom {RecordImporter}, and
10
+ # process each record in the given parser (see: {Parser#records}).
11
+ #
12
+ # @example Importing in bulk from a file
13
+ # parser = Zizia::Parser.for(file: File.new('path/to/file.ext'))
14
+ #
15
+ # Zizia::Importer.new(parser: parser).import if parser.validate
16
+ #
17
+ # @example A basic configuration
18
+ # Zizia.config do |config|
19
+ # # error/info streams must respond to `#<<`
20
+ # config.default_error_stream = MyErrorStream.new
21
+ # config.default_info_stream = STDOUT
22
+ # end
23
+ #
24
+ module Zizia
25
+ ##
26
+ # @yield the current configuration
27
+ # @yieldparam config [Zizia::Configuration]
28
+ #
29
+ # @return [Zizia::Configuration] the current configuration
30
+ def config
31
+ yield @configuration if block_given?
32
+ @configuration
33
+ end
34
+ module_function :config
35
+
36
+ require 'zizia/log_stream'
37
+ ##
38
+ # Module-wide options for `Zizia`.
39
+ class Configuration
40
+ ##
41
+ # @!attribute [rw] default_error_stream
42
+ # @return [#<<]
43
+ # @!attribute [rw] default_info_stream
44
+ # @return [#<<]
45
+ attr_accessor :default_error_stream, :default_info_stream
46
+
47
+ def initialize
48
+ self.default_error_stream = Zizia::LogStream.new
49
+ self.default_info_stream = Zizia::LogStream.new
50
+ end
51
+ end
52
+
53
+ @configuration = Configuration.new
54
+
55
+ require 'zizia/version'
56
+ require 'zizia/metadata_mapper'
57
+ require 'zizia/hash_mapper'
58
+ require 'zizia/hyrax_basic_metadata_mapper'
59
+
60
+ require 'zizia/importer'
61
+ require 'zizia/record_importer'
62
+ require 'zizia/hyrax_record_importer'
63
+
64
+ require 'zizia/input_record'
65
+
66
+ require 'zizia/validator'
67
+ require 'zizia/validators/csv_format_validator'
68
+ require 'zizia/validators/title_validator'
69
+
70
+ require 'zizia/parser'
71
+ require 'zizia/parsers/csv_parser'
72
+ require 'zizia/metadata_only_stack'
73
+ end
data/log/.keep ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ title,description
2
+ fake title, "fake description--quote wrap after delimiter then space is invalid"
@@ -0,0 +1,4 @@
1
+ title,description
2
+ Fake Item,A description of a fake item.
3
+ Fake Item with Quoted Description,"Lorem ipsum dolor sit amet, cu mel habeo antiopam, id pri mucius oporteat. No graeco accumsan deterruisset est. Vix te sonet doctus perpetua, mei at odio eius nostrum. Ex postea quidam menandri duo. Rebum ullum cu mel.\nAperiam malorum indoctum ad nec.\nIn duo nonumes accusata, detraxit adipisci philosophia quo id. Ei usu volutpat vituperatoribus. Ut veniam dolorem qui. Ei legendos erroribus usu. Mentitum moderatius ei est, mei eius magna alterum no. Legere vivendum per ad, vim ei putent facilis."
4
+ Fake Item with Unicode Description,"Лорем ипсум долор сит амет, ех мовет толлит модератиус ест. Еу яуидам сенсерит цонсецтетуер про, при иисяуе ерудити цоррумпит ат. Ех усу нусяуам пхаедрум темпорибус, ест ат омнесяуе инструцтиор.\nЯуо ех мелиоре инсоленс праесент, иудицо тантас еурипидис хис ут. Аццусам урбанитас инструцтиор ан еам. Но хас вениам дицунт дебитис, нец ут суас аццусам перицула, нец риденс аетерно виртуте не.\nАт про еним вереар, ут солум юсто меи."
@@ -0,0 +1,3 @@
1
+ title,depositor,date_modified,label,relative_path,import_url,resource type,creator,contributor,abstract or summary,keyword,license,rights statement,publisher,date created,subject,language,identifier,location,related url,bibliographic_citation,source
2
+ Work 1 Title,user@example.com,2018-01-01,Work 1 Label,tmp/files,https://example.com,Work 1 Type,Work 1 creator,Work 1 contrib,Desc 1,Key 1,Lic 1,RS 1,Pub 1,2018-06-06,Subj 1,English|~|Japanese,Ident 1,Based 1,https://example.com/related,Bib 1,Source 1
3
+ Work 2 Title,1970-12-21,,Work 2 Label,,,Work 2 Type,,,Desc 2,,,,Pub 2,,Subj 2
Binary file
Binary file
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'importing a csv batch', :clean do
6
+ subject(:importer) { Zizia::Importer.new(parser: parser) }
7
+ let(:parser) { Zizia::CsvParser.new(file: file) }
8
+ let(:file) { File.open('spec/fixtures/example.csv') }
9
+
10
+ load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__)
11
+ include_context 'with a work type'
12
+
13
+ it 'creates a record for each CSV line' do
14
+ expect { importer.import }.to change { Work.count }.to 3
15
+ end
16
+
17
+ describe 'validation' do
18
+ context 'with invalid CSV' do
19
+ let(:file) { File.open('spec/fixtures/bad_example.csv') }
20
+
21
+ it 'outputs invalid file notice to error stream' do
22
+ expect { parser.validate }
23
+ .to output(/^CSV::MalformedCSVError.*line 2/)
24
+ .to_stdout_from_any_process
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+
4
+ describe 'importing a CSV with Hyrax defaults', :clean do
5
+ subject(:importer) { Zizia::Importer.new(parser: parser, record_importer: record_importer) }
6
+ let(:parser) { Zizia::CsvParser.new(file: csv_file) }
7
+ let(:record_importer) { Zizia::HyraxRecordImporter.new }
8
+
9
+ let(:csv_file) { File.open('spec/fixtures/hyrax/example.csv') }
10
+ after { csv_file.close }
11
+
12
+ load File.expand_path("../../support/shared_contexts/with_work_type.rb", __FILE__)
13
+ include_context 'with a work type'
14
+
15
+ it 'creates the record(s)' do
16
+ expect { importer.import }.to change { Work.count }.to 2
17
+
18
+ works = Work.all
19
+ work1 = works.find { |w| w.title == ['Work 1 Title'] }
20
+ work2 = works.find { |w| w.title == ['Work 2 Title'] }
21
+
22
+ # First Record
23
+ expect(work1.depositor).to eq 'batchuser@example.com'
24
+ expect(work1.date_modified).to eq '2018-01-01'
25
+ expect(work1.label).to eq 'Work 1 Label'
26
+ expect(work1.relative_path).to eq 'tmp/files'
27
+ expect(work1.import_url).to eq 'https://example.com'
28
+ expect(work1.resource_type).to eq ['Work 1 Type']
29
+ expect(work1.creator).to eq ['Work 1 creator']
30
+ expect(work1.contributor).to eq ['Work 1 contrib']
31
+ expect(work1.description).to eq ['Desc 1']
32
+ expect(work1.keyword).to eq ['Key 1']
33
+ expect(work1.license).to eq ['Lic 1']
34
+ expect(work1.rights_statement).to eq ['RS 1']
35
+ expect(work1.publisher).to eq ['Pub 1']
36
+ expect(work1.date_created).to eq ['2018-06-06']
37
+ expect(work1.subject).to eq ['Subj 1']
38
+
39
+ # An example with 2 values
40
+ expect(work1.language).to contain_exactly('English', 'Japanese')
41
+
42
+ expect(work1.identifier).to eq ['Ident 1']
43
+ expect(work1.based_near).to eq ['Based 1']
44
+ expect(work1.related_url).to eq ['https://example.com/related']
45
+ expect(work1.bibliographic_citation).to eq ['Bib 1']
46
+ expect(work1.source).to eq ['Source 1']
47
+
48
+ # Second Record
49
+ expect(work2.depositor).to eq 'batchuser@example.com'
50
+ expect(work2.date_modified).to be_nil
51
+ expect(work2.label).to eq 'Work 2 Label'
52
+ expect(work2.relative_path).to be_nil
53
+ expect(work2.import_url).to be_nil
54
+ expect(work2.resource_type).to eq ['Work 2 Type']
55
+ expect(work2.creator).to eq []
56
+ expect(work2.contributor).to eq []
57
+ expect(work2.description).to eq ['Desc 2']
58
+ expect(work2.keyword).to eq []
59
+ expect(work2.license).to eq []
60
+ expect(work2.rights_statement).to eq []
61
+ expect(work2.publisher).to eq ['Pub 2']
62
+ expect(work2.date_created).to eq []
63
+ expect(work2.subject).to eq ['Subj 2']
64
+ expect(work2.language).to eq []
65
+ expect(work2.identifier).to eq []
66
+ expect(work2.based_near).to eq []
67
+ expect(work2.related_url).to eq []
68
+ expect(work2.bibliographic_citation).to eq []
69
+ expect(work2.source).to eq []
70
+ end
71
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pry' unless ENV['CI']
4
+ ENV['environment'] ||= 'test'
5
+
6
+ require 'bundler/setup'
7
+ require 'active_fedora'
8
+ require 'active_fedora/cleaner'
9
+ require 'zizia'
10
+ require 'zizia/spec'
11
+ require 'byebug'
12
+
13
+ RSpec.configure do |config|
14
+ config.filter_run focus: true
15
+ config.run_all_when_everything_filtered = true
16
+
17
+ config.before(:each, clean: true) { ActiveFedora::Cleaner.clean! }
18
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'STDOUT as a MessageStream' do
6
+ subject(:stream) { STDOUT }
7
+
8
+ it_behaves_like 'a Zizia::MessageStream'
9
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ module BasicMetadata
5
+ def self.included(work)
6
+ work.property :label, predicate: ActiveFedora::RDF::Fcrepo::Model.downloadFilename, multiple: false
7
+ work.property :relative_path, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#relativePath'), multiple: false
8
+ work.property :import_url, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#importUrl'), multiple: false
9
+ work.property :resource_type, predicate: ::RDF::Vocab::DC.type
10
+ work.property :creator, predicate: ::RDF::Vocab::DC11.creator
11
+ work.property :contributor, predicate: ::RDF::Vocab::DC11.contributor
12
+ work.property :description, predicate: ::RDF::Vocab::DC11.description
13
+ work.property :keyword, predicate: ::RDF::Vocab::DC11.relation
14
+ work.property :license, predicate: ::RDF::Vocab::DC.rights
15
+ work.property :rights_statement, predicate: ::RDF::Vocab::EDM.rights
16
+ work.property :publisher, predicate: ::RDF::Vocab::DC11.publisher
17
+ work.property :date_created, predicate: ::RDF::Vocab::DC.created
18
+ work.property :subject, predicate: ::RDF::Vocab::DC11.subject
19
+ work.property :language, predicate: ::RDF::Vocab::DC11.language
20
+ work.property :identifier, predicate: ::RDF::Vocab::DC.identifier
21
+
22
+ # Note: based_near is defined differently here than in Hyrax.
23
+ work.property :based_near, predicate: ::RDF::Vocab::FOAF.based_near
24
+
25
+ work.property :related_url, predicate: ::RDF::RDFS.seeAlso
26
+ work.property :bibliographic_citation, predicate: ::RDF::Vocab::DC.bibliographicCitation
27
+ work.property :source, predicate: ::RDF::Vocab::DC.source
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hyrax
4
+ module CoreMetadata
5
+ def self.included(work)
6
+ work.property :depositor, predicate: ::RDF::URI.new('http://id.loc.gov/vocabulary/relators/dpt'), multiple: false
7
+
8
+ work.property :title, predicate: ::RDF::Vocab::DC.title
9
+
10
+ work.property :date_uploaded, predicate: ::RDF::Vocab::DC.dateSubmitted, multiple: false
11
+
12
+ work.property :date_modified, predicate: ::RDF::Vocab::DC.modified, multiple: false
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ shared_context 'with a work type' do
4
+ # A work type must be defined for the default `RecordImporter` to save objects
5
+ before do
6
+ load './spec/support/hyrax/core_metadata.rb'
7
+ load './spec/support/hyrax/basic_metadata.rb'
8
+
9
+ class Work < ActiveFedora::Base
10
+ attr_accessor :visibility
11
+ attr_accessor :based_near_attributes
12
+ include ::Hyrax::CoreMetadata
13
+ include ::Hyrax::BasicMetadata
14
+ end
15
+
16
+ class User < Struct.new(:id, :user_key)
17
+ def initialize(inputs = {})
18
+ self.id = inputs[:id]
19
+ self.user_key = inputs[:user_key] || batch_user_key
20
+ end
21
+
22
+ def self.find_by_user_key(email)
23
+ User.new(user_key: email)
24
+ end
25
+
26
+ def self.find_or_create_system_user(_email)
27
+ User.new
28
+ end
29
+
30
+ def batch_user_key
31
+ 'batchuser@example.com'
32
+ end
33
+ end
34
+
35
+ class Ability
36
+ def initialize(user); end
37
+ end
38
+
39
+ module Hyrax
40
+ def self.config
41
+ Config.new
42
+ end
43
+
44
+ class Config
45
+ def curation_concerns
46
+ [Work]
47
+ end
48
+ end
49
+
50
+ class Institution
51
+ def self.name
52
+ 'my_institution'
53
+ end
54
+
55
+ def self.name_full
56
+ 'my full institution name'
57
+ end
58
+ end
59
+
60
+ class UploadedFile < ActiveFedora::Base
61
+ def self.create(*)
62
+ h = Hyrax::UploadedFile.new
63
+ h.save
64
+ h
65
+ end
66
+ end
67
+
68
+ module Actors
69
+ class Environment
70
+ attr_reader :new_object, :attributes
71
+ def initialize(new_object, _ability, attributes)
72
+ @new_object = new_object
73
+ @attributes = attributes
74
+ end
75
+ end
76
+ end
77
+
78
+ class Actor
79
+ def create(actor_env)
80
+ attrs = actor_env.attributes
81
+ attrs.delete(:uploaded_files)
82
+ actor_env.new_object.attributes = attrs
83
+ actor_env.new_object.save
84
+ end
85
+ end
86
+
87
+ class CurationConcern
88
+ def self.actor
89
+ Hyrax::Actor.new
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ after do
96
+ Object.send(:remove_const, :Hyrax) if defined?(Hyrax)
97
+ Object.send(:remove_const, :Work) if defined?(Work)
98
+ Object.send(:remove_const, :User) if defined?(User)
99
+ Object.send(:remove_const, :Ability) if defined?(Ability)
100
+ end
101
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Zizia::CsvFormatValidator do
6
+ subject(:validator) { described_class.new(error_stream: []) }
7
+ let(:invalid_parser) { Zizia::CsvParser.new(file: invalid_file) }
8
+ let(:invalid_file) { File.open('spec/fixtures/bad_example.csv') }
9
+
10
+ it_behaves_like 'a Zizia::Validator' do
11
+ let(:valid_parser) { Zizia::CsvParser.new(file: valid_file) }
12
+ let(:valid_file) { File.open('spec/fixtures/example.csv') }
13
+ end
14
+
15
+ define :a_validator_error do
16
+ match do |error|
17
+ return false unless error.respond_to?(:validator)
18
+
19
+ if fields
20
+ return false if fields[:validator] && error.validator != fields[:validator]
21
+ return false if fields[:name] && error.name != fields[:name]
22
+ end
23
+
24
+ true
25
+ end
26
+
27
+ chain :with, :fields
28
+ end
29
+
30
+ describe '#validate' do
31
+ it 'returns a Validator::Error' do
32
+ expect(validator.validate(parser: invalid_parser))
33
+ .to contain_exactly a_validator_error
34
+ .with(validator: validator.class,
35
+ name: CSV::MalformedCSVError)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,73 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+
4
+ require 'spec_helper'
5
+ require 'tempfile'
6
+
7
+ describe Zizia::CsvParser do
8
+ subject(:parser) { described_class.new(file: file) }
9
+ let(:file) { Tempfile.new(['fake', '.csv']) }
10
+
11
+ shared_context 'with content' do
12
+ let(:csv_content) do
13
+ <<-EOS
14
+ title,description,date created
15
+ The Moomins and the Great Flood,"The Moomins and the Great Flood (Swedish: Småtrollen och den stora översvämningen, literally The Little Trolls and the Great Flood) is a book written by Finnish author Tove Jansson in 1945, during the end of World War II. It was the first book to star the Moomins, but is often seen as a prelude to the main Moomin books, as most of the main characters are introduced in the next book.",1945
16
+ Comet in Moominland,"Comet in Moominland is the second in Tove Jansson's series of Moomin books. Published in 1946, it marks the first appearance of several main characters, like Snufkin and the Snork Maiden.",1946
17
+ EOS
18
+ end
19
+
20
+ let(:record_count) { 2 }
21
+
22
+ before do
23
+ file.write(csv_content)
24
+ file.rewind
25
+ end
26
+ end
27
+
28
+ it_behaves_like 'a Zizia::Parser' do
29
+ include_context 'with content'
30
+ end
31
+
32
+ it 'matches .csv files' do
33
+ expect(Zizia::Parser.for(file: file)).to be_a described_class
34
+ end
35
+
36
+ describe '#records' do
37
+ context 'with valid content' do
38
+ include_context 'with content'
39
+
40
+ it 'has the correct titles' do
41
+ expect(parser.records.map(&:title))
42
+ .to contain_exactly(['The Moomins and the Great Flood'],
43
+ ['Comet in Moominland'])
44
+ end
45
+
46
+ it 'has correct other fields' do
47
+ expect(parser.records.map(&:date_created)).to contain_exactly(['1945'], ['1946'])
48
+ end
49
+ end
50
+
51
+ context 'with invalid file' do
52
+ let(:file) { File.open('spec/fixtures/bad_example.csv') }
53
+
54
+ it 'is empty' do
55
+ expect(parser.records.to_a).to be_empty
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#validate' do
61
+ it 'is valid' do
62
+ expect(parser.validate).to be_truthy
63
+ end
64
+
65
+ context 'with invalid file' do
66
+ let(:file) { File.open('spec/fixtures/bad_example.csv') }
67
+
68
+ it 'is invalid' do
69
+ expect(parser.validate).to be_falsey
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'zizia/streams/formatted_message_stream'
5
+
6
+ describe Zizia::FormattedMessageStream do
7
+ subject(:stream) { described_class.new(stream: fake_stream) }
8
+ let(:fake_stream) { [] }
9
+
10
+ it_behaves_like 'a Zizia::MessageStream'
11
+
12
+ describe '#stream' do
13
+ subject(:stream) { described_class.new }
14
+
15
+ it 'is STDOUT by default' do
16
+ expect(stream.stream).to eq STDOUT
17
+ end
18
+ end
19
+
20
+ describe '#<<' do
21
+ it 'appends newlines by default' do
22
+ expect { stream << 'moomin' }
23
+ .to change { fake_stream }
24
+ .to contain_exactly("moomin\n")
25
+ end
26
+
27
+ it 'uses other % formatters' do
28
+ stream.formatter = "!!!%s!!!"
29
+
30
+ expect { stream << 'moomin' }
31
+ .to change { fake_stream }
32
+ .to contain_exactly('!!!moomin!!!')
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ describe Zizia::HashMapper do
4
+ it_behaves_like 'a Zizia::Mapper' do
5
+ let(:expected_fields) { metadata.keys.map(&:to_sym) }
6
+ let(:metadata) { { 'a_field' => 'a', 'b_field' => 'b', 'c_field' => 'c' } }
7
+ end
8
+ end