From e1cdbfccde1c9c32d793b80efa036961208d1327 Mon Sep 17 00:00:00 2001 From: Tank Tang <kat@microsoft.com> Date: Sun, 1 Mar 2020 23:39:43 +0800 Subject: [PATCH] Added the test cases from Azure Core --- test/fixtures/files/test.png | Bin 0 -> 4054 bytes test/fixtures/http_invalid_header.xml | 7 + test/support/fixtures.rb | 47 ++++- test/unit/core/auth/shared_key_lite_test.rb | 51 +++++ test/unit/core/auth/shared_key_test.rb | 59 ++++++ test/unit/core/auth/signer_test.rb | 30 +++ test/unit/core/filter_service_test.rb | 41 ++++ test/unit/core/http/http_error_test.rb | 113 +++++++++++ test/unit/core/http/http_request_test.rb | 201 ++++++++++++++++++++ test/unit/core/http/http_response_test.rb | 20 ++ test/unit/core/service_test.rb | 73 +++++++ test/unit/core/utility_test.rb | 123 ++++++++++++ 12 files changed, 764 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/files/test.png create mode 100644 test/fixtures/http_invalid_header.xml create mode 100644 test/unit/core/auth/shared_key_lite_test.rb create mode 100644 test/unit/core/auth/shared_key_test.rb create mode 100644 test/unit/core/auth/signer_test.rb create mode 100644 test/unit/core/filter_service_test.rb create mode 100644 test/unit/core/http/http_error_test.rb create mode 100644 test/unit/core/http/http_request_test.rb create mode 100644 test/unit/core/http/http_response_test.rb create mode 100644 test/unit/core/service_test.rb create mode 100644 test/unit/core/utility_test.rb diff --git a/test/fixtures/files/test.png b/test/fixtures/files/test.png new file mode 100644 index 0000000000000000000000000000000000000000..afbbb4f8b77c0826e00104e7a685a732184bb5f5 GIT binary patch literal 4054 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/http_invalid_header.xml b/test/fixtures/http_invalid_header.xml new file mode 100644 index 0000000..6ddfedc --- /dev/null +++ b/test/fixtures/http_invalid_header.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes"?> +<Error xmlns="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"> + <Code>InvalidHeaderValue</Code> + <Message>The value for one of the HTTP headers is not in the correct format.\nRequestId:266be2bc-0001-0018-1256-d3bb92000000\nTime:2016-07-01T05:06:32.5922690Z</Message> + <HeaderName>Range</HeaderName> + <HeaderValue>bytes=0-512</HeaderValue> +</Error> diff --git a/test/support/fixtures.rb b/test/support/fixtures.rb index 087df4f..4398aa1 100644 --- a/test/support/fixtures.rb +++ b/test/support/fixtures.rb @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. #-------------------------------------------------------------------------- -require "pathname" +require "azure/core/http/retry_policy" Fixtures = Hash.new do |hash, fixture| if path = Fixtures.xml?(fixture) @@ -49,3 +49,48 @@ end def Fixtures.json?(fixture) file?("#{fixture}.json") end + +module Azure + module Core + Fixtures = Hash.new do |hash, fixture| + if path = Fixtures.xml?(fixture) + hash[fixture] = path.read + elsif path = Fixtures.file?(fixture) + hash[fixture] = path + end + end + def Fixtures.root + Pathname("../../fixtures").expand_path(__FILE__) + end + def Fixtures.file?(fixture) + path = root.join(fixture) + path.file? && path + end + def Fixtures.xml?(fixture) + file?("#{fixture}.xml") + end + + class FixtureRetryPolicy < Azure::Core::Http::RetryPolicy + def initialize + super &:should_retry? + end + def should_retry?(response, retry_data) + retry_data[:error].inspect.include?('Error: Retry') + end + end + + class NewUriRetryPolicy < Azure::Core::Http::RetryPolicy + def initialize + @count = 1 + super &:should_retry? + end + + def should_retry?(response, retry_data) + retry_data[:uri] = URI.parse "http://bar.com" + @count = @count - 1 + @count >= 0 + end + end + + end +end diff --git a/test/unit/core/auth/shared_key_lite_test.rb b/test/unit/core/auth/shared_key_lite_test.rb new file mode 100644 index 0000000..75a49e9 --- /dev/null +++ b/test/unit/core/auth/shared_key_lite_test.rb @@ -0,0 +1,51 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#-------------------------------------------------------------------------- +require 'test_helper' +require 'azure/core/auth/shared_key_lite' + +describe Azure::Core::Auth::SharedKeyLite do + subject { Azure::Core::Auth::SharedKeyLite.new 'account-name', 'YWNjZXNzLWtleQ==' } + + let(:verb) { 'POST' } + let(:uri) { URI.parse 'http://dummy.uri/resource' } + let(:headers) do + { + 'Content-MD5' => 'foo', + 'Content-Type' => 'foo', + 'Date' => 'foo' + } + end + let(:headers_without_date) { + headers_without_date = headers.clone + headers_without_date.delete 'Date' + headers_without_date + } + + describe 'sign' do + it 'creates a signature from the provided HTTP method, uri, and reduced set of standard headers' do + subject.sign(verb, uri, headers).must_equal 'account-name:vVFnj/+27JFABZgpt5H8g/JVU2HuWFnjv5aeUIxQvBE=' + end + + it 'ignores standard headers other than Content-MD5, Content-Type, and Date' do + subject.sign(verb, uri, headers.merge({'Content-Encoding' => 'foo'})).must_equal 'account-name:vVFnj/+27JFABZgpt5H8g/JVU2HuWFnjv5aeUIxQvBE=' + end + + it 'throws IndexError when there is no Date header' do + assert_raises IndexError do + subject.sign(verb, uri, headers_without_date) + end + end + end +end diff --git a/test/unit/core/auth/shared_key_test.rb b/test/unit/core/auth/shared_key_test.rb new file mode 100644 index 0000000..0cd8646 --- /dev/null +++ b/test/unit/core/auth/shared_key_test.rb @@ -0,0 +1,59 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#-------------------------------------------------------------------------- +require 'test_helper' +require 'azure/core/auth/shared_key' + +describe Azure::Core::Auth::SharedKey do + subject { Azure::Core::Auth::SharedKey.new 'account-name', 'YWNjZXNzLWtleQ==' } + + let(:verb) { 'POST' } + let(:uri) { URI.parse 'http://dummy.uri/resource' } + let(:headers) do + { + 'Content-Encoding' => 'foo', + 'Content-Language' => 'foo', + 'Content-Length' => 'foo', + 'Content-MD5' => 'foo', + 'Content-Type' => 'foo', + 'Date' => 'foo', + 'If-Modified-Since' => 'foo', + 'If-Match' => 'foo', + 'If-None-Match' => 'foo', + 'If-Unmodified-Since' => 'foo', + 'Range' => 'foo', + 'x-ms-ImATeapot' => 'teapot', + 'x-ms-ShortAndStout' => 'True', + 'x-ms-reserve-spaces' => 'two speces' + } + end + + describe 'sign' do + it 'creates a signature from the provided HTTP method, uri, and a specific set of standard headers' do + subject.sign(verb, uri, headers).must_equal 'account-name:TVilUAfUwtHIVp+eonglFDXfS5r0/OE0/vVX3GHcaxU=' + end + end + + describe 'canonicalized_headers' do + it 'creates a canonicalized header string' do + subject.canonicalized_headers(headers).must_equal "x-ms-imateapot:teapot\nx-ms-reserve-spaces:two speces\nx-ms-shortandstout:True" + end + end + + describe 'canonicalized_resource' do + it 'creates a canonicalized resource string' do + subject.canonicalized_resource(uri).must_equal '/account-name/resource' + end + end +end diff --git a/test/unit/core/auth/signer_test.rb b/test/unit/core/auth/signer_test.rb new file mode 100644 index 0000000..917194b --- /dev/null +++ b/test/unit/core/auth/signer_test.rb @@ -0,0 +1,30 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#-------------------------------------------------------------------------- +require "test_helper" +require "azure/core/auth/signer" + +describe Azure::Core::Auth::Signer do + subject { Azure::Core::Auth::Signer.new "YWNjZXNzLWtleQ==" } + + it "decodes the base64 encoded access_key" do + subject.access_key.must_equal "access-key" + end + + describe "sign" do + it "creates a signature for the body, as a base64 encoded string, which represents a HMAC hash using the access_key" do + subject.sign("body").must_equal "iuUxVhs1E7PeSNx/90ViyJNO24160qYpoWeCcOsnMoM=" + end + end +end diff --git a/test/unit/core/filter_service_test.rb b/test/unit/core/filter_service_test.rb new file mode 100644 index 0000000..d16b222 --- /dev/null +++ b/test/unit/core/filter_service_test.rb @@ -0,0 +1,41 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#-------------------------------------------------------------------------- +require 'test_helper' +require 'azure/core' +require "azure/core/http/debug_filter" +require "azure/core/http/retry_policy" + +describe 'Azure core service' do + subject do + Azure::Core::FilteredService.new + end + + it 'works with default' do + subject.filters.count.must_equal 0 + end + + it 'works with a debug filter' do + service = Azure::Core::FilteredService.new + service.with_filter Azure::Core::Http::DebugFilter.new + service.filters.count.must_equal 1 + end + + it 'works with retry policy filter' do + service = Azure::Core::FilteredService.new + service.with_filter Azure::Core::Http::DebugFilter.new + service.with_filter Azure::Core::Http::RetryPolicy.new + service.filters.count.must_equal 2 + end +end diff --git a/test/unit/core/http/http_error_test.rb b/test/unit/core/http/http_error_test.rb new file mode 100644 index 0000000..49b5040 --- /dev/null +++ b/test/unit/core/http/http_error_test.rb @@ -0,0 +1,113 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#-------------------------------------------------------------------------- +require 'test_helper' +require 'azure/core/http/http_error' + +describe Azure::Core::Http::HTTPError do + let :http_response do + stub(body: Azure::Core::Fixtures[:http_error], status_code: 409, uri: 'http://dummy.uri', headers: { 'Content-Type' => 'application/atom+xml' }) + end + + subject do + Azure::Core::Http::HTTPError.new(http_response) + end + + it 'is an instance of Azure::Core::Error' do + subject.must_be_kind_of Azure::Core::Error + end + + it 'lets us see the original uri' do + subject.uri.must_equal 'http://dummy.uri' + end + + it "lets us see the errors'status code" do + subject.status_code.must_equal 409 + end + + it "lets us see the error's type" do + subject.type.must_equal 'TableAlreadyExists' + end + + it "lets us see the error's description" do + subject.description.must_equal 'The table specified already exists.' + end + + it 'generates an error message that wraps both the type and description' do + subject.message.must_equal 'TableAlreadyExists (409): The table specified already exists.' + end + + describe 'with invalid http_response body' do + let :http_response do + stub(:body => "\r\nInvalid request\r\n", :status_code => 409, :uri => 'http://dummy.uri', headers: {}) + end + + it 'sets the type to unknown if the response body is not an XML' do + subject.type.must_equal 'Unknown' + subject.description.must_equal 'Invalid request' + end + end + + describe 'with invalid headers' do + let :http_response do + stub(body: Azure::Core::Fixtures[:http_invalid_header], status_code: 400, uri: 'http://dummy.uri', headers: { 'Content-Type' => 'application/atom+xml'}) + end + + it 'sets the invalid header in the error details' do + subject.status_code.must_equal 400 + subject.type.must_equal 'InvalidHeaderValue' + subject.description.must_include 'The value for one of the HTTP headers is not in the correct format' + subject.header.must_equal 'Range' + subject.header_value.must_equal 'bytes=0-512' + end + end + + describe 'with JSON payload' do + let :http_response do + body = "{\"odata.error\":{\"code\":\"ErrorCode\",\"message\":{\"lang\":\"en-US\",\"value\":\"ErrorDescription\"}}}" + stub(body: body, status_code: 400, uri: 'http://dummy.uri', headers: { 'Content-Type' => 'application/json' }) + end + + it 'parse error response with JSON payload' do + subject.status_code.must_equal 400 + subject.type.must_equal 'ErrorCode' + subject.description.must_include 'ErrorDescription' + end + end + + describe 'with unknown payload' do + let :http_response do + body = 'Unknown Payload Format with Unknown Error Description' + stub(body: body, status_code: 400, uri: 'http://dummy.uri', headers: {}) + end + + it 'parse error response with JSON payload' do + subject.status_code.must_equal 400 + subject.type.must_equal 'Unknown' + subject.description.must_include 'Error Description' + end + end + + describe 'with no response body' do + let :http_response do + body = '' + stub(body: body, status_code: 404, uri: 'http://dummy.uri', headers: {}, reason_phrase: 'dummy reason') + end + + it 'message has value assigned from reason_phrase' do + subject.status_code.must_equal 404 + subject.message.must_equal 'Unknown (404): dummy reason' + end + end +end diff --git a/test/unit/core/http/http_request_test.rb b/test/unit/core/http/http_request_test.rb new file mode 100644 index 0000000..fa781fe --- /dev/null +++ b/test/unit/core/http/http_request_test.rb @@ -0,0 +1,201 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#-------------------------------------------------------------------------- +require 'test_helper' +require 'azure/core/http/http_request' + +describe Azure::Core::Http::HttpRequest do + let(:uri) { URI('http://example.com') } + + describe ' default_headers ' do + subject do + Azure::Core::Http::HttpRequest.new(:get, uri, body: nil, current_time: 'Thu, 04 Oct 2012 06:38:27 GMT') + end + + it 'sets the x-ms-date header to the current_time' do + subject.headers['x-ms-date'] = 'Thu, 04 Oct 2012 06:38:27 GMT' + end + + it 'sets the x-ms-version header to the current API version' do + subject.headers['x-ms-version'] = '2011-08-18' + end + + it 'sets the DataServiceVersion header to the current API version' do + subject.headers['DataServiceVersion'] = '1.0;NetFx' + end + + it 'sets the MaxDataServiceVersion header to the current max` API version' do + subject.headers['MaxDataServiceVersion'] = '2.0;NetFx' + end + end + + describe 'when passed custom headers' do + subject do + Azure::Core::Http::HttpRequest.new(:get, + uri, + body: nil, + headers: { + 'blah' => 'something', + 'x-ms-version' => '123' + }) + end + + it 'should have overridden the value of x-ms-version' do + subject.headers['x-ms-version'].must_equal '123' + end + + it 'should have added in the blah = something header' do + subject.headers['blah'].must_equal 'something' + end + + end + + describe ' when passed a body ' do + describe " of type IO" do + subject do + file = File.open(File.expand_path("../../../../fixtures/files/test.png", __FILE__)) + Azure::Core::Http::HttpRequest.new(:post, uri, body: file) + end + + it 'sets the default Content-Type header' do + subject.headers['Content-Type'].must_equal 'application/atom+xml; charset=utf-8' + end + + it 'sets the Content-Length header' do + subject.headers['Content-Length'].must_equal '4054' + end + + it 'sets the Content-MD5 header to a Base64 encoded representation of the MD5 hash of the body' do + subject.headers['Content-MD5'].must_equal 'nxTCAVCgA9fOTeV8KY8Pug==' + end + end + + describe 'of type Tempfile' do + subject do + tempfile = Tempfile.open('azure') + file = File.open(File.expand_path('../../../../fixtures/files/test.png', __FILE__)) + IO.copy_stream(file, tempfile) + + Azure::Core::Http::HttpRequest.new(:post, uri, body: tempfile) + end + + it 'sets the default Content-Type header' do + subject.headers['Content-Type'].must_equal 'application/atom+xml; charset=utf-8' + end + + it 'sets the Content-Length header' do + subject.headers['Content-Length'].must_equal '4054' + end + + it 'sets the Content-MD5 header to a Base64 encoded representation of the MD5 hash of the body' do + subject.headers['Content-MD5'].must_equal 'nxTCAVCgA9fOTeV8KY8Pug==' + end + end + + describe ' of type StringIO' do + subject do + Azure::Core::Http::HttpRequest.new(:post, uri, body: StringIO.new('<body/>')) + end + + it 'sets the default Content-Type header' do + subject.headers['Content-Type'].must_equal 'application/atom+xml; charset=utf-8' + end + + it 'sets the Content-Length header' do + subject.headers['Content-Length'].must_equal '7' + end + + it 'sets the Content-MD5 header to a Base64 encoded representation of the MD5 hash of the body' do + subject.headers['Content-MD5'].must_equal 'PNeJy7qyzV4XUoBBHkVu0g==' + end + end + + + describe ' of type String' do + subject do + Azure::Core::Http::HttpRequest.new(:post, uri, body: '<body/>') + end + + it 'sets the default Content-Type header' do + subject.headers['Content-Type'].must_equal 'application/atom+xml; charset=utf-8' + end + + it 'sets the Content-Length header' do + subject.headers['Content-Length'].must_equal '7' + end + + it 'sets the Content-MD5 header to a Base64 encoded representation of the MD5 hash of the body' do + subject.headers['Content-MD5'].must_equal 'PNeJy7qyzV4XUoBBHkVu0g==' + end + end + end + + describe ' when the body is nil ' do + subject do + Azure::Core::Http::HttpRequest.new(:get, uri) + end + + it 'leaves the Content-Type, Content-Length, and Content-MD5 headers blank' do + subject.headers['Content-Length'].must_equal '0' + subject.headers['Content-MD5'].must_be_nil + end + end + + describe '#call' do + + let(:mock_conn) do + conn = mock + conn.expects(:run_request, [uri, nil, nil]).returns(mock_res) + conn + end + + subject do + sub = Azure::Core::Http::HttpRequest.new(:get, uri) + sub.expects(:http_setup).returns(mock_conn) + sub + end + + describe 'on success' do + let(:body) { '</body>' } + + let(:mock_res) do + res = mock + res.expects(:success?).returns(true) + res.expects(:body).returns(body) + res + end + + it 'should return a response' do + subject.call.body.must_equal(body) + end + end + + describe 'on failure' do + let(:body) { 'OH NO!!' } + + let(:mock_res) do + res = mock + res.expects(:success?).returns(false).at_least_once + res.expects(:status).returns(401).at_least_once + res.expects(:body).returns(body).at_least_once + res.expects(:headers).returns({}).at_least_once + res + end + + it 'should return a response' do + -> { subject.call }.must_raise(Azure::Core::Http::HTTPError) + end + end + end +end diff --git a/test/unit/core/http/http_response_test.rb b/test/unit/core/http/http_response_test.rb new file mode 100644 index 0000000..95a127d --- /dev/null +++ b/test/unit/core/http/http_response_test.rb @@ -0,0 +1,20 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#-------------------------------------------------------------------------- +require 'test_helper' +require 'azure/core/http/http_response' + +describe Azure::Core::Http::HttpResponse do + # TODO: fill in with better tests. +end diff --git a/test/unit/core/service_test.rb b/test/unit/core/service_test.rb new file mode 100644 index 0000000..2f706c9 --- /dev/null +++ b/test/unit/core/service_test.rb @@ -0,0 +1,73 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#-------------------------------------------------------------------------- +require 'test_helper' +require 'azure/core' + +describe 'Azure core service' do + subject do + Azure::Core::Service.new + end + + it 'generate_uri should return URI instance' do + subject.host = 'http://dumyhost.uri' + subject.generate_uri.must_be_kind_of ::URI + subject.generate_uri.to_s.must_equal 'http://dumyhost.uri/' + end + + it 'generate_uri should add path to the url' do + subject.generate_uri('resource/entity/').path.must_equal '/resource/entity/' + end + + it 'generate_uri should correctly join the path if host url contained a path' do + subject.host = 'http://dummy.uri/host/path' + subject.generate_uri('resource/entity/').path.must_equal '/host/path/resource/entity/' + end + + it 'generate_uri should encode the keys' do + subject.generate_uri('', {'key !' => 'value'}).query.must_include 'key+%21=value' + end + + it 'generate_uri should encode the values' do + subject.generate_uri('', {'key' => 'value !'}).query.must_include 'key=value+%21' + end + + it 'generate_uri should set query string to the encoded result' do + subject.generate_uri('', {'key' => 'value !', 'key !' => 'value'}).query.must_equal 'key=value+%21&key+%21=value' + end + + it 'generate_uri should override the default timeout' do + subject.generate_uri('', {'timeout' => 45}).query.must_equal 'timeout=45' + end + + it 'generate_uri should not include any query parameters' do + subject.generate_uri('', nil).query.must_be_nil + end + + it 'generate_uri should not re-encode path with spaces' do + subject.host = 'http://dumyhost.uri' + encoded_path = 'blob%20name%20with%20spaces' + uri = subject.generate_uri(encoded_path, nil) + uri.host.must_equal 'dumyhost.uri' + uri.path.must_equal '/blob%20name%20with%20spaces' + end + + it 'generate_uri should not re-encode path with special characters' do + subject.host = 'http://dumyhost.uri' + encoded_path = 'host/path/%D1%84%D0%B1%D0%B0%D1%84.jpg' + uri = subject.generate_uri(encoded_path, nil) + uri.host.must_equal 'dumyhost.uri' + uri.path.must_equal '/host/path/%D1%84%D0%B1%D0%B0%D1%84.jpg' + end +end diff --git a/test/unit/core/utility_test.rb b/test/unit/core/utility_test.rb new file mode 100644 index 0000000..a8f77a3 --- /dev/null +++ b/test/unit/core/utility_test.rb @@ -0,0 +1,123 @@ +#------------------------------------------------------------------------- +# # Copyright (c) Microsoft and contributors. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +#-------------------------------------------------------------------------- +require 'test_helper' +require 'azure/core/utility' + +describe Azure::Core::Logger do + subject { Azure::Core::Logger } + let(:msg) { "message" } + + after { + subject.initialize_external_logger(nil) + } + + describe "Log without external logger" do + before { + subject.initialize_external_logger(nil) + } + + it "#info" do + out, err = capture_io { subject.info(msg) } + assert_equal("\e[37m\e[1m" + msg + "\e[0m\e[0m\n", out) + end + + it "#error_with_exit" do + out, err = capture_io do + error = assert_raises(RuntimeError) do + subject.error_with_exit(msg) + end + assert_equal("\e[31m\e[1m" + msg + "\e[0m\e[0m", error.message) + end + assert_equal("\e[31m\e[1m" + msg + "\e[0m\e[0m\n", out) + end + + it "#warn" do + out, err = capture_io do + warn = subject.warn(msg) + assert_equal(msg, warn) + end + assert_equal("\e[33m" + msg + "\e[0m\n", out) + end + + it "#error" do + out, err = capture_io do + error = subject.error(msg) + assert_equal(msg, error) + end + assert_equal("\e[31m\e[1m" + msg + "\e[0m\e[0m\n", out) + end + + it "#exception_message" do + out, err = capture_io do + exception = assert_raises(RuntimeError) do + subject.exception_message(msg) + end + assert_equal("\e[31m\e[1m" + msg + "\e[0m\e[0m", exception.message) + end + assert_equal("\e[31m\e[1m" + msg + "\e[0m\e[0m\n", out) + end + + it "#success" do + out, err = capture_io { subject.success(msg) } + assert_equal("\e[32m" + msg + "\n\e[0m", out) + end + end + + describe "Log with external logger" do + let(:fake_output) { StringIO.new } + + before { + subject.initialize_external_logger(Logger.new(fake_output)) + } + + it "#info" do + subject.info(msg) + assert_match(/INFO -- : #{msg}\n/, fake_output.string) + end + + it "#error_with_exit" do + error = assert_raises(RuntimeError) do + subject.error_with_exit(msg) + end + assert_match(/ERROR -- : #{msg}\n/, fake_output.string) + assert_equal("\e[31m\e[1m" + msg + "\e[0m\e[0m", error.message) + end + + it "#warn" do + warn = subject.warn(msg) + assert_match(/WARN -- : #{msg}\n/, fake_output.string) + assert_equal(msg, warn) + end + + it "#error" do + error = subject.error(msg) + assert_match(/ERROR -- : #{msg}\n/, fake_output.string) + assert_equal(msg, error) + end + + it "#exception_message" do + exception = assert_raises(RuntimeError) do + subject.exception_message(msg) + end + assert_match(/WARN -- : #{msg}\n/, fake_output.string) + assert_equal("\e[31m\e[1m" + msg + "\e[0m\e[0m", exception.message) + end + + it "#success" do + subject.success(msg) + assert_match(/INFO -- : #{msg}\n/, fake_output.string) + end + end +end \ No newline at end of file -- GitLab