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,15 @@
1
+ <service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom">
2
+ <workspace>
3
+ <atom:title type="text">Merritt</atom:title>
4
+ <collection href="http://merritt.cdlib.org/sword/v2/dash_ucb">
5
+ <atom:title type="text">UCB Dash</atom:title>
6
+ <accept>*/*</accept>
7
+ <accept alternate="multipart-related">*/*</accept>
8
+ <mediation xmlns="http://purl.org/net/sword/terms/">true</mediation>
9
+ <acceptPackaging xmlns="http://purl.org/net/sword/terms/">http://purl.org/net/sword/package/SimpleZip</acceptPackaging>
10
+ </collection>
11
+ </workspace>
12
+ <generator xmlns="http://www.w3.org/2005/Atom" uri="http://www.swordapp.org/" version="2.0" />
13
+ <version xmlns="http://purl.org/net/sword/terms/">2.0</version>
14
+ <maxUploadSize xmlns="http://purl.org/net/sword/terms/">10000000</maxUploadSize>
15
+ </service>
data/spec/.rubocop.yml ADDED
@@ -0,0 +1,10 @@
1
+ inherit_from: ../.rubocop.yml
2
+
3
+ Metrics/MethodLength:
4
+ Enabled: false
5
+
6
+ Metrics/ModuleLength:
7
+ Enabled: false
8
+
9
+ Metrics/ClassLength:
10
+ Enabled: false
@@ -0,0 +1,25 @@
1
+ <entry xmlns="http://www.w3.org/2005/Atom">
2
+ <author>
3
+ <name>ucop_dash_submitter</name>
4
+ </author>
5
+ <title type="text">doi:10.5072/FK1465406644</title>
6
+ <id>http://n2t.net/ark:/99999/fk47h1tz4k</id>
7
+ <updated>2016-06-08T17:24:12.643Z</updated>
8
+ <generator uri="http://www.swordapp.org/" version="2.0"/>
9
+ <link
10
+ href="http://sword-aws-dev.cdlib.org:39001/mrtsword/edit/dash_cdl/doi%3A10.5072%2FFK1465406644"
11
+ rel="edit"
12
+ />
13
+ <link
14
+ href="http://sword-aws-dev.cdlib.org:39001/mrtsword/edit/dash_cdl/doi%3A10.5072%2FFK1465406644"
15
+ rel="http://purl.org/net/sword/terms/add"
16
+ />
17
+ <link
18
+ href="http://merritt-dev.cdlib.org/d/ark%3A%2F99999%2Ffk47h1tz4k"
19
+ rel="edit-media"
20
+ />
21
+ <treatment xmlns="http://purl.org/net/sword/terms/">no treatment information available</treatment>
22
+ <link
23
+ href="http://n2t.net/ark:/99999/fk47h1tz4k" rel="alternate"
24
+ />
25
+ </entry>
@@ -0,0 +1,58 @@
1
+ <entry xmlns="http://www.w3.org/2005/Atom"
2
+ xmlns:sword="http://purl.org/net/sword/"
3
+ xmlns:dcterms="http://purl.org/dc/terms/">
4
+
5
+ <title>My Deposit</title>
6
+ <id>info:something:1</id>
7
+ <updated>2008-08-18T14:27:08Z</updated>
8
+ <summary type="text">A summary</summary>
9
+ <generator uri="http://www.myrepository.ac.uk/sword-plugin" version="1.0"/>
10
+
11
+ <!-- the item's metadata -->
12
+ <dcterms:abstract>The abstract</dcterms:abstract>
13
+ <dcterms:accessRights>Access Rights</dcterms:accessRights>
14
+ <dcterms:alternative>Alternative Title</dcterms:alternative>
15
+ <dcterms:available>Date Available</dcterms:available>
16
+ <dcterms:bibliographicCitation>Bibliographic Citation</dcterms:bibliographicCitation>
17
+ <dcterms:contributor>Contributor</dcterms:contributor>
18
+ <dcterms:description>Description</dcterms:description>
19
+ <dcterms:hasPart>Has Part</dcterms:hasPart>
20
+ <dcterms:hasVersion>Has Version</dcterms:hasVersion>
21
+ <dcterms:identifier>Identifier</dcterms:identifier>
22
+ <dcterms:isPartOf>Is Part Of</dcterms:isPartOf>
23
+ <dcterms:publisher>Publisher</dcterms:publisher>
24
+ <dcterms:references>References</dcterms:references>
25
+ <dcterms:rightsHolder>Rights Holder</dcterms:rightsHolder>
26
+ <dcterms:source>Source</dcterms:source>
27
+ <dcterms:title>Title</dcterms:title>
28
+ <dcterms:type>Type</dcterms:type>
29
+
30
+ <sword:verboseDescription>Verbose description</sword:verboseDescription>
31
+ <sword:treatment>Unpacked. JPEG contents converted to JPEG2000.</sword:treatment>
32
+
33
+ <link rel="alternate" href="http://www.swordserver.ac.uk/col1/mydeposit.html"/>
34
+ <content type="application/zip" src="http://www.swordserver.ac.uk/col1/mydeposit"/>
35
+ <link rel="edit-media" href="http://www.swordserver.ac.uk/col1/mydeposit"/>
36
+ <link rel="edit" href="http://www.swordserver.ac.uk/col1/mydeposit.atom" />
37
+ <link rel="http://purl.org/net/sword/terms/add" href="http://www.swordserver.ac.uk/col1/mydeposit.atom" />
38
+ <sword:packaging>http://purl.org/net/sword/package/BagIt</sword:packaging>
39
+
40
+ <link rel="http://purl.org/net/sword/terms/originalDeposit"
41
+ type="application/zip"
42
+ href="http://www.swordserver.ac.uk/col1/mydeposit/package.zip"/>
43
+ <link rel="http://purl.org/net/sword/terms/derivedResource"
44
+ type="application/pdf"
45
+ href="http://www.swordserver.ac.uk/col1/mydeposit/file1.pdf"/>
46
+ <link rel="http://purl.org/net/sword/terms/derivedResource"
47
+ type="application/pdf"
48
+ href="http://www.swordserver.ac.uk/col1/mydeposit/file2.pdf"/>
49
+
50
+ <link rel="http://purl.org/net/sword/terms/statement"
51
+ type="application/atom+xml;type=feed"
52
+ href="http://www.swordserver.ac.uk/col1/mydeposit.feed"/>
53
+ <link rel="http://purl.org/net/sword/terms/statement"
54
+ type="application/rdf+xml"
55
+ href="http://www.swordserver.ac.uk/col1/mydeposit.rdf"/>
56
+
57
+
58
+ </entry>
@@ -0,0 +1,118 @@
1
+ require 'rspec/expectations'
2
+
3
+ # Workaround for https://github.com/rspec/rspec-mocks/issues/1086
4
+ class RSpec::Mocks::ErrorGenerator # rubocop:disable Style/ClassAndModuleChildren
5
+ unless respond_to?(:_default_error_message)
6
+ alias _default_error_message default_error_message
7
+
8
+ def default_error_message(expectation, expected_args, actual_args)
9
+ failures = [_default_error_message(expectation, expected_args, actual_args)]
10
+ expectation.expected_args.each do |expected|
11
+ if expected.respond_to?(:failure_message)
12
+ failures << expected.failure_message
13
+ end
14
+ end
15
+ failures.join("\n ")
16
+ end
17
+ end
18
+ end
19
+
20
+ def value_for(key:, in_hash:)
21
+ matching_key = in_hash.keys.find { |k| k.to_s.casecmp(key.to_s.downcase).zero? }
22
+ in_hash[matching_key] if matching_key
23
+ end
24
+
25
+ def header?(key:, value:, in_hash:)
26
+ actual_values = value_for(key: key, in_hash: in_hash)
27
+ actual_values = [actual_values] unless actual_values.is_a?(Array)
28
+ actual_values.find do |actual_value|
29
+ if value.nil?
30
+ actual_value.nil?
31
+ elsif value.respond_to?(:match)
32
+ actual_value && value.match(actual_value)
33
+ else
34
+ value == actual_value
35
+ end
36
+ end
37
+ end
38
+
39
+ def all?(headers:, in_hash:)
40
+ headers.each do |k, v|
41
+ return false unless header?(key: k, value: v, in_hash: in_hash)
42
+ end
43
+ true
44
+ end
45
+
46
+ def basic_auth(username, password)
47
+ 'Basic ' + ["#{username}:#{password}"].pack('m').delete("\r\n")
48
+ end
49
+
50
+ RSpec::Matchers.define :request do
51
+ match do |actual|
52
+ failures_for(actual).empty?
53
+ end
54
+
55
+ failure_message do |actual|
56
+ failures_for(actual).join('; ')
57
+ end
58
+
59
+ chain :with_method do |method|
60
+ @method = method
61
+ end
62
+
63
+ chain :with_uri do |expected_uri|
64
+ @expected_uri = expected_uri
65
+ # actual.uri == expected_uri
66
+ end
67
+
68
+ chain :with_headers do |expected_headers|
69
+ @expected_headers = expected_headers
70
+ # has_all(headers: expected_headers, in_hash: actual.to_hash).empty
71
+ end
72
+
73
+ chain :with_auth do |username, password|
74
+ @expected_auth = basic_auth(username, password)
75
+ # value_for(key: 'Authorization', in_hash: actual.to_hash) == @expected_auth ? true : false
76
+ end
77
+
78
+ def failures_for(actual) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
79
+ return ["Expected Net::HTTPRequest, got: #{actual.class}"] unless actual.is_a?(Net::HTTPRequest)
80
+ failures = []
81
+ failures << "Expected method #{@method}, got: #{actual.method}" if bad_method(actual)
82
+ failures << "Expected uri #{@expected_uri}, got: #{actual.uri}" if bad_uri(actual)
83
+ failures << "Expected headers #{expected_headers}, got: #{actual.to_hash}" if bad_headers(actual)
84
+ failures << "Expected Authorization header #{@expected_auth}, got: #{value_for(key: 'Authorization', in_hash: actual.to_hash) || 'nil'}" if bad_auth(actual)
85
+ failures
86
+ end
87
+
88
+ def bad_method(actual)
89
+ actual.method != @method.to_s.upcase if @method
90
+ end
91
+
92
+ def bad_uri(actual)
93
+ actual.uri != @expected_uri if @expected_uri
94
+ end
95
+
96
+ def bad_headers(actual)
97
+ !all?(headers: @expected_headers, in_hash: actual.to_hash) if @expected_headers
98
+ end
99
+
100
+ def bad_auth(actual)
101
+ !header?(key: 'Authorization', value: @expected_auth, in_hash: actual.to_hash) if @expected_auth
102
+ end
103
+
104
+ end
105
+
106
+ RSpec::Matchers.define :include_header do |k, v|
107
+ def matching_key(k, actual)
108
+ actual.keys.find { |k2| k2.to_s.casecmp(k.to_s.downcase).zero? }
109
+ end
110
+
111
+ match do |actual|
112
+ all?(headers: { k => v }, in_hash: actual)
113
+ end
114
+
115
+ failure_message do |actual|
116
+ "expected #{k}: #{v} but found #{value_for(key: k, in_hash: actual) || 'nil'}"
117
+ end
118
+ end
@@ -0,0 +1,33 @@
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
+ require 'rspec'
22
+
23
+ RSpec.configure do |config|
24
+ config.raise_errors_for_deprecations!
25
+ config.mock_with :rspec
26
+ end
27
+
28
+ require 'rspec_custom_matchers'
29
+
30
+ # ------------------------------------------------------------
31
+ # Code under test
32
+
33
+ require 'stash/sword'
@@ -0,0 +1,110 @@
1
+ require 'spec_helper'
2
+ require 'webmock/rspec'
3
+
4
+ module Stash
5
+ module Sword
6
+ describe Client do
7
+ attr_reader :username, :client, :password, :on_behalf_of, :zipfile, :doi, :collection_uri
8
+
9
+ before(:each) do
10
+ @username = 'ucb_dash_submitter'
11
+ @password = 'ucb_dash_password'
12
+ @collection_uri = 'http://uc3-mrtsword-dev.cdlib.org:39001/mrtsword/collection/dash_ucb'
13
+ @on_behalf_of = 'ucb_dash_author'
14
+ @client = Client.new(collection_uri: @collection_uri, username: @username, password: @password, on_behalf_of: @on_behalf_of)
15
+ @zipfile = 'examples/uploads/example.zip'
16
+ @doi = "doi:10.5072/FK#{Time.now.getutc.xmlschema.gsub(/[^0-9a-z]/i, '')}"
17
+ end
18
+
19
+ describe '#create' do
20
+ it 'POSTs with the correct headers' do
21
+ authorized_uri = collection_uri.sub('http://', "http://#{username}:#{password}@")
22
+
23
+ stub_request(:post, authorized_uri).to_return(
24
+ body: '<entry xmlns="http://www.w3.org/2005/Atom"><id>http://merritt.cdlib.org/sword/v2/object/ark:/99999/fk4t157x4p</id><author><name>ucb_dash_submitter</name></author><generator uri="http://www.swordapp.org/" version="2.0" /><link href="http://merritt.cdlib.org/sword/v2/object/ark:/99999/fk4t157x4p" rel="edit" /><link href="http://merritt.cdlib.org/sword/v2/object/ark:/99999/fk4t157x4p" rel="http://purl.org/net/sword/terms/add" /><link href="http://merritt.cdlib.org/sword/v2/object/ark:/99999/fk4t157x4p" rel="edit-media" /><treatment xmlns="http://purl.org/net/sword/terms/">no treatment information available</treatment></entry>'
25
+ )
26
+
27
+ client.create(zipfile: zipfile, doi: doi)
28
+
29
+ md5 = Digest::MD5.file(zipfile).to_s
30
+
31
+ actual_headers = nil
32
+ expect(a_request(:post, authorized_uri).with do |req|
33
+ actual_headers = req.headers
34
+ end).to have_been_made
35
+
36
+ aggregate_failures('request headers') do
37
+ {
38
+ 'On-Behalf-Of' => on_behalf_of,
39
+ 'Packaging' => 'http://purl.org/net/sword/package/SimpleZip',
40
+ 'Slug' => doi,
41
+ 'Content-Disposition' => 'attachment; filename=example.zip',
42
+ 'Content-MD5' => md5,
43
+ 'Content-Length' => /[0-9]+/,
44
+ 'Content-Type' => 'application/zip'
45
+ }.each do |k, v|
46
+ expect(actual_headers).to include_header(k, v)
47
+ end
48
+ end
49
+ end
50
+
51
+ it 'returns the entry'
52
+ it "gets the entry from the Edit-IRI in the Location: header if it isn't returned in the body"
53
+ it 'forwards a success response'
54
+ it 'forwards a 4xx error'
55
+ it 'forwards a 5xx error'
56
+ it 'forwards an internal exception'
57
+ end
58
+
59
+ describe '#update' do
60
+ it 'PUTs with the correct headers' do
61
+ edit_iri = "http://merritt.cdlib.org/sword/v2/object/#{doi}"
62
+ authorized_uri = edit_iri.sub('http://', "http://#{username}:#{password}@")
63
+
64
+ stub_request(:put, authorized_uri)
65
+
66
+ client.update(edit_iri: edit_iri, zipfile: zipfile)
67
+
68
+ md5 = Digest::MD5.file(zipfile).to_s
69
+
70
+ actual_body = nil
71
+ actual_headers = nil
72
+ expect(a_request(:put, authorized_uri).with do |req|
73
+ actual_body = req.body
74
+ actual_headers = req.headers
75
+ end).to have_been_made
76
+
77
+ aggregate_failures('request headers') do
78
+ {
79
+ 'Content-Length' => /[0-9]+/,
80
+ 'Content-Type' => %r{multipart/related; type="application/atom\+xml"; boundary=.*},
81
+ 'On-Behalf-Of' => on_behalf_of
82
+ }.each do |k, v|
83
+ expect(actual_headers).to include_header(k, v)
84
+ end
85
+ end
86
+
87
+ mime_headers = {
88
+ 'Packaging' => 'http://purl.org/net/sword/package/SimpleZip',
89
+ 'Content-Disposition' => 'attachment; name=payload; filename="example.zip"',
90
+ 'Content-Type' => 'application/zip',
91
+ 'Content-MD5' => md5
92
+ }
93
+
94
+ aggregate_failures('MIME headers') do
95
+ mime_headers.each do |k, v|
96
+ expect(actual_body).to include("#{k}: #{v}"), "expected #{k}: #{v}, closest match was #{actual_body[/#{k}[^\n]+/m]}"
97
+ end
98
+ end
99
+ end
100
+
101
+ it 'does something clever and asynchronous'
102
+ it 'forwards a success response'
103
+ it 'forwards a 4xx error'
104
+ it 'forwards a 5xx error'
105
+ it 'forwards an internal exception'
106
+ end
107
+
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ module Stash
4
+ module Sword
5
+ describe DepositReceipt do
6
+ describe '#parse_xml' do
7
+
8
+ it 'parses the response from the spec' do
9
+ xml = File.read('spec/data/deposit_receipt_spec.xml')
10
+ receipt = DepositReceipt.parse_xml(xml)
11
+ expect(receipt).to be_a(DepositReceipt)
12
+
13
+ em_iri = receipt.link(rel: 'edit-media')
14
+ expect(em_iri.href).to eq(URI('http://www.swordserver.ac.uk/col1/mydeposit'))
15
+
16
+ se_iri = receipt.link(rel: URI('http://purl.org/net/sword/terms/add'))
17
+ expect(se_iri.href).to eq(URI('http://www.swordserver.ac.uk/col1/mydeposit.atom'))
18
+
19
+ edit_iri = receipt.link(rel: 'edit')
20
+ expect(edit_iri.href).to eq(URI('http://www.swordserver.ac.uk/col1/mydeposit.atom'))
21
+
22
+ expect(receipt.em_iri).to eq(em_iri.href)
23
+ expect(receipt.se_iri).to eq(se_iri.href)
24
+ expect(receipt.edit_iri).to eq(edit_iri.href)
25
+ end
26
+
27
+ it 'parses a Merritt response' do
28
+ xml = File.read('spec/data/deposit_receipt_merritt.xml')
29
+ receipt = DepositReceipt.parse_xml(xml)
30
+ expect(receipt).to be_a(DepositReceipt)
31
+
32
+ em_iri = receipt.link(rel: 'edit-media')
33
+ expect(em_iri.href).to eq(URI('http://merritt-dev.cdlib.org/d/ark%3A%2F99999%2Ffk47h1tz4k'))
34
+
35
+ se_iri = receipt.link(rel: URI('http://purl.org/net/sword/terms/add'))
36
+ expect(se_iri.href).to eq(URI('http://sword-aws-dev.cdlib.org:39001/mrtsword/edit/dash_cdl/doi%3A10.5072%2FFK1465406644'))
37
+
38
+ edit_iri = receipt.link(rel: 'edit')
39
+ expect(edit_iri.href).to eq(URI('http://sword-aws-dev.cdlib.org:39001/mrtsword/edit/dash_cdl/doi%3A10.5072%2FFK1465406644'))
40
+
41
+ expect(receipt.em_iri).to eq(em_iri.href)
42
+ expect(receipt.se_iri).to eq(se_iri.href)
43
+ expect(receipt.edit_iri).to eq(edit_iri.href)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,131 @@
1
+ require 'spec_helper'
2
+
3
+ module Stash
4
+ module Sword
5
+ describe HTTPHelper do
6
+
7
+ # ------------------------------------------------------------
8
+ # Fixture
9
+
10
+ attr_writer :user_agent
11
+
12
+ def user_agent
13
+ @user_agent ||= 'elvis'
14
+ end
15
+
16
+ attr_writer :helper
17
+
18
+ def helper
19
+ @helper ||= HTTPHelper.new(user_agent: user_agent)
20
+ end
21
+
22
+ # ------------------------------------------------------------
23
+ # Tests
24
+
25
+ describe '#get' do
26
+
27
+ # ------------------------------
28
+ # Fixture
29
+
30
+ before(:each) do
31
+ @http = instance_double(Net::HTTP)
32
+ allow(Net::HTTP).to receive(:new).and_return(@http)
33
+ allow(@http).to receive(:start).and_yield(@http)
34
+ @success = Net::HTTPOK.allocate
35
+ @body = 'I am the body of the response'
36
+ allow(@success).to receive(:body).and_return(@body)
37
+ end
38
+
39
+ # ------------------------------
40
+ # Tests
41
+
42
+ it 'gets the specified URI' do
43
+ uri = URI('http://example.org/')
44
+ expect(@http).to receive(:request).with(request.with_method('GET').with_uri(uri)).and_yield(@success)
45
+ helper.get(uri: uri)
46
+ end
47
+
48
+ it 'gets a response' do
49
+ expect(@http).to receive(:request).and_yield(@success)
50
+ expect(helper.get(uri: URI('http://example.org/'))).to be(@body)
51
+ end
52
+
53
+ it 'sets the User-Agent header' do
54
+ agent = 'Not Elvis'
55
+ helper = HTTPHelper.new(user_agent: agent)
56
+ expect(@http).to receive(:request).with(request.with_method('GET').with_headers('User-Agent' => agent)).and_yield(@success)
57
+ helper.get(uri: URI('http://example.org/'))
58
+ end
59
+
60
+ it 'sets Basic-Auth headers' do
61
+ uri = URI('http://example.org/')
62
+ expect(@http).to receive(:request).with(request.with_method('GET').with_uri(uri).with_auth('elvis', 'presley')).and_yield(@success)
63
+ helper = HTTPHelper.new(user_agent: user_agent, username: 'elvis', password: 'presley')
64
+ helper.get(uri: uri)
65
+ end
66
+
67
+ it 'uses SSL for https requests' do
68
+ uri = URI('https://example.org/')
69
+ expect(Net::HTTP).to receive(:start).with(uri.hostname, uri.port, use_ssl: true).and_call_original
70
+ expect(@http).to receive(:request).and_yield(@success)
71
+ helper.get(uri: uri)
72
+ end
73
+
74
+ it 're-requests on receiving a 1xx' do
75
+ uri = URI('http://example.org/')
76
+ @info = Net::HTTPContinue.allocate
77
+
78
+ expected = [@info, @success]
79
+ expect(@http).to receive(:request).twice.with(request.with_method('GET').with_uri(uri).with_headers('User-Agent' => user_agent)) do |&block|
80
+ block.call(expected.shift)
81
+ end
82
+
83
+ expect(helper.get(uri: uri)).to be(@body)
84
+ end
85
+
86
+ it 'redirects on receiving a 3xx' do
87
+ uri = URI('http://example.org/')
88
+ uri2 = URI('http://example.org/new')
89
+ @redirect = Net::HTTPMovedPermanently.allocate
90
+ allow(@redirect).to receive(:[]).with('location').and_return(uri2.to_s)
91
+ expect(@http).to receive(:request).with(request.with_method('GET').with_uri(uri).with_headers('User-Agent' => user_agent)).and_yield(@redirect)
92
+ expect(@http).to receive(:request).with(request.with_method('GET').with_uri(uri2).with_headers('User-Agent' => user_agent)).and_yield(@success)
93
+ expect(helper.get(uri: uri)).to be(@body)
94
+ end
95
+
96
+ it 'only redirects a limited number of times' do
97
+ uri = URI('http://example.org/')
98
+ @redirect = Net::HTTPMovedPermanently.allocate
99
+ allow(@redirect).to receive(:[]).with('location').and_return(uri.to_s)
100
+ expect(@http).to receive(:request).with(request.with_method('GET').with_uri(uri).with_headers('User-Agent' => user_agent)).exactly(HTTPHelper::DEFAULT_MAX_REDIRECTS).times.and_yield(@redirect)
101
+ expect { helper.get(uri: uri) }.to raise_error do |e|
102
+ expect(e.message).to match(/Redirect limit.*exceeded.*#{uri.to_s}/)
103
+ end
104
+ end
105
+
106
+ it 'fails on a 4xx' do
107
+ @error = Net::HTTPForbidden
108
+ allow(@error).to receive(:code).and_return(403)
109
+ allow(@error).to receive(:message).and_return('Forbidden')
110
+ expect(@http).to receive(:request).and_yield(@error)
111
+ uri = URI('http://example.org/')
112
+ expect { helper.get(uri: uri) }.to raise_error do |e|
113
+ expect(e.message).to match(/403.*Forbidden.*#{uri.to_s}/)
114
+ end
115
+ end
116
+
117
+ it 'fails on a 5xx' do
118
+ @error = Net::HTTPServerError
119
+ allow(@error).to receive(:code).and_return(500)
120
+ allow(@error).to receive(:message).and_return('Internal Server Error')
121
+ expect(@http).to receive(:request).and_yield(@error)
122
+ uri = URI('http://example.org/')
123
+ expect { helper.get(uri: uri) }.to raise_error do |e|
124
+ expect(e.message).to match(/500.*Internal Server Error.*#{uri.to_s}/)
125
+ end
126
+ end
127
+ end
128
+
129
+ end
130
+ end
131
+ end