lolsoap 0.1.0
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.
- data/.document +5 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +22 -0
- data/LICENSE.txt +20 -0
- data/README.md +124 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/lib/lolsoap.rb +11 -0
- data/lib/lolsoap/builder.rb +93 -0
- data/lib/lolsoap/client.rb +25 -0
- data/lib/lolsoap/envelope.rb +94 -0
- data/lib/lolsoap/errors.rb +15 -0
- data/lib/lolsoap/fault.rb +26 -0
- data/lib/lolsoap/hash_builder.rb +48 -0
- data/lib/lolsoap/request.rb +54 -0
- data/lib/lolsoap/response.rb +50 -0
- data/lib/lolsoap/wsdl.rb +98 -0
- data/lib/lolsoap/wsdl/element.rb +28 -0
- data/lib/lolsoap/wsdl/null_element.rb +15 -0
- data/lib/lolsoap/wsdl/null_type.rb +19 -0
- data/lib/lolsoap/wsdl/operation.rb +18 -0
- data/lib/lolsoap/wsdl/type.rb +38 -0
- data/lib/lolsoap/wsdl_parser.rb +121 -0
- data/lolsoap.gemspec +97 -0
- data/test/fixtures/stock_quote.wsdl +74 -0
- data/test/fixtures/stock_quote_fault.xml +16 -0
- data/test/fixtures/stock_quote_response.xml +8 -0
- data/test/helper.rb +14 -0
- data/test/integration/test_client.rb +20 -0
- data/test/integration/test_envelope.rb +45 -0
- data/test/integration/test_request.rb +19 -0
- data/test/integration/test_response.rb +15 -0
- data/test/integration/test_wsdl.rb +28 -0
- data/test/unit/test_builder.rb +95 -0
- data/test/unit/test_client.rb +12 -0
- data/test/unit/test_envelope.rb +112 -0
- data/test/unit/test_fault.rb +33 -0
- data/test/unit/test_hash_builder.rb +127 -0
- data/test/unit/test_request.rb +48 -0
- data/test/unit/test_response.rb +39 -0
- data/test/unit/test_wsdl.rb +143 -0
- data/test/unit/test_wsdl_parser.rb +105 -0
- data/test/unit/wsdl/test_element.rb +31 -0
- data/test/unit/wsdl/test_type.rb +44 -0
- metadata +152 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'lolsoap/envelope'
|
3
|
+
|
4
|
+
module LolSoap
|
5
|
+
describe Envelope do
|
6
|
+
let(:wsdl) { OpenStruct.new(:type_namespaces => { 'foo' => 'http://example.com/foo' }) }
|
7
|
+
let(:operation) do
|
8
|
+
OpenStruct.new(:input => OpenStruct.new(:prefix => 'foo', :name => 'WashHandsRequest'))
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { Envelope.new(wsdl, operation) }
|
12
|
+
|
13
|
+
let(:doc) { subject.doc }
|
14
|
+
let(:header) { doc.at_xpath('/soap:Envelope/soap:Header', doc.namespaces) }
|
15
|
+
let(:input) { doc.at_xpath('/soap:Envelope/soap:Body/foo:WashHandsRequest', doc.namespaces) }
|
16
|
+
|
17
|
+
it 'has a skeleton SOAP envelope structure when first created' do
|
18
|
+
doc.namespaces.must_equal(
|
19
|
+
'xmlns:soap' => Envelope::SOAP_NAMESPACE,
|
20
|
+
'xmlns:foo' => 'http://example.com/foo'
|
21
|
+
)
|
22
|
+
|
23
|
+
header.wont_equal nil
|
24
|
+
header.children.length.must_equal 0
|
25
|
+
|
26
|
+
input.wont_equal nil
|
27
|
+
input.children.length.must_equal 0
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#body' do
|
31
|
+
it 'yields and returns a builder object for the body' do
|
32
|
+
builder = Object.new
|
33
|
+
|
34
|
+
builder_klass = MiniTest::Mock.new
|
35
|
+
builder_klass.expect(:new, builder, [input, operation.input])
|
36
|
+
|
37
|
+
block = nil
|
38
|
+
ret = subject.body(builder_klass) { |b| block = b }
|
39
|
+
|
40
|
+
ret.must_equal builder
|
41
|
+
block.must_equal builder
|
42
|
+
end
|
43
|
+
|
44
|
+
it "doesn't require a block" do
|
45
|
+
builder = Object.new
|
46
|
+
|
47
|
+
builder_klass = MiniTest::Mock.new
|
48
|
+
builder_klass.expect(:new, builder, [input, operation.input])
|
49
|
+
|
50
|
+
subject.body(builder_klass).must_equal builder
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#header' do
|
55
|
+
it 'yields and returns the xml builder object for the header' do
|
56
|
+
builder = Object.new
|
57
|
+
|
58
|
+
builder_klass = MiniTest::Mock.new
|
59
|
+
builder_klass.expect(:new, builder, [header])
|
60
|
+
|
61
|
+
block = nil
|
62
|
+
ret = subject.header(builder_klass) { |b| block = b }
|
63
|
+
|
64
|
+
ret.must_equal builder
|
65
|
+
block.must_equal builder
|
66
|
+
end
|
67
|
+
|
68
|
+
it "doesn't require a block" do
|
69
|
+
builder = Object.new
|
70
|
+
|
71
|
+
builder_klass = MiniTest::Mock.new
|
72
|
+
builder_klass.expect(:new, builder, [header])
|
73
|
+
|
74
|
+
subject.header(builder_klass).must_equal builder
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#endpoint' do
|
79
|
+
it 'delegates to wsdl' do
|
80
|
+
wsdl.endpoint = 'lol'
|
81
|
+
subject.endpoint.must_equal 'lol'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#to_xml' do
|
86
|
+
it 'returns the xml of the doc' do
|
87
|
+
def subject.doc; OpenStruct.new(:to_xml => '<lol>'); end
|
88
|
+
subject.to_xml.must_equal '<lol>'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe '#action' do
|
93
|
+
it "returns the operation's action" do
|
94
|
+
operation.action = 'lol'
|
95
|
+
subject.action.must_equal 'lol'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#input_type' do
|
100
|
+
it "returns the operation's input" do
|
101
|
+
subject.input_type.must_equal operation.input
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe '#output' do
|
106
|
+
it "returns the operation's output" do
|
107
|
+
operation.output = 'lol'
|
108
|
+
subject.output_type.must_equal 'lol'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'lolsoap/envelope'
|
3
|
+
require 'lolsoap/fault'
|
4
|
+
|
5
|
+
module LolSoap
|
6
|
+
describe Fault do
|
7
|
+
let(:request) { OpenStruct.new(:soap_namespace => Envelope::SOAP_NAMESPACE) }
|
8
|
+
let(:node) do
|
9
|
+
doc = Nokogiri::XML(File.read(TEST_ROOT + '/fixtures/stock_quote_fault.xml'))
|
10
|
+
doc.at_xpath('//soap:Fault', 'soap' => Envelope::SOAP_NAMESPACE)
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { Fault.new(request, node) }
|
14
|
+
|
15
|
+
describe '#code' do
|
16
|
+
it 'returns the code' do
|
17
|
+
subject.code.must_equal 'soap:Sender'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#reason' do
|
22
|
+
it 'returns the reason' do
|
23
|
+
subject.reason.must_match(/^Omg.*crashed!$/)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#detail' do
|
28
|
+
it 'returns the detail' do
|
29
|
+
subject.detail.must_equal '<Foo>Some detail</Foo>'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'lolsoap/wsdl'
|
3
|
+
require 'lolsoap/hash_builder'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module LolTypes
|
7
|
+
class Type
|
8
|
+
def element(name)
|
9
|
+
elements.fetch(name) { LolSoap::WSDL::NullElement.new }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Person < Type
|
14
|
+
def elements
|
15
|
+
@elements ||= {
|
16
|
+
'name' => OpenStruct.new(:type => LolTypes.name, :singular? => true),
|
17
|
+
'age' => OpenStruct.new(:type => LolSoap::WSDL::NullType.new, :singular? => true),
|
18
|
+
'friends' => OpenStruct.new(:type => LolTypes.person, :singular? => false)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Name < Type
|
24
|
+
def elements
|
25
|
+
@elements ||= {
|
26
|
+
'firstName' => OpenStruct.new(:type => LolSoap::WSDL::NullType.new, :singular? => true),
|
27
|
+
'lastName' => OpenStruct.new(:type => LolSoap::WSDL::NullType.new, :singular? => true)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.person; @person ||= Person.new; end
|
33
|
+
def self.name; @name ||= Name.new; end
|
34
|
+
end
|
35
|
+
|
36
|
+
module LolSoap
|
37
|
+
describe HashBuilder do
|
38
|
+
it 'converts an XML node to a hash using the type' do
|
39
|
+
xml = Nokogiri::XML::Builder.new do
|
40
|
+
person do
|
41
|
+
name do
|
42
|
+
firstName 'Jon'
|
43
|
+
lastName 'Leighton'
|
44
|
+
end
|
45
|
+
age '22'
|
46
|
+
end
|
47
|
+
end
|
48
|
+
node = xml.doc.root
|
49
|
+
|
50
|
+
builder = HashBuilder.new(node, LolTypes.person)
|
51
|
+
builder.output.must_equal({
|
52
|
+
'name' => {
|
53
|
+
'firstName' => 'Jon',
|
54
|
+
'lastName' => 'Leighton'
|
55
|
+
},
|
56
|
+
'age' => '22'
|
57
|
+
})
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'converts nodes that have an unknown type' do
|
61
|
+
xml = Nokogiri::XML::Builder.new { person { foo 'bar' } }
|
62
|
+
node = xml.doc.root
|
63
|
+
|
64
|
+
builder = HashBuilder.new(node, LolTypes.person)
|
65
|
+
builder.output.must_equal({ 'foo' => 'bar' })
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'converts fields which can occur multiple times into arrays' do
|
69
|
+
xml = Nokogiri::XML::Builder.new do
|
70
|
+
person do
|
71
|
+
friends { age '20' }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
node = xml.doc.root
|
75
|
+
|
76
|
+
builder = HashBuilder.new(node, LolTypes.person)
|
77
|
+
builder.output.must_equal({
|
78
|
+
'friends' => [
|
79
|
+
{ 'age' => '20' }
|
80
|
+
]
|
81
|
+
})
|
82
|
+
|
83
|
+
xml = Nokogiri::XML::Builder.new do
|
84
|
+
person do
|
85
|
+
friends { age '20' }
|
86
|
+
friends { age '50' }
|
87
|
+
friends { age '30' }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
node = xml.doc.root
|
91
|
+
|
92
|
+
builder = HashBuilder.new(node, LolTypes.person)
|
93
|
+
builder.output.must_equal({
|
94
|
+
'friends' => [
|
95
|
+
{ 'age' => '20' },
|
96
|
+
{ 'age' => '50' },
|
97
|
+
{ 'age' => '30' }
|
98
|
+
]
|
99
|
+
})
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'converts fields which occur multiple times, even if their element says they shouldnt, into arrays' do
|
103
|
+
xml = Nokogiri::XML::Builder.new do
|
104
|
+
person do
|
105
|
+
age '20'
|
106
|
+
age '30'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
node = xml.doc.root
|
110
|
+
|
111
|
+
builder = HashBuilder.new(node, LolTypes.person)
|
112
|
+
builder.output.must_equal({ 'age' => ['20', '30'] })
|
113
|
+
|
114
|
+
xml = Nokogiri::XML::Builder.new do
|
115
|
+
person do
|
116
|
+
age '20'
|
117
|
+
age '30'
|
118
|
+
age '40'
|
119
|
+
end
|
120
|
+
end
|
121
|
+
node = xml.doc.root
|
122
|
+
|
123
|
+
builder = HashBuilder.new(node, LolTypes.person)
|
124
|
+
builder.output.must_equal({ 'age' => ['20', '30', '40'] })
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'lolsoap/request'
|
3
|
+
|
4
|
+
module LolSoap
|
5
|
+
describe Request do
|
6
|
+
let(:envelope) { OpenStruct.new }
|
7
|
+
subject { Request.new(envelope) }
|
8
|
+
|
9
|
+
[:header, :body, :soap_namespace, :input_type, :output_type].each do |method|
|
10
|
+
describe "##{method}" do
|
11
|
+
let(:envelope) { MiniTest::Mock.new }
|
12
|
+
|
13
|
+
it 'delegates to the envelope' do
|
14
|
+
ret = Object.new
|
15
|
+
envelope.expect(method, ret)
|
16
|
+
subject.send(method).must_equal ret
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#url' do
|
22
|
+
it 'returns the envelope endpoint' do
|
23
|
+
envelope.endpoint = 'lol'
|
24
|
+
subject.url.must_equal 'lol'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#headers' do
|
29
|
+
it 'returns the necessary headers' do
|
30
|
+
envelope.to_xml = '<lol>'
|
31
|
+
envelope.action = 'http://example.com/LolOutLoud'
|
32
|
+
|
33
|
+
subject.headers.must_equal({
|
34
|
+
'Content-Type' => 'application/soap+xml;charset=UTF-8',
|
35
|
+
'Content-Length' => '5',
|
36
|
+
'SOAPAction' => 'http://example.com/LolOutLoud'
|
37
|
+
})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#content' do
|
42
|
+
it 'returns the envelope as an xml string' do
|
43
|
+
envelope.to_xml = '<lol>'
|
44
|
+
subject.content.must_equal '<lol>'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'lolsoap/envelope'
|
3
|
+
require 'lolsoap/response'
|
4
|
+
|
5
|
+
module LolSoap
|
6
|
+
describe Response do
|
7
|
+
let(:request) { OpenStruct.new(:soap_namespace => Envelope::SOAP_NAMESPACE, :output => Object.new) }
|
8
|
+
let(:doc) { Nokogiri::XML(File.read(TEST_ROOT + '/fixtures/stock_quote_response.xml')) }
|
9
|
+
|
10
|
+
subject { Response.new(request, doc) }
|
11
|
+
|
12
|
+
describe '#body' do
|
13
|
+
it 'returns the first node under the envelope body' do
|
14
|
+
subject.body.must_equal doc.at_xpath('/soap:Envelope/soap:Body/m:GetStockPriceResponse')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#body_hash' do
|
19
|
+
it 'builds a hash from the body node' do
|
20
|
+
builder = OpenStruct.new(:output => Object.new)
|
21
|
+
builder_klass = MiniTest::Mock.new
|
22
|
+
builder_klass.expect(:new, builder, [subject.body, request.output])
|
23
|
+
|
24
|
+
subject.body_hash(builder_klass).must_equal builder.output
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#header' do
|
29
|
+
it 'returns the header element' do
|
30
|
+
subject.header.must_equal doc.at_xpath('/soap:Envelope/soap:Header')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should raise a FaultRaised error when initialized, if there is a SOAP fault' do
|
35
|
+
lambda { Response.new(request, Nokogiri::XML(File.read(TEST_ROOT + '/fixtures/stock_quote_fault.xml'))) }.
|
36
|
+
must_raise FaultRaised
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'lolsoap/wsdl'
|
3
|
+
|
4
|
+
module LolSoap
|
5
|
+
describe WSDL do
|
6
|
+
describe 'with a doc that can be parsed' do
|
7
|
+
let(:namespace) { 'http://lolsoap.api/bla' }
|
8
|
+
let(:parser) { OpenStruct.new(:namespaces => { 'bla' => namespace }) }
|
9
|
+
|
10
|
+
subject { WSDL.new(parser) }
|
11
|
+
|
12
|
+
describe 'with operations' do
|
13
|
+
before do
|
14
|
+
def subject.type(n)
|
15
|
+
@types ||= { 'WashHandsRequest' => Object.new, 'WashHandsResponse' => Object.new }
|
16
|
+
@types[n]
|
17
|
+
end
|
18
|
+
|
19
|
+
parser.operations = {
|
20
|
+
'washHands' => {
|
21
|
+
:action => 'urn:washHands',
|
22
|
+
:input => { :name => 'WashHandsRequest' },
|
23
|
+
:output => { :name => 'WashHandsResponse' }
|
24
|
+
}
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#operations' do
|
29
|
+
it 'returns a hash of operations' do
|
30
|
+
subject.operations.length.must_equal 1
|
31
|
+
subject.operations['washHands'].tap do |op|
|
32
|
+
op.wsdl.must_equal subject
|
33
|
+
op.action.must_equal "urn:washHands"
|
34
|
+
op.input.must_equal subject.types['WashHandsRequest']
|
35
|
+
op.output.must_equal subject.types['WashHandsResponse']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#operation' do
|
41
|
+
it 'returns a single operation' do
|
42
|
+
subject.operation('washHands').must_equal subject.operations['washHands']
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#endpoint' do
|
48
|
+
it 'returns the endpoint' do
|
49
|
+
parser.endpoint = 'http://lolsoap.api/v1'
|
50
|
+
subject.endpoint.must_equal 'http://lolsoap.api/v1'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#namespaces' do
|
55
|
+
it 'returns a namespaces hash' do
|
56
|
+
subject.namespaces.must_equal({ 'bla' => namespace })
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#prefixes' do
|
61
|
+
it 'returns the prefixes-to-namespace mapping' do
|
62
|
+
subject.prefixes.must_equal({ namespace => 'bla' })
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'with types' do
|
67
|
+
before do
|
68
|
+
parser.types = {
|
69
|
+
'Brush' => {
|
70
|
+
:elements => {
|
71
|
+
'handleColor' => {
|
72
|
+
:name => 'handleColor',
|
73
|
+
:type => 'bla:Color',
|
74
|
+
:singular => true
|
75
|
+
},
|
76
|
+
'age' => {
|
77
|
+
:name => 'age',
|
78
|
+
:type => 'xs:int',
|
79
|
+
:singular => false
|
80
|
+
}
|
81
|
+
},
|
82
|
+
:namespace => namespace
|
83
|
+
},
|
84
|
+
'Color' => {
|
85
|
+
:elements => {
|
86
|
+
'name' => {
|
87
|
+
:name => 'name',
|
88
|
+
:type => 'xs:string',
|
89
|
+
:singular => true
|
90
|
+
},
|
91
|
+
'hex' => {
|
92
|
+
:name => 'hex',
|
93
|
+
:type => 'xs:string',
|
94
|
+
:singular => true
|
95
|
+
}
|
96
|
+
},
|
97
|
+
:namespace => namespace
|
98
|
+
}
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
describe '#types' do
|
103
|
+
it 'returns a hash of types' do
|
104
|
+
subject.types.length.must_equal 2
|
105
|
+
|
106
|
+
subject.types['Brush'].tap do |t|
|
107
|
+
t.namespace.must_equal namespace
|
108
|
+
t.elements.length.must_equal 2
|
109
|
+
t.element('handleColor').type.must_equal subject.types['Color']
|
110
|
+
t.element('handleColor').singular?.must_equal true
|
111
|
+
t.element('age').type.must_equal WSDL::NullType.new
|
112
|
+
t.element('age').singular?.must_equal false
|
113
|
+
end
|
114
|
+
|
115
|
+
subject.types['Color'].tap do |t|
|
116
|
+
t.namespace.must_equal namespace
|
117
|
+
t.elements.length.must_equal 2
|
118
|
+
t.element('name').type.must_equal WSDL::NullType.new
|
119
|
+
t.element('hex').type.must_equal WSDL::NullType.new
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe '#type' do
|
125
|
+
it 'returns a single type' do
|
126
|
+
subject.type('Color').must_equal subject.types['Color']
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'returns a null object if a type is missing' do
|
130
|
+
subject.type('FooBar').must_equal WSDL::NullType.new
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe '#type_namespaces' do
|
135
|
+
it 'returns only the namespaces that used by types' do
|
136
|
+
parser.namespaces['foo'] = 'bar'
|
137
|
+
subject.type_namespaces.must_equal 'bla' => namespace
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|