stash-sword 0.1.0

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