wash_out_fork 0.0.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 (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +36 -0
  5. data/Appraisals +25 -0
  6. data/CHANGELOG.md +102 -0
  7. data/Gemfile +17 -0
  8. data/Guardfile +12 -0
  9. data/LICENSE +22 -0
  10. data/README.md +242 -0
  11. data/Rakefile +25 -0
  12. data/app/helpers/wash_out_fork_helper.rb +110 -0
  13. data/app/views/wash_out_fork/document/error.builder +9 -0
  14. data/app/views/wash_out_fork/document/response.builder +8 -0
  15. data/app/views/wash_out_fork/document/wsdl.builder +68 -0
  16. data/app/views/wash_out_fork/rpc/error.builder +10 -0
  17. data/app/views/wash_out_fork/rpc/response.builder +9 -0
  18. data/app/views/wash_out_fork/rpc/wsdl.builder +68 -0
  19. data/gemfiles/rails_3.2.13.gemfile +21 -0
  20. data/gemfiles/rails_4.0.0.gemfile +20 -0
  21. data/gemfiles/rails_4.1.0.gemfile +20 -0
  22. data/gemfiles/rails_4.2.0.gemfile +20 -0
  23. data/gemfiles/rails_5.0.0.beta2.gemfile +19 -0
  24. data/gemfiles/rails_5.0.0.gemfile +19 -0
  25. data/init.rb +1 -0
  26. data/lib/wash_out_fork.rb +60 -0
  27. data/lib/wash_out_fork/configurable.rb +41 -0
  28. data/lib/wash_out_fork/dispatcher.rb +232 -0
  29. data/lib/wash_out_fork/engine.rb +12 -0
  30. data/lib/wash_out_fork/middleware.rb +41 -0
  31. data/lib/wash_out_fork/model.rb +29 -0
  32. data/lib/wash_out_fork/param.rb +200 -0
  33. data/lib/wash_out_fork/router.rb +131 -0
  34. data/lib/wash_out_fork/soap.rb +48 -0
  35. data/lib/wash_out_fork/soap_config.rb +93 -0
  36. data/lib/wash_out_fork/type.rb +29 -0
  37. data/lib/wash_out_fork/version.rb +3 -0
  38. data/lib/wash_out_fork/wsse.rb +101 -0
  39. data/spec/dummy/Rakefile +7 -0
  40. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  41. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  42. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  43. data/spec/dummy/config.ru +4 -0
  44. data/spec/dummy/config/application.rb +51 -0
  45. data/spec/dummy/config/boot.rb +10 -0
  46. data/spec/dummy/config/environment.rb +5 -0
  47. data/spec/dummy/config/environments/development.rb +23 -0
  48. data/spec/dummy/config/environments/test.rb +30 -0
  49. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  50. data/spec/dummy/config/initializers/inflections.rb +10 -0
  51. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  52. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  53. data/spec/dummy/config/initializers/session_store.rb +8 -0
  54. data/spec/dummy/config/locales/en.yml +5 -0
  55. data/spec/dummy/config/routes.rb +58 -0
  56. data/spec/dummy/public/404.html +26 -0
  57. data/spec/dummy/public/422.html +26 -0
  58. data/spec/dummy/public/500.html +26 -0
  59. data/spec/dummy/public/favicon.ico +0 -0
  60. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  61. data/spec/dummy/script/rails +6 -0
  62. data/spec/fixtures/nested_refs_to_arrays.xml +19 -0
  63. data/spec/fixtures/ref_to_one_array.xml +11 -0
  64. data/spec/fixtures/refs_to_arrays.xml +16 -0
  65. data/spec/lib/wash_out/dispatcher_spec.rb +206 -0
  66. data/spec/lib/wash_out/middleware_spec.rb +33 -0
  67. data/spec/lib/wash_out/param_spec.rb +94 -0
  68. data/spec/lib/wash_out/router_spec.rb +50 -0
  69. data/spec/lib/wash_out/type_spec.rb +41 -0
  70. data/spec/lib/wash_out_spec.rb +797 -0
  71. data/spec/spec_helper.rb +88 -0
  72. data/wash_out_fork.gemspec +20 -0
  73. metadata +130 -0
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+ require 'wash_out_fork/middleware'
3
+ require 'rexml/document'
4
+
5
+ describe WashOutFork::Middleware do
6
+ it 'handles Rack environment variables' do
7
+ err = begin
8
+ REXML::Document.new '<hi>'
9
+ rescue REXML::ParseException => e
10
+ e
11
+ end
12
+
13
+ env = {}
14
+ expect {
15
+ WashOutFork::Middleware.raise_or_render_rexml_parse_error err, env
16
+ }.to raise_exception(REXML::ParseException)
17
+
18
+ env['HTTP_SOAPACTION'] = 'pretend_action'
19
+ env['rack.errors'] = double 'logger', {:puts => true}
20
+ env['rack.input'] = double 'basic-rack-input', {:string => '<hi>'}
21
+ result = WashOutFork::Middleware.raise_or_render_rexml_parse_error err, env
22
+ expect(result[0]).to eq 400
23
+ expect(result[1]['Content-Type']).to eq 'text/xml'
24
+ msg = result[2][0]
25
+ expect(msg).to include 'Error parsing SOAP Request XML'
26
+ expect(msg).to include 'soap:Fault'
27
+ expect(msg).not_to include __FILE__
28
+
29
+ env['rack.input'] = double 'passenger-input', {:read => '<hi>'}
30
+ result = WashOutFork::Middleware.raise_or_render_rexml_parse_error err, env
31
+ expect(result[0]).to eq 400
32
+ end
33
+ end
@@ -0,0 +1,94 @@
1
+ #encoding:utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe WashOutFork::Param do
6
+
7
+ context "custom types" do
8
+
9
+ class Abraka1 < WashOutFork::Type
10
+ map(
11
+ :test => :string
12
+ )
13
+ end
14
+
15
+ class Abraka2 < WashOutFork::Type
16
+ type_name 'test'
17
+ map :foo => Abraka1
18
+ end
19
+
20
+ it "loads custom_types" do
21
+ soap_config = WashOutFork::SoapConfig.new({ camelize_wsdl: false })
22
+ map = WashOutFork::Param.parse_def soap_config, Abraka2
23
+
24
+ expect(map).to be_a_kind_of(Array)
25
+ expect(map[0].name).to eq 'foo'
26
+ expect(map[0].map[0].name).to eq 'test'
27
+ end
28
+
29
+ it "respects camelization setting" do
30
+ soap_config = WashOutFork::SoapConfig.new({ camelize_wsdl: true })
31
+
32
+ map = WashOutFork::Param.parse_def soap_config, Abraka2
33
+
34
+ expect(map).to be_a_kind_of(Array)
35
+ expect(map[0].name).to eq 'Foo'
36
+ expect(map[0].map[0].name).to eq 'Test'
37
+ end
38
+ end
39
+
40
+ it "should accept nested empty arrays" do
41
+ soap_config = WashOutFork::SoapConfig.new({ camelize_wsdl: false })
42
+ map = WashOutFork::Param.parse_def(soap_config, {:nested => {:some_attr => :string, :empty => [:integer] }} )
43
+ expect(map[0].load( {:nested => nil}, :nested)).to eq({})
44
+ end
45
+
46
+ describe "booleans" do
47
+ let(:soap_config) { WashOutFork::SoapConfig.new({ camelize_wsdl: false }) }
48
+ # following http://www.w3.org/TR/xmlschema-2/#boolean, only true, false, 0 and 1 are allowed.
49
+ # Nori maps the strings true and false to TrueClass and FalseClass, but not 0 and 1.
50
+ let(:map) { WashOutFork::Param.parse_def(soap_config, :value => :boolean) }
51
+
52
+ it "should accept 'true' and '1'" do
53
+ expect(map[0].load({:value => true}, :value)).to be true
54
+ expect(map[0].load({:value => "1"}, :value)).to be true
55
+ end
56
+
57
+ it "should accept 'false' and '0'" do
58
+ expect(map[0].load({:value => false}, :value)).to be false
59
+ expect(map[0].load({:value => "0"}, :value)).to be false
60
+ end
61
+ end
62
+
63
+ describe 'longs' do
64
+ let(:soap_config) { WashOutFork::SoapConfig.new({ camelize_wsdl: false }) }
65
+ let(:map) { WashOutFork::Param.parse_def(soap_config, :value => :long) }
66
+
67
+ it "should accept positive long" do
68
+ expect(map[0].load({:value => 9223372036854775807}, :value)).to eq 9223372036854775807
69
+ end
70
+
71
+ it "should accept negative long" do
72
+ expect(map[0].load({:value => -9223372036854775807}, :value)).to eq -9223372036854775807
73
+ end
74
+ end
75
+
76
+ describe '#flat_copy' do
77
+ it 'should copy everything' do
78
+ soap_config = WashOutFork::SoapConfig.new({})
79
+ type = :foo
80
+ multiplied = "of course"
81
+
82
+ param = WashOutFork::Param.new(soap_config, 'name', type, multiplied)
83
+ param.source_class = "middle class"
84
+ evil_clone = param.flat_copy
85
+
86
+ expect(evil_clone.source_class).to eq "middle class"
87
+ expect(evil_clone.name).to eq 'name'
88
+ expect(evil_clone.raw_name).to eq 'name'
89
+ expect(evil_clone.type).to eq "foo"
90
+ expect(evil_clone.multiplied).to eq "of course"
91
+ expect(evil_clone.soap_config).to eq soap_config
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require 'wash_out_fork/router'
3
+
4
+ describe WashOutFork::Router do
5
+ it 'returns a 200 with empty soap action' do
6
+
7
+ mock_controller do
8
+ # nothing
9
+ end
10
+
11
+ env = {}
12
+ env['REQUEST_METHOD'] = 'GET'
13
+ env['rack.input'] = double 'basic-rack-input', {:string => ''}
14
+ result = WashOutFork::Router.new('Route::Space::Api').call env
15
+
16
+ expect(result[0]).to eq(500)
17
+ expect(result[1]['Content-Type']).to eq('text/xml; charset=utf-8')
18
+ end
19
+
20
+ def parse_soap_params_from_xml(filename)
21
+ xml = File.read(File.expand_path("../../../fixtures/#{filename}", __FILE__))
22
+ env = {'rack.input' => StringIO.new(xml)}
23
+
24
+ router = WashOutFork::Router.new('')
25
+ controller = double("controller", soap_config: WashOutFork::SoapConfig.new)
26
+ allow(router).to receive(:controller).and_return(controller)
27
+
28
+ router.parse_soap_parameters(env)[:Envelope][:Body]
29
+ end
30
+
31
+ it "returns refs to arrays correctly" do
32
+ body = parse_soap_params_from_xml('ref_to_one_array.xml')
33
+
34
+ expect(body[:list][:Item]).to eq(["1", "2"])
35
+ end
36
+
37
+ it "returns refs to multiple arrays correctly" do
38
+ body = parse_soap_params_from_xml('refs_to_arrays.xml')
39
+
40
+ expect(body[:first_list][:Item]).to eq(["1", "2"])
41
+ expect(body[:second_list][:Item]).to eq(["11", "22"])
42
+ end
43
+
44
+ it "returns nested refs to multiple arrays correctly" do
45
+ body = parse_soap_params_from_xml('nested_refs_to_arrays.xml')
46
+
47
+ expect(body[:parent][:first_list][:Item]).to eq(["1", "2"])
48
+ expect(body[:parent][:second_list][:Item]).to eq(["11", "22"])
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ #encoding:utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe WashOutFork::Type do
6
+
7
+ it "defines custom type" do
8
+
9
+ class Abraka1 < WashOutFork::Type
10
+ map :test => :string
11
+ end
12
+
13
+ class Abraka2 < WashOutFork::Type
14
+ type_name 'test'
15
+ map :foo => Abraka1
16
+ end
17
+
18
+ expect(Abraka1.wash_out_fork_param_name).to eq 'abraka1'
19
+ expect(Abraka1.wash_out_fork_param_map).to eq({:test => :string})
20
+
21
+ expect(Abraka2.wash_out_fork_param_name).to eq 'test'
22
+ expect(Abraka2.wash_out_fork_param_map).to eq({:foo => Abraka1})
23
+ end
24
+
25
+ it "allows arrays inside custom types" do
26
+ class Abraka1 < WashOutFork::Type
27
+ map :test => :string
28
+ end
29
+ class Abraka2 < WashOutFork::Type
30
+ type_name 'test'
31
+ map :foo => [:bar => Abraka1]
32
+ end
33
+
34
+ expect(Abraka1.wash_out_fork_param_name).to eq 'abraka1'
35
+ expect(Abraka1.wash_out_fork_param_map).to eq({:test => :string})
36
+
37
+ expect(Abraka2.wash_out_fork_param_name).to eq 'test'
38
+ expect(Abraka2.wash_out_fork_param_map).to eq({:foo => [:bar => Abraka1]})
39
+ end
40
+
41
+ end
@@ -0,0 +1,797 @@
1
+ #encoding:utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe WashOutFork do
6
+
7
+ let :nori do
8
+ Nori.new(
9
+ :strip_namespaces => true,
10
+ :advanced_typecasting => true,
11
+ :convert_tags_to => lambda {|x| x.snakecase.to_sym}
12
+ )
13
+ end
14
+
15
+ def savon(method, message={}, hashify=true, &block)
16
+ message = {:value => message} unless message.is_a?(Hash)
17
+
18
+ savon = Savon::Client.new(:log => false, :wsdl => 'http://app/route/api/wsdl', &block)
19
+ result = savon.call(method, :message => message)
20
+ result = result.to_hash if hashify
21
+ result
22
+ end
23
+
24
+ def savon!(method, message={}, &block)
25
+ message = {:value => message} unless message.is_a?(Hash)
26
+
27
+ savon = Savon::Client.new(:log => true, :wsdl => 'http://app/route/api/wsdl', &block)
28
+ savon.call(method, :message => message).to_hash
29
+ end
30
+
31
+ describe "Module" do
32
+ it "includes" do
33
+ expect {
34
+ mock_controller do
35
+ # nothing
36
+ end
37
+ }.not_to raise_exception
38
+ end
39
+
40
+ it "allows definition of a simple action" do
41
+ expect {
42
+ mock_controller do
43
+ soap_action "answer", :args => nil, :return => :integer
44
+ end
45
+ }.not_to raise_exception
46
+ end
47
+ end
48
+
49
+ describe "WSDL" do
50
+ let :wsdl do
51
+ mock_controller do
52
+ soap_action :result, :args => nil, :return => :int
53
+
54
+ soap_action "getArea", :args => {
55
+ :circle => [{
56
+ :center => { :x => [:integer], :y => :integer },
57
+ :radius => :double
58
+ }]},
59
+ :return => { :area => :double }
60
+ soap_action "rocky", :args => { :circle1 => { :x => :integer } },
61
+ :return => { :circle2 => { :y => :integer } }
62
+ end
63
+
64
+ HTTPI.get("http://app/route/api/wsdl").body
65
+ end
66
+
67
+ let :xml do
68
+ nori.parse wsdl
69
+ end
70
+
71
+ it "lists operations" do
72
+ operations = xml[:definitions][:binding][:operation]
73
+ expect(operations).to be_a_kind_of(Array)
74
+
75
+ expect(operations.map{|e| e[:'@name']}.sort).to eq ['Result', 'getArea', 'rocky'].sort
76
+ end
77
+
78
+ it "defines complex types" do
79
+ expect(wsdl.include?('<xsd:complexType name="Circle1">')).to be true
80
+ end
81
+
82
+ it "defines arrays" do
83
+ x = xml[:definitions][:types][:schema][:complex_type].
84
+ find{|x| x[:'@name'] == 'Center'}[:sequence][:element].
85
+ find{|x| x[:'@name'] == 'X'}
86
+
87
+ expect(x[:'@min_occurs']).to eq "0"
88
+ expect(x[:'@max_occurs']).to eq "unbounded"
89
+ expect(x[:'@nillable']).to eq "true"
90
+ end
91
+
92
+ it "adds nillable to all type definitions" do
93
+ types = xml[:definitions][:message].map { |d| d[:part] }.compact
94
+ nillable = types.map { |t| t[:"@xsi:nillable"] }
95
+ expect(nillable.all? { |v| v == "true" }).to be true
96
+ end
97
+ end
98
+
99
+ describe "Dispatcher" do
100
+
101
+ context "simple actions" do
102
+ it "accepts requests with no HTTP header" do
103
+ mock_controller do
104
+ soap_action "answer", :args => nil, :return => :int
105
+ def answer
106
+ render :soap => "42"
107
+ end
108
+ end
109
+
110
+ request = <<-XML
111
+ <?xml version="1.0" encoding="UTF-8"?>
112
+ <env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="false" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
113
+ <env:Body>
114
+ <tns:answer>
115
+ <value>42</value>
116
+ </tns:answer>
117
+ </env:Body>
118
+ </env:Envelope>
119
+ XML
120
+
121
+ expect(HTTPI.post("http://app/route/api/action", request).body).to eq <<-XML
122
+ <?xml version="1.0" encoding="UTF-8"?>
123
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="false">
124
+ <soap:Body>
125
+ <tns:answerResponse>
126
+ <Value xsi:type="xsd:int">42</Value>
127
+ </tns:answerResponse>
128
+ </soap:Body>
129
+ </soap:Envelope>
130
+ XML
131
+ end
132
+
133
+ it "accept no parameters" do
134
+ mock_controller do
135
+ soap_action "answer", :args => nil, :return => :int
136
+ def answer
137
+ render :soap => "42"
138
+ end
139
+ end
140
+
141
+ expect(savon(:answer)[:answer_response][:value]).
142
+ to eq "42"
143
+ end
144
+
145
+ it "accept insufficient parameters" do
146
+ mock_controller do
147
+ soap_action "answer", :args => {:a => :integer}, :return => :integer
148
+ def answer
149
+ render :soap => "42"
150
+ end
151
+ end
152
+
153
+ expect(savon(:answer)[:answer_response][:value]).
154
+ to eq "42"
155
+ end
156
+
157
+ it "shows date in correct format" do
158
+ mock_controller do
159
+ soap_action "answer", :args => {}, :return => {:a => :date}
160
+ def answer
161
+ render :soap => {:a => DateTime.new(2000, 1, 1)}
162
+ end
163
+ end
164
+ result = Hash.from_xml savon(:answer, {}, false).http.body
165
+ expect(result['Envelope']['Body']['answerResponse']['A']).to eq '2000-01-01T00:00:00+00:00'
166
+ end
167
+
168
+ it "accept empty parameter" do
169
+ mock_controller do
170
+ soap_action "answer", :args => {:a => :string}, :return => {:a => :string}
171
+ def answer
172
+ render :soap => {:a => params[:a]}
173
+ end
174
+ end
175
+ expect(savon(:answer, :a => '')[:answer_response][:a]).to be_nil
176
+ end
177
+
178
+ it "accept one parameter" do
179
+ mock_controller do
180
+ soap_action "checkAnswer", :args => :integer, :return => :boolean, :to => 'check_answer'
181
+ def check_answer
182
+ render :soap => (params[:value] == 42)
183
+ end
184
+ end
185
+
186
+ expect(savon(:check_answer, 42)[:check_answer_response][:value]).to be true
187
+ expect(savon(:check_answer, 13)[:check_answer_response][:value]).to be false
188
+ end
189
+
190
+ it "accept two parameters" do
191
+ mock_controller do
192
+ soap_action "funky", :args => { :a => :integer, :b => :string }, :return => :string
193
+ def funky
194
+ render :soap => ((params[:a] * 10).to_s + params[:b])
195
+ end
196
+ end
197
+
198
+ expect(savon(:funky, :a => 42, :b => 'k')[:funky_response][:value]).to eq '420k'
199
+ end
200
+ end
201
+
202
+ context "complex actions" do
203
+ it "accept nested structures" do
204
+ mock_controller do
205
+ soap_action "getArea", :args => { :circle => { :center => { :x => :integer,
206
+ :y => :integer },
207
+ :radius => :double } },
208
+ :return => { :area => :double,
209
+ :distance_from_o => :double },
210
+ :to => :get_area
211
+ def get_area
212
+ circle = params[:circle]
213
+ render :soap => { :area => Math::PI * circle[:radius] ** 2,
214
+ :distance_from_o => Math.sqrt(circle[:center][:x] ** 2 + circle[:center][:y] ** 2) }
215
+ end
216
+ end
217
+
218
+ message = { :circle => { :center => { :x => 3, :y => 4 },
219
+ :radius => 5 } }
220
+
221
+ expect(savon(:get_area, message)[:get_area_response]).
222
+ to eq ({ :area => (Math::PI * 25).to_s, :distance_from_o => (5.0).to_s })
223
+ end
224
+
225
+ it "accept arrays" do
226
+ mock_controller do
227
+ soap_action "rumba",
228
+ :args => {
229
+ :rumbas => [:integer]
230
+ },
231
+ :return => nil
232
+ def rumba
233
+ expect(params).to eq({"rumbas" => [1, 2, 3]})
234
+ render :soap => nil
235
+ end
236
+ end
237
+
238
+ savon(:rumba, :rumbas => [1, 2, 3])
239
+ end
240
+
241
+ it "accept empty arrays" do
242
+ mock_controller do
243
+ soap_action "rumba",
244
+ :args => {
245
+ :my_array => [:integer]
246
+ },
247
+ :return => nil
248
+ def rumba
249
+ expect(params).to eq({})
250
+ render :soap => nil
251
+ end
252
+ end
253
+ savon(:rumba, :empty => [])
254
+ end
255
+
256
+ it "accept nested empty arrays" do
257
+ mock_controller do
258
+ soap_action "rumba",
259
+ :args => {
260
+ :nested => {:my_array => [:integer] }
261
+ },
262
+ :return => nil
263
+ def rumba
264
+ expect(params).to eq({"nested" => {}})
265
+ render :soap => nil
266
+ end
267
+ end
268
+ savon(:rumba, :nested => {:my_array => []})
269
+ end
270
+
271
+ it "accept nested structures inside arrays" do
272
+ mock_controller do
273
+ soap_action "rumba",
274
+ :args => {
275
+ :rumbas => [ {
276
+ :zombies => :string,
277
+ :puppies => :string
278
+ } ]
279
+ },
280
+ :return => nil
281
+ def rumba
282
+ expect(params).to eq({
283
+ "rumbas" => [
284
+ {"zombies" => 'suck', "puppies" => 'rock'},
285
+ {"zombies" => 'slow', "puppies" => 'fast'}
286
+ ]
287
+ })
288
+ render :soap => nil
289
+ end
290
+ end
291
+
292
+ savon :rumba, :rumbas => [
293
+ {:zombies => 'suck', :puppies => 'rock'},
294
+ {:zombies => 'slow', :puppies => 'fast'}
295
+ ]
296
+ end
297
+
298
+ it "respond with nested structures" do
299
+ mock_controller do
300
+ soap_action "gogogo",
301
+ :args => nil,
302
+ :return => {
303
+ :zoo => :string,
304
+ :boo => { :moo => :string, :doo => :string }
305
+ }
306
+ def gogogo
307
+ render :soap => {
308
+ :zoo => 'zoo',
309
+ :boo => { :moo => 'moo', :doo => 'doo' }
310
+ }
311
+ end
312
+ end
313
+
314
+ expect(savon(:gogogo)[:gogogo_response]).
315
+ to eq({
316
+ :zoo=>"zoo",
317
+ :boo=>{
318
+ :moo=>"moo",
319
+ :doo=>"doo",
320
+ :"@xsi:type"=>"tns:Boo"
321
+ }
322
+ })
323
+ end
324
+
325
+ it "respond with arrays" do
326
+ mock_controller do
327
+ soap_action "rumba",
328
+ :args => nil,
329
+ :return => [:integer]
330
+ def rumba
331
+ render :soap => [1, 2, 3]
332
+ end
333
+ end
334
+
335
+ expect(savon(:rumba)[:rumba_response]).to eq({
336
+ :value => ["1", "2", "3"]
337
+ })
338
+ end
339
+
340
+ it "respond with complex structures inside arrays" do
341
+ mock_controller do
342
+ soap_action "rumba",
343
+ :args => nil,
344
+ :return => {
345
+ :rumbas => [{:@level => :integer, :zombies => :string, :puppies => :string}]
346
+ }
347
+ def rumba
348
+ render :soap =>
349
+ {:rumbas => [
350
+ {:@level => 80, :zombies => "suck1", :puppies => "rock1" },
351
+ {:zombies => "suck2", :puppies => "rock2" }
352
+ ]
353
+ }
354
+ end
355
+ end
356
+
357
+ expect(savon(:rumba)[:rumba_response]).to eq({
358
+ :rumbas => [
359
+ {:zombies => "suck1",:puppies => "rock1", :"@xsi:type"=>"tns:Rumbas", :@level => "80"},
360
+ {:zombies => "suck2", :puppies => "rock2", :"@xsi:type"=>"tns:Rumbas" }
361
+ ]
362
+ })
363
+ end
364
+
365
+ it "respond with structs in structs in arrays" do
366
+ mock_controller do
367
+ soap_action "rumba",
368
+ :args => nil,
369
+ :return => [{:rumbas => {:@level => :integer, :zombies => :integer}}]
370
+
371
+ def rumba
372
+ render :soap => [{:rumbas => {:@level => 80, :zombies => 100000}}, {:rumbas => {:@level => 90, :zombies => 2}}]
373
+ end
374
+ end
375
+
376
+ expect(savon(:rumba)[:rumba_response]).to eq({
377
+ :value => [
378
+ {
379
+ :rumbas => {
380
+ :zombies => "100000",
381
+ :"@xsi:type" => "tns:Rumbas",
382
+ :"@level" => "80"
383
+ },
384
+ :"@xsi:type" => "tns:Value"
385
+ },
386
+ {
387
+ :rumbas => {
388
+ :zombies => "2",
389
+ :"@xsi:type" => "tns:Rumbas",
390
+ :@level => "90",
391
+ },
392
+ :"@xsi:type"=>"tns:Value"
393
+ }
394
+ ]
395
+ })
396
+ end
397
+
398
+ context "with arrays missing" do
399
+ it "respond with simple definition" do
400
+ mock_controller do
401
+ soap_action "rocknroll",
402
+ :args => nil, :return => { :my_value => [:integer] }
403
+ def rocknroll
404
+ render :soap => {}
405
+ end
406
+ end
407
+
408
+ expect(savon(:rocknroll)[:rocknroll_response]).to be nil
409
+ end
410
+
411
+ it "respond with complext definition" do
412
+ mock_controller do
413
+ soap_action "rocknroll",
414
+ :args => nil, :return => { :my_value => [{ :value => :integer }] }
415
+ def rocknroll
416
+ render :soap => {}
417
+ end
418
+ end
419
+
420
+ expect(savon(:rocknroll)[:rocknroll_response]).to be nil
421
+ end
422
+
423
+ it "respond with nested simple definition" do
424
+ mock_controller do
425
+ soap_action "rocknroll",
426
+ :args => nil, :return => { :my_value => { :my_array => [{ :value => :integer }] } }
427
+ def rocknroll
428
+ render :soap => {}
429
+ end
430
+ end
431
+
432
+ expect(savon(:rocknroll)[:rocknroll_response][:my_value]).to be_nil
433
+ end
434
+
435
+ it "responds with missing parameters" do
436
+ mock_controller do
437
+ soap_action "rocknroll",
438
+ args: nil,
439
+ return: {my_value: :integer}
440
+ def rocknroll
441
+ render soap: {my_value: nil}
442
+ end
443
+ end
444
+
445
+ expect(savon(:rocknroll)[:rocknroll_response][:my_value]).to be_nil
446
+ end
447
+
448
+ it "handles incomplete array response" do
449
+ mock_controller do
450
+ soap_action "rocknroll",
451
+ :args => nil, :return => { :my_value => [{ :value => :string }] }
452
+ def rocknroll
453
+ render :soap => { :my_value => [nil] }
454
+ end
455
+ end
456
+
457
+ expect{savon(:rocknroll)}.not_to raise_error
458
+ end
459
+ end
460
+ end
461
+
462
+ context "types" do
463
+ it "recognize boolean" do
464
+ mock_controller do
465
+ soap_action "true", :args => :boolean, :return => :nil
466
+ def true
467
+ expect(params[:value]).to be true
468
+ render :soap => nil
469
+ end
470
+
471
+ soap_action "false", :args => :boolean, :return => :nil
472
+ def false
473
+ expect(params[:value]).to be false
474
+ render :soap => nil
475
+ end
476
+ end
477
+
478
+ savon(:true, :value => "true")
479
+ savon(:true, :value => "1")
480
+ savon(:false, :value => "false")
481
+ savon(:false, :value => "0")
482
+ end
483
+
484
+ it "recognize dates" do
485
+ mock_controller do
486
+ soap_action "date", :args => :date, :return => :nil
487
+ def date
488
+ expect(params[:value]).to eq Date.parse('2000-12-30') unless params[:value].blank?
489
+ render :soap => nil
490
+ end
491
+ end
492
+
493
+ savon(:date, :value => '2000-12-30')
494
+ expect { savon(:date) }.not_to raise_exception
495
+ end
496
+
497
+ it "recognize base64Binary" do
498
+ mock_controller do
499
+ soap_action "base64", :args => :base64Binary, :return => :nil
500
+ def base64
501
+ expect(params[:value]).to eq('test') unless params[:value].blank?
502
+ render :soap => nil
503
+ end
504
+ end
505
+
506
+ savon(:base64, :value => Base64.encode64('test'))
507
+ expect { savon(:base64) }.not_to raise_exception
508
+ end
509
+ end
510
+
511
+ context "errors" do
512
+ it "raise for incorrect requests" do
513
+ mock_controller do
514
+ soap_action "duty",
515
+ :args => {:bad => {:a => :string, :b => :string}, :good => {:a => :string, :b => :string}},
516
+ :return => nil
517
+ def duty
518
+ render :soap => nil
519
+ end
520
+ end
521
+
522
+ expect {
523
+ savon(:duty, :bad => 42, :good => nil)
524
+ }.to raise_exception(Savon::SOAPFault)
525
+ end
526
+
527
+ it "raise for date in incorrect format" do
528
+ mock_controller do
529
+ soap_action "date", :args => :date, :return => :nil
530
+ def date
531
+ render :soap => nil
532
+ end
533
+ end
534
+ expect {
535
+ savon(:date, :value => 'incorrect format')
536
+ }.to raise_exception(Savon::SOAPFault)
537
+ end
538
+
539
+ it "raise to report SOAP errors" do
540
+ mock_controller do
541
+ soap_action "error", :args => { :need_error => :boolean }, :return => nil
542
+ def error
543
+ raise self.class.const_get(:SOAPError), "you wanted one" if params[:need_error]
544
+ render :soap => nil
545
+ end
546
+ end
547
+
548
+ expect { savon(:error, :need_error => false) }.not_to raise_exception
549
+ expect { savon(:error, :need_error => true) }.to raise_exception(Savon::SOAPFault)
550
+ end
551
+
552
+ it "misses basic exceptions" do
553
+ mock_controller do
554
+ soap_action "error", :args => { :need_error => :boolean }, :return => nil
555
+ def error
556
+ raise self.class.const_get(:Exception), "you wanted one" if params[:need_error]
557
+ render :soap => nil
558
+ end
559
+ end
560
+
561
+ expect { savon(:error, :need_error => false) }.not_to raise_exception
562
+ expect { savon(:error, :need_error => true) }.to raise_exception(Exception)
563
+ end
564
+
565
+ it "raise for manual throws" do
566
+ mock_controller do
567
+ soap_action "error", :args => nil, :return => nil
568
+ def error
569
+ render_soap_error "a message"
570
+ end
571
+ end
572
+
573
+ expect { savon(:error) }.to raise_exception(Savon::SOAPFault)
574
+ end
575
+
576
+ it "raise when response structure mismatches" do
577
+ mock_controller do
578
+ soap_action 'bad', :args => :integer, :return => {
579
+ :basic => :string,
580
+ :stallions => {
581
+ :stallion => [
582
+ :name => :string,
583
+ :wyldness => :integer,
584
+ ]
585
+ },
586
+ }
587
+ def bad
588
+ render :soap => {
589
+ :basic => 'hi',
590
+ :stallions => [{:name => 'ted', :wyldness => 11}]
591
+ }
592
+ end
593
+
594
+ soap_action 'bad2', :args => :integer, :return => {
595
+ :basic => :string,
596
+ :telephone_booths => [:string]
597
+ }
598
+ def bad2
599
+ render :soap => {
600
+ :basic => 'hihi',
601
+ :telephone_booths => 'oops'
602
+ }
603
+ end
604
+ end
605
+
606
+ expect { savon(:bad) }.to raise_exception(
607
+ WashOutFork::Dispatcher::ProgrammerError,
608
+ /SOAP response .*wyldness.*Array.*Hash.*stallion/
609
+ )
610
+
611
+ expect { savon(:bad2) }.to raise_exception(
612
+ WashOutFork::Dispatcher::ProgrammerError,
613
+ /SOAP response .*oops.*String.*telephone_booths.*Array/
614
+ )
615
+ end
616
+ end
617
+
618
+ context "deprecates" do
619
+ # This test uses deprecated rspec expectations
620
+ # and it's not clear how to rewrite it.
621
+ xit "old syntax" do
622
+ # save rspec context check
623
+ raise_runtime_exception = raise_exception(RuntimeError)
624
+
625
+ mock_controller do
626
+
627
+ lambda {
628
+ soap_action "rumba",
629
+ :args => :integer,
630
+ :return => []
631
+ }.should raise_runtime_exception
632
+ def rumba
633
+ render :soap => nil
634
+ end
635
+ end
636
+ end
637
+ end
638
+
639
+ it "allows arbitrary action names" do
640
+ name = 'AnswerToTheUltimateQuestionOfLifeTheUniverseAndEverything'
641
+
642
+ mock_controller do
643
+ soap_action name, :args => nil, :return => :integer, :to => :answer
644
+ def answer
645
+ render :soap => "forty two"
646
+ end
647
+ end
648
+
649
+ expect(savon(name.underscore.to_sym)["#{name.underscore}_response".to_sym][:value]).to eq "forty two"
650
+ end
651
+
652
+ it "respects :response_tag option" do
653
+ mock_controller do
654
+ soap_action "specific", :response_tag => "test", :return => :string
655
+ def specific
656
+ render :soap => "test"
657
+ end
658
+ end
659
+
660
+ expect(savon(:specific)).to eq({:test => {:value=>"test"}})
661
+ end
662
+
663
+ it "handles snakecase option properly" do
664
+ mock_controller(snakecase_input: false, camelize_wsdl: false) do
665
+ soap_action "rocknroll", :args => {:ZOMG => :string}, :return => nil
666
+ def rocknroll
667
+ expect(params["ZOMG"]).to eq "yam!"
668
+ render :soap => nil
669
+ end
670
+ end
671
+
672
+ savon(:rocknroll, "ZOMG" => 'yam!')
673
+ end
674
+ end
675
+
676
+ describe "Router" do
677
+ it "raises when SOAP message without SOAP Envelope arrives" do
678
+ mock_controller do; end
679
+ invalid_request = '<a></a>'
680
+ response_hash = Nori.new.parse(HTTPI.post("http://app/route/api/action", invalid_request).body)
681
+ expect(response_hash["soap:Envelope"]["soap:Body"]["soap:Fault"]['faultstring']).to eq "Invalid SOAP request"
682
+ end
683
+
684
+ it "raises when SOAP message without SOAP Body arrives" do
685
+ mock_controller do; end
686
+ invalid_request = '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"></s:Envelope>'
687
+ response_hash = Nori.new.parse(HTTPI.post("http://app/route/api/action", invalid_request).body)
688
+ expect(response_hash["soap:Envelope"]["soap:Body"]["soap:Fault"]['faultstring']).to eq "Invalid SOAP request"
689
+ end
690
+ end
691
+
692
+ describe "WS Security" do
693
+ it "appends username_token to params" do
694
+ mock_controller(wsse_username: "gorilla", wsse_password: "secret") do
695
+ soap_action "checkToken", :args => :integer, :return => nil, :to => 'check_token'
696
+ def check_token
697
+ expect(request.env['WSSE_TOKEN']['username']).to eq "gorilla"
698
+ expect(request.env['WSSE_TOKEN']['password']).to eq "secret"
699
+ render :soap => nil
700
+ end
701
+ end
702
+
703
+ savon(:check_token, 42) do
704
+ wsse_auth "gorilla", "secret"
705
+ end
706
+ end
707
+
708
+ it "handles PasswordText auth" do
709
+ mock_controller(wsse_username: "gorilla", wsse_password: "secret") do
710
+ soap_action "checkAuth", :args => :integer, :return => :boolean, :to => 'check_auth'
711
+ def check_auth
712
+ render :soap => (params[:value] == 42)
713
+ end
714
+ end
715
+
716
+ # correct auth
717
+ expect { savon(:check_auth, 42){ wsse_auth "gorilla", "secret" } }.
718
+ not_to raise_exception
719
+
720
+ # wrong user
721
+ expect { savon(:check_auth, 42){ wsse_auth "chimpanzee", "secret" } }.
722
+ to raise_exception(Savon::SOAPFault)
723
+
724
+ # wrong pass
725
+ expect { savon(:check_auth, 42){ wsse_auth "gorilla", "nicetry" } }.
726
+ to raise_exception(Savon::SOAPFault)
727
+
728
+ # no auth
729
+ expect { savon(:check_auth, 42) }.
730
+ to raise_exception(Savon::SOAPFault)
731
+ end
732
+
733
+ it "handles PasswordDigest auth" do
734
+ mock_controller(wsse_username: "gorilla", wsse_password: "secret") do
735
+ soap_action "checkAuth", :args => :integer, :return => :boolean, :to => 'check_auth'
736
+ def check_auth
737
+ render :soap => (params[:value] == 42)
738
+ end
739
+ end
740
+
741
+ # correct auth
742
+ expect { savon(:check_auth, 42){ wsse_auth "gorilla", "secret" } }.
743
+ not_to raise_exception
744
+
745
+ # correct digest auth
746
+ expect { savon(:check_auth, 42){ wsse_auth "gorilla", "secret", :digest } }.
747
+ not_to raise_exception
748
+
749
+ # wrong user
750
+ expect { savon(:check_auth, 42){ wsse_auth "chimpanzee", "secret", :digest } }.
751
+ to raise_exception(Savon::SOAPFault)
752
+
753
+ # wrong pass
754
+ expect { savon(:check_auth, 42){ wsse_auth "gorilla", "nicetry", :digest } }.
755
+ to raise_exception(Savon::SOAPFault)
756
+
757
+ # no auth
758
+ expect { savon(:check_auth, 42) }.
759
+ to raise_exception(Savon::SOAPFault)
760
+ end
761
+
762
+ it "handles auth callback" do
763
+ mock_controller(
764
+ wsse_auth_callback: lambda {|user, password|
765
+ return user == "gorilla" && password == "secret"
766
+ }
767
+ ) do
768
+ soap_action "checkAuth", :args => :integer, :return => :boolean, :to => 'check_auth'
769
+ def check_auth
770
+ render :soap => (params[:value] == 42)
771
+ end
772
+ end
773
+
774
+ # correct auth
775
+ expect { savon(:check_auth, 42){ wsse_auth "gorilla", "secret" } }.
776
+ not_to raise_exception
777
+
778
+ # correct digest auth
779
+ expect { savon(:check_auth, 42){ wsse_auth "gorilla", "secret", :digest } }.
780
+ to raise_exception(Savon::SOAPFault)
781
+
782
+ # wrong user
783
+ expect { savon(:check_auth, 42){ wsse_auth "chimpanzee", "secret", :digest } }.
784
+ to raise_exception(Savon::SOAPFault)
785
+
786
+ # wrong pass
787
+ expect { savon(:check_auth, 42){ wsse_auth "gorilla", "nicetry", :digest } }.
788
+ to raise_exception(Savon::SOAPFault)
789
+
790
+ # no auth
791
+ expect { savon(:check_auth, 42) }.
792
+ to raise_exception(Savon::SOAPFault)
793
+ end
794
+
795
+ end
796
+
797
+ end