savon 2.2.0 → 2.12.1

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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +20 -9
  4. data/CHANGELOG.md +157 -10
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +10 -2
  7. data/README.md +38 -13
  8. data/donate.png +0 -0
  9. data/lib/savon/builder.rb +81 -15
  10. data/lib/savon/client.rb +6 -2
  11. data/lib/savon/core_ext/string.rb +0 -1
  12. data/lib/savon/header.rb +68 -17
  13. data/lib/savon/log_message.rb +7 -3
  14. data/lib/savon/message.rb +6 -7
  15. data/lib/savon/mock/expectation.rb +12 -2
  16. data/lib/savon/model.rb +4 -0
  17. data/lib/savon/operation.rb +45 -38
  18. data/lib/savon/options.rb +149 -22
  19. data/lib/savon/qualified_message.rb +31 -25
  20. data/lib/savon/request.rb +24 -4
  21. data/lib/savon/request_logger.rb +48 -0
  22. data/lib/savon/response.rb +35 -18
  23. data/lib/savon/soap_fault.rb +11 -11
  24. data/lib/savon/version.rb +1 -3
  25. data/savon.gemspec +12 -11
  26. data/spec/fixtures/response/empty_soap_fault.xml +13 -0
  27. data/spec/fixtures/response/f5.xml +39 -0
  28. data/spec/fixtures/response/no_body.xml +1 -0
  29. data/spec/fixtures/response/soap_fault_funky.xml +8 -0
  30. data/spec/fixtures/wsdl/brand.xml +624 -0
  31. data/spec/fixtures/wsdl/elements_in_types.xml +43 -0
  32. data/spec/fixtures/wsdl/no_message_tag.xml +1267 -0
  33. data/spec/fixtures/wsdl/vies.xml +176 -0
  34. data/spec/integration/centra_spec.rb +67 -0
  35. data/spec/integration/email_example_spec.rb +1 -1
  36. data/spec/integration/random_quote_spec.rb +23 -0
  37. data/spec/integration/stockquote_example_spec.rb +7 -1
  38. data/spec/integration/support/application.rb +1 -1
  39. data/spec/integration/zipcode_example_spec.rb +1 -1
  40. data/spec/savon/builder_spec.rb +50 -0
  41. data/spec/savon/client_spec.rb +78 -0
  42. data/spec/savon/core_ext/string_spec.rb +9 -9
  43. data/spec/savon/features/message_tag_spec.rb +5 -0
  44. data/spec/savon/http_error_spec.rb +2 -2
  45. data/spec/savon/log_message_spec.rb +18 -1
  46. data/spec/savon/message_spec.rb +70 -0
  47. data/spec/savon/mock_spec.rb +31 -0
  48. data/spec/savon/model_spec.rb +28 -0
  49. data/spec/savon/operation_spec.rb +69 -3
  50. data/spec/savon/options_spec.rb +515 -87
  51. data/spec/savon/qualified_message_spec.rb +101 -0
  52. data/spec/savon/request_logger_spec.rb +37 -0
  53. data/spec/savon/request_spec.rb +85 -10
  54. data/spec/savon/response_spec.rb +118 -27
  55. data/spec/savon/soap_fault_spec.rb +25 -5
  56. data/spec/savon/softlayer_spec.rb +27 -0
  57. data/spec/spec_helper.rb +5 -2
  58. data/spec/support/adapters.rb +48 -0
  59. data/spec/support/integration.rb +1 -1
  60. metadata +76 -93
@@ -0,0 +1,176 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <wsdl:definitions targetNamespace="urn:ec.europa.eu:taxud:vies:services:checkVat" xmlns:tns1="urn:ec.europa.eu:taxud:vies:services:checkVat:types" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:impl="urn:ec.europa.eu:taxud:vies:services:checkVat" xmlns:apachesoap="http://xml.apache.org/xml-soap" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
3
+ <xsd:documentation>
4
+ Specific disclaimer for this service ----------------------------------------- The
5
+ objective of this Internet site is to allow persons involved in the intra-Community supply of
6
+ goods or of services to obtain confirmation of the validity of the VAT identification number of
7
+ any specified person, in accordance to article 27 of Council Regulation (EC) No. 1798/2003 of 7
8
+ October 2003. Any other use and any extraction and use of the data which is not in conformity
9
+ with the objective of this site is strictly forbidden. Any retransmission of the contents of
10
+ this site, whether for a commercial purpose or otherwise, as well as any more general use other
11
+ than as far as is necessary to support the activity of a legitimate user (for example: to draw
12
+ up their own invoices) is expressly forbidden. In addition, any copying or reproduction of the
13
+ contents of this site is strictly forbidden. The European Commission maintains this website to
14
+ enhance the access by taxable persons making intra-Community supplies to verification of their
15
+ customers VAT identification numbers. Our goal is to supply instantaneous and accurate
16
+ information. However the Commission accepts no responsibility or liability whatsoever with
17
+ regard to the information obtained using this site. This information: - is obtained from Member
18
+ States databases over which the Commission services have no control and for which the Commission
19
+ assumes no responsibility; it is the responsibility of the Member States to keep their databases
20
+ complete, accurate and up to date; - is not professional or legal advice (if you need specific
21
+ advice, you should always consult a suitably qualified professional); - does not in itself give
22
+ a right to exempt intra-Community supplies from Value Added Tax; - does not change any
23
+ obligations imposed on taxable persons in relation to intra-Community supplies. It is our goal
24
+ to minimise disruption caused by technical errors. However some data or information on our site
25
+ may have been created or structured in files or formats which are not error-free and we cannot
26
+ guarantee that our service will not be interrupted or otherwise affected by such problems. The
27
+ Commission accepts no responsibility with regard to such problems incurred as a result of using
28
+ this site or any linked external sites. This disclaimer is not intended to limit the liability
29
+ of the Commission in contravention of any requirements laid down in applicable national law nor
30
+ to exclude its liability for matters which may not be excluded under that law. Usage: The
31
+ countryCode input parameter must follow the pattern [A-Z]{2} The vatNumber input parameter must
32
+ follow the [0-9A-Za-z\+\*\.]{2,12} In case of problem, the returned FaultString can take the
33
+ following specific values: - INVALID_INPUT: The provided CountryCode is invalid or the VAT
34
+ number is empty; - SERVICE_UNAVAILABLE: The SOAP service is unavailable, try again later; -
35
+ MS_UNAVAILABLE: The Member State service is unavailable, try again later or with another Member
36
+ State; - TIMEOUT: The Member State service could not be reach in time, try again later or with
37
+ another Member State; - SERVER_BUSY: The service can't process your request. Try again latter.
38
+ </xsd:documentation>
39
+
40
+ <wsdl:types>
41
+ <xsd:schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="urn:ec.europa.eu:taxud:vies:services:checkVat:types" xmlns="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
42
+ <xsd:element name="checkVat">
43
+ <xsd:complexType>
44
+ <xsd:sequence>
45
+ <xsd:element name="countryCode" type="xsd:string"/>
46
+ <xsd:element name="vatNumber" type="xsd:string"/>
47
+ </xsd:sequence>
48
+ </xsd:complexType>
49
+ </xsd:element>
50
+ <xsd:element name="checkVatResponse">
51
+ <xsd:complexType>
52
+ <xsd:sequence>
53
+ <xsd:element name="countryCode" type="xsd:string"/>
54
+ <xsd:element name="vatNumber" type="xsd:string"/>
55
+ <xsd:element name="requestDate" type="xsd:date"/>
56
+ <xsd:element name="valid" type="xsd:boolean"/>
57
+ <xsd:element maxOccurs="1" minOccurs="0" name="name" nillable="true" type="xsd:string"/>
58
+ <xsd:element maxOccurs="1" minOccurs="0" name="address" nillable="true" type="xsd:string"/>
59
+ </xsd:sequence>
60
+ </xsd:complexType>
61
+ </xsd:element>
62
+ <xsd:element name="checkVatApprox">
63
+ <xsd:complexType>
64
+ <xsd:sequence>
65
+ <xsd:element name="countryCode" type="xsd:string"/>
66
+ <xsd:element name="vatNumber" type="xsd:string"/>
67
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderName" type="xsd:string"/>
68
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderCompanyType" type="tns1:companyTypeCode"/>
69
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderStreet" type="xsd:string"/>
70
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderPostcode" type="xsd:string"/>
71
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderCity" type="xsd:string"/>
72
+ <xsd:element maxOccurs="1" minOccurs="0" name="requesterCountryCode" type="xsd:string"/>
73
+ <xsd:element maxOccurs="1" minOccurs="0" name="requesterVatNumber" type="xsd:string"/>
74
+ </xsd:sequence>
75
+ </xsd:complexType>
76
+ </xsd:element>
77
+ <xsd:element name="checkVatApproxResponse">
78
+ <xsd:complexType>
79
+ <xsd:sequence>
80
+ <xsd:element name="countryCode" type="xsd:string"/>
81
+ <xsd:element name="vatNumber" type="xsd:string"/>
82
+ <xsd:element name="requestDate" type="xsd:date"/>
83
+ <xsd:element name="valid" type="xsd:boolean"/>
84
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderName" nillable="true" type="xsd:string"/>
85
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderCompanyType" nillable="true" type="tns1:companyTypeCode"/>
86
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderAddress" type="xsd:string"/>
87
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderStreet" type="xsd:string"/>
88
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderPostcode" type="xsd:string"/>
89
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderCity" type="xsd:string"/>
90
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderNameMatch" type="tns1:matchCode"/>
91
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderCompanyTypeMatch" type="tns1:matchCode"/>
92
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderStreetMatch" type="tns1:matchCode"/>
93
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderPostcodeMatch" type="tns1:matchCode"/>
94
+ <xsd:element maxOccurs="1" minOccurs="0" name="traderCityMatch" type="tns1:matchCode"/>
95
+ <xsd:element name="requestIdentifier" type="xsd:string"/>
96
+ </xsd:sequence>
97
+ </xsd:complexType>
98
+ </xsd:element>
99
+ <xsd:simpleType name="companyTypeCode">
100
+ <xsd:restriction base="xsd:string">
101
+ <xsd:pattern value="[A-Z]{2}\-[1-9][0-9]?"/>
102
+ </xsd:restriction>
103
+ </xsd:simpleType>
104
+ <xsd:simpleType name="matchCode">
105
+ <xsd:restriction base="xsd:string">
106
+ <xsd:enumeration value="1">
107
+ <xsd:annotation>
108
+ <xsd:documentation>VALID</xsd:documentation>
109
+ </xsd:annotation>
110
+ </xsd:enumeration>
111
+ <xsd:enumeration value="2">
112
+ <xsd:annotation>
113
+ <xsd:documentation>INVALID</xsd:documentation>
114
+ </xsd:annotation>
115
+ </xsd:enumeration>
116
+ </xsd:restriction>
117
+ </xsd:simpleType>
118
+ </xsd:schema>
119
+ </wsdl:types>
120
+ <wsdl:message name="checkVatRequest">
121
+ <wsdl:part name="parameters" element="tns1:checkVat">
122
+ </wsdl:part>
123
+ </wsdl:message>
124
+ <wsdl:message name="checkVatApproxResponse">
125
+ <wsdl:part name="parameters" element="tns1:checkVatApproxResponse">
126
+ </wsdl:part>
127
+ </wsdl:message>
128
+ <wsdl:message name="checkVatApproxRequest">
129
+ <wsdl:part name="parameters" element="tns1:checkVatApprox">
130
+ </wsdl:part>
131
+ </wsdl:message>
132
+ <wsdl:message name="checkVatResponse">
133
+ <wsdl:part name="parameters" element="tns1:checkVatResponse">
134
+ </wsdl:part>
135
+ </wsdl:message>
136
+ <wsdl:portType name="checkVatPortType">
137
+ <wsdl:operation name="checkVat">
138
+ <wsdl:input name="checkVatRequest" message="impl:checkVatRequest">
139
+ </wsdl:input>
140
+ <wsdl:output name="checkVatResponse" message="impl:checkVatResponse">
141
+ </wsdl:output>
142
+ </wsdl:operation>
143
+ <wsdl:operation name="checkVatApprox">
144
+ <wsdl:input name="checkVatApproxRequest" message="impl:checkVatApproxRequest">
145
+ </wsdl:input>
146
+ <wsdl:output name="checkVatApproxResponse" message="impl:checkVatApproxResponse">
147
+ </wsdl:output>
148
+ </wsdl:operation>
149
+ </wsdl:portType>
150
+ <wsdl:binding name="checkVatBinding" type="impl:checkVatPortType">
151
+ <wsdlsoap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
152
+ <wsdl:operation name="checkVat">
153
+ <wsdlsoap:operation soapAction=""/>
154
+ <wsdl:input name="checkVatRequest">
155
+ <wsdlsoap:body use="literal"/>
156
+ </wsdl:input>
157
+ <wsdl:output name="checkVatResponse">
158
+ <wsdlsoap:body use="literal"/>
159
+ </wsdl:output>
160
+ </wsdl:operation>
161
+ <wsdl:operation name="checkVatApprox">
162
+ <wsdlsoap:operation soapAction=""/>
163
+ <wsdl:input name="checkVatApproxRequest">
164
+ <wsdlsoap:body use="literal"/>
165
+ </wsdl:input>
166
+ <wsdl:output name="checkVatApproxResponse">
167
+ <wsdlsoap:body use="literal"/>
168
+ </wsdl:output>
169
+ </wsdl:operation>
170
+ </wsdl:binding>
171
+ <wsdl:service name="checkVatService">
172
+ <wsdl:port name="checkVatPort" binding="impl:checkVatBinding">
173
+ <wsdlsoap:address location="http://ec.europa.eu/taxation_customs/vies/services/checkVatService"/>
174
+ </wsdl:port>
175
+ </wsdl:service>
176
+ </wsdl:definitions>
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ module LogInterceptor
4
+ @@intercepted_request = ""
5
+ def self.debug(message = nil)
6
+ message ||= yield if block_given?
7
+
8
+ # save only the first XMLly message
9
+ if message.include? "xml version"
10
+ @@intercepted_request = message if @@intercepted_request == ""
11
+ end
12
+ end
13
+
14
+ def self.info(message = nil)
15
+ end
16
+
17
+ def self.get_intercepted_request
18
+ @@intercepted_request
19
+ end
20
+
21
+ def self.reset_intercepted_request
22
+ @@intercepted_request = ""
23
+ end
24
+ end
25
+
26
+ describe 'Correct translation of attributes to XML' do
27
+ it "new :@attr syntax: correctly maps a Ruby Hash to XML attributes" do
28
+ LogInterceptor.reset_intercepted_request
29
+
30
+ client = Savon.client(
31
+ :wsdl => "http://mt205.sabameeting.com/CWS/CWS.asmx?WSDL",
32
+ :log => true,
33
+ :logger => LogInterceptor
34
+ )
35
+
36
+ response = nil
37
+ begin
38
+ response = call_and_fail_gracefully(client, :add_new_user, :message => { :user => { :@userID => "test" } })
39
+ rescue
40
+ end
41
+
42
+ xml_doc = Nokogiri::XML(LogInterceptor.get_intercepted_request)
43
+ xml_doc.remove_namespaces!
44
+
45
+ attributes_element_not_present = xml_doc.xpath("//AddNewUser/attributes").blank?
46
+ expect(attributes_element_not_present).to eq true
47
+ end
48
+
49
+ it "old :attributes! syntax: correctly maps a Ruby Hash to XML attributes" do
50
+ LogInterceptor.reset_intercepted_request
51
+
52
+ client = Savon.client(
53
+ :wsdl => "http://mt205.sabameeting.com/CWS/CWS.asmx?WSDL",
54
+ :log => true,
55
+ :logger => LogInterceptor
56
+ )
57
+
58
+ response = nil
59
+ response = call_and_fail_gracefully(client, :add_new_user, :message => { :user => {}, :attributes! => { :user => { :userID => "test" } } })
60
+
61
+ xml_doc = Nokogiri::XML(LogInterceptor.get_intercepted_request)
62
+ xml_doc.remove_namespaces!
63
+
64
+ attributes_element_not_present = xml_doc.xpath("//AddNewUser/attributes").blank?
65
+ expect(attributes_element_not_present).to eq true
66
+ end
67
+ end
@@ -25,7 +25,7 @@ describe "Email example" do
25
25
  pending "API limit exceeded"
26
26
  else
27
27
  # The expected result. We unfortunately don't have a license key for this service.
28
- response_text.should == "Email Domain Not Found"
28
+ expect(response_text).to eq("Email Domain Not Found")
29
29
  end
30
30
  end
31
31
 
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'rpc/encoded binding test' do
4
+
5
+ it 'should should work with WSDLs that have rpc/encoded SOAP binding' do
6
+ client = Savon.client(
7
+ :wsdl => "http://www.boyzoid.com/comp/randomQuote.cfc?wsdl",
8
+ :open_timeout => 10,
9
+ :read_timeout => 10,
10
+ :log => false
11
+ )
12
+
13
+ begin
14
+ client.call(:get_quote)
15
+ rescue Savon::SOAPFault => e
16
+ $stderr.puts e.to_hash.inspect
17
+ f_c = e.to_hash[:fault][:faultstring]
18
+ expect(f_c).not_to eq('No such operation \'getQuoteRequest\'')
19
+ expect(f_c).to eq('lucee.runtime.exp.DatabaseException: ')
20
+ pending e
21
+ end
22
+ end
23
+ end
@@ -19,10 +19,16 @@ describe "Stockquote example" do
19
19
 
20
20
  cdata = response.body[:get_quote_response][:get_quote_result]
21
21
 
22
+ if cdata == "exception"
23
+ # Fallback to not fail the specs when the service's API limit is reached,
24
+ # but to mark the spec as pending instead.
25
+ pending "Exception on API"
26
+ end
27
+
22
28
  nori_options = { :convert_tags_to => lambda { |tag| tag.snakecase.to_sym } }
23
29
  result = Nori.new(nori_options).parse(cdata)
24
30
 
25
- result[:stock_quotes][:stock][:symbol].should == "AAPL"
31
+ expect(result[:stock_quotes][:stock][:symbol]).to eq("AAPL")
26
32
  end
27
33
 
28
34
  end
@@ -1,4 +1,4 @@
1
- require "rack/builder"
1
+ require "rack"
2
2
  require "json"
3
3
 
4
4
  class IntegrationServer
@@ -36,7 +36,7 @@ describe "ZIP code example" do
36
36
  threads.each(&:kill)
37
37
  values = threads.map { |thr| thr[:value] }.compact
38
38
 
39
- values.uniq.size.should == values.size
39
+ expect(values.uniq.size).to eq(values.size)
40
40
  end
41
41
 
42
42
  end
@@ -82,6 +82,56 @@ describe Savon::Builder do
82
82
  expect(builder.to_s).to include("<tns:username>luke</tns:username>")
83
83
  end
84
84
 
85
+ describe "#wsse_signature" do
86
+ fixture_dir = File.join(File.dirname(__FILE__), '..', 'fixtures', 'ssl')
87
+
88
+ let(:cert) { File.join(fixture_dir, 'client_cert.pem') }
89
+ let(:private_key) { File.join(fixture_dir, 'client_key.pem') }
90
+ let(:signature) do
91
+ Akami::WSSE::Signature.new(
92
+ Akami::WSSE::Certs.new(
93
+ :cert_file => cert,
94
+ :private_key_file => private_key
95
+ )
96
+ )
97
+ end
98
+ let(:globals) { Savon::GlobalOptions.new(wsse_signature: signature) }
99
+
100
+ subject(:signed_message_nn) {Nokogiri::XML(builder.to_s).remove_namespaces!}
101
+ subject(:signed_message) {Nokogiri::XML(builder.to_s)}
102
+
103
+ it "should contain a header" do
104
+ expect(signed_message_nn.xpath('/Envelope/Header').size).to eq(1)
105
+ end
106
+
107
+ it "should contain a wsse:Security" do
108
+ expect(signed_message_nn.xpath('/Envelope/Header/Security').size).to eq(1)
109
+ end
110
+
111
+ it "should have a Body[@wsu:Id]" do
112
+ #must investigate: acts funny in mri ruby
113
+ #expect(signed_message.xpath('//soapenv:Body', soapenv: "http://schemas.xmlsoap.org/soap/envelope/").attribute('ws:Id').value).to include('Body-')
114
+ expect(signed_message_nn.xpath('//Body').attr('Id').value).to include('Body-')
115
+ end
116
+
117
+ it "signature should be valid" do
118
+ certs = Akami::WSSE::Certs.new(:cert_file => cert, :private_key_file => private_key)
119
+ signature_value = signed_message_nn.xpath('//SignatureValue').text
120
+ signed_info_fragment = signed_message.xpath('//default:SignedInfo', default: "http://www.w3.org/2000/09/xmldsig#").to_xml
121
+ data = Nokogiri::XML(signed_info_fragment){|config| config.options = Nokogiri::XML::ParseOptions::NOBLANKS}
122
+ data.root.default_namespace='http://www.w3.org/2000/09/xmldsig#'
123
+
124
+ signed_info = data.canonicalize Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
125
+
126
+ signature = certs.private_key.sign(OpenSSL::Digest::SHA1.new, signed_info)
127
+ expect(Base64.encode64(signature).gsub("\n", '')).to eq(signature_value)
128
+ end
129
+ end
85
130
  end
86
131
 
132
+ describe '#body_attributes' do
133
+ it 'should not be nil' do
134
+ expect(builder.body_attributes).to eq({})
135
+ end
136
+ end
87
137
  end
@@ -62,6 +62,11 @@ describe Savon::Client do
62
62
  it "returns the current set of global options" do
63
63
  expect(new_client.globals).to be_an_instance_of(Savon::GlobalOptions)
64
64
  end
65
+
66
+ fit "defaults :log to false" do
67
+ client = Savon.client(:wsdl => Fixture.wsdl(:authentication))
68
+ expect(client.globals[:log]).to be_falsey
69
+ end
65
70
  end
66
71
 
67
72
  describe "#service_name" do
@@ -165,6 +170,79 @@ describe Savon::Client do
165
170
  end
166
171
  end
167
172
 
173
+ describe "#build_request" do
174
+ it "returns the request without making an actual call" do
175
+ expected_request = mock('request')
176
+ wsdl = Wasabi::Document.new('http://example.com')
177
+
178
+ operation = Savon::Operation.new(
179
+ :authenticate,
180
+ wsdl,
181
+ Savon::GlobalOptions.new
182
+ )
183
+ operation.expects(:request).returns(expected_request)
184
+
185
+ Savon::Operation.expects(:create).with(
186
+ :authenticate,
187
+ instance_of(Wasabi::Document),
188
+ instance_of(Savon::GlobalOptions)
189
+ ).returns(operation)
190
+
191
+ operation.expects(:call).never
192
+
193
+ client = new_client(:endpoint => @server.url(:repeat))
194
+ request = client.build_request(:authenticate) do
195
+ message(:symbol => "AAPL" )
196
+ end
197
+
198
+ expect(request).to eq expected_request
199
+ end
200
+
201
+ it "accepts a block without arguments" do
202
+ client = new_client(:endpoint => @server.url(:repeat))
203
+ request = client.build_request(:authenticate) do
204
+ message(:symbol => "AAPL" )
205
+ end
206
+
207
+ expect(request.body).
208
+ to include('<tns:authenticate><symbol>AAPL</symbol></tns:authenticate>')
209
+ end
210
+
211
+ it "accepts a block with one argument" do
212
+ client = new_client(:endpoint => @server.url(:repeat))
213
+
214
+ # supports instance variables!
215
+ @instance_variable = { :symbol => "AAPL" }
216
+
217
+ request = client.build_request(:authenticate) do |locals|
218
+ locals.message(@instance_variable)
219
+ end
220
+
221
+ expect(request.body).
222
+ to include("<tns:authenticate><symbol>AAPL</symbol></tns:authenticate>")
223
+ end
224
+
225
+ it "accepts argument for the message tag" do
226
+ client = new_client(:endpoint => @server.url(:repeat))
227
+ request = client.build_request(:authenticate, :attributes => { "ID" => "ABC321" })
228
+
229
+ expect(request.body).
230
+ to include("<tns:authenticate ID=\"ABC321\"></tns:authenticate>")
231
+ end
232
+
233
+ it "raises when the operation name is not a symbol" do
234
+ expect { new_client.build_request("not a symbol") }.to raise_error
235
+ end
236
+
237
+ 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
+ end
240
+
241
+ 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
+ end
244
+ end
245
+
168
246
  def new_http_response(options = {})
169
247
  defaults = { :code => 200, :headers => {}, :body => Fixture.response(:authentication) }
170
248
  response = defaults.merge options