savon 2.2.0 → 2.12.1

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