Skip to content

Commit abcb3ed

Browse files
committed
Fix bugs in deep_replace_href and deep_select
The fixtures `refs_to_arrays.xml` and `nested_refs_to_arrays.xml` both could not be parsed correctly even though they are valid XML. Adding these tests to the router allowed me to trace down the errors to `deep_select`. `deep_select` didn't support finding elements inside arrays. Unit tests were added to the `dispatcher_spec` to show these cases and they were fixed. In order to make it easier to test that `deep_select` was working properly, I changed its signature so that it doesn't take a block and it always just selects ids which was they only way it was used in anycase. Searching through arrays was also not supported by `deep_replace_href`. Unit tests were adding showing these bugs. And now this function can also replace arrays in nested hashes.
1 parent 77b0512 commit abcb3ed

File tree

7 files changed

+212
-17
lines changed

7 files changed

+212
-17
lines changed

lib/wash_out/dispatcher.rb

+23-9
Original file line numberDiff line numberDiff line change
@@ -173,28 +173,42 @@ def self.included(controller)
173173
controller.send :"skip_before_#{entity}", :verify_authenticity_token
174174
end
175175

176-
def self.deep_select(hash, result=[], &blk)
177-
result += Hash[hash.select(&blk)].values
178-
179-
hash.each do |key, value|
180-
result = deep_select(value, result, &blk) if value.is_a? Hash
176+
def self.deep_select(collection, result = [])
177+
is_id = lambda { |v| v.is_a?(Hash) && v.has_key?(:@id) }
178+
values = collection.respond_to?(:values) ? collection.values : collection
179+
result += values.select(&is_id)
180+
181+
values.each do |value|
182+
if value.is_a?(Hash) || value.is_a?(Array)
183+
result = deep_select(value, result)
184+
end
181185
end
182186

183187
result
184188
end
185189

186190
def self.deep_replace_href(hash, replace)
187-
return replace[hash[:@href]] if hash.has_key?(:@href)
191+
hash.each do |key, value|
192+
if value.respond_to?(:has_key?) && value.has_key?(:@href)
193+
hash[key] = replace[value[:@href]]
194+
hash = deep_replace_href(hash, replace) if hash.is_a?(Hash)
195+
elsif key == :@href
196+
hash = replace[value]
197+
end
188198

189-
hash.keys.each do |key, value|
190-
hash[key] = deep_replace_href(hash[key], replace) if hash[key].is_a?(Hash)
199+
if hash[key].is_a?(Hash)
200+
hash[key] = deep_replace_href(hash[key], replace)
201+
elsif hash[key].is_a?(Array)
202+
hash[key] = hash[key].map do |v|
203+
v.is_a?(Hash) ? deep_replace_href(v, replace) : v
204+
end
205+
end
191206
end
192207

193208
hash
194209
end
195210

196211
private
197-
198212
def action_spec
199213
self.class.soap_actions[soap_action]
200214
end

lib/wash_out/router.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,8 @@ def soap_body(env)
6161

6262
def parse_soap_parameters(env)
6363
return env['wash_out.soap_data'] if env['wash_out.soap_data']
64-
6564
env['wash_out.soap_data'] = nori(controller.soap_config.snakecase_input).parse(soap_body env)
66-
references = WashOut::Dispatcher.deep_select(env['wash_out.soap_data']){|k,v| v.is_a?(Hash) && v.has_key?(:@id)}
65+
references = WashOut::Dispatcher.deep_select(env['wash_out.soap_data'])
6766

6867
unless references.blank?
6968
replaces = {}; references.each{|r| replaces['#'+r[:@id]] = r}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
2+
<s:Body s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
5+
<parent href="#id1" />
6+
<q2:child id="id1" xsi:type="q2:child" xmlns:q2="urn:FakeService">
7+
<first_list href="#id2" />
8+
<second_list href="#id3" />
9+
</q2:child>
10+
<q3:Array id="id2" q3:arrayType="xsd:int[1]" xmlns:q3="http://schemas.xmlsoap.org/soap/encoding/">
11+
<Item>1</Item>
12+
<Item>2</Item>
13+
</q3:Array>
14+
<q4:Array id="id3" q4:arrayType="xsd:int[1]" xmlns:q4="http://schemas.xmlsoap.org/soap/encoding/">
15+
<Item>11</Item>
16+
<Item>22</Item>
17+
</q4:Array>
18+
</s:Body>
19+
</s:Envelope>

spec/fixtures/ref_to_one_array.xml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
2+
<s:Body s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
5+
<list href="#id1" />
6+
<q1:Array id="id1" q1:arrayType="xsd:int[1]" xmlns:q1="http://schemas.xmlsoap.org/soap/encoding/">
7+
<Item>1</Item>
8+
<Item>2</Item>
9+
</q1:Array>
10+
</s:Body>
11+
</s:Envelope>

spec/fixtures/refs_to_arrays.xml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
2+
<s:Body s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
5+
<first_list href="#id1" />
6+
<second_list href="#id2" />
7+
<q1:Array id="id1" q1:arrayType="xsd:int[1]" xmlns:q1="http://schemas.xmlsoap.org/soap/encoding/">
8+
<Item>1</Item>
9+
<Item>2</Item>
10+
</q1:Array>
11+
<q2:Array id="id2" q2:arrayType="xsd:int[1]" xmlns:q2="http://schemas.xmlsoap.org/soap/encoding/">
12+
<Item>11</Item>
13+
<Item>22</Item>
14+
</q2:Array>
15+
</s:Body>
16+
</s:Envelope>

spec/lib/wash_out/dispatcher_spec.rb

+111-6
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,119 @@ def params
1212
end
1313
end
1414

15-
it "finds nested hashes" do
16-
expect(WashOut::Dispatcher.deep_select(:foo => 1){|k,v| k == :foo}).to eq [1]
17-
expect(WashOut::Dispatcher.deep_select({:foo => {:foo => 1}}){|k,v| k == :foo}).to eq([{:foo => 1}, 1])
15+
describe ".deep_select" do
16+
it "find no elements if there aren't any ids" do
17+
expect(WashOut::Dispatcher.deep_select({k: {v: :v2}})).to eq []
18+
end
19+
20+
it "finds elements with ids in a hash" do
21+
expect(WashOut::Dispatcher.deep_select({k: {:@id => 5, x: :y}})).to eq [{:@id => 5, x: :y}]
22+
end
23+
24+
it "finds elements with ids in a array" do
25+
expect(WashOut::Dispatcher.deep_select({k: [{:@id => 5, x: :y}]})).to eq [{:@id => 5, x: :y}]
26+
end
27+
28+
it "finds elements with ids in hashes" do
29+
expect(WashOut::Dispatcher.deep_select(
30+
{
31+
k: {:@id => 5, x: :y},
32+
k2: {:@id => 6, n: :m}
33+
})).to eq [{:@id => 5, x: :y}, {:@id => 6, n: :m}]
34+
end
35+
36+
it "finds elements in a hash and in a array" do
37+
expect(WashOut::Dispatcher.deep_select(
38+
{
39+
k: [{:@id => 5, x: :y}],
40+
k2: {:@id => 6, n: :m}
41+
})).to contain_exactly({:@id => 5, x: :y}, {:@id => 6, n: :m})
42+
end
43+
44+
it "finds elements with ids in multiple arrays" do
45+
expect(WashOut::Dispatcher.deep_select(
46+
{
47+
k: [{:@id => 5, x: :y}],
48+
k2: [{:@id => 6, n: :m}]
49+
})).to eq [{:@id => 5, x: :y}, {:@id => 6, n: :m}]
50+
end
1851
end
1952

20-
it "replaces nested hashed" do
21-
expect(WashOut::Dispatcher.deep_replace_href({:foo => {:@href => 1}}, {1 => 2})).to eq({:foo => 2})
22-
expect(WashOut::Dispatcher.deep_replace_href({:bar => {:foo => {:@href => 1}}}, {1 => 2})).to eq({:bar => {:foo => 2}})
53+
describe ".deep_replace_href" do
54+
it "replaces nested hashed" do
55+
expect(WashOut::Dispatcher.deep_replace_href(
56+
{:foo => {:@href => 1}},
57+
{1 => 2})).to eq(
58+
{:foo => 2}
59+
)
60+
end
61+
62+
it "replaces deeper nested hashes" do
63+
expect(WashOut::Dispatcher.deep_replace_href(
64+
{:bar => {:foo => {:@href => 1}}},
65+
{1 => 2}
66+
)).to eq(
67+
{:bar => {:foo => 2}}
68+
)
69+
end
70+
71+
it "replace nested refs" do
72+
hash = {fizz: {:@href => "#id4"}}
73+
replaces = {
74+
"#id4" => {:@href => "#id6"},
75+
"#id6" => {foo: :bar}
76+
}
77+
expect(WashOut::Dispatcher.deep_replace_href(hash, replaces)).to eq({
78+
fizz: {foo: :bar}})
79+
end
80+
81+
it "replace really nested refs" do
82+
hash = {fizz: {:@href => "#id4"}}
83+
replaces = {
84+
"#id4" => {:@href => "#id6"},
85+
"#id6" => {:@href => "#id7"},
86+
"#id7" => {foo: :bar}
87+
}
88+
expect(WashOut::Dispatcher.deep_replace_href(hash, replaces)).to eq({
89+
fizz: {foo: :bar}})
90+
end
91+
92+
it "replaces arrays in nested hashes" do
93+
hash = {
94+
fizz: {:@href => "#id4"},
95+
Array: [
96+
{Item: [{:@href => "#id6"}, {:@href => "#id7"}]},
97+
{Item: {loo: :iioo}}
98+
]
99+
}
100+
replaces = {
101+
"#id4" => {Item: [{:@href => "#id6"}, {:@href => "#id7"}]},
102+
"#id6" => {foo: :bar},
103+
"#id7" => {baz: :bats}
104+
}
105+
expect(WashOut::Dispatcher.deep_replace_href(hash, replaces)).to eq({
106+
fizz: {Item: [
107+
{foo: :bar},
108+
{baz: :bats}
109+
]},
110+
Array: [
111+
{Item: [{foo: :bar}, {baz: :bats}]},
112+
{Item: {loo: :iioo}}
113+
]
114+
})
115+
end
116+
117+
it "can traverse arrays that do not contain hashes" do
118+
hash = {
119+
fizz: {:@href => "#id1"},
120+
}
121+
replaces = {
122+
"#id1" => {Item: ["1", "2"]},
123+
}
124+
expect(WashOut::Dispatcher.deep_replace_href(hash, replaces)).to eq({
125+
fizz: {Item: ["1", "2"]},
126+
})
127+
end
23128
end
24129

25130
xit "parses typical request" do

spec/lib/wash_out/router_spec.rb

+31
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,35 @@
1919
msg = result[2][0]
2020
expect(msg).to eq('OK')
2121
end
22+
23+
def parse_soap_params_from_xml(filename)
24+
xml = File.read(File.expand_path("../../../fixtures/#{filename}", __FILE__))
25+
env = {'rack.input' => StringIO.new(xml)}
26+
27+
router = WashOut::Router.new('')
28+
controller = double("controller", soap_config: WashOut::SoapConfig.new)
29+
allow(router).to receive(:controller).and_return(controller)
30+
31+
router.parse_soap_parameters(env)[:Envelope][:Body]
32+
end
33+
34+
it "returns refs to arrays correctly" do
35+
body = parse_soap_params_from_xml('ref_to_one_array.xml')
36+
37+
expect(body[:list][:Item]).to eq(["1", "2"])
38+
end
39+
40+
it "returns refs to multiple arrays correctly" do
41+
body = parse_soap_params_from_xml('refs_to_arrays.xml')
42+
43+
expect(body[:first_list][:Item]).to eq(["1", "2"])
44+
expect(body[:second_list][:Item]).to eq(["11", "22"])
45+
end
46+
47+
it "returns nested refs to multiple arrays correctly" do
48+
body = parse_soap_params_from_xml('nested_refs_to_arrays.xml')
49+
50+
expect(body[:parent][:first_list][:Item]).to eq(["1", "2"])
51+
expect(body[:parent][:second_list][:Item]).to eq(["11", "22"])
52+
end
2253
end

0 commit comments

Comments
 (0)