right_cloud_api_base 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.
- checksums.yaml +7 -0
- data/HISTORY +2 -0
- data/LICENSE +19 -0
- data/README.md +14 -0
- data/Rakefile +37 -0
- data/lib/base/api_manager.rb +707 -0
- data/lib/base/helpers/cloud_api_logger.rb +214 -0
- data/lib/base/helpers/http_headers.rb +239 -0
- data/lib/base/helpers/http_parent.rb +103 -0
- data/lib/base/helpers/http_request.rb +173 -0
- data/lib/base/helpers/http_response.rb +122 -0
- data/lib/base/helpers/net_http_patch.rb +31 -0
- data/lib/base/helpers/query_api_patterns.rb +862 -0
- data/lib/base/helpers/support.rb +270 -0
- data/lib/base/helpers/support.xml.rb +306 -0
- data/lib/base/helpers/utils.rb +380 -0
- data/lib/base/manager.rb +122 -0
- data/lib/base/parsers/json.rb +38 -0
- data/lib/base/parsers/plain.rb +36 -0
- data/lib/base/parsers/rexml.rb +83 -0
- data/lib/base/parsers/sax.rb +200 -0
- data/lib/base/routines/cache_validator.rb +184 -0
- data/lib/base/routines/connection_proxies/net_http_persistent_proxy.rb +194 -0
- data/lib/base/routines/connection_proxies/right_http_connection_proxy.rb +224 -0
- data/lib/base/routines/connection_proxy.rb +66 -0
- data/lib/base/routines/request_analyzer.rb +122 -0
- data/lib/base/routines/request_generator.rb +48 -0
- data/lib/base/routines/request_initializer.rb +52 -0
- data/lib/base/routines/response_analyzer.rb +152 -0
- data/lib/base/routines/response_parser.rb +79 -0
- data/lib/base/routines/result_wrapper.rb +75 -0
- data/lib/base/routines/retry_manager.rb +106 -0
- data/lib/base/routines/routine.rb +98 -0
- data/lib/right_cloud_api_base.rb +72 -0
- data/lib/right_cloud_api_base_version.rb +37 -0
- data/right_cloud_api_base.gemspec +63 -0
- data/spec/helpers/query_api_pattern_spec.rb +312 -0
- data/spec/helpers/support_spec.rb +211 -0
- data/spec/helpers/support_xml_spec.rb +207 -0
- data/spec/helpers/utils_spec.rb +179 -0
- data/spec/routines/connection_proxies/test_net_http_persistent_proxy_spec.rb +143 -0
- data/spec/routines/test_cache_validator_spec.rb +152 -0
- data/spec/routines/test_connection_proxy_spec.rb +44 -0
- data/spec/routines/test_request_analyzer_spec.rb +106 -0
- data/spec/routines/test_response_analyzer_spec.rb +132 -0
- data/spec/routines/test_response_parser_spec.rb +228 -0
- data/spec/routines/test_result_wrapper_spec.rb +63 -0
- data/spec/routines/test_retry_manager_spec.rb +84 -0
- data/spec/spec_helper.rb +15 -0
- metadata +215 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
|
25
|
+
|
26
|
+
describe "RightScale::CloudApi::ConnectionProxy" do
|
27
|
+
before(:each) do
|
28
|
+
logger = Logger.new(STDOUT)
|
29
|
+
logger.level = Logger::INFO
|
30
|
+
@connectionproxy = RightScale::CloudApi::ConnectionProxy.new
|
31
|
+
@test_data = {}
|
32
|
+
@test_data[:options] = {:cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new({:logger => logger})}
|
33
|
+
@test_data[:callbacks] = {}
|
34
|
+
@test_data[:response] = {}
|
35
|
+
allow(@connectionproxy).to receive(:log)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "creates a close connection callback" do
|
39
|
+
proxy = double(:request => nil)
|
40
|
+
expect(RightScale::CloudApi::ConnectionProxy::NetHttpPersistentProxy).to receive(:new).and_return(proxy)
|
41
|
+
@connectionproxy.execute(@test_data)
|
42
|
+
expect(@test_data[:callbacks][:close_current_connection]).to be_a(Proc)
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
|
25
|
+
|
26
|
+
describe "RightScale::CloudApi::RequestAnalyzer" do
|
27
|
+
before(:each) do
|
28
|
+
logger = Logger.new(STDOUT)
|
29
|
+
logger.level = Logger::INFO
|
30
|
+
@api_manager = RightScale::CloudApi::ApiManager.new({'x' => 'y'},'endpoint', {:logger => logger})
|
31
|
+
@api_manager.class.set_routine RightScale::CloudApi::RequestAnalyzer
|
32
|
+
@api_manager.class.options[:error_patterns] = []
|
33
|
+
end
|
34
|
+
|
35
|
+
context "request keys" do
|
36
|
+
before(:each) do
|
37
|
+
@valid_request_keys = RightScale::CloudApi::RequestAnalyzer::REQUEST_KEYS.inject({}) { |result, k| result.merge(k => k.to_s)}
|
38
|
+
@valid_request_actions = RightScale::CloudApi::RequestAnalyzer::REQUEST_ACTIONS
|
39
|
+
end
|
40
|
+
it "creates a pattern for expected keys" do
|
41
|
+
expected_result = []
|
42
|
+
@valid_request_actions.each do |action|
|
43
|
+
expected_result << @valid_request_keys.merge(:action => action)
|
44
|
+
expect(@api_manager.class.error_pattern(action, @valid_request_keys)).to eq expected_result
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "response keys" do
|
50
|
+
before(:each) do
|
51
|
+
@valid_response_keys = RightScale::CloudApi::RequestAnalyzer::RESPONSE_KEYS.inject({}) { |result, k| result.merge(k => k.to_s)}
|
52
|
+
@valid_response_actions = RightScale::CloudApi::RequestAnalyzer::RESPONSE_ACTIONS
|
53
|
+
end
|
54
|
+
it "creates a pattern for expected keys" do
|
55
|
+
expected_result = []
|
56
|
+
@valid_response_actions.each do |action|
|
57
|
+
expected_result << @valid_response_keys.merge(:action => action)
|
58
|
+
expect(@api_manager.class.error_pattern(action, @valid_response_keys)).to eq expected_result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context "with unexpected keys" do
|
64
|
+
it "fails to create a pattern" do
|
65
|
+
valid_request_action = :abort_on_timeout
|
66
|
+
expect { @api_manager.class.error_pattern(valid_request_action, {:bad_key => "BadKey"}) }.to raise_error(RightScale::CloudApi::RequestAnalyzer::Error)
|
67
|
+
expect { @api_manager.class.error_pattern(:bad_action, {:verb => /verb/}) }.to raise_error(RightScale::CloudApi::RequestAnalyzer::Error)
|
68
|
+
expect { @api_manager.class.error_pattern(valid_request_action, {:response => /verb/}) }.to raise_error(RightScale::CloudApi::RequestAnalyzer::Error)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "process" do
|
73
|
+
before(:each) do
|
74
|
+
logger = Logger.new(STDOUT)
|
75
|
+
logger.level = Logger::INFO
|
76
|
+
error_patterns = [{:path =>/Action=(Run|Create)/,
|
77
|
+
:action =>:abort_on_timeout},
|
78
|
+
{:response => /InternalError|Internal Server Error|internal service error/i,
|
79
|
+
:action => :retry}]
|
80
|
+
@data = {:request => {:instance => 'some_instance',
|
81
|
+
:verb => 'some_verb',
|
82
|
+
:orig_params => {}},
|
83
|
+
:options => {:error_patterns => error_patterns,
|
84
|
+
:cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new({:logger => logger,
|
85
|
+
:log_filters => [:request_generator] })}}
|
86
|
+
@requestanalyzer = RightScale::CloudApi::RequestAnalyzer.new
|
87
|
+
allow(@requestanalyzer).to receive(:log)
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when patern does not match" do
|
91
|
+
it "does not set :abort_on_timeout flag" do
|
92
|
+
expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).and_return(false)
|
93
|
+
@requestanalyzer.execute(@data)
|
94
|
+
expect(@data[:options].has_key?(:abort_on_timeout)).to be(false)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when patern matches" do
|
99
|
+
it "sets :abort_on_timeout flag" do
|
100
|
+
expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).and_return(true)
|
101
|
+
@requestanalyzer.execute(@data)
|
102
|
+
expect(@data[:options][:abort_on_timeout]).to be(true)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
|
25
|
+
|
26
|
+
describe "RightScale::CloudApi::ResponseAnalyzer" do
|
27
|
+
before(:each) do
|
28
|
+
logger = Logger.new(STDOUT)
|
29
|
+
logger.level = Logger::INFO
|
30
|
+
@responseanalyzer = RightScale::CloudApi::ResponseAnalyzer.new
|
31
|
+
@test_data = {}
|
32
|
+
@test_data[:request] = { :verb => 'some_verb', :orig_params => {}, :instance => 'some_request'}
|
33
|
+
@test_data[:vars] = { :retry => {} }
|
34
|
+
@test_data[:options] = {:error_patterns => [], :cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new(
|
35
|
+
{:logger => logger, :log_filters => [:response_analyzer, :response_analyzer_body_error]})
|
36
|
+
}
|
37
|
+
@close_current_connection_callback = double
|
38
|
+
@test_data[:callbacks] = {:close_current_connection => @close_current_connection_callback}
|
39
|
+
@test_data[:connection] = {}
|
40
|
+
allow(@responseanalyzer).to receive(:log)
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with no error patterns" do
|
44
|
+
it "fails on 5xx, 4xx errors" do
|
45
|
+
[500, 400].each do |http_error|
|
46
|
+
@test_data[:response] = {:instance => generate_http_response(http_error)}
|
47
|
+
expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
it "fails on redirect when there is no location tin the response" do
|
51
|
+
@test_data[:response] = {:instance => generate_http_response(300)}
|
52
|
+
expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
|
53
|
+
end
|
54
|
+
it "works for 2xx codes" do
|
55
|
+
@test_data[:response] = {:instance => generate_http_response(200)}
|
56
|
+
expect { @responseanalyzer.execute(@test_data) }.to_not raise_error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when retry is requested" do
|
61
|
+
before(:each) do
|
62
|
+
error_patterns = [{:action => :retry}]
|
63
|
+
@test_data[:options][:error_patterns] = error_patterns
|
64
|
+
allow(RightScale::CloudApi::Utils).to receive(:pattern_matches?).and_return(true)
|
65
|
+
end
|
66
|
+
it "raises a retry attempt exception for 4xx and 5xx errors" do
|
67
|
+
[500, 400].each do |http_error|
|
68
|
+
@test_data[:response] = {:instance => generate_http_response(http_error)}
|
69
|
+
expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::RetryAttempt)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
it "fails when there is no location for 3xx redirect" do
|
73
|
+
response = generate_http_response(301, 'body', {'location' => ''})
|
74
|
+
@test_data[:response] = {:instance => response}
|
75
|
+
expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
|
76
|
+
end
|
77
|
+
it "raises a retry attempt exception when there is a location (in headers) for 3xx redirect" do
|
78
|
+
response = generate_http_response(301, 'body', {'location' => 'www.some-new-location.com'})
|
79
|
+
@test_data[:response] = {:instance => response}
|
80
|
+
expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::RetryAttempt)
|
81
|
+
end
|
82
|
+
it "raises a retry attempt exception when there is a location (in body) for 3xx redirect" do
|
83
|
+
response = generate_http_response(301, '<Endpoint> www.some-new-location.com </Endpoint>', {'location' => ''})
|
84
|
+
@test_data[:response] = {:instance => response}
|
85
|
+
uri_object = double(:host= => 'www.some-new-location.com', :to_s => 'www.some-new-location.com')
|
86
|
+
@test_data[:connection][:uri] = uri_object
|
87
|
+
expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::RetryAttempt)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "when reconnect_and_retry is requested" do
|
92
|
+
before(:each) do
|
93
|
+
@test_data[:options][:error_patterns] = [{:action => :reconnect_and_retry}]
|
94
|
+
expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).at_least(1).and_return(true)
|
95
|
+
expect(@close_current_connection_callback).to receive(:call).twice.with(/Error code/)
|
96
|
+
end
|
97
|
+
it "raises a retry attempt exception for 4xx and 5xx errors" do
|
98
|
+
[500, 400].each do |http_error|
|
99
|
+
@test_data[:response] = {:instance => generate_http_response(http_error)}
|
100
|
+
expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::RetryAttempt)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when disconnect_and_abort is requested" do
|
106
|
+
before(:each) do
|
107
|
+
@test_data[:options][:error_patterns] = [{:action => :disconnect_and_abort}]
|
108
|
+
expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).at_least(1).and_return(true)
|
109
|
+
expect(@close_current_connection_callback).to receive(:call).twice.with(/Error code/)
|
110
|
+
end
|
111
|
+
it "failse for 4xx and 5xx errors" do
|
112
|
+
[500, 400].each do |http_error|
|
113
|
+
@test_data[:response] = {:instance => generate_http_response(http_error)}
|
114
|
+
expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context "when abort is requested" do
|
120
|
+
before(:each) do
|
121
|
+
@test_data[:options][:error_patterns] = [{:action => :abort}]
|
122
|
+
expect(RightScale::CloudApi::Utils).to receive(:pattern_matches?).at_least(1).and_return(true)
|
123
|
+
expect(@close_current_connection_callback).to receive(:call).never
|
124
|
+
end
|
125
|
+
it "failse for 4xx and 5xx errors" do
|
126
|
+
[500, 400].each do |http_error|
|
127
|
+
@test_data[:response] = {:instance => generate_http_response(http_error)}
|
128
|
+
expect { @responseanalyzer.execute(@test_data) }.to raise_error(RightScale::CloudApi::HttpError)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
|
25
|
+
|
26
|
+
|
27
|
+
describe "RightScale::CloudApi::ResponseParser" do
|
28
|
+
before :each do
|
29
|
+
logger = Logger.new(STDOUT)
|
30
|
+
logger.level = Logger::INFO
|
31
|
+
@response_parser = RightScale::CloudApi::ResponseParser.new
|
32
|
+
@test_data = {}
|
33
|
+
@test_data[:request] = { :verb => 'some_verb', :orig_params => {}, :instance => 'some_request'}
|
34
|
+
@test_data[:options] = {:error_patterns => [], :cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new(
|
35
|
+
{:logger => logger, :log_filters => [:response_parser]})
|
36
|
+
}
|
37
|
+
@callback = double
|
38
|
+
@test_data[:callbacks] = {:close_current_connection => @callback}
|
39
|
+
@test_data[:connection] = {}
|
40
|
+
allow(@response_parser).to receive(:log)
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
context "encoding" do
|
45
|
+
context "text/xml;charset=UTF-8" do
|
46
|
+
before :each do
|
47
|
+
response = double(
|
48
|
+
:code => '200',
|
49
|
+
:body => 'body',
|
50
|
+
:headers => {"content-type" => ["text/xml;charset=UTF-8"]},
|
51
|
+
:is_io? => false,
|
52
|
+
:is_error? => true,
|
53
|
+
:body_info_for_log => 'body',
|
54
|
+
:header_info_for_log => ""
|
55
|
+
)
|
56
|
+
@test_data[:response] = {:instance => response}
|
57
|
+
expect(RightScale::CloudApi::Parser::Sax).to receive(:parse).\
|
58
|
+
once.with('body', {:encoding => 'UTF-8'})
|
59
|
+
end
|
60
|
+
|
61
|
+
it "works" do
|
62
|
+
@response_parser.execute(@test_data)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
context "text/xml;charset=utf-8" do
|
68
|
+
before :each do
|
69
|
+
response = double(
|
70
|
+
:code => '200',
|
71
|
+
:body => 'body',
|
72
|
+
:headers => {"content-type" => ["text/xml;charset=utf-8"]},
|
73
|
+
:is_io? => false,
|
74
|
+
:is_error? => true,
|
75
|
+
:body_info_for_log => 'body',
|
76
|
+
:header_info_for_log => ""
|
77
|
+
)
|
78
|
+
@test_data[:response] = {:instance => response}
|
79
|
+
expect(RightScale::CloudApi::Parser::Sax).to receive(:parse).\
|
80
|
+
once.with('body', {:encoding => 'UTF-8'})
|
81
|
+
end
|
82
|
+
|
83
|
+
it "works" do
|
84
|
+
@response_parser.execute(@test_data)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
context "text/xml;charset=ISO" do
|
90
|
+
before :each do
|
91
|
+
response = double(
|
92
|
+
:code => '200',
|
93
|
+
:body => 'body',
|
94
|
+
:headers => {"content-type" => ["text/xml;charset=ISO"]},
|
95
|
+
:is_io? => false,
|
96
|
+
:is_error? => true,
|
97
|
+
:body_info_for_log => 'body',
|
98
|
+
:header_info_for_log => ""
|
99
|
+
)
|
100
|
+
@test_data[:response] = {:instance => response}
|
101
|
+
expect(RightScale::CloudApi::Parser::Sax).to receive(:parse).\
|
102
|
+
once.with('body', {})
|
103
|
+
end
|
104
|
+
|
105
|
+
it "works" do
|
106
|
+
@response_parser.execute(@test_data)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
context "text/xml" do
|
112
|
+
before :each do
|
113
|
+
response = double(
|
114
|
+
:code => '200',
|
115
|
+
:body => 'body',
|
116
|
+
:headers => {"content-type" => ["text/xml"]},
|
117
|
+
:is_io? => false,
|
118
|
+
:is_error? => true,
|
119
|
+
:body_info_for_log => 'body',
|
120
|
+
:header_info_for_log => ""
|
121
|
+
)
|
122
|
+
@test_data[:response] = {:instance => response}
|
123
|
+
expect(RightScale::CloudApi::Parser::Sax).to receive(:parse).\
|
124
|
+
once.with('body', {})
|
125
|
+
end
|
126
|
+
|
127
|
+
it "works" do
|
128
|
+
@response_parser.execute(@test_data)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
context "text/json;charset=ISO" do
|
134
|
+
before :each do
|
135
|
+
response = double(
|
136
|
+
:code => '200',
|
137
|
+
:body => 'body',
|
138
|
+
:headers => {"content-type" => ["text/json;charset=ISO"]},
|
139
|
+
:is_io? => false,
|
140
|
+
:is_error? => true,
|
141
|
+
:body_info_for_log => 'body',
|
142
|
+
:header_info_for_log => ""
|
143
|
+
)
|
144
|
+
@test_data[:response] = {:instance => response}
|
145
|
+
expect(RightScale::CloudApi::Parser::Json).to receive(:parse).\
|
146
|
+
once.with('body', {})
|
147
|
+
end
|
148
|
+
|
149
|
+
it "works" do
|
150
|
+
@response_parser.execute(@test_data)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
context "text/json;charset=utf-8" do
|
156
|
+
before :each do
|
157
|
+
response = double(
|
158
|
+
:code => '200',
|
159
|
+
:body => 'body',
|
160
|
+
:headers => {"content-type" => ["text/json;charset=utf-8"]},
|
161
|
+
:is_io? => false,
|
162
|
+
:is_error? => true,
|
163
|
+
:body_info_for_log => 'body',
|
164
|
+
:header_info_for_log => ""
|
165
|
+
)
|
166
|
+
@test_data[:response] = {:instance => response}
|
167
|
+
expect(RightScale::CloudApi::Parser::Json).to receive(:parse).\
|
168
|
+
once.with('body', {:encoding => 'UTF-8'})
|
169
|
+
end
|
170
|
+
|
171
|
+
it "works" do
|
172
|
+
@response_parser.execute(@test_data)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
|
178
|
+
context "parsing" do
|
179
|
+
context "XML, default" do
|
180
|
+
before :each do
|
181
|
+
@expectation = {'xml' => { 'a' => '1' }}
|
182
|
+
response = double(
|
183
|
+
:code => '200',
|
184
|
+
:body => @expectation._to_xml,
|
185
|
+
:headers => {"content-type" => ["text/xml"]},
|
186
|
+
:is_io? => false,
|
187
|
+
:is_error? => true,
|
188
|
+
:body_info_for_log => 'body',
|
189
|
+
:header_info_for_log => ""
|
190
|
+
)
|
191
|
+
@test_data[:response] = {:instance => response}
|
192
|
+
end
|
193
|
+
|
194
|
+
it "returns parsed response" do
|
195
|
+
@response_parser.execute(@test_data)
|
196
|
+
result = @test_data[:result]
|
197
|
+
expect(result).to be_a(Hash)
|
198
|
+
expect(result).to eq(@expectation)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
context "XML, do not parse flag is set" do
|
204
|
+
before :each do
|
205
|
+
@expectation = {'xml' => { 'a' => '1' }}
|
206
|
+
response = double(
|
207
|
+
:code => '200',
|
208
|
+
:body => @expectation._to_xml,
|
209
|
+
:headers => {"content-type" => ["text/xml"]},
|
210
|
+
:is_io? => false,
|
211
|
+
:is_error? => true,
|
212
|
+
:body_info_for_log => 'body',
|
213
|
+
:header_info_for_log => ""
|
214
|
+
)
|
215
|
+
@test_data[:response] = {:instance => response}
|
216
|
+
@test_data[:options][:raw_response] = true
|
217
|
+
end
|
218
|
+
|
219
|
+
it "returns raw response" do
|
220
|
+
@response_parser.execute(@test_data)
|
221
|
+
result = @test_data[:result]
|
222
|
+
expect(result).to be_a(String)
|
223
|
+
expect(result).to eq(@expectation._to_xml)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
|
25
|
+
|
26
|
+
describe "RightScale::CloudApi::ResultWrapper" do
|
27
|
+
before (:each) do
|
28
|
+
logger = Logger.new(STDOUT)
|
29
|
+
logger.level = Logger::INFO
|
30
|
+
@resultwrapper = RightScale::CloudApi::ResultWrapper.new
|
31
|
+
@test_data = {}
|
32
|
+
@test_data[:options] = { :user_agent => 'user_agent_data',
|
33
|
+
:cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new({:logger => logger})}
|
34
|
+
@test_data[:vars] = {}
|
35
|
+
@test_data[:callbacks] = {}
|
36
|
+
|
37
|
+
@parsed_response = "parsed_response"
|
38
|
+
@test_data[:result] = @parsed_response
|
39
|
+
|
40
|
+
@headers = { 'header1' => ['1'], 'header2' => ['2'] }
|
41
|
+
@response= generate_http_response(201, 'body', @headers)
|
42
|
+
@test_data[:response] = {:instance => @response, :parsed => @parsed_response }
|
43
|
+
allow(@resultwrapper).to receive(:log)
|
44
|
+
@test_data[:vars][:current_cache_key] = "cache_key"
|
45
|
+
@test_data[:vars][:cache] = {'cache_key' => { :key => 'old_cache_record_key', :record => 'haha' }}
|
46
|
+
@result = @resultwrapper.execute(@test_data)
|
47
|
+
end
|
48
|
+
|
49
|
+
context "metadata" do
|
50
|
+
it "looks like a parsed body object but responds to metadata" do
|
51
|
+
expect(@result).to eq 'parsed_response'
|
52
|
+
expect(@result).to be_a(String)
|
53
|
+
expect { @result.metadata }.to_not raise_error
|
54
|
+
end
|
55
|
+
it "contains the last respose headers and code" do
|
56
|
+
expect(@result.metadata[:headers]).to eq @headers
|
57
|
+
expect(@result.metadata[:code]).to eq @response.code
|
58
|
+
end
|
59
|
+
it "may contain the cache key" do
|
60
|
+
expect(@result.metadata[:cache]).to eq @test_data[:vars][:cache]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale, Inc.
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# 'Software'), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
18
|
+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
19
|
+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
20
|
+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
21
|
+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.dirname(__FILE__)) + "/../spec_helper"
|
25
|
+
|
26
|
+
describe "" do
|
27
|
+
before(:each) do
|
28
|
+
logger = Logger.new(STDOUT)
|
29
|
+
logger.level = Logger::INFO
|
30
|
+
@retrymanager = RightScale::CloudApi::RetryManager.new
|
31
|
+
allow(@retrymanager).to receive(:log)
|
32
|
+
@request = double(:verb => 'get', :path => 'some/path', :body => 'body', :is_io? => false, :is_error? => false, :is_redirect? => false, :headers => {'header1' => 'val1', 'header2' => 'val2'})
|
33
|
+
@test_data = {
|
34
|
+
:options => { :user_agent => 'user_agent_data',
|
35
|
+
:cloud_api_logger => RightScale::CloudApi::CloudApiLogger.new({}),
|
36
|
+
:retry => {
|
37
|
+
:count => 2,
|
38
|
+
:reiteration_time => 10,
|
39
|
+
:sleep_time => 0.2
|
40
|
+
},
|
41
|
+
},
|
42
|
+
:vars => { :system => {:block => 'block', :started_at => Time.now}},
|
43
|
+
:credentials => {},
|
44
|
+
:callbacks => {},
|
45
|
+
:connection => {:uri => double(:host => 'host.com', :port => '777', :scheme => 'scheme')},
|
46
|
+
:request => {:instance => @request}
|
47
|
+
}
|
48
|
+
@response = double(:code => '200', :body => 'body', :to_hash => {:code => '200', :body => 'body'})
|
49
|
+
@connection = double(:request => @response)
|
50
|
+
end
|
51
|
+
|
52
|
+
context "RightScale::CloudApi::RetryManager" do
|
53
|
+
it "works" do
|
54
|
+
# 1st run
|
55
|
+
@retrymanager.execute(@test_data)
|
56
|
+
expect(@test_data[:vars][:retry][:count]).to eq 0
|
57
|
+
expect(@test_data[:vars][:retry][:sleep_time]).to eq 0.2
|
58
|
+
|
59
|
+
# 2nd run, +1 count *2 sleep
|
60
|
+
@retrymanager.execute(@test_data)
|
61
|
+
expect(@test_data[:vars][:retry][:count]).to eq 1
|
62
|
+
expect(@test_data[:vars][:retry][:sleep_time]).to eq 0.4
|
63
|
+
|
64
|
+
# 3rd run, +1 count, *2 sleep
|
65
|
+
@retrymanager.execute(@test_data)
|
66
|
+
expect(@test_data[:vars][:retry][:count]).to eq 2
|
67
|
+
expect(@test_data[:vars][:retry][:sleep_time]).to eq 0.8
|
68
|
+
|
69
|
+
#4th run, case 1: default error
|
70
|
+
default_rm_error = "RetryManager: No more retries left."
|
71
|
+
expect do
|
72
|
+
@retrymanager.execute(@test_data)
|
73
|
+
end.to raise_error(RightScale::CloudApi::RetryManager::Error, default_rm_error)
|
74
|
+
|
75
|
+
#4th run, case 2: cloud_error + default error
|
76
|
+
http_error = 'Banana.'
|
77
|
+
expectation = "#{http_error}\n#{default_rm_error}"
|
78
|
+
@test_data[:vars][:retry][:http] = { :code => 777, :message => http_error }
|
79
|
+
expect do
|
80
|
+
@retrymanager.execute(@test_data)
|
81
|
+
end.to raise_error(RightScale::CloudApi::RetryManager::Error, expectation)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'right_cloud_api_base'
|
2
|
+
require 'rspec'
|
3
|
+
|
4
|
+
# Generates a fake response object.
|
5
|
+
#
|
6
|
+
# @param [String] code HTTP response code
|
7
|
+
# @param [String] body HTTP response body
|
8
|
+
# @param [Hash] headers HTTP response code
|
9
|
+
# @param [HttpResponse] raw HTTP response object
|
10
|
+
#
|
11
|
+
# @return [RightScale::CloudApi::HTTPResponse]
|
12
|
+
#
|
13
|
+
def generate_http_response(code, body='body', headers={}, raw=nil)
|
14
|
+
RightScale::CloudApi::HTTPResponse.new(code.to_s, body, headers, raw)
|
15
|
+
end
|