transport 1.0.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/LICENSE +20 -0
- data/README.rdoc +109 -0
- data/Rakefile +50 -0
- data/lib/transport.rb +9 -0
- data/lib/transport/common.rb +41 -0
- data/lib/transport/common/request_builder.rb +43 -0
- data/lib/transport/http.rb +43 -0
- data/lib/transport/http/formatter.rb +85 -0
- data/lib/transport/http/request_builder.rb +73 -0
- data/lib/transport/http/request_builder/parameter_serializer.rb +61 -0
- data/lib/transport/json.rb +37 -0
- data/lib/transport/json/request_builder.rb +56 -0
- data/lib/transport/json/response_parser.rb +33 -0
- data/lib/transport/spec.rb +10 -0
- data/lib/transport/spec/faker.rb +75 -0
- data/lib/transport/unexpected_status_code_error.rb +21 -0
- data/spec/acceptance/http_spec.rb +20 -0
- data/spec/acceptance/json_spec.rb +10 -0
- data/spec/lib/transport/common/request_builder_spec.rb +61 -0
- data/spec/lib/transport/common_spec.rb +68 -0
- data/spec/lib/transport/http/formatter_spec.rb +28 -0
- data/spec/lib/transport/http/request_builder/parameter_serializer_spec.rb +18 -0
- data/spec/lib/transport/http/request_builder_spec.rb +134 -0
- data/spec/lib/transport/http_spec.rb +61 -0
- data/spec/lib/transport/json/request_builder_spec.rb +78 -0
- data/spec/lib/transport/json/response_parser_spec.rb +52 -0
- data/spec/lib/transport/json_spec.rb +58 -0
- data/spec/lib/transport/spec/faker_spec.rb +87 -0
- data/spec/lib/transport/unexpected_status_code_error_spec.rb +18 -0
- data/spec/spec_helper.rb +6 -0
- metadata +133 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "lib", "transport", "http", "formatter"))
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
describe Transport::HTTP::Formatter do
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
@logger = mock Logger, :info => nil
|
9
|
+
|
10
|
+
@formatter = described_class.new @logger
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "log_transport" do
|
14
|
+
|
15
|
+
before :each do
|
16
|
+
@uri = mock URI, :host => "host", :port => 1234
|
17
|
+
@request = mock Net::HTTP::Get, :class => Net::HTTP::Get, :path => "/path", :each_capitalized_name => nil, :body => "line one\nline two"
|
18
|
+
@response = mock Net::HTTPResponse, :code => "200", :body => "body"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should write the formatted request to the logger" do
|
22
|
+
@logger.should_receive(:info).with("transport to host 1234\nrequest: Net::HTTP::Get /path\n body:\n line one\n line two\nresponse: 200\n body")
|
23
|
+
@formatter.log_transport @uri, @request, @response
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "..", "lib", "transport", "http", "request_builder", "parameter_serializer"))
|
3
|
+
|
4
|
+
describe Transport::HTTP::RequestBuilder::ParameterSerializer do
|
5
|
+
|
6
|
+
it "should return nil on an empty parameter hash" do
|
7
|
+
serializer = described_class.new
|
8
|
+
serializer.perform
|
9
|
+
serializer.result.should be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return a correctly encoded query string" do
|
13
|
+
serializer = described_class.new :foo => "bar", :test => [ "value1", "value2" ]
|
14
|
+
serializer.perform
|
15
|
+
serializer.result.should == "foo=bar&test=value1&test=value2"
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "lib", "transport", "http", "request_builder"))
|
3
|
+
|
4
|
+
describe Transport::HTTP::RequestBuilder do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@url = "http://host:1234/test"
|
8
|
+
@options = {
|
9
|
+
:headers => { "Test-Header" => "test" },
|
10
|
+
:parameters => { "test_parameter" => "test" }
|
11
|
+
}
|
12
|
+
@parameter_serializer = mock described_class::ParameterSerializer, :perform => nil, :result => "test_parameter=test"
|
13
|
+
described_class::ParameterSerializer.stub(:new).and_return(@parameter_serializer)
|
14
|
+
end
|
15
|
+
|
16
|
+
def mock_request(http_method)
|
17
|
+
@request_class = Net::HTTP.const_get(:"#{http_method.to_s.capitalize}")
|
18
|
+
@request = mock @request_class, :path => "/test", :basic_auth => nil, :body= => nil
|
19
|
+
@request_class.stub(:new).and_return(@request)
|
20
|
+
end
|
21
|
+
|
22
|
+
shared_examples_for "any http request builder perform" do
|
23
|
+
|
24
|
+
it "should initialize the parameter serializer correctly" do
|
25
|
+
described_class::ParameterSerializer.should_receive(:new).with(@options[:parameters]).and_return(@parameter_serializer)
|
26
|
+
@request_builder.perform
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should set basic authentication for the request" do
|
30
|
+
@request.should_receive(:basic_auth).with("test_username", "test_password")
|
31
|
+
|
32
|
+
@request_builder.options.merge! :auth_type => :basic, :username => "test_username", :password => "test_password"
|
33
|
+
@request_builder.perform
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should raise a #{NotImplementedError} if auth_type is not supported" do
|
37
|
+
@request_builder.options.merge! :auth_type => :invalid
|
38
|
+
lambda do
|
39
|
+
@request_builder.perform
|
40
|
+
end.should raise_error(NotImplementedError)
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
shared_examples_for "any http request builder request" do
|
46
|
+
|
47
|
+
before :each do
|
48
|
+
@request_builder.perform
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should return the request" do
|
52
|
+
@request_builder.request.should == @request
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
described_class::HTTP_METHODS_WITH_PARAMETERS.each do |http_method|
|
58
|
+
|
59
|
+
context "#{http_method} request" do
|
60
|
+
|
61
|
+
before :each do
|
62
|
+
mock_request http_method
|
63
|
+
@request_builder = described_class.new http_method, @url, @options
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "perform" do
|
67
|
+
|
68
|
+
it_should_behave_like "any http request builder perform"
|
69
|
+
|
70
|
+
it "should initialize the request correctly" do
|
71
|
+
@request_class.should_receive(:new).with("/test?test_parameter=test", @options[:headers]).and_return(@request)
|
72
|
+
@request_builder.perform
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should add no query string if parameter serializer returns nil" do
|
76
|
+
@request_class.should_receive(:new).with("/test", @options[:headers]).and_return(@request)
|
77
|
+
|
78
|
+
@parameter_serializer.stub(:result).and_return(nil)
|
79
|
+
@request_builder.perform
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should not set the request body" do
|
83
|
+
@request.should_not_receive(:body=)
|
84
|
+
@request_builder.perform
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "request" do
|
90
|
+
|
91
|
+
it_should_behave_like "any http request builder request"
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
described_class::HTTP_METHODS_WITH_BODY.each do |http_method|
|
100
|
+
|
101
|
+
context "#{http_method} request" do
|
102
|
+
|
103
|
+
before :each do
|
104
|
+
mock_request http_method
|
105
|
+
@request_builder = described_class.new http_method, @url, @options
|
106
|
+
end
|
107
|
+
|
108
|
+
describe "perform" do
|
109
|
+
|
110
|
+
it_should_behave_like "any http request builder perform"
|
111
|
+
|
112
|
+
it "should initialize the request correctly" do
|
113
|
+
@request_class.should_receive(:new).with("/test", @options[:headers]).and_return(@request)
|
114
|
+
@request_builder.perform
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should set the request body" do
|
118
|
+
@request.should_receive(:body=).with("test_parameter=test")
|
119
|
+
@request_builder.perform
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "request" do
|
125
|
+
|
126
|
+
it_should_behave_like "any http request builder request"
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "transport", "http"))
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
describe Transport::HTTP do
|
6
|
+
|
7
|
+
before :each do
|
8
|
+
@uri = mock URI, :host => "host", :port => 1234
|
9
|
+
@request = mock Net::HTTPRequest, :path => "/path"
|
10
|
+
@logger = mock ::Logger, :info => nil
|
11
|
+
@options = { :expected_status_code => 200, :logger => @logger }
|
12
|
+
|
13
|
+
@response = mock Net::HTTPResponse, :code => "200", :body => "test\ntest"
|
14
|
+
Net::HTTP.stub(:start).and_return(@response)
|
15
|
+
|
16
|
+
@formatter = mock described_class::Formatter, :log_transport => nil
|
17
|
+
described_class::Formatter.stub(:new).and_return(@formatter)
|
18
|
+
|
19
|
+
@transport = described_class.new @uri, @request, @options
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "perform" do
|
23
|
+
|
24
|
+
it "should perform the request" do
|
25
|
+
Net::HTTP.should_receive(:start).with("host", 1234).and_return(@response)
|
26
|
+
@transport.perform
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should initialize the formatter" do
|
30
|
+
described_class::Formatter.should_receive(:new).and_return(@formatter)
|
31
|
+
@transport.perform
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should log the transport" do
|
35
|
+
@formatter.should_receive(:log_transport).with(@uri, @request, @response)
|
36
|
+
@transport.perform
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise UnexpectedStatusCodeError if responded status code is wrong" do
|
40
|
+
@transport.options.merge! :expected_status_code => 201
|
41
|
+
lambda do
|
42
|
+
@transport.perform
|
43
|
+
end.should raise_error(Transport::UnexpectedStatusCodeError)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "response" do
|
49
|
+
|
50
|
+
before :each do
|
51
|
+
@transport.perform
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should return the response body" do
|
55
|
+
@transport.perform
|
56
|
+
@transport.response.should == "test\ntest"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "lib", "transport", "json", "request_builder"))
|
3
|
+
|
4
|
+
describe Transport::JSON::RequestBuilder do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@http_method = :get
|
8
|
+
@url = "test_url"
|
9
|
+
@options = { :encode_parameters => true }
|
10
|
+
|
11
|
+
@request = mock Net::HTTP::Get
|
12
|
+
|
13
|
+
@http_request_builder = mock Transport::HTTP::RequestBuilder, :perform => nil, :request => @request
|
14
|
+
Transport::HTTP::RequestBuilder.stub(:new).and_return(@http_request_builder)
|
15
|
+
|
16
|
+
@request_builder = described_class.new @http_method, @url, @options
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "perform" do
|
20
|
+
|
21
|
+
it "should initialize the http request builder correctly" do
|
22
|
+
Transport::HTTP::RequestBuilder.should_receive(:new).with(
|
23
|
+
@http_method,
|
24
|
+
@url,
|
25
|
+
@options.merge(:headers => { "Accept" => "application/json" })
|
26
|
+
).and_return(@http_request_builder)
|
27
|
+
|
28
|
+
@request_builder.perform
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should set the content type to 'application/json' and convert body to json if a body is given" do
|
32
|
+
Transport::HTTP::RequestBuilder.should_receive(:new).with(
|
33
|
+
@http_method,
|
34
|
+
@url,
|
35
|
+
@options.merge(
|
36
|
+
:headers => { "Accept" => "application/json", "Content-Type" => "application/json" },
|
37
|
+
:body => "{\"test\":\"body\"}"
|
38
|
+
)
|
39
|
+
).and_return(@http_request_builder)
|
40
|
+
|
41
|
+
@request_builder.options.merge! :body => { "test" => "body" }
|
42
|
+
@request_builder.perform
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should convert the parameters to json if requested" do
|
46
|
+
Transport::HTTP::RequestBuilder.should_receive(:new).with(
|
47
|
+
@http_method,
|
48
|
+
@url,
|
49
|
+
@options.merge(
|
50
|
+
:headers => { "Accept" => "application/json" },
|
51
|
+
:parameters => { "test_parameter" => "\"test\"" }
|
52
|
+
)
|
53
|
+
).and_return(@http_request_builder)
|
54
|
+
|
55
|
+
@request_builder.options.merge! :parameters => { "test_parameter" => "test" }
|
56
|
+
@request_builder.perform
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should perform a http request build" do
|
60
|
+
@http_request_builder.should_receive(:perform)
|
61
|
+
@request_builder.perform
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "request" do
|
67
|
+
|
68
|
+
before :each do
|
69
|
+
@request_builder.perform
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should return the request" do
|
73
|
+
@request_builder.request.should == @request
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "lib", "transport", "json", "response_parser"))
|
3
|
+
|
4
|
+
describe Transport::JSON::ResponseParser do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@http_response = "{\"test\":\"test value\"}"
|
8
|
+
|
9
|
+
@response_parser = described_class.new @http_response
|
10
|
+
end
|
11
|
+
|
12
|
+
describe "perform" do
|
13
|
+
|
14
|
+
it "should parse the response body" do
|
15
|
+
JSON.should_receive(:parse).with(@http_response).and_return("test" => "test value")
|
16
|
+
@response_parser.perform
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should set result to nil if http response is nil" do
|
20
|
+
response_parser = described_class.new nil
|
21
|
+
response_parser.perform
|
22
|
+
response_parser.result.should be_nil
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should set result to nil if http response is empty" do
|
26
|
+
response_parser = described_class.new " "
|
27
|
+
response_parser.perform
|
28
|
+
response_parser.result.should be_nil
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should raise a #{Transport::JSON::ParserError} if a #{JSON::ParserError} is raised" do
|
32
|
+
JSON.stub(:parse).and_raise(JSON::ParserError)
|
33
|
+
lambda do
|
34
|
+
@response_parser.perform
|
35
|
+
end.should raise_error(Transport::JSON::ParserError)
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "result" do
|
41
|
+
|
42
|
+
before :each do
|
43
|
+
@response_parser.perform
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should return the result" do
|
47
|
+
@response_parser.result.should == { "test" => "test value" }
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "transport", "json"))
|
3
|
+
|
4
|
+
describe Transport::JSON do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@uri = mock URI
|
8
|
+
@request = mock Net::HTTPRequest
|
9
|
+
@options = { }
|
10
|
+
|
11
|
+
@http_response = "test_http_response"
|
12
|
+
|
13
|
+
@http_transport = mock Transport::HTTP, :perform => nil, :response => @http_response
|
14
|
+
Transport::HTTP.stub(:new).and_return(@http_transport)
|
15
|
+
|
16
|
+
@response_parser = mock described_class::ResponseParser, :perform => nil, :result => :test_response
|
17
|
+
described_class::ResponseParser.stub(:new).and_return(@response_parser)
|
18
|
+
|
19
|
+
@transport = described_class.new @uri, @request, @options
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "perform" do
|
23
|
+
|
24
|
+
it "should initialize the http transport" do
|
25
|
+
Transport::HTTP.should_receive(:new).with(@uri, @request, @options).and_return(@http_transport)
|
26
|
+
@transport.perform
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should perform the http transport" do
|
30
|
+
@http_transport.should_receive(:perform)
|
31
|
+
@transport.perform
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should initialize the response parser" do
|
35
|
+
described_class::ResponseParser.should_receive(:new).with(@http_response).and_return(@response_parser)
|
36
|
+
@transport.perform
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should perform the response parse" do
|
40
|
+
@response_parser.should_receive(:perform)
|
41
|
+
@transport.perform
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "response" do
|
47
|
+
|
48
|
+
before :each do
|
49
|
+
@transport.perform
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return the response parser result" do
|
53
|
+
@transport.response.should == :test_response
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "lib", "transport", "spec", "faker"))
|
3
|
+
|
4
|
+
describe Transport::Spec::Faker do
|
5
|
+
|
6
|
+
before :each do
|
7
|
+
@fake = [
|
8
|
+
{
|
9
|
+
:http_method => "get",
|
10
|
+
:url => "http://host:1234/test.html",
|
11
|
+
:response => {
|
12
|
+
:code => 200,
|
13
|
+
:body => "<html></html>"
|
14
|
+
}
|
15
|
+
}, {
|
16
|
+
:http_method => "get",
|
17
|
+
:url => "http://host:1234/test.json",
|
18
|
+
:response => {
|
19
|
+
:code => 200,
|
20
|
+
:body => { "test" => "body" }
|
21
|
+
}
|
22
|
+
}
|
23
|
+
]
|
24
|
+
YAML.stub(:load_file).and_return(@fake)
|
25
|
+
@filename = "test_filename"
|
26
|
+
end
|
27
|
+
|
28
|
+
def do_fake
|
29
|
+
described_class.fake! @filename
|
30
|
+
end
|
31
|
+
|
32
|
+
shared_examples_for "any fake" do
|
33
|
+
|
34
|
+
it "should load the yaml file" do
|
35
|
+
YAML.should_receive(:load_file).with(@filename).and_return(@fake)
|
36
|
+
do_fake
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should raise a #{described_class::NoFakeRequestError} if fake request is matching" do
|
40
|
+
do_fake
|
41
|
+
lambda do
|
42
|
+
do_request :get, "invalid"
|
43
|
+
end.should raise_error(described_class::NoFakeRequestError)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should raise an #{Transport::UnexpectedStatusCodeError} if the wrong status code is returned" do
|
47
|
+
do_fake
|
48
|
+
lambda do
|
49
|
+
do_request :get, "http://host:1234/test.html", :expected_status_code => 201
|
50
|
+
end.should raise_error(Transport::UnexpectedStatusCodeError)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "fake an http transport" do
|
56
|
+
|
57
|
+
def do_request(*arguments)
|
58
|
+
Transport::HTTP.request *arguments
|
59
|
+
end
|
60
|
+
|
61
|
+
it_should_behave_like "any fake"
|
62
|
+
|
63
|
+
it "should fake a http transport" do
|
64
|
+
do_fake
|
65
|
+
response = do_request :get, "http://host:1234/test.html"
|
66
|
+
response.should == "<html></html>"
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "fake an json transport" do
|
72
|
+
|
73
|
+
def do_request(*arguments)
|
74
|
+
Transport::JSON.request *arguments
|
75
|
+
end
|
76
|
+
|
77
|
+
it_should_behave_like "any fake"
|
78
|
+
|
79
|
+
it "should fake a json transport" do
|
80
|
+
do_fake
|
81
|
+
response = do_request :get, "http://host:1234/test.json"
|
82
|
+
response.should == { "test" => "body" }
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|