stash-merritt 0.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 (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +193 -0
  3. data/.rubocop.yml +32 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +12 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +326 -0
  8. data/LICENSE.md +22 -0
  9. data/README.md +53 -0
  10. data/Rakefile +49 -0
  11. data/lib/datacite/mapping/datacite_xml_factory.rb +212 -0
  12. data/lib/stash/merritt/ezid_helper.rb +50 -0
  13. data/lib/stash/merritt/module_info.rb +12 -0
  14. data/lib/stash/merritt/repository.rb +17 -0
  15. data/lib/stash/merritt/submission_job.rb +90 -0
  16. data/lib/stash/merritt/submission_package/data_one_manifest_builder.rb +41 -0
  17. data/lib/stash/merritt/submission_package/merritt_datacite_builder.rb +22 -0
  18. data/lib/stash/merritt/submission_package/merritt_delete_builder.rb +25 -0
  19. data/lib/stash/merritt/submission_package/merritt_oaidc_builder.rb +130 -0
  20. data/lib/stash/merritt/submission_package/stash_wrapper_builder.rb +59 -0
  21. data/lib/stash/merritt/submission_package.rb +125 -0
  22. data/lib/stash/merritt/sword_helper.rb +58 -0
  23. data/lib/stash/merritt.rb +5 -0
  24. data/lib/stash.rb +5 -0
  25. data/spec/.rubocop.yml +10 -0
  26. data/spec/config/app_config.yml +3 -0
  27. data/spec/config/database.yml +7 -0
  28. data/spec/config/licenses.yml +18 -0
  29. data/spec/data/archive/mrt-datacite.xml +121 -0
  30. data/spec/data/archive/mrt-dataone-manifest.txt +32 -0
  31. data/spec/data/archive/mrt-oaidc.xml +38 -0
  32. data/spec/data/archive/stash-wrapper.xml +213 -0
  33. data/spec/data/archive.zip +0 -0
  34. data/spec/data/dc4-with-funding-references.xml +123 -0
  35. data/spec/db/datacite/mapping/datacite_xml_factory_spec.rb +56 -0
  36. data/spec/db/stash/merritt/merritt_oaidc_builder_spec.rb +72 -0
  37. data/spec/db/stash/merritt/submission_package_spec.rb +174 -0
  38. data/spec/db/stash/merritt/sword_helper_spec.rb +162 -0
  39. data/spec/db_spec_helper.rb +31 -0
  40. data/spec/rspec_custom_matchers.rb +92 -0
  41. data/spec/spec_helper.rb +86 -0
  42. data/spec/unit/stash/merritt/ezid_helper_spec.rb +88 -0
  43. data/spec/unit/stash/merritt/repository_spec.rb +19 -0
  44. data/spec/unit/stash/merritt/submission_job_spec.rb +127 -0
  45. data/spec/util/resource_builder.rb +333 -0
  46. data/stash-merritt.gemspec +48 -0
  47. data/stash-merritt.iml +147 -0
  48. data/stash-merritt.ipr +127 -0
  49. data/travis-local-deps.sh +43 -0
  50. metadata +337 -0
@@ -0,0 +1,92 @@
1
+ require 'rspec/expectations'
2
+ require 'equivalent-xml'
3
+ require 'diffy'
4
+
5
+ module Stash
6
+ module XMLMatchUtils
7
+ def self.to_nokogiri(xml)
8
+ return nil unless xml
9
+ case xml
10
+ when Nokogiri::XML::Element
11
+ xml
12
+ when Nokogiri::XML::Document
13
+ xml.root
14
+ when String
15
+ to_nokogiri(Nokogiri::XML(xml, &:noblanks))
16
+ when REXML::Element
17
+ to_nokogiri(xml.to_s)
18
+ else
19
+ raise "be_xml() expected XML, got #{xml.class}"
20
+ end
21
+ end
22
+
23
+ def self.to_pretty(nokogiri)
24
+ return nil unless nokogiri
25
+ out = StringIO.new
26
+ save_options = Nokogiri::XML::Node::SaveOptions::FORMAT | Nokogiri::XML::Node::SaveOptions::NO_DECLARATION
27
+ nokogiri.write_xml_to(out, encoding: 'UTF-8', indent: 2, save_with: save_options)
28
+ out.string
29
+ end
30
+
31
+ def self.equivalent?(expected, actual, filename = nil)
32
+ expected_xml = to_nokogiri(expected) || raise("expected value #{expected || 'nil'} does not appear to be XML#{" in #{filename}" if filename}")
33
+ actual_xml = to_nokogiri(actual)
34
+
35
+ EquivalentXml.equivalent?(expected_xml, actual_xml, element_order: false, normalize_whitespace: true)
36
+ end
37
+
38
+ def self.failure_message(expected, actual, filename = nil)
39
+ expected_string = to_pretty(to_nokogiri(expected))
40
+ actual_string = to_pretty(to_nokogiri(actual)) || actual
41
+
42
+ now = Time.now.to_i
43
+ FileUtils.mkdir('tmp') unless File.directory?('tmp')
44
+ File.open("tmp/#{now}-expected.xml", 'w') { |f| f.write(expected_string) }
45
+ File.open("tmp/#{now}-actual.xml", 'w') { |f| f.write(actual_string) }
46
+
47
+ diff = Diffy::Diff.new(expected_string, actual_string).to_s(:text)
48
+
49
+ "expected XML differs from actual#{" in #{filename}" if filename}:\n#{diff}"
50
+ end
51
+
52
+ def self.to_xml_string(actual)
53
+ to_pretty(to_nokogiri(actual))
54
+ end
55
+
56
+ def self.failure_message_when_negated(actual, filename = nil)
57
+ "expected not to get XML#{" in #{filename}" if filename}:\n\t#{to_xml_string(actual) || 'nil'}"
58
+ end
59
+ end
60
+ end
61
+
62
+ RSpec::Matchers.define :be_xml do |expected, filename = nil|
63
+ match do |actual|
64
+ Stash::XMLMatchUtils.equivalent?(expected, actual, filename)
65
+ end
66
+
67
+ failure_message do |actual|
68
+ Stash::XMLMatchUtils.failure_message(expected, actual, filename)
69
+ end
70
+
71
+ failure_message_when_negated do |actual|
72
+ Stash::XMLMatchUtils.failure_message_when_negated(actual, filename)
73
+ end
74
+ end
75
+
76
+ RSpec::Matchers.define :be_time do |expected|
77
+ def to_string(time)
78
+ time.is_a?(Time) ? time.utc.round(2).iso8601(2) : time.to_s
79
+ end
80
+
81
+ match do |actual|
82
+ return actual.nil? unless expected
83
+ raise "Expected value #{expected} is not a Time" unless expected.is_a?(Time)
84
+ actual.is_a?(Time) && (to_string(expected) == to_string(actual))
85
+ end
86
+
87
+ failure_message do |actual|
88
+ expected_str = to_string(expected)
89
+ actual_str = to_string(actual)
90
+ "expected time:\n#{expected_str}\n\nbut was:\n#{actual_str}"
91
+ end
92
+ end
@@ -0,0 +1,86 @@
1
+ # ------------------------------------------------------------
2
+ # SimpleCov setup
3
+
4
+ if ENV['COVERAGE']
5
+ require 'simplecov'
6
+ require 'simplecov-console'
7
+
8
+ SimpleCov.minimum_coverage 100
9
+ SimpleCov.start do
10
+ add_filter '/spec/'
11
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
12
+ SimpleCov::Formatter::HTMLFormatter,
13
+ SimpleCov::Formatter::Console,
14
+ ]
15
+ end
16
+ end
17
+
18
+ # ------------------------------------------------------------
19
+ # Rspec configuration
20
+
21
+ RSpec.configure do |config|
22
+ config.raise_errors_for_deprecations!
23
+ config.mock_with :rspec
24
+ end
25
+
26
+ require 'rspec_custom_matchers'
27
+
28
+ # ------------------------------------------------------------
29
+ # ActiveRecord
30
+
31
+ require 'active_record'
32
+ # Note: Even if we're not doing any database work, ActiveRecord callbacks will still raise warnings
33
+ ActiveRecord::Base.raise_in_transactional_callbacks = true
34
+
35
+ # ------------------------------------------------------------
36
+ # StashEngine
37
+
38
+ ENV['STASH_ENV'] = 'test'
39
+ ENV['RAILS_ENV'] = 'test'
40
+
41
+ ::LICENSES = YAML.load_file('spec/config/licenses.yml').with_indifferent_access
42
+ ::APP_CONFIG = OpenStruct.new(YAML.load_file('spec/config/app_config.yml')['test'])
43
+
44
+ stash_engine_path = Gem::Specification.find_by_name('stash_engine').gem_dir
45
+ require "#{stash_engine_path}/config/initializers/hash_to_ostruct.rb"
46
+
47
+ require 'stash_engine'
48
+
49
+ %w(
50
+ app/models/stash_engine
51
+ app/mailers
52
+ app/mailers/stash_engine
53
+ app/jobs/stash_engine
54
+ lib/stash_engine
55
+ ).each do |dir|
56
+ Dir.glob("#{stash_engine_path}/#{dir}/**/*.rb").sort.each(&method(:require))
57
+ end
58
+
59
+ # ------------------------------------------------------------
60
+ # StashDatacite
61
+
62
+ module StashDatacite
63
+ @@resource_class = 'StashEngine::Resource' # rubocop:disable Style/ClassVars
64
+ end
65
+
66
+ require 'stash_datacite'
67
+
68
+ # TODO: do we need all of these?
69
+ stash_datacite_path = Gem::Specification.find_by_name('stash_datacite').gem_dir
70
+ %w(
71
+ app/models/stash_datacite
72
+ app/models/stash_datacite/resource
73
+ lib/stash_datacite
74
+ lib
75
+ ).each do |dir|
76
+ Dir.glob("#{stash_datacite_path}/#{dir}/**/*.rb").sort.each(&method(:require))
77
+ end
78
+
79
+ StashDatacite::ResourcePatch.associate_with_resource(StashEngine::Resource)
80
+
81
+ require 'util/resource_builder'
82
+
83
+ # ------------------------------------------------------------
84
+ # Stash::Merritt
85
+
86
+ require 'stash/merritt'
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ module Stash
5
+ module Merritt
6
+ describe EzidHelper do
7
+ attr_reader :resource_id
8
+ attr_reader :resource
9
+ attr_reader :identifier_str
10
+ attr_reader :landing_page_url
11
+ attr_reader :helper
12
+ attr_reader :url_helpers
13
+ attr_reader :tenant
14
+
15
+ before(:each) do
16
+ @resource_id = 17
17
+ @resource = double(StashEngine::Resource)
18
+ allow(StashEngine::Resource).to receive(:find).with(resource_id).and_return(resource)
19
+
20
+ @identifier_str = 'doi:10.15146/R38675309'
21
+ @url_helpers = double(Module)
22
+ @landing_page_url = "http://stash.example.edu/stash/#{identifier_str}"
23
+ allow(url_helpers).to receive(:show_path).with(identifier_str).and_return(landing_page_url)
24
+
25
+ @tenant = double(StashEngine::Tenant)
26
+ id_params = {
27
+ shoulder: 'doi:10.15146/R3',
28
+ owner: 'stash_admin',
29
+ account: 'stash',
30
+ password: '3cc9d3fbd9788148c6a32a1415fa673a',
31
+ id_scheme: 'doi'
32
+ }
33
+ allow(tenant).to receive(:identifier_service).and_return(OpenStruct.new(id_params))
34
+ allow(tenant).to receive(:tenant_id).and_return('dataone')
35
+ allow(resource).to receive(:tenant).and_return(tenant)
36
+
37
+ @helper = EzidHelper.new(resource: resource, url_helpers: url_helpers)
38
+ end
39
+
40
+ describe :ensure_identifier do
41
+ it 'returns an existing identifier without bothering EZID' do
42
+ expect(resource).to receive(:identifier_str).and_return(identifier_str)
43
+ expect(::Ezid::Client).not_to receive(:new)
44
+ expect(helper.ensure_identifier).to eq(identifier_str)
45
+ end
46
+
47
+ it 'mints and assigns a new identifier if none is present' do
48
+ identifier = instance_double(::Ezid::MintIdentifierResponse)
49
+ allow(identifier).to receive(:id).and_return(identifier_str)
50
+
51
+ ezid_client = instance_double(::Ezid::Client)
52
+ allow(::Ezid::Client).to receive(:new)
53
+ .with(user: 'stash', password: '3cc9d3fbd9788148c6a32a1415fa673a')
54
+ .and_return(ezid_client)
55
+
56
+ expect(resource).to receive(:identifier_str).and_return(nil)
57
+ expect(ezid_client).to receive(:mint_identifier)
58
+ .with('doi:10.15146/R3', status: 'reserved', profile: 'datacite')
59
+ .and_return(identifier)
60
+ expect(resource).to receive(:ensure_identifier).with(identifier_str)
61
+ expect(helper.ensure_identifier).to eq(identifier_str)
62
+ end
63
+ end
64
+
65
+ describe :update_metadata do
66
+ it 'updates the metadata and landing page' do
67
+ dc3_xml = '<resource/>'
68
+
69
+ ezid_client = instance_double(::Ezid::Client)
70
+ allow(::Ezid::Client).to receive(:new)
71
+ .with(user: 'stash', password: '3cc9d3fbd9788148c6a32a1415fa673a')
72
+ .and_return(ezid_client)
73
+
74
+ expect(ezid_client).to receive(:modify_identifier).with(
75
+ identifier_str,
76
+ datacite: dc3_xml,
77
+ target: landing_page_url,
78
+ status: 'public',
79
+ owner: 'stash_admin'
80
+ )
81
+
82
+ expect(resource).to receive(:identifier_str).and_return(identifier_str)
83
+ helper.update_metadata(dc3_xml: dc3_xml)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ module Stash
4
+ module Merritt
5
+ describe Repository do
6
+ describe :create_submission_job do
7
+ it 'creates a submission job' do
8
+ url_helpers = double(Module) # yes, apparently URL helpers are an anonymous module
9
+ repo = Repository.new(url_helpers: url_helpers)
10
+ resource_id = 17
11
+ job = repo.create_submission_job(resource_id: resource_id)
12
+ expect(job).to be_a(SubmissionJob)
13
+ expect(job.resource_id).to eq(resource_id)
14
+ expect(job.url_helpers).to be(url_helpers)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,127 @@
1
+ require 'spec_helper'
2
+
3
+ module Stash
4
+ module Merritt
5
+ describe SubmissionJob do
6
+ attr_reader :logger
7
+ attr_reader :tenant
8
+ attr_reader :resource_id
9
+ attr_reader :resource
10
+ attr_reader :url_helpers
11
+ attr_reader :ezid_helper
12
+ attr_reader :package
13
+ attr_reader :sword_helper
14
+ attr_reader :job
15
+
16
+ before(:each) do
17
+ @logger = instance_double(Logger)
18
+ allow(logger).to receive(:debug) { |msg| puts "debug: #{msg}" }
19
+ allow(logger).to receive(:info) { |msg| puts "info: #{msg}" }
20
+ allow(logger).to receive(:warn) { |msg| puts "warn: #{msg}" }
21
+ allow(logger).to receive(:error) { |msg| puts "error: #{msg}" }
22
+
23
+ @rails_logger = Rails.logger
24
+ Rails.logger = logger
25
+
26
+ @tenant = double(StashEngine::Tenant)
27
+ sword_params = {
28
+ collection_uri: 'http://example.edu/sword/example',
29
+ username: 'elvis',
30
+ password: 'presley'
31
+ }.freeze
32
+ allow(tenant).to receive(:sword_params).and_return(sword_params)
33
+ allow(tenant).to receive(:id).and_return('example_u')
34
+
35
+ @resource_id = 37
36
+ @resource = double(StashEngine::Resource)
37
+ allow(StashEngine::Resource).to receive(:find).with(resource_id).and_return(resource)
38
+ allow(resource).to receive(:identifier_str).and_return('doi:10.123/456')
39
+ allow(resource).to receive(:update_uri).and_return(nil)
40
+ allow(resource).to receive(:tenant).and_return(tenant)
41
+ allow(resource).to receive(:tenant_id).and_return('example_u')
42
+
43
+ @url_helpers = double(Module) # yes, apparently URL helpers are an anonymous module
44
+ allow(url_helpers).to(receive(:show_path)) { |identifier| identifier }
45
+
46
+ @ezid_helper = instance_double(EzidHelper)
47
+ allow(EzidHelper).to receive(:new).with(resource: resource, url_helpers: url_helpers).and_return(ezid_helper)
48
+ allow(ezid_helper).to receive(:ensure_identifier)
49
+ allow(ezid_helper).to receive(:update_metadata)
50
+
51
+ @package = instance_double(SubmissionPackage)
52
+ allow(SubmissionPackage).to receive(:new).with(resource: resource).and_return(package)
53
+ allow(package).to receive(:dc3_xml)
54
+ allow(package).to receive(:cleanup!)
55
+
56
+ @sword_helper = instance_double(SwordHelper)
57
+ allow(SwordHelper).to receive(:new).with(package: package, logger: logger).and_return(sword_helper)
58
+ allow(sword_helper).to receive(:submit!)
59
+
60
+ @job = SubmissionJob.new(resource_id: resource_id, url_helpers: url_helpers)
61
+ end
62
+
63
+ after(:each) do
64
+ Rails.logger = @rails_logger
65
+ end
66
+
67
+ describe :submit! do
68
+ it 'ensures an identifier' do
69
+ expect(ezid_helper).to receive(:ensure_identifier)
70
+ job.submit!
71
+ end
72
+
73
+ it 'submits the package' do
74
+ expect(sword_helper).to receive(:submit!)
75
+ job.submit!
76
+ end
77
+
78
+ it 'updates the metadata' do
79
+ dc3_xml = '<resource/>'
80
+ expect(package).to receive(:dc3_xml).and_return(dc3_xml)
81
+ expect(ezid_helper).to receive(:update_metadata).with(dc3_xml: dc3_xml)
82
+ job.submit!
83
+ end
84
+
85
+ it 'cleans up the package' do
86
+ expect(package).to receive(:cleanup!)
87
+ job.submit!
88
+ end
89
+
90
+ it 'returns a result' do
91
+ result = job.submit!
92
+ expect(result).to be_a(Stash::Repo::SubmissionResult)
93
+ expect(result.success?).to be_truthy
94
+ end
95
+
96
+ describe 'error handling' do
97
+ it 'fails on a bad resource ID' do
98
+ bad_id = resource_id * 17
99
+ job = SubmissionJob.new(resource_id: bad_id, url_helpers: url_helpers)
100
+ allow(StashEngine::Resource).to receive(:find).with(bad_id).and_raise(ActiveRecord::RecordNotFound)
101
+ expect(job.submit!.error).to be_a(ActiveRecord::RecordNotFound)
102
+ end
103
+
104
+ it 'fails on an ID minting error' do
105
+ expect(ezid_helper).to receive(:ensure_identifier).and_raise(Ezid::NotAllowedError)
106
+ expect(job.submit!.error).to be_a(Ezid::NotAllowedError)
107
+ end
108
+
109
+ it 'fails on a SWORD submission error' do
110
+ expect(sword_helper).to receive(:submit!).and_raise(RestClient::RequestFailed)
111
+ expect(job.submit!.error).to be_a(RestClient::RequestFailed)
112
+ end
113
+
114
+ it 'fails on a metadata update error' do
115
+ expect(ezid_helper).to receive(:update_metadata).and_raise(Ezid::IdentifierNotFoundError)
116
+ expect(job.submit!.error).to be_a(Ezid::IdentifierNotFoundError)
117
+ end
118
+
119
+ it 'fails on a package cleanup error' do
120
+ expect(package).to receive(:cleanup!).and_raise(Errno::ENOENT)
121
+ expect(job.submit!.error).to be_a(Errno::ENOENT)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end