savon 2.12.0 → 2.13.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -0
- data/CHANGELOG.md +98 -76
- data/CONTRIBUTING.md +15 -19
- data/Gemfile +2 -7
- data/README.md +15 -19
- data/RELEASING.md +10 -0
- data/lib/savon/block_interface.rb +1 -0
- data/lib/savon/builder.rb +98 -29
- data/lib/savon/client.rb +1 -0
- data/lib/savon/core_ext/string.rb +1 -0
- data/lib/savon/header.rb +2 -6
- data/lib/savon/http_error.rb +4 -4
- data/lib/savon/log_message.rb +1 -0
- data/lib/savon/message.rb +1 -0
- data/lib/savon/mock/expectation.rb +1 -0
- data/lib/savon/mock/spec_helper.rb +1 -0
- data/lib/savon/mock.rb +1 -0
- data/lib/savon/model.rb +1 -0
- data/lib/savon/operation.rb +20 -18
- data/lib/savon/options.rb +56 -0
- data/lib/savon/qualified_message.rb +4 -3
- data/lib/savon/request.rb +5 -0
- data/lib/savon/request_logger.rb +8 -2
- data/lib/savon/response.rb +48 -1
- data/lib/savon/soap_fault.rb +2 -1
- data/lib/savon/version.rb +2 -1
- data/lib/savon.rb +1 -0
- data/savon.gemspec +9 -8
- data/spec/fixtures/response/empty_soap_fault.xml +13 -0
- data/spec/fixtures/wsdl/elements_in_types.xml +43 -0
- data/spec/integration/support/application.rb +33 -1
- data/spec/integration/support/server.rb +1 -0
- data/spec/integration/zipcode_example_spec.rb +5 -8
- data/spec/savon/builder_spec.rb +2 -1
- data/spec/savon/client_spec.rb +5 -4
- data/spec/savon/core_ext/string_spec.rb +2 -1
- data/spec/savon/features/message_tag_spec.rb +2 -1
- data/spec/savon/http_error_spec.rb +9 -1
- data/spec/savon/log_message_spec.rb +2 -1
- data/spec/savon/message_spec.rb +2 -11
- data/spec/savon/mock_spec.rb +2 -1
- data/spec/savon/model_spec.rb +2 -1
- data/spec/savon/multipart_request_spec.rb +46 -0
- data/spec/savon/observers_spec.rb +2 -1
- data/spec/savon/operation_spec.rb +20 -43
- data/spec/savon/options_spec.rb +51 -1
- data/spec/savon/qualified_message_spec.rb +2 -1
- data/spec/savon/request_logger_spec.rb +2 -1
- data/spec/savon/request_spec.rb +47 -6
- data/spec/savon/response_spec.rb +2 -1
- data/spec/savon/soap_fault_spec.rb +12 -1
- data/spec/savon/softlayer_spec.rb +17 -2
- data/spec/spec_helper.rb +5 -4
- data/spec/support/adapters.rb +1 -0
- data/spec/support/endpoint.rb +1 -0
- data/spec/support/fixture.rb +1 -0
- data/spec/support/integration.rb +1 -0
- data/spec/support/stdout.rb +1 -0
- metadata +57 -34
- data/.travis.yml +0 -18
- data/donate.png +0 -0
- data/spec/integration/centra_spec.rb +0 -67
- data/spec/integration/email_example_spec.rb +0 -32
- data/spec/integration/random_quote_spec.rb +0 -23
- data/spec/integration/ratp_example_spec.rb +0 -28
- data/spec/integration/stockquote_example_spec.rb +0 -34
- data/spec/integration/temperature_example_spec.rb +0 -46
data/lib/savon/response.rb
CHANGED
@@ -1,14 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "nori"
|
2
3
|
require "savon/soap_fault"
|
3
4
|
require "savon/http_error"
|
4
5
|
|
5
6
|
module Savon
|
6
7
|
class Response
|
8
|
+
CRLF = /\r\n/
|
9
|
+
WSP = /[#{%Q|\x9\x20|}]/
|
7
10
|
|
8
11
|
def initialize(http, globals, locals)
|
9
12
|
@http = http
|
10
13
|
@globals = globals
|
11
14
|
@locals = locals
|
15
|
+
@attachments = []
|
16
|
+
@xml = ''
|
17
|
+
@has_parsed_body = false
|
12
18
|
|
13
19
|
build_soap_and_http_errors!
|
14
20
|
raise_soap_and_http_errors! if @globals[:raise_errors]
|
@@ -53,7 +59,12 @@ module Savon
|
|
53
59
|
end
|
54
60
|
|
55
61
|
def xml
|
56
|
-
|
62
|
+
if multipart?
|
63
|
+
parse_body unless @has_parsed_body
|
64
|
+
@xml
|
65
|
+
else
|
66
|
+
@http.body
|
67
|
+
end
|
57
68
|
end
|
58
69
|
|
59
70
|
alias_method :to_xml, :xml
|
@@ -74,8 +85,44 @@ module Savon
|
|
74
85
|
nori.find(envelope, *path)
|
75
86
|
end
|
76
87
|
|
88
|
+
def attachments
|
89
|
+
if multipart?
|
90
|
+
parse_body unless @has_parsed_body
|
91
|
+
@attachments
|
92
|
+
else
|
93
|
+
[]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def multipart?
|
98
|
+
!(http.headers['content-type'] =~ /^multipart/im).nil?
|
99
|
+
end
|
100
|
+
|
77
101
|
private
|
78
102
|
|
103
|
+
def boundary
|
104
|
+
return unless multipart?
|
105
|
+
Mail::Field.new('content-type', http.headers['content-type']).parameters['boundary']
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse_body
|
109
|
+
http.body.force_encoding Encoding::ASCII_8BIT
|
110
|
+
parts = http.body.split(/(?:\A|\r\n)--#{Regexp.escape(boundary)}(?=(?:--)?\s*$)/)
|
111
|
+
parts[1..-1].to_a.each_with_index do |part, index|
|
112
|
+
header_part, body_part = part.lstrip.split(/#{CRLF}#{CRLF}|#{CRLF}#{WSP}*#{CRLF}(?!#{WSP})/m, 2)
|
113
|
+
section = Mail::Part.new(
|
114
|
+
body: body_part
|
115
|
+
)
|
116
|
+
section.header = header_part
|
117
|
+
if index == 0
|
118
|
+
@xml = section.body.to_s
|
119
|
+
else
|
120
|
+
@attachments << section
|
121
|
+
end
|
122
|
+
end
|
123
|
+
@has_parsed_body = true
|
124
|
+
end
|
125
|
+
|
79
126
|
def build_soap_and_http_errors!
|
80
127
|
@soap_fault = SOAPFault.new(@http, nori, xml) if soap_fault?
|
81
128
|
@http_error = HTTPError.new(@http) if http_error?
|
data/lib/savon/soap_fault.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module Savon
|
2
3
|
class SOAPFault < Error
|
3
4
|
|
4
5
|
def self.present?(http, xml = nil)
|
5
6
|
xml ||= http.body
|
6
7
|
fault_node = xml.include?("Fault>")
|
7
|
-
soap1_fault = xml.
|
8
|
+
soap1_fault = xml.match(/faultcode\/?\>/) && xml.match(/faultstring\/?\>/)
|
8
9
|
soap2_fault = xml.include?("Code>") && xml.include?("Reason>")
|
9
10
|
|
10
11
|
fault_node && (soap1_fault || soap2_fault)
|
data/lib/savon/version.rb
CHANGED
data/lib/savon.rb
CHANGED
data/savon.gemspec
CHANGED
@@ -12,29 +12,30 @@ Gem::Specification.new do |s|
|
|
12
12
|
s.homepage = "http://savonrb.com"
|
13
13
|
s.summary = "Heavy metal SOAP client"
|
14
14
|
s.description = s.summary
|
15
|
-
s.required_ruby_version = '>=
|
15
|
+
s.required_ruby_version = '>= 2.7.0'
|
16
16
|
|
17
|
-
s.rubyforge_project = s.name
|
18
17
|
s.license = 'MIT'
|
19
18
|
|
20
19
|
s.add_dependency "nori", "~> 2.4"
|
21
|
-
s.add_dependency "httpi", "
|
20
|
+
s.add_dependency "httpi", ">= 2.4.5"
|
22
21
|
s.add_dependency "wasabi", "~> 3.4"
|
23
22
|
s.add_dependency "akami", "~> 1.2"
|
24
23
|
s.add_dependency "gyoku", "~> 1.2"
|
25
24
|
s.add_dependency "builder", ">= 2.1.2"
|
26
25
|
s.add_dependency "nokogiri", ">= 1.8.1"
|
26
|
+
s.add_dependency "mail", "~> 2.5"
|
27
27
|
|
28
28
|
s.add_development_dependency "rack"
|
29
|
-
s.add_development_dependency "puma", "
|
29
|
+
s.add_development_dependency "puma", ">= 4.3.8"
|
30
30
|
|
31
|
-
s.add_development_dependency "
|
32
|
-
s.add_development_dependency "
|
31
|
+
s.add_development_dependency "byebug"
|
32
|
+
s.add_development_dependency "rake", ">= 12.3.3"
|
33
|
+
s.add_development_dependency "rspec", "~> 3.9"
|
33
34
|
s.add_development_dependency "mocha", "~> 0.14"
|
34
|
-
s.add_development_dependency "json", "
|
35
|
+
s.add_development_dependency "json", ">= 2.3.0"
|
35
36
|
|
36
37
|
ignores = File.readlines(".gitignore").grep(/\S+/).map(&:chomp)
|
37
|
-
dotfiles = %w[.gitignore .
|
38
|
+
dotfiles = %w[.gitignore .yardopts]
|
38
39
|
|
39
40
|
all_files_without_ignores = Dir["**/*"].reject { |f|
|
40
41
|
File.directory?(f) || ignores.any? { |i| File.fnmatch(i, f) }
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
|
2
|
+
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
|
3
|
+
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
|
4
|
+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
5
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
6
|
+
<SOAP-ENV:Body>
|
7
|
+
<SOAP-ENV:Fault>
|
8
|
+
<faultcode/>
|
9
|
+
<faultstring/>
|
10
|
+
<detail><soapVal><ERRNO xsi:type="xsd:string">80:1289245853:55</ERRNO></soapVal></detail>
|
11
|
+
</SOAP-ENV:Fault>
|
12
|
+
</SOAP-ENV:Body>
|
13
|
+
</SOAP-ENV:Envelope>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<wsdl:definitions xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:tns="www.example.com/XML" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:s1="http://microsoft.com/wsdl/types/" xmlns:s="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" targetNamespace="www.example.com/XML" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
|
3
|
+
<wsdl:types>
|
4
|
+
<s:schema elementFormDefault="qualified" targetNamespace="www.example.com/XML">
|
5
|
+
<s:complexType name="Transaction" abstract="true">
|
6
|
+
<s:sequence>
|
7
|
+
<s:element minOccurs="0" maxOccurs="1" name="Qualified" type="s:string" />
|
8
|
+
</s:sequence>
|
9
|
+
</s:complexType>
|
10
|
+
<s:complexType name="TopLevelTransaction">
|
11
|
+
<s:complexContent mixed="false">
|
12
|
+
<s:extension base="tns:Transaction">
|
13
|
+
</s:extension>
|
14
|
+
</s:complexContent>
|
15
|
+
</s:complexType>
|
16
|
+
<s:element name="TopLevelTransaction">
|
17
|
+
<s:complexType>
|
18
|
+
<s:sequence>
|
19
|
+
<s:element minOccurs="0" maxOccurs="1" name="TopLevelTransaction" type="tns:TopLevelTransaction" />
|
20
|
+
</s:sequence>
|
21
|
+
</s:complexType>
|
22
|
+
</s:element>
|
23
|
+
</s:schema>
|
24
|
+
</wsdl:types>
|
25
|
+
<wsdl:message name="TopLevelTransactionSoapIn">
|
26
|
+
<wsdl:part name="parameters" element="tns:TopLevelTransaction" />
|
27
|
+
</wsdl:message>
|
28
|
+
<wsdl:portType name="XMLTESoap">
|
29
|
+
<wsdl:operation name="TopLevelTransaction">
|
30
|
+
<wsdl:documentation xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">TopLevelTransaction.</wsdl:documentation>
|
31
|
+
<wsdl:input message="tns:TopLevelTransactionSoapIn" />
|
32
|
+
</wsdl:operation>
|
33
|
+
</wsdl:portType>
|
34
|
+
<wsdl:binding name="XMLTESoap12" type="tns:XMLTESoap">
|
35
|
+
<soap12:binding transport="http://schemas.xmlsoap.org/soap/http" />
|
36
|
+
<wsdl:operation name="TopLevelTransaction">
|
37
|
+
<soap12:operation soapAction="www.example.com/XML/TopLevelTransaction" style="document" />
|
38
|
+
<wsdl:input>
|
39
|
+
<soap12:body use="literal" />
|
40
|
+
</wsdl:input>
|
41
|
+
</wsdl:operation>
|
42
|
+
</wsdl:binding>
|
43
|
+
</wsdl:definitions>
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "rack"
|
2
3
|
require "json"
|
3
4
|
|
@@ -6,7 +7,7 @@ class IntegrationServer
|
|
6
7
|
def self.respond_with(options = {})
|
7
8
|
code = options.fetch(:code, 200)
|
8
9
|
body = options.fetch(:body, "")
|
9
|
-
headers = { "Content-Type" => "text/plain", "Content-Length" => body.size.to_s }
|
10
|
+
headers = { "Content-Type" => "text/plain", "Content-Length" => body.size.to_s }.merge options.fetch(:headers, {})
|
10
11
|
|
11
12
|
[code, headers, [body]]
|
12
13
|
end
|
@@ -78,5 +79,36 @@ class IntegrationServer
|
|
78
79
|
run app
|
79
80
|
end
|
80
81
|
|
82
|
+
map "/multipart" do
|
83
|
+
run lambda { |env|
|
84
|
+
boundary = 'mimepart_boundary'
|
85
|
+
message = Mail.new
|
86
|
+
xml_part = Mail::Part.new do
|
87
|
+
content_type 'text/xml'
|
88
|
+
body %{<?xml version='1.0' encoding='UTF-8'?>
|
89
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
90
|
+
<soapenv:Header>response header</soapenv:Header>
|
91
|
+
<soapenv:Body>response body</soapenv:Body>
|
92
|
+
</soapenv:Envelope>}
|
93
|
+
# in Content-Type the start parameter is recommended (RFC 2387)
|
94
|
+
content_id '<soap-request-body@soap>'
|
95
|
+
end
|
96
|
+
message.add_part xml_part
|
97
|
+
|
98
|
+
message.add_file File.expand_path("../../../fixtures/gzip/message.gz", __FILE__)
|
99
|
+
message.parts.last.content_location = 'message.gz'
|
100
|
+
message.parts.last.content_id = 'attachment1'
|
101
|
+
|
102
|
+
message.ready_to_send!
|
103
|
+
message.body.set_sort_order [ "text/xml" ]
|
104
|
+
message.body.encoded(message.content_transfer_encoding)
|
105
|
+
|
106
|
+
IntegrationServer.respond_with({
|
107
|
+
headers: { "Content-Type" => "multipart/related; boundary=\"#{message.body.boundary}\"; type=\"text/xml\"; start=\"#{xml_part.content_id}\"" },
|
108
|
+
body: message.body.encoded(message.content_transfer_encoding)
|
109
|
+
})
|
110
|
+
}
|
111
|
+
end
|
112
|
+
|
81
113
|
end
|
82
114
|
end
|
@@ -1,18 +1,16 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
describe "ZIP code example" do
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
4
3
|
|
4
|
+
RSpec.describe "ZIP code example" do
|
5
5
|
it "supports threads making requests simultaneously" do
|
6
6
|
client = Savon.client(
|
7
|
-
# The WSDL document provided by the service.
|
8
7
|
:wsdl => "http://www.thomas-bayer.com/axis2/services/BLZService?wsdl",
|
9
8
|
|
10
9
|
# Lower timeouts so these specs don't take forever when the service is not available.
|
11
10
|
:open_timeout => 10,
|
12
11
|
:read_timeout => 10,
|
13
12
|
|
14
|
-
# Disable logging for cleaner spec output.
|
15
|
-
:log => false
|
13
|
+
:log => false # Disable logging for cleaner spec output.
|
16
14
|
)
|
17
15
|
|
18
16
|
mutex = Mutex.new
|
@@ -22,7 +20,7 @@ describe "ZIP code example" do
|
|
22
20
|
|
23
21
|
threads = request_data.map do |blz|
|
24
22
|
thread = Thread.new do
|
25
|
-
response = call_and_fail_gracefully
|
23
|
+
response = call_and_fail_gracefully(client, :get_bank, :message => { :blz => blz })
|
26
24
|
Thread.current[:value] = response.body[:get_bank_response][:details]
|
27
25
|
mutex.synchronize { threads_waiting -= 1 }
|
28
26
|
end
|
@@ -38,5 +36,4 @@ describe "ZIP code example" do
|
|
38
36
|
|
39
37
|
expect(values.uniq.size).to eq(values.size)
|
40
38
|
end
|
41
|
-
|
42
39
|
end
|
data/spec/savon/builder_spec.rb
CHANGED
data/spec/savon/client_spec.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "spec_helper"
|
2
3
|
require "integration/support/server"
|
3
4
|
|
4
|
-
describe Savon::Client do
|
5
|
+
RSpec.describe Savon::Client do
|
5
6
|
|
6
7
|
before :all do
|
7
8
|
@server = IntegrationServer.run
|
@@ -231,15 +232,15 @@ describe Savon::Client do
|
|
231
232
|
end
|
232
233
|
|
233
234
|
it "raises when the operation name is not a symbol" do
|
234
|
-
expect { new_client.build_request("not a symbol") }.to raise_error
|
235
|
+
expect { new_client.build_request("not a symbol") }.to raise_error ArgumentError
|
235
236
|
end
|
236
237
|
|
237
238
|
it "raises when given an unknown option via the Hash syntax" do
|
238
|
-
expect { new_client.build_request(:authenticate, :invalid_local_option => true) }.to raise_error
|
239
|
+
expect { new_client.build_request(:authenticate, :invalid_local_option => true) }.to raise_error Savon::UnknownOptionError
|
239
240
|
end
|
240
241
|
|
241
242
|
it "raises when given an unknown option via the block syntax" do
|
242
|
-
expect { new_client.build_request(:authenticate) { another_invalid_local_option true } }.to raise_error
|
243
|
+
expect { new_client.build_request(:authenticate) { another_invalid_local_option true } }.to raise_error Savon::UnknownOptionError
|
243
244
|
end
|
244
245
|
end
|
245
246
|
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "spec_helper"
|
2
3
|
|
3
|
-
describe Savon::HTTPError do
|
4
|
+
RSpec.describe Savon::HTTPError do
|
4
5
|
let(:http_error) { Savon::HTTPError.new new_response(:code => 404, :body => "Not Found") }
|
6
|
+
let(:http_error_with_empty_body) { Savon::HTTPError.new new_response(:code => 404, :body => "") }
|
5
7
|
let(:no_error) { Savon::HTTPError.new new_response }
|
6
8
|
|
7
9
|
it "inherits from Savon::Error" do
|
@@ -30,6 +32,12 @@ describe Savon::HTTPError do
|
|
30
32
|
it "returns the HTTP error message" do
|
31
33
|
expect(http_error.send method).to eq("HTTP error (404): Not Found")
|
32
34
|
end
|
35
|
+
|
36
|
+
context "when the body is empty" do
|
37
|
+
it "returns the HTTP error without the body message" do
|
38
|
+
expect(http_error_with_empty_body.send method).to eq("HTTP error (404)")
|
39
|
+
end
|
40
|
+
end
|
33
41
|
end
|
34
42
|
end
|
35
43
|
|
data/spec/savon/message_spec.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "spec_helper"
|
2
3
|
require "integration/support/server"
|
3
4
|
|
4
|
-
describe Savon::Message do
|
5
|
+
RSpec.describe Savon::Message do
|
5
6
|
|
6
7
|
before do
|
7
8
|
@server = IntegrationServer.run
|
@@ -55,16 +56,6 @@ describe Savon::Message do
|
|
55
56
|
end
|
56
57
|
end
|
57
58
|
end
|
58
|
-
|
59
|
-
context 'wsa:MessageID' do
|
60
|
-
let(:message_id_tag) {
|
61
|
-
'<wsa:MessageID xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">'
|
62
|
-
}
|
63
|
-
it 'should include xmlns:wsa attribute' do
|
64
|
-
response = client.call(:something, message: {})
|
65
|
-
expect(response.xml).to include(message_id_tag)
|
66
|
-
end
|
67
|
-
end
|
68
59
|
end
|
69
60
|
|
70
61
|
end
|
data/spec/savon/mock_spec.rb
CHANGED
data/spec/savon/model_spec.rb
CHANGED
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Savon::Builder do
|
4
|
+
|
5
|
+
let(:globals) { Savon::GlobalOptions.new({ :endpoint => "http://example.co", :namespace => "http://v1.example.com" }) }
|
6
|
+
let(:no_wsdl) { Wasabi::Document.new }
|
7
|
+
|
8
|
+
it "building multipart request from inline content" do
|
9
|
+
locals = {
|
10
|
+
attachments: [
|
11
|
+
{ filename: 'x1.xml', content: '<xml>abc1</xml>'},
|
12
|
+
{ filename: 'x2.xml', content: '<xml>abc2</xml>'},
|
13
|
+
]
|
14
|
+
}
|
15
|
+
builder = Savon::Builder.new(:operation1, no_wsdl, globals, Savon::LocalOptions.new(locals))
|
16
|
+
request_body = builder.to_s
|
17
|
+
|
18
|
+
expect(request_body).to include('Content-Type')
|
19
|
+
expect(request_body).to match(/<[a-z]+:operation1>/)
|
20
|
+
|
21
|
+
locals[:attachments].each do |attachment|
|
22
|
+
expect(request_body).to match(/^Content-Location: #{attachment[:filename]}\s$/)
|
23
|
+
expect(request_body).to include(Base64.encode64(attachment[:content]).strip)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
it "building multipart request from file" do
|
29
|
+
locals = {
|
30
|
+
attachments: {
|
31
|
+
'file.gz' => File.expand_path("../../fixtures/gzip/message.gz", __FILE__)
|
32
|
+
}
|
33
|
+
}
|
34
|
+
builder = Savon::Builder.new(:operation1, no_wsdl, globals, Savon::LocalOptions.new(locals))
|
35
|
+
request_body = builder.to_s
|
36
|
+
|
37
|
+
expect(request_body).to include('Content-Type')
|
38
|
+
expect(request_body).to match(/<[a-z]+:operation1>/)
|
39
|
+
|
40
|
+
locals[:attachments].each do |id, file|
|
41
|
+
expect(request_body).to match(/^Content-Location: #{id}\s$/)
|
42
|
+
expect(request_body.gsub("\r", "")).to include(Base64.encode64(File.read(file)).strip)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -1,9 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "spec_helper"
|
2
3
|
require "integration/support/server"
|
3
4
|
require "json"
|
4
5
|
require "ostruct"
|
5
6
|
|
6
|
-
describe Savon::Operation do
|
7
|
+
RSpec.describe Savon::Operation do
|
7
8
|
|
8
9
|
let(:globals) { Savon::GlobalOptions.new(:endpoint => @server.url(:repeat), :log => false) }
|
9
10
|
let(:wsdl) { Wasabi::Document.new Fixture.wsdl(:taxcloud) }
|
@@ -108,7 +109,7 @@ describe Savon::Operation do
|
|
108
109
|
it "sets the Content-Length header" do
|
109
110
|
# XXX: probably the worst spec ever written. refactor! [dh, 2013-01-05]
|
110
111
|
http_request = HTTPI::Request.new
|
111
|
-
http_request.headers.expects(:[]=).with("Content-Length", "
|
112
|
+
http_request.headers.expects(:[]=).with("Content-Length", "723")
|
112
113
|
Savon::SOAPRequest.any_instance.expects(:build).returns(http_request)
|
113
114
|
|
114
115
|
new_operation(:verify_address, wsdl, globals).call
|
@@ -165,40 +166,28 @@ describe Savon::Operation do
|
|
165
166
|
expect(actual_soap_action).to eq(%("authenticate"))
|
166
167
|
end
|
167
168
|
|
168
|
-
it "
|
169
|
-
globals.multipart
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
169
|
+
it "handle multipart response" do
|
170
|
+
globals.endpoint @server.url(:multipart)
|
171
|
+
operation = new_operation(:example, no_wsdl, globals)
|
172
|
+
response = operation.call do
|
173
|
+
attachments [
|
174
|
+
{ filename: 'x1.xml', content: '<xml>abc</xml>'},
|
175
|
+
{ filename: 'x2.xml', content: '<xml>cde</xml>'},
|
176
|
+
]
|
176
177
|
end
|
177
|
-
end
|
178
|
-
|
179
|
-
it "returns a Savon::Multipart::Response if available and requested locally" do
|
180
|
-
with_multipart_mocked do
|
181
|
-
operation = new_operation(:authenticate, no_wsdl, globals)
|
182
|
-
response = operation.call(:multipart => true)
|
183
178
|
|
184
|
-
|
185
|
-
|
179
|
+
expect(response.multipart?).to be true
|
180
|
+
expect(response.header).to eq 'response header'
|
181
|
+
expect(response.body).to eq 'response body'
|
182
|
+
expect(response.attachments.first.content_id).to eq 'attachment1'
|
186
183
|
end
|
187
184
|
|
188
|
-
it "
|
189
|
-
globals
|
190
|
-
|
191
|
-
operation = new_operation(:authenticate, no_wsdl, globals)
|
185
|
+
it "simple request is not multipart" do
|
186
|
+
operation = new_operation(:example, no_wsdl, globals)
|
187
|
+
response = operation.call
|
192
188
|
|
193
|
-
expect
|
194
|
-
|
195
|
-
end
|
196
|
-
|
197
|
-
it "raises if savon-multipart is not available and it was requested locally" do
|
198
|
-
operation = new_operation(:authenticate, no_wsdl, globals)
|
199
|
-
|
200
|
-
expect { operation.call(:multipart => true) }.
|
201
|
-
to raise_error RuntimeError, /Unable to find Savon::Multipart/
|
189
|
+
expect(response.multipart?).to be false
|
190
|
+
expect(response.attachments).to be_empty
|
202
191
|
end
|
203
192
|
end
|
204
193
|
|
@@ -211,18 +200,6 @@ describe Savon::Operation do
|
|
211
200
|
end
|
212
201
|
end
|
213
202
|
|
214
|
-
def with_multipart_mocked
|
215
|
-
multipart_response = Class.new { def initialize(*args); end }
|
216
|
-
multipart_mock = Module.new
|
217
|
-
multipart_mock.const_set('Response', multipart_response)
|
218
|
-
|
219
|
-
Savon.const_set('Multipart', multipart_mock)
|
220
|
-
|
221
|
-
yield
|
222
|
-
ensure
|
223
|
-
Savon.send(:remove_const, :Multipart) if Savon.const_defined? :Multipart
|
224
|
-
end
|
225
|
-
|
226
203
|
def inspect_request(response)
|
227
204
|
hash = JSON.parse(response.http.body)
|
228
205
|
OpenStruct.new(hash)
|