stash-sword 0.1.0

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 (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +42 -0
  3. data/.rubocop.yml +25 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +2 -0
  6. data/.yardopts +3 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE.md +22 -0
  9. data/README.md +4 -0
  10. data/Rakefile +49 -0
  11. data/examples/example.rb +26 -0
  12. data/examples/uploads/example.zip +0 -0
  13. data/examples/uploads/example/lorem-ipsum.txt +7 -0
  14. data/examples/uploads/example/mrt-datacite.xml +22 -0
  15. data/examples/uploads/example/mrt-dc.xml +13 -0
  16. data/examples/uploads/example/stash-wrapper.xml +56 -0
  17. data/lib/stash/sword.rb +39 -0
  18. data/lib/stash/sword/client.rb +132 -0
  19. data/lib/stash/sword/deposit_receipt.rb +44 -0
  20. data/lib/stash/sword/header_utils.rb +42 -0
  21. data/lib/stash/sword/http_helper.rb +120 -0
  22. data/lib/stash/sword/iri.rb +12 -0
  23. data/lib/stash/sword/log_utils.rb +39 -0
  24. data/lib/stash/sword/module_info.rb +12 -0
  25. data/lib/stash/sword/namespace.rb +30 -0
  26. data/lib/stash/sword/sequence_io.rb +105 -0
  27. data/notes/Dash_Submission_To_Merritt.txt +40 -0
  28. data/notes/service-document.xml +15 -0
  29. data/spec/.rubocop.yml +10 -0
  30. data/spec/data/deposit_receipt_merritt.xml +25 -0
  31. data/spec/data/deposit_receipt_spec.xml +58 -0
  32. data/spec/rspec_custom_matchers.rb +118 -0
  33. data/spec/spec_helper.rb +33 -0
  34. data/spec/unit/stash/sword2/client_spec.rb +110 -0
  35. data/spec/unit/stash/sword2/deposit_receipt_spec.rb +48 -0
  36. data/spec/unit/stash/sword2/http_helper_get_spec.rb +131 -0
  37. data/spec/unit/stash/sword2/http_helper_post_spec.rb +143 -0
  38. data/spec/unit/stash/sword2/http_helper_put_spec.rb +143 -0
  39. data/spec/unit/stash/sword2/log_spec.rb +23 -0
  40. data/spec/unit/stash/sword2/namespaces_spec.rb +31 -0
  41. data/spec/unit/stash/sword2/sequence_io_spec.rb +153 -0
  42. data/stash-sword.gemspec +47 -0
  43. metadata +279 -0
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+ require 'webmock/rspec'
3
+
4
+ module Stash
5
+ module Sword
6
+ describe HTTPHelper do
7
+
8
+ # ------------------------------------------------------------
9
+ # Fixture
10
+
11
+ attr_writer :user_agent
12
+
13
+ def user_agent
14
+ @user_agent ||= 'elvis'
15
+ end
16
+
17
+ attr_writer :helper
18
+
19
+ def helper
20
+ @helper ||= HTTPHelper.new(user_agent: user_agent)
21
+ end
22
+
23
+ # ------------------------------------------------------------
24
+ # Tests
25
+
26
+ describe '#post' do
27
+ it 'posts to the specified URI' do
28
+ uri = URI('http://example.org/')
29
+ stub_request(:post, uri)
30
+ helper.post(uri: uri, payload: 'the payload')
31
+ expect(a_request(:post, uri)).to have_been_made
32
+ end
33
+
34
+ it 'sends the payload' do
35
+ uri = URI('http://example.org/')
36
+ stub_request(:post, uri)
37
+
38
+ payload = 'the payload'
39
+ helper.post(uri: uri, payload: payload)
40
+
41
+ expect(a_request(:post, uri).with do |req|
42
+ expect(req.body).to eq(payload)
43
+ end).to have_been_made
44
+ end
45
+
46
+ it 'sets the User-Agent header' do
47
+ uri = URI('http://example.org/')
48
+ stub_request(:post, uri)
49
+
50
+ helper.post(uri: uri, payload: 'the payload')
51
+
52
+ expect(a_request(:post, uri).with do |req|
53
+ expect(req.headers).to include_header('User-Agent', user_agent)
54
+ end).to have_been_made
55
+ end
56
+
57
+ it 'sets Basic-Auth headers' do
58
+ username = 'elvis'
59
+ password = 'presley'
60
+ helper = HTTPHelper.new(user_agent: user_agent, username: username, password: password)
61
+
62
+ uri = URI('http://example.org/')
63
+ authorized_uri = URI(uri.to_s.sub('http://', "http://#{username}:#{password}@"))
64
+ stub_request(:post, authorized_uri)
65
+
66
+ helper.post(uri: uri, payload: 'the payload')
67
+ expect(a_request(:post, authorized_uri)).to have_been_made
68
+ end
69
+
70
+ it 'sets other specified headers' do
71
+ uri = URI('http://example.org/')
72
+ stub_request(:post, uri)
73
+
74
+ headers = {
75
+ 'Packaging' => 'http://purl.org/net/sword/package/SimpleZip',
76
+ 'Content-Type' => 'application/zip'
77
+ }
78
+ helper.post(uri: uri, payload: 'the payload', headers: headers)
79
+
80
+ expect(a_request(:post, uri).with do |req|
81
+ headers.each do |k, v|
82
+ expect(req.headers).to include_header(k, v)
83
+ end
84
+ end).to have_been_made
85
+ end
86
+
87
+ it 'uses SSL for https requests' do
88
+ uri = URI('https://example.org/')
89
+ uri_with_port = uri.to_s.sub('org/', 'org:443/')
90
+ stub_request(:post, uri_with_port)
91
+ helper.post(uri: uri, payload: 'the payload')
92
+ expect(a_request(:post, uri_with_port)).to have_been_made
93
+ end
94
+
95
+ describe 'continuation' do
96
+ it 'sends Expect: 100-continue'
97
+ it 'sends the request body on a 100 Continue'
98
+ it 'continues on a timeout in lieu of a 100 Continue'
99
+ it 'redirects to post to a 302 Found'
100
+ it 'respects the redirect limit'
101
+ it 'fails on a 417 Expectation Failed'
102
+ end
103
+ describe 'responses' do
104
+ it 'accepts a 200 OK'
105
+ it 'accepts a 204 No Content'
106
+ it 'accepts a 201 Created'
107
+ it 'redirects to get a 303 See Other'
108
+ it 'respects the redirect limit'
109
+ it 'fails on a 4xx'
110
+ it 'fails on a 5xx'
111
+ end
112
+ end
113
+
114
+ describe '#post_file' do
115
+ it 'posts to the specified URI'
116
+ it 'sends the payload'
117
+ it 'sets the User-Agent header'
118
+ it 'sets Basic-Auth headers'
119
+ it 'sets other specified headers'
120
+ it 'uses SSL for https requests'
121
+
122
+ describe 'continuation' do
123
+ it 'sends Expect: 100-continue'
124
+ it 'sends the request body on a 100 Continue'
125
+ it 'continues on a timeout in lieu of a 100 Continue'
126
+ it 'redirects to post to a 302 Found'
127
+ it 'respects the redirect limit'
128
+ it 'fails on a 417 Expectation Failed'
129
+ end
130
+ describe 'responses' do
131
+ it 'accepts a 200 OK'
132
+ it 'accepts a 204 No Content'
133
+ it 'accepts a 201 Created'
134
+ it 'redirects to get a 303 See Other'
135
+ it 'respects the redirect limit'
136
+ it 'fails on a 4xx'
137
+ it 'fails on a 5xx'
138
+ end
139
+ end
140
+
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,143 @@
1
+ require 'spec_helper'
2
+ require 'webmock/rspec'
3
+
4
+ module Stash
5
+ module Sword
6
+ describe HTTPHelper do
7
+
8
+ # ------------------------------------------------------------
9
+ # Fixture
10
+
11
+ attr_writer :user_agent
12
+
13
+ def user_agent
14
+ @user_agent ||= 'elvis'
15
+ end
16
+
17
+ attr_writer :helper
18
+
19
+ def helper
20
+ @helper ||= HTTPHelper.new(user_agent: user_agent)
21
+ end
22
+
23
+ # ------------------------------------------------------------
24
+ # Tests
25
+
26
+ describe '#put' do
27
+ it 'puts to the specified URI' do
28
+ uri = URI('http://example.org/')
29
+ stub_request(:put, uri)
30
+ helper.put(uri: uri, payload: 'the payload')
31
+ expect(a_request(:put, uri)).to have_been_made
32
+ end
33
+
34
+ it 'sends the payload' do
35
+ uri = URI('http://example.org/')
36
+ stub_request(:put, uri)
37
+
38
+ payload = 'the payload'
39
+ helper.put(uri: uri, payload: payload)
40
+
41
+ expect(a_request(:put, uri).with do |req|
42
+ expect(req.body).to eq(payload)
43
+ end).to have_been_made
44
+ end
45
+
46
+ it 'sets the User-Agent header' do
47
+ uri = URI('http://example.org/')
48
+ stub_request(:put, uri)
49
+
50
+ helper.put(uri: uri, payload: 'the payload')
51
+
52
+ expect(a_request(:put, uri).with do |req|
53
+ expect(req.headers).to include_header('User-Agent', user_agent)
54
+ end).to have_been_made
55
+ end
56
+
57
+ it 'sets Basic-Auth headers' do
58
+ username = 'elvis'
59
+ password = 'presley'
60
+ helper = HTTPHelper.new(user_agent: user_agent, username: username, password: password)
61
+
62
+ uri = URI('http://example.org/')
63
+ authorized_uri = URI(uri.to_s.sub('http://', "http://#{username}:#{password}@"))
64
+ stub_request(:put, authorized_uri)
65
+
66
+ helper.put(uri: uri, payload: 'the payload')
67
+ expect(a_request(:put, authorized_uri)).to have_been_made
68
+ end
69
+
70
+ it 'sets other specified headers' do
71
+ uri = URI('http://example.org/')
72
+ stub_request(:put, uri)
73
+
74
+ headers = {
75
+ 'Packaging' => 'http://purl.org/net/sword/package/SimpleZip',
76
+ 'Content-Type' => 'application/zip'
77
+ }
78
+ helper.put(uri: uri, payload: 'the payload', headers: headers)
79
+
80
+ expect(a_request(:put, uri).with do |req|
81
+ headers.each do |k, v|
82
+ expect(req.headers).to include_header(k, v)
83
+ end
84
+ end).to have_been_made
85
+ end
86
+
87
+ it 'uses SSL for https requests' do
88
+ uri = URI('https://example.org/')
89
+ uri_with_port = uri.to_s.sub('org/', 'org:443/')
90
+ stub_request(:put, uri_with_port)
91
+ helper.put(uri: uri, payload: 'the payload')
92
+ expect(a_request(:put, uri_with_port)).to have_been_made
93
+ end
94
+
95
+ describe 'continuation' do
96
+ it 'sends Expect: 100-continue'
97
+ it 'sends the request body on a 100 Continue'
98
+ it 'continues on a timeout in lieu of a 100 Continue'
99
+ it 'redirects to put to a 302 Found'
100
+ it 'respects the redirect limit'
101
+ it 'fails on a 417 Expectation Failed'
102
+ end
103
+ describe 'responses' do
104
+ it 'accepts a 200 OK'
105
+ it 'accepts a 204 No Content'
106
+ it 'accepts a 201 Created'
107
+ it 'redirects to get a 303 See Other'
108
+ it 'respects the redirect limit'
109
+ it 'fails on a 4xx'
110
+ it 'fails on a 5xx'
111
+ end
112
+ end
113
+
114
+ describe '#put_file' do
115
+ it 'puts to the specified URI'
116
+ it 'sends the payload'
117
+ it 'sets the User-Agent header'
118
+ it 'sets Basic-Auth headers'
119
+ it 'sets other specified headers'
120
+ it 'uses SSL for https requests'
121
+
122
+ describe 'continuation' do
123
+ it 'sends Expect: 100-continue'
124
+ it 'sends the request body on a 100 Continue'
125
+ it 'continues on a timeout in lieu of a 100 Continue'
126
+ it 'redirects to put to a 302 Found'
127
+ it 'respects the redirect limit'
128
+ it 'fails on a 417 Expectation Failed'
129
+ end
130
+ describe 'responses' do
131
+ it 'accepts a 200 OK'
132
+ it 'accepts a 204 No Content'
133
+ it 'accepts a 201 Created'
134
+ it 'redirects to get a 303 See Other'
135
+ it 'respects the redirect limit'
136
+ it 'fails on a 4xx'
137
+ it 'fails on a 5xx'
138
+ end
139
+ end
140
+
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ module Stash
4
+ module Sword
5
+ describe 'log' do
6
+ it 'logs to stdout in a timestamp-first format' do
7
+ out = StringIO.new
8
+ Sword.log_device = out
9
+ begin
10
+ msg = 'I am a log message'
11
+ Sword.log.warn(msg)
12
+ logged = out.string
13
+ expect(logged).to include(msg)
14
+ timestamp_str = logged.split[0]
15
+ timestamp = DateTime.parse(timestamp_str)
16
+ expect(timestamp.to_date).to eq(Time.now.utc.to_date)
17
+ ensure
18
+ Sword.log_device = $stdout
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ module Stash
4
+ module Sword
5
+ describe Namespace do
6
+
7
+ it 'defines namespaces' do
8
+ by_namespace = {}
9
+ by_uri = {}
10
+ by_prefix = {}
11
+
12
+ Namespace.each do |ns_enum|
13
+ expect(ns_enum.value).to be_an(XML::MappingExtensions::Namespace)
14
+
15
+ expect(by_namespace.key?(ns_enum.value)).to be(false), -> { "Duplicate namespace: #{by_namespace[ns_enum.value].key} and #{ns_enum.key} both declare #{ns_enum.value}" }
16
+ by_namespace[ns_enum.value] = ns_enum
17
+
18
+ expect(by_uri.key?(ns_enum.uri)).to be(false), -> { "Duplicate URI: #{by_uri[ns_enum.uri].key} and #{ns_enum.key} both declare #{ns_enum.uri}" }
19
+ by_uri[ns_enum.uri] = ns_enum
20
+
21
+ if ns_enum.prefix
22
+ expect(by_prefix.key?(ns_enum.prefix)).to be(false), -> { "Duplicate prefix: #{by_prefix[ns_enum.prefix].key} and #{ns_enum.key} both declare #{ns_enum.prefix}" }
23
+ by_prefix[ns_enum.prefix] = ns_enum
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,153 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+
4
+ module Stash
5
+ module Sword
6
+
7
+ RSpec.shared_examples 'reading' do
8
+ it 'reads' do
9
+ expect(sqio.read).to eq(content)
10
+ end
11
+
12
+ it 'reads into a buffer' do
13
+ expect(sqio.read(len, outbuf)).to eq(content)
14
+ expect(outbuf).to eq(content)
15
+ end
16
+
17
+ it 'reads n < length chars' do
18
+ length = len / 2
19
+ expected = content.slice(0, length)
20
+ expect(sqio.read(length)).to eq(expected)
21
+ end
22
+
23
+ it 'reads n < length chars into a buffer' do
24
+ length = len / 2
25
+ expected = content.slice(0, length)
26
+ expect(sqio.read(length, outbuf)).to eq(expected)
27
+ expect(outbuf).to eq(expected)
28
+ end
29
+
30
+ it 'reads n == length chars' do
31
+ expect(sqio.read(len)).to eq(content)
32
+ end
33
+
34
+ it 'reads n == length chars into a buffer' do
35
+ expect(sqio.read(len, outbuf)).to eq(content)
36
+ expect(outbuf).to eq(content)
37
+ end
38
+
39
+ it 'reads n > length chars' do
40
+ expect(sqio.read(len * 2)).to eq(content)
41
+ end
42
+
43
+ it 'reads n > length chars into a buffer' do
44
+ expect(sqio.read(len * 2, outbuf)).to eq(content)
45
+ expect(outbuf).to eq(content)
46
+ end
47
+
48
+ it 'calculates the size' do
49
+ expect(sqio.size).to eq(len)
50
+ end
51
+ end
52
+
53
+ describe SequenceIO do
54
+
55
+ ALPHANUMERIC = Array('A'..'Z') + Array('a'..'z') + Array('0'..'9')
56
+
57
+ attr_reader :len
58
+ attr_reader :content
59
+ attr_reader :sqio
60
+ attr_reader :outbuf
61
+ attr_reader :tempfiles
62
+
63
+ before(:each) do
64
+ @len = 100
65
+ @outbuf = '[overwrite me!]'
66
+ end
67
+
68
+ after(:each) do
69
+ sqio.close if sqio
70
+ tempfiles.each { |f| File.delete(f) } if tempfiles
71
+ end
72
+
73
+ def make_alphanumeric_string(chars)
74
+ Array.new(chars) { ALPHANUMERIC.sample }.join
75
+ end
76
+
77
+ def make_tempfile(base_name, content)
78
+ f = Tempfile.new([base_name, 'bin'])
79
+ f.binmode
80
+ begin
81
+ f.write(content)
82
+ ensure
83
+ f.close
84
+ end
85
+ f.path
86
+ end
87
+
88
+ describe 'a string' do
89
+ before(:each) do
90
+ @content = make_alphanumeric_string(len)
91
+ @sqio = SequenceIO.new(@content)
92
+ end
93
+ include_examples 'reading'
94
+ end
95
+
96
+ describe 'a file' do
97
+ before(:each) do
98
+ @content = Random.new.bytes(len)
99
+ @tempfiles = [make_tempfile('array_stream-spec', content)]
100
+ @sqio = SequenceIO.new(File.open(tempfiles[0], 'rb'))
101
+ end
102
+ include_examples 'reading'
103
+ end
104
+
105
+ describe 'an array of strings' do
106
+ before(:each) do
107
+ inputs = Array.new(10) { make_alphanumeric_string(10) }
108
+ @content = inputs.join
109
+ @sqio = SequenceIO.new(inputs)
110
+ end
111
+ include_examples 'reading'
112
+ end
113
+
114
+ describe 'an array of files' do
115
+ before(:each) do
116
+ @content = ''
117
+ @tempfiles = Array.new(10) do |i|
118
+ content = Random.new.bytes(10)
119
+ @content << content
120
+ make_tempfile("array_stream-spec-#{i}", content)
121
+ end
122
+ @sqio = SequenceIO.new(@tempfiles.map { |f| File.open(f, 'rb') })
123
+ end
124
+ include_examples 'reading'
125
+ end
126
+
127
+ describe 'a mix of strings and files' do
128
+ before(:each) do
129
+ @content = ''
130
+ @tempfiles = []
131
+ inputs = (0..9).map do |i|
132
+ content = nil
133
+ begin
134
+ if i.even?
135
+ content = make_alphanumeric_string(10)
136
+ else
137
+ content = Random.new.bytes(10)
138
+ @tempfiles << make_tempfile("array_stream-spec-#{i}", content)
139
+ File.open(tempfiles.last, 'rb')
140
+ end
141
+ ensure
142
+ @content << content
143
+ end
144
+ end
145
+
146
+ @sqio = SequenceIO.new(inputs)
147
+ end
148
+
149
+ include_examples 'reading'
150
+ end
151
+ end
152
+ end
153
+ end