right_cloud_api_base 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|