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.
- checksums.yaml +7 -0
- data/.gitignore +42 -0
- data/.rubocop.yml +25 -0
- data/.ruby-version +1 -0
- data/.travis.yml +2 -0
- data/.yardopts +3 -0
- data/Gemfile +6 -0
- data/LICENSE.md +22 -0
- data/README.md +4 -0
- data/Rakefile +49 -0
- data/examples/example.rb +26 -0
- data/examples/uploads/example.zip +0 -0
- data/examples/uploads/example/lorem-ipsum.txt +7 -0
- data/examples/uploads/example/mrt-datacite.xml +22 -0
- data/examples/uploads/example/mrt-dc.xml +13 -0
- data/examples/uploads/example/stash-wrapper.xml +56 -0
- data/lib/stash/sword.rb +39 -0
- data/lib/stash/sword/client.rb +132 -0
- data/lib/stash/sword/deposit_receipt.rb +44 -0
- data/lib/stash/sword/header_utils.rb +42 -0
- data/lib/stash/sword/http_helper.rb +120 -0
- data/lib/stash/sword/iri.rb +12 -0
- data/lib/stash/sword/log_utils.rb +39 -0
- data/lib/stash/sword/module_info.rb +12 -0
- data/lib/stash/sword/namespace.rb +30 -0
- data/lib/stash/sword/sequence_io.rb +105 -0
- data/notes/Dash_Submission_To_Merritt.txt +40 -0
- data/notes/service-document.xml +15 -0
- data/spec/.rubocop.yml +10 -0
- data/spec/data/deposit_receipt_merritt.xml +25 -0
- data/spec/data/deposit_receipt_spec.xml +58 -0
- data/spec/rspec_custom_matchers.rb +118 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/unit/stash/sword2/client_spec.rb +110 -0
- data/spec/unit/stash/sword2/deposit_receipt_spec.rb +48 -0
- data/spec/unit/stash/sword2/http_helper_get_spec.rb +131 -0
- data/spec/unit/stash/sword2/http_helper_post_spec.rb +143 -0
- data/spec/unit/stash/sword2/http_helper_put_spec.rb +143 -0
- data/spec/unit/stash/sword2/log_spec.rb +23 -0
- data/spec/unit/stash/sword2/namespaces_spec.rb +31 -0
- data/spec/unit/stash/sword2/sequence_io_spec.rb +153 -0
- data/stash-sword.gemspec +47 -0
- 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
|