# Copyright 2014 Amazon.com, Inc. or its affiliates. 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. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file 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.
import botocore.config
from tests import unittest
import mock

import botocore
from botocore import utils
from botocore import client
from botocore.endpoint import DEFAULT_TIMEOUT
from botocore import hooks
from botocore.client import ClientEndpointBridge
from botocore.credentials import Credentials
from botocore.configprovider import ConfigValueStore
from botocore.configprovider import EnvironmentProvider
from botocore.configprovider import ChainProvider
from botocore.exceptions import ParamValidationError
from botocore.exceptions import InvalidS3AddressingStyleError
from botocore.exceptions import UnknownSignatureVersionError
from botocore.exceptions import InvalidRetryConfigurationError
from botocore.exceptions import InvalidMaxRetryAttemptsError
from botocore.exceptions import InvalidRetryModeError
from botocore.errorfactory import ClientExceptionsFactory
from botocore.stub import Stubber
from botocore import exceptions
from botocore.compat import six


class TestAutoGeneratedClient(unittest.TestCase):
    def setUp(self):
        self.service_description = {
            'metadata': {
                'serviceFullName': 'AWS MyService',
                'apiVersion': '2014-01-01',
                'endpointPrefix': 'myservice',
                'signatureVersion': 'v4',
                'protocol': 'query',
                'serviceId': 'MyService',
            },
            'operations': {
                'TestOperation': {
                    'name': 'TestOperation',
                    'http': {
                        'method': 'POST',
                        'requestUri': '/',
                    },
                    'input': {'shape': 'TestOperationRequest'},
                    'errors': [{'shape': 'TestOperationException'}],
                    'documentation': 'Documents TestOperation'
                }
            },
            'shapes': {
                'TestOperationRequest': {
                    'type': 'structure',
                    'required': ['Foo'],
                    'members': {
                        'Foo': {'shape': 'StringType',
                                'documentation': 'Documents Foo'},
                        'Bar': {'shape': 'StringType',
                                'documentation': 'Documents Bar'},
                    }
                },
                "TestOperationException": {
                    'type': 'structure',
                    'exception': True,
                    'error': {
                        'code': 'TestOperationErrorCode'
                    }
                },
                'StringType': {'type': 'string'}
            }
        }
        self.retry_config = {
            "retry": {
                "__default__": {
                    "max_attempts": 5,
                    "delay": {
                        "type": "exponential",
                        "base": "rand",
                        "growth_factor": 2
                    },
                    "policies": {}
                }
            }
        }
        self.loader = mock.Mock()
        self.loader.load_service_model.return_value = self.service_description
        self.loader.load_data.return_value = self.retry_config

        self.credentials = Credentials('access-key', 'secret-key')

        self.endpoint_creator_patch = mock.patch(
            'botocore.args.EndpointCreator')
        self.endpoint_creator_cls = self.endpoint_creator_patch.start()
        self.endpoint_creator = self.endpoint_creator_cls.return_value

        self.endpoint = mock.Mock()
        self.endpoint.host = 'https://myservice.amazonaws.com'
        self.endpoint.make_request.return_value = (
            mock.Mock(status_code=200), {})
        self.endpoint_creator.create_endpoint.return_value = self.endpoint

        self.resolver = mock.Mock()
        self.endpoint_data = {
            'partition': 'aws',
            'hostname': 'foo',
            'endpointName': 'us-west-2',
            'signatureVersions': ['v4'],
        }
        self.resolver.construct_endpoint.return_value = self.endpoint_data
        self.resolver.get_available_endpoints.return_value = ['us-west-2']
        self.config_store = ConfigValueStore()

    def tearDown(self):
        self.endpoint_creator_patch.stop()

    def create_mock_emitter(self, responses=None):
        if responses is None:
            responses = []

        emitter = mock.Mock()
        emitter.emit.return_value = responses
        return emitter

    def create_client_creator(self, endpoint_creator=None, event_emitter=None,
                              retry_handler_factory=None,
                              retry_config_translator=None,
                              response_parser_factory=None,
                              endpoint_prefix=None,
                              exceptions_factory=None,
                              config_store=None):
        if event_emitter is None:
            event_emitter = hooks.HierarchicalEmitter()
        if retry_handler_factory is None:
            retry_handler_factory = botocore.retryhandler
        if retry_config_translator is None:
            retry_config_translator = botocore.translate
        if endpoint_prefix is not None:
            self.service_description['metadata']['endpointPrefix']\
                = endpoint_prefix

        if endpoint_creator is not None:
            self.endpoint_creator_cls.return_value = endpoint_creator
        if exceptions_factory is None:
            exceptions_factory = ClientExceptionsFactory()
        if config_store is None:
            config_store = self.config_store
        creator = client.ClientCreator(
            self.loader, self.resolver, 'user-agent', event_emitter,
            retry_handler_factory, retry_config_translator,
            response_parser_factory, exceptions_factory,
            config_store
        )
        return creator

    def assert_no_param_error_raised(self, client):
        try:
            self.make_api_call_with_missing_param(client)
        except ParamValidationError:
            self.fail("ParamValidationError shouldn't be raised "
                      "with validation disabled")

    def make_api_call_with_missing_param(self, service_client):
        # Missing required 'Foo' param.
        service_client.test_operation(Bar='two')

    def test_client_name(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertTrue(service_client.__class__.__name__, 'MyService')

    def test_client_name_with_amazon(self):
        self.service_description['metadata']['serviceFullName'] = (
            'Amazon MyService')
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertTrue(service_client.__class__.__name__, 'MyService')

    def test_client_name_using_abreviation(self):
        self.service_description['metadata']['serviceAbbreviation'] = (
            'Abbreviation')
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertTrue(service_client.__class__.__name__, 'Abbreviation')

    def test_client_name_with_non_alphabet_characters(self):
        self.service_description['metadata']['serviceFullName'] = (
            'Amazon My-Service')
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertTrue(service_client.__class__.__name__, 'MyService')

    def test_client_name_with_no_full_name_or_abbreviation(self):
        del self.service_description['metadata']['serviceFullName']
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertTrue(service_client.__class__.__name__, 'myservice')

    def test_client_generated_from_model(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertTrue(hasattr(service_client, 'test_operation'))

    def test_client_method_docstring(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        method_docstring = str(service_client.test_operation.__doc__)
        ref_docstring_lines = [
            'Documents TestOperation',
            '**Request Syntax**',
            '  response = client.test_operation(',
            '      Bar=\'string\'',
            '      Foo=\'string\'',
            '  )',
            ':type Bar: string',
            ':param Bar: Documents Bar',
            ':type Foo: string',
            ':param Foo: **[REQUIRED]** Documents Foo'
        ]
        for line in ref_docstring_lines:
            self.assertIn(line, method_docstring)

    def test_client_method_help(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        with mock.patch('sys.stdout', six.StringIO()) as mock_stdout:
            help(service_client.test_operation)
        method_docstring = mock_stdout.getvalue()
        ref_docstring_lines = [
            'Documents TestOperation',
            '**Request Syntax**',
            '  response = client.test_operation(',
            '      Bar=\'string\'',
            '      Foo=\'string\'',
            '  )',
            ':type Bar: string',
            ':param Bar: Documents Bar',
            ':type Foo: string',
            ':param Foo: **[REQUIRED]** Documents Foo'
        ]
        for line in ref_docstring_lines:
            self.assertIn(line, method_docstring)

    def test_client_create_unicode(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            u'myservice', 'us-west-2', credentials=self.credentials)
        self.assertTrue(hasattr(service_client, 'test_operation'))

    def test_client_has_region_name_on_meta(self):
        creator = self.create_client_creator()
        region_name = 'us-west-2'
        self.endpoint.region_name = region_name
        service_client = creator.create_client(
            'myservice', region_name, credentials=self.credentials)
        self.assertEqual(service_client.meta.region_name, region_name)

    def test_client_has_endpoint_url_on_meta(self):
        creator = self.create_client_creator()
        self.endpoint.host = 'https://foo.bar'
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertEqual(service_client.meta.endpoint_url,
                         'https://foo.bar')

    def test_client_has_standard_partition_on_meta(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertEqual(service_client.meta.partition,
                         'aws')

    def test_client_has_non_standard_partition_on_meta(self):
        creator = self.create_client_creator()
        self.resolver.construct_endpoint.return_value = {
            'partition': 'aws-cn',
            'hostname': 'foo',
            'endpointName': 'cn-north-1',
            'signatureVersions': ['v4'],
        }
        service_client = creator.create_client(
            'myservice', 'cn-north-1', credentials=self.credentials)
        self.assertEqual(service_client.meta.partition,
                         'aws-cn')

    def test_client_has_exceptions_attribute(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertTrue(hasattr(service_client, 'exceptions'))

    def test_client_has_modeled_exceptions(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertTrue(
            issubclass(service_client.exceptions.TestOperationException,
                       client.ClientError)
        )

    def test_api_version_is_passed_to_loader_if_provided(self):
        creator = self.create_client_creator()
        self.endpoint.host = 'https://foo.bar'
        specific_api_version = '2014-03-01'
        creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials,
            api_version=specific_api_version)
        self.loader.load_service_model.assert_called_with(
            'myservice', 'service-2', api_version=specific_api_version)

    def test_create_client_class_creates_class(self):
        creator = self.create_client_creator()
        client_class = creator.create_client_class('myservice')
        self.assertTrue(hasattr(client_class, 'test_operation'))

    def test_create_client_class_forwards_api_version(self):
        creator = self.create_client_creator()
        specific_api_version = '2014-03-01'
        creator.create_client_class('myservice',
                                    api_version=specific_api_version)
        self.loader.load_service_model.assert_called_with(
            'myservice', 'service-2', api_version=specific_api_version)

    def test_signing_region_does_not_change_client_region(self):
        with mock.patch('botocore.args.RequestSigner') as mock_signer:
            credential_scope_region = 'us-east-1'
            self.resolver.construct_endpoint.return_value = {
                'partition': 'aws',
                'hostname': 'endpoint.url',
                'endpointName': 'us-west-2',
                'signatureVersions': ['v4'],
                'credentialScope': {'region': credential_scope_region,}
            }
            creator = self.create_client_creator()
            service_client = creator.create_client(
                'myservice', 'us-west-2', credentials=self.credentials)
            self.assertEqual(service_client.meta.region_name, 'us-west-2')
            call_args = mock_signer.call_args
            self.assertEqual(credential_scope_region, call_args[0][1])

    def test_client_uses_signing_region_from_credential_scope(self):
        with mock.patch('botocore.args.RequestSigner') as mock_signer:
            credential_scope_region = 'us-east-1'
            self.resolver.construct_endpoint.return_value = {
                'partition': 'aws',
                'endpointName': 'us-west-2',
                'hostname': 'endpoint.url',
                'signatureVersions': ['v4'],
                'credentialScope': {'region': credential_scope_region}
            }
            creator = self.create_client_creator()
            service_client = creator.create_client(
                service_name='myservice', region_name='us-west-2',
                credentials=self.credentials)
            # Use the resolved region as the region value.
            self.assertEqual(service_client.meta.region_name, 'us-west-2')
            # Ensure that we use the credential scope region for signing,
            # and not the resolved region name.
            call_args = mock_signer.call_args
            self.assertEqual(credential_scope_region, call_args[0][1])

    def test_client_uses_signing_name_from_credential_scope(self):
        with mock.patch('botocore.args.RequestSigner') as mock_signer:
            self.resolver.construct_endpoint.return_value = {
                'partition': 'aws',
                'endpointName': 'us-west-2',
                'hostname': 'endpoint.url',
                'signatureVersions': ['v4'],
                'credentialScope': {'service': 'override'}
            }
            creator = self.create_client_creator()
            service_client = creator.create_client(
                service_name='myservice', region_name='us-west-2',
                credentials=self.credentials)
            call_args = mock_signer.call_args
            self.assertEqual('MyService', call_args[0][0])
            self.assertEqual('override', call_args[0][2])

    def test_client_uses_given_region_name_and_endpoint_url_when_present(self):
        with mock.patch('botocore.args.RequestSigner') as mock_signer:
            credential_scope_region = 'us-east-1'
            self.resolver.construct_endpoint.return_value = {
                'partition': 'aws',
                'endpointName': 'us-west-2',
                'hostname': 'endpoint.url',
                'signatureVersions': ['v4'],
                'credentialScope': {'region': credential_scope_region}
            }
            creator = self.create_client_creator()
            service_client = creator.create_client(
                service_name='myservice', region_name='us-west-2',
                credentials=self.credentials, endpoint_url='https://foo')
            self.assertEqual(service_client.meta.region_name, 'us-west-2')
            call_args = mock_signer.call_args
            self.assertEqual('us-west-2', call_args[0][1])

    def test_client_uses_signing_name_from_model_if_present_if_resolved(self):
        self.service_description['metadata']['signingName'] = 'otherName'
        with mock.patch('botocore.args.RequestSigner') as mock_signer:
            self.resolver.construct_endpoint.return_value = {
                'partition': 'aws',
                'endpointName': 'us-west-2',
                'hostname': 'endpoint.url',
                'signatureVersions': ['v4'],
            }
            creator = self.create_client_creator()
            service_client = creator.create_client(
                service_name='myservice', region_name='us-west-2',
                credentials=self.credentials, endpoint_url='https://foo')
            self.assertEqual(service_client.meta.region_name, 'us-west-2')
            call_args = mock_signer.call_args[0]
            self.assertEqual('otherName', call_args[2])

    def test_client_uses_signing_name_even_with_no_resolve(self):
        self.service_description['metadata']['signingName'] = 'otherName'
        with mock.patch('botocore.args.RequestSigner') as mock_signer:
            self.resolver.construct_endpoint.return_value = {}
            creator = self.create_client_creator()
            service_client = creator.create_client(
                service_name='myservice', region_name='us-west-2',
                credentials=self.credentials, endpoint_url='https://foo')
            self.assertEqual(service_client.meta.region_name, 'us-west-2')
            call_args = mock_signer.call_args[0]
            self.assertEqual('otherName', call_args[2])

    @mock.patch('botocore.args.RequestSigner')
    def test_client_signature_no_override(self, request_signer):
        creator = self.create_client_creator()
        creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials,
            scoped_config={})
        request_signer.assert_called_with(
            mock.ANY, mock.ANY, mock.ANY, 'v4', mock.ANY, mock.ANY)

    @mock.patch('botocore.args.RequestSigner')
    def test_client_signature_override_config_file(self, request_signer):
        creator = self.create_client_creator()
        config = {
            'myservice': {'signature_version': 'foo'}
        }
        creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials,
            scoped_config=config)
        request_signer.assert_called_with(
            mock.ANY, mock.ANY, mock.ANY, 'foo', mock.ANY, mock.ANY)

    @mock.patch('botocore.args.RequestSigner')
    def test_client_signature_override_arg(self, request_signer):
        creator = self.create_client_creator()
        config = botocore.config.Config(signature_version='foo')
        creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials,
            client_config=config)
        request_signer.assert_called_with(
            mock.ANY, mock.ANY, mock.ANY, 'foo', mock.ANY, mock.ANY)

    def test_client_method_to_api_mapping(self):
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        self.assertEqual(service_client.meta.method_to_api_mapping,
                         {'test_operation': 'TestOperation'})

    def test_anonymous_client_request(self):
        creator = self.create_client_creator()
        config = botocore.config.Config(signature_version=botocore.UNSIGNED)
        service_client = creator.create_client(
            'myservice', 'us-west-2', client_config=config)

        service_client.test_operation(Foo='one')

        # Make sure a request has been attempted
        self.assertTrue(self.endpoint.make_request.called)

        # Make sure the request parameters do NOT include auth
        # information. The service defined above for these tests
        # uses sigv4 by default (which we disable).
        params = dict((k.lower(), v) for k, v in
                      self.endpoint.make_request.call_args[0][1].items())
        self.assertNotIn('authorization', params)
        self.assertNotIn('x-amz-signature', params)

    def test_client_user_agent_in_request(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2')

        service_client.test_operation(Foo='one')

        self.assertTrue(self.endpoint.make_request.called)
        params = dict((k.lower(), v) for k, v in
                      self.endpoint.make_request.call_args[0][1].items())
        self.assertEqual(params['headers']['User-Agent'], 'user-agent')

    def test_client_custom_user_agent_in_request(self):
        creator = self.create_client_creator()
        config = botocore.config.Config(user_agent='baz')
        service_client = creator.create_client(
            'myservice', 'us-west-2', client_config=config)

        service_client.test_operation(Foo='one')

        self.assertTrue(self.endpoint.make_request.called)
        params = dict((k.lower(), v) for k, v in
                      self.endpoint.make_request.call_args[0][1].items())
        self.assertEqual(params['headers']['User-Agent'], 'baz')

    def test_client_custom_user_agent_extra_in_request(self):
        creator = self.create_client_creator()
        config = botocore.config.Config(user_agent_extra='extrastuff')
        service_client = creator.create_client(
            'myservice', 'us-west-2', client_config=config)
        service_client.test_operation(Foo='one')
        headers = self.endpoint.make_request.call_args[0][1]['headers']
        self.assertEqual(headers['User-Agent'], 'user-agent extrastuff')

    def test_client_registers_request_created_handler(self):
        event_emitter = self.create_mock_emitter()
        creator = self.create_client_creator(event_emitter=event_emitter)
        creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        self.assertIn(
            mock.call('request-created.myservice', mock.ANY),
            event_emitter.register.call_args_list)

    def test_client_makes_call(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        self.assertTrue(self.endpoint_creator.create_endpoint.called)

        response = service_client.test_operation(Foo='one', Bar='two')
        self.assertEqual(response, {})

    def test_client_error_message_for_positional_args(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        with self.assertRaisesRegexp(
                TypeError, 'only accepts keyword arguments'):
            service_client.test_operation('foo')

    @mock.patch('botocore.args.RequestSigner.sign')
    def test_client_signs_call(self, signer_mock):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        request = mock.Mock()

        # Emit the request created event to see if it would be signed.
        # We tested above to ensure this event is registered when
        # a client is created. This prevents testing the entire client
        # call logic.
        service_client.meta.events.emit(
            'request-created.myservice.test_operation', request=request,
            operation_name='test_operation')

        signer_mock.assert_called_with(
            'test_operation', request)

    def test_client_validates_params_by_default(self):
        creator = self.create_client_creator()

        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        with self.assertRaises(ParamValidationError):
            self.make_api_call_with_missing_param(service_client)

    def test_client_doesnt_validate_params_when_validation_disabled(self):
        creator = self.create_client_creator()

        client_config = botocore.config.Config()
        client_config.parameter_validation = False
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials,
            client_config=client_config)

        self.assert_no_param_error_raised(service_client)

    def test_can_disable_param_validation_from_scoped_config(self):
        creator = self.create_client_creator()
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials,
            scoped_config={'parameter_validation': False})
        self.assert_no_param_error_raised(service_client)

    def test_client_config_trumps_scoped_config(self):
        creator = self.create_client_creator()
        scoped_config = {'parameter_validation': True}
        client_config = botocore.config.Config(parameter_validation=False)
        # Client config should win and param validation is disabled.
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials,
            scoped_config=scoped_config, client_config=client_config)
        self.assert_no_param_error_raised(service_client)

    def test_client_with_custom_both_timeout(self):
        self.create_client_creator().create_client(
            'myservice', 'us-west-2',
            client_config=botocore.config.Config(connect_timeout=123, read_timeout=234))
        call_kwargs = self.endpoint_creator.create_endpoint.call_args[1]
        self.assertEqual(call_kwargs['timeout'], (123, 234))

    def test_client_with_custom_connect_timeout(self):
        self.create_client_creator().create_client(
            'myservice', 'us-west-2',
            client_config=botocore.config.Config(connect_timeout=123))
        call_kwargs = self.endpoint_creator.create_endpoint.call_args[1]
        self.assertEqual(call_kwargs['timeout'], (123, DEFAULT_TIMEOUT))

    def test_client_with_custom_read_timeout(self):
        self.create_client_creator().create_client(
            'myservice', 'us-west-2',
            client_config=botocore.config.Config(read_timeout=234))
        call_kwargs = self.endpoint_creator.create_endpoint.call_args[1]
        self.assertEqual(call_kwargs['timeout'], (DEFAULT_TIMEOUT, 234))

    def test_client_with_custom_neither_timeout(self):
        self.create_client_creator().create_client('myservice', 'us-west-2')
        call_kwargs = self.endpoint_creator.create_endpoint.call_args[1]
        self.assertEqual(call_kwargs['timeout'],
                         (DEFAULT_TIMEOUT, DEFAULT_TIMEOUT))

    def test_client_with_custom_params(self):
        creator = self.create_client_creator()
        creator.create_client('myservice', 'us-west-2',
                              is_secure=False, verify=False)
        call_kwargs = self.endpoint_creator.create_endpoint.call_args[1]
        self.assertFalse(call_kwargs['verify'])
        self.assertNotIn('is_secure', call_kwargs)

    def test_client_with_custom_proxy_config(self):
        proxies_config = {
            'proxy_ca_bundle': 'foo_ca_bundle',
            'proxy_client_cert': 'foo_cert',
            'proxy_use_forwarding_for_https': False
        }
        self.create_client_creator().create_client(
            'myservice', 'us-west-2',
            client_config=botocore.config.Config(proxies_config=proxies_config)
        )
        call_kwargs = self.endpoint_creator.create_endpoint.call_args[1]
        self.assertEqual(call_kwargs['proxies_config'], proxies_config)

    def test_client_with_endpoint_url(self):
        creator = self.create_client_creator()
        creator.create_client('myservice', 'us-west-2',
                              endpoint_url='http://custom.foo')
        call_kwargs = self.endpoint_creator.create_endpoint.call_args[1]
        self.assertEqual(call_kwargs['endpoint_url'], 'http://custom.foo')

    def test_client_can_use_guessed_endpoints(self):
        # Ensure the resolver returns None (meaning a guess is made)
        self.resolver.construct_endpoint.return_value = None
        creator = self.create_client_creator()
        client = creator.create_client('myservice', region_name='invalid')
        self.assertEqual('invalid', client.meta.region_name)

    def test_client_with_response_parser_factory(self):
        factory = mock.Mock()
        creator = self.create_client_creator(response_parser_factory=factory)
        creator.create_client('myservice', 'us-west-2')
        call_kwargs = self.endpoint_creator.create_endpoint.call_args[1]
        self.assertEqual(call_kwargs['response_parser_factory'], factory)

    def test_operation_cannot_paginate(self):
        pagination_config = {
            'pagination': {
                # Note that there's no pagination config for
                # 'TestOperation', indicating that TestOperation
                # is not pageable.
                'SomeOtherOperation': {
                    "input_token": "Marker",
                    "output_token": "Marker",
                    "more_results": "IsTruncated",
                    "limit_key": "MaxItems",
                    "result_key": "Users"
                }
            }
        }
        self.loader.load_service_model.side_effect = [
            self.service_description, pagination_config]
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        self.assertFalse(service_client.can_paginate('test_operation'))

    def test_operation_can_paginate(self):
        pagination_config = {
            'pagination': {
                'TestOperation': {
                    "input_token": "Marker",
                    "output_token": "Marker",
                    "more_results": "IsTruncated",
                    "limit_key": "MaxItems",
                    "result_key": "Users"
                }
            }
        }
        self.loader.load_service_model.side_effect = [
            self.service_description, pagination_config]
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        self.assertTrue(service_client.can_paginate('test_operation'))
        # Also, the config is cached, but we want to make sure we get
        # the same answer when we ask again.
        self.assertTrue(service_client.can_paginate('test_operation'))

    def test_service_has_no_pagination_configs(self):
        # This is the case where there is an actual *.paginator.json, file,
        # but the specific operation itself is not actually pageable.
        # If the loader cannot load pagination configs, it communicates
        # this by raising a DataNotFoundError.
        self.loader.load_service_model.side_effect = [
            self.service_description,
            exceptions.DataNotFoundError(data_path='/foo')]
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        self.assertFalse(service_client.can_paginate('test_operation'))

    def test_waiter_config_uses_service_name_not_endpoint_prefix(self):
        waiter_config = {
            'version': 2,
            'waiters': {}
        }
        self.loader.load_service_model.side_effect = [
            self.service_description,
            waiter_config
        ]
        creator = self.create_client_creator()
        # We're going to verify that the loader loads a service called
        # 'other-service-name', and even though the endpointPrefix is
        # 'myservice', we use 'other-service-name' for waiters/paginators, etc.
        service_client = creator.create_client('other-service-name',
                                               'us-west-2')
        self.assertEqual(service_client.waiter_names, [])
        # Note we're using other-service-name, not
        # 'myservice', which is the endpointPrefix.
        self.loader.load_service_model.assert_called_with(
            'other-service-name', 'waiters-2', '2014-01-01')

    def test_service_has_waiter_configs(self):
        waiter_config = {
            'version': 2,
            'waiters': {
                "Waiter1": {
                    'operation': 'TestOperation',
                    'delay': 5,
                    'maxAttempts': 20,
                    'acceptors': [],
                },
                "Waiter2": {
                    'operation': 'TestOperation',
                    'delay': 5,
                    'maxAttempts': 20,
                    'acceptors': [],
                },
            }
        }
        self.loader.load_service_model.side_effect = [
            self.service_description,
            waiter_config
        ]
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        self.assertEqual(sorted(service_client.waiter_names),
                         sorted(['waiter1', 'waiter2']))
        self.assertTrue(hasattr(service_client.get_waiter('waiter1'), 'wait'))

    def test_service_has_no_waiter_configs(self):
        self.loader.load_service_model.side_effect = [
            self.service_description,
            exceptions.DataNotFoundError(data_path='/foo')]
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        self.assertEqual(service_client.waiter_names, [])
        with self.assertRaises(ValueError):
            service_client.get_waiter("unknown_waiter")

    def test_service_has_retry_event(self):
        # A retry event should be registered for the service.
        event_emitter = self.create_mock_emitter()
        creator = self.create_client_creator(event_emitter=event_emitter)
        creator.create_client('myservice', 'us-west-2')

        event_emitter.register.assert_any_call(
            'needs-retry.myservice', mock.ANY,
            unique_id='retry-config-myservice')

    def test_service_creates_retryhandler(self):
        # A retry handler with the expected configuration should be
        # created when instantiating a client.
        retry_handler_factory = mock.Mock()
        creator = self.create_client_creator(
            retry_handler_factory=retry_handler_factory)
        creator.create_client('myservice', 'us-west-2')

        retry_handler_factory.create_retry_handler.assert_called_with({
            '__default__': {
                'delay': {
                    'growth_factor': 2,
                    'base': 'rand',
                    'type': 'exponential'
                },
                'policies': {},
                'max_attempts': 5
            }
        }, 'myservice')

    def test_service_registers_retry_handler(self):
        # The retry handler returned from ``create_retry_handler``
        # that was tested above needs to be set as the handler for
        # the event emitter.
        retry_handler_factory = mock.Mock()
        handler = mock.Mock()
        event_emitter = self.create_mock_emitter()
        retry_handler_factory.create_retry_handler.return_value = handler

        creator = self.create_client_creator(
            event_emitter=event_emitter,
            retry_handler_factory=retry_handler_factory)
        creator.create_client('myservice', 'us-west-2')

        event_emitter.register.assert_any_call(
            mock.ANY, handler, unique_id=mock.ANY)

    def test_service_retry_missing_config(self):
        # No config means we should never see any retry events registered.
        self.loader.load_data.return_value = {}

        event_emitter = self.create_mock_emitter()
        creator = self.create_client_creator(event_emitter=event_emitter)
        creator.create_client('myservice', 'us-west-2')

        for call in event_emitter.register.call_args_list:
            self.assertNotIn('needs-retry', call[0][0])

    def test_emits_after_call_error(self):
        event_emitter = hooks.HierarchicalEmitter()

        recorded_kwargs = []

        def record(event_name, **kwargs):
            recorded_kwargs.append(kwargs)

        event_emitter.register(
            'after-call-error.myservice.TestOperation', record)

        raised_error = RuntimeError('Unexpected error')
        self.endpoint.make_request.side_effect = raised_error
        creator = self.create_client_creator(event_emitter=event_emitter)
        client = creator.create_client('myservice', 'us-west-2')
        with self.assertRaises(RuntimeError):
            client.test_operation(Foo='one', Bar='two')
        self.assertEqual(
            recorded_kwargs,
            [{'exception': raised_error, 'context': mock.ANY}])

    def test_can_override_max_attempts(self):
        retry_handler_factory = mock.Mock(botocore.retryhandler)
        creator = self.create_client_creator(
            retry_handler_factory=retry_handler_factory)
        creator.create_client(
            'myservice', 'us-west-2',
            client_config=botocore.config.Config(retries={'max_attempts': 9,
                                                          'mode': 'legacy'}))

        retry_handler_factory.create_retry_handler.assert_called_with({
            '__default__': {
                'delay': {
                    'growth_factor': 2,
                    'base': 'rand',
                    'type': 'exponential'
                },
                'policies': {},
                'max_attempts': 10
            }
        }, 'myservice')

    def test_can_register_standard_retry_mode(self):
        with mock.patch('botocore.client.standard') as standard:
            creator = self.create_client_creator()
            creator.create_client(
                'myservice', 'us-west-2',
                client_config=botocore.config.Config(
                    retries={'mode': 'standard'}))
        self.assertTrue(standard.register_retry_handler.called)

    def test_can_register_standard_retry_mode_from_config_store(self):
        fake_env = {'AWS_RETRY_MODE': 'standard'}
        config_store = ConfigValueStore(
            mapping={
                'retry_mode': ChainProvider([
                    EnvironmentProvider('AWS_RETRY_MODE', fake_env),
                ])
            }
        )
        creator = self.create_client_creator(config_store=config_store)
        with mock.patch('botocore.client.standard') as standard:
            creator.create_client( 'myservice', 'us-west-2')
        self.assertTrue(standard.register_retry_handler.called)

    def test_try_to_paginate_non_paginated(self):
        self.loader.load_service_model.side_effect = [
            self.service_description,
            exceptions.DataNotFoundError(data_path='/foo')
        ]
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        with self.assertRaises(exceptions.OperationNotPageableError):
            service_client.get_paginator('test_operation')

    def test_successful_pagination_object_created(self):
        pagination_config = {
            'pagination': {
                'TestOperation': {
                    "input_token": "Marker",
                    "output_token": "Marker",
                    "more_results": "IsTruncated",
                    "limit_key": "MaxItems",
                    "result_key": "Users"
                }
            }
        }
        self.loader.load_service_model.side_effect = [
            self.service_description,
            pagination_config
        ]
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        paginator = service_client.get_paginator('test_operation')
        # The pagination logic itself is tested elsewhere (test_paginate.py),
        # but we can at least make sure it looks like a paginator.
        self.assertTrue(hasattr(paginator, 'paginate'))

    def test_paginator_class_name_from_client(self):
        pagination_config = {
            'pagination': {
                'TestOperation': {
                    "input_token": "Marker",
                    "output_token": "Marker",
                    "more_results": "IsTruncated",
                    "limit_key": "MaxItems",
                    "result_key": "Users"
                }
            }
        }
        self.loader.load_service_model.side_effect = [
            self.service_description,
            pagination_config
        ]
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        paginator = service_client.get_paginator('test_operation')
        self.assertEqual(
            paginator.__class__.__name__,
            'MyService.Paginator.TestOperation')

    def test_paginator_help_from_client(self):
        pagination_config = {
            'pagination': {
                'TestOperation': {
                    "input_token": "Marker",
                    "output_token": "Marker",
                    "more_results": "IsTruncated",
                    "limit_key": "MaxItems",
                    "result_key": "Users"
                }
            }
        }
        self.loader.load_service_model.side_effect = [
            self.service_description,
            pagination_config
        ]
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        paginator = service_client.get_paginator('test_operation')
        with mock.patch('sys.stdout', six.StringIO()) as mock_stdout:
            help(paginator.paginate)
        contents = mock_stdout.getvalue()
        lines = [
            ('    Creates an iterator that will paginate through responses '
             'from :py:meth:`MyService.Client.test_operation`.'),
            '    **Request Syntax** ',
            '    ::',
            '      response_iterator = paginator.paginate(',
            "          Foo='string',",
            "          Bar='string',",
            '          PaginationConfig={',
            "              'MaxItems': 123,",
            "              'PageSize': 123,",
            "              'StartingToken': 'string'",
            '          }',
            '      )',
            '    :type Foo: string',
            '    :param Foo: **[REQUIRED]** Documents Foo',
            '    :type Bar: string',
            '    :param Bar: Documents Bar',
            '    :type PaginationConfig: dict',
            '    :param PaginationConfig: ',
            ('      A dictionary that provides parameters to control '
             'pagination.'),
            '      - **MaxItems** *(integer) --* ',
            ('        The total number of items to return. If the total '
             'number of items available is more than the value specified '
             'in max-items then a ``NextToken`` will be provided in the '
             'output that you can use to resume pagination.'),
            '      - **PageSize** *(integer) --* ',
            '        The size of each page.',
            '      - **StartingToken** *(string) --* ',
            ('        A token to specify where to start paginating. This is '
             'the ``NextToken`` from a previous response.'),
            '    :returns: None',
        ]
        for line in lines:
            self.assertIn(line, contents)

    def test_can_set_credentials_in_client_init(self):
        creator = self.create_client_creator()
        credentials = Credentials(
            access_key='access_key', secret_key='secret_key',
            token='session_token')
        client = creator.create_client(
            'myservice', 'us-west-2', credentials=credentials)

        # Verify that we create an endpoint with a credentials object
        # matching our creds arguments.
        self.assertEqual(client._request_signer._credentials, credentials)

    def test_event_emitted_when_invoked(self):
        event_emitter = hooks.HierarchicalEmitter()
        creator = self.create_client_creator(event_emitter=event_emitter)

        calls = []
        handler = lambda **kwargs: calls.append(kwargs)
        event_emitter.register('before-call', handler)

        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        service_client.test_operation(Foo='one', Bar='two')
        self.assertEqual(len(calls), 1)

    def test_events_are_per_client(self):
        event_emitter = hooks.HierarchicalEmitter()
        creator = self.create_client_creator(event_emitter=event_emitter)

        first_calls = []
        first_handler = lambda **kwargs: first_calls.append(kwargs)

        second_calls = []
        second_handler = lambda **kwargs: second_calls.append(kwargs)

        first_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        second_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        first_client.meta.events.register('before-call', first_handler)
        second_client.meta.events.register('before-call', second_handler)

        # Now, if we invoke an operation from either client, only
        # the handlers registered with the specific client will be invoked.
        # So if we invoke the first client.
        first_client.test_operation(Foo='one', Bar='two')
        # Only first_calls is populated, not second_calls.
        self.assertEqual(len(first_calls), 1)
        self.assertEqual(len(second_calls), 0)

        # If we invoke an operation from the second client,
        # only second_calls will be populated, not first_calls.
        second_client.test_operation(Foo='one', Bar='two')
        # first_calls == 1 from the previous first_client.test_operation()
        # call.
        self.assertEqual(len(first_calls), 1)
        self.assertEqual(len(second_calls), 1)

    def test_clients_inherit_handlers_from_session(self):
        # Even though clients get their own event emitters, they still
        # inherit any handlers that were registered on the event emitter
        # at the time the client was created.
        event_emitter = hooks.HierarchicalEmitter()
        creator = self.create_client_creator(event_emitter=event_emitter)

        # So if an event handler is registered before any clients are created:

        base_calls = []
        base_handler = lambda **kwargs: base_calls.append(kwargs)
        event_emitter.register('before-call', base_handler)

        # Then any client created from this point forward from the
        # event_emitter passed into the ClientCreator will have this
        # handler.
        first_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        first_client.test_operation(Foo='one', Bar='two')
        self.assertEqual(len(base_calls), 1)

        # Same thing if we create another client.
        second_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        second_client.test_operation(Foo='one', Bar='two')
        self.assertEqual(len(base_calls), 2)

    def test_clients_inherit_only_at_create_time(self):
        # If event handlers are added to the copied event emitter
        # _after_ a client is created, we don't pick those up.
        event_emitter = hooks.HierarchicalEmitter()
        creator = self.create_client_creator(event_emitter=event_emitter)

        # 1. Create a client.
        first_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        # 2. Now register an event handler from the originating event emitter.
        base_calls = []
        base_handler = lambda **kwargs: base_calls.append(kwargs)
        event_emitter.register('before-call', base_handler)

        # 3. The client will _not_ see this because it already has its
        #    own copy of the event handlers.
        first_client.test_operation(Foo='one', Bar='two')
        self.assertEqual(len(base_calls), 0)

    def test_clients_have_meta_object(self):
        creator = self.create_client_creator()
        service_client = creator.create_client('myservice', 'us-west-2')
        self.assertTrue(hasattr(service_client, 'meta'))
        self.assertTrue(hasattr(service_client.meta, 'events'))
        # Sanity check the event emitter has an .emit() method.
        self.assertTrue(hasattr(service_client.meta.events, 'emit'))

    def test_client_register_seperate_unique_id_event(self):
        event_emitter = hooks.HierarchicalEmitter()
        creator = self.create_client_creator(event_emitter=event_emitter)

        client1 = creator.create_client('myservice', 'us-west-2')
        client2 = creator.create_client('myservice', 'us-west-2')

        def ping(**kwargs):
            return 'foo'

        client1.meta.events.register('some-event', ping, 'my-unique-id')
        client2.meta.events.register('some-event', ping, 'my-unique-id')

        # Ensure both clients can register a function with an unique id
        client1_responses = client1.meta.events.emit('some-event')
        self.assertEqual(len(client1_responses), 1)
        self.assertEqual(client1_responses[0][1], 'foo')

        client2_responses = client2.meta.events.emit('some-event')
        self.assertEqual(len(client2_responses), 1)
        self.assertEqual(client2_responses[0][1], 'foo')

        # Ensure when a client is unregistered the other client has
        # the unique-id event still registered.
        client1.meta.events.unregister('some-event', ping, 'my-unique-id')
        client1_responses = client1.meta.events.emit('some-event')
        self.assertEqual(len(client1_responses), 0)

        client2_responses = client2.meta.events.emit('some-event')
        self.assertEqual(len(client2_responses), 1)
        self.assertEqual(client2_responses[0][1], 'foo')

        # Ensure that the other client can unregister the event
        client2.meta.events.unregister('some-event', ping, 'my-unique-id')
        client2_responses = client2.meta.events.emit('some-event')
        self.assertEqual(len(client2_responses), 0)

    def test_client_created_emits_events(self):
        called = []

        def on_client_create(class_attributes, **kwargs):
            called.append(class_attributes)

        event_emitter = hooks.HierarchicalEmitter()
        event_emitter.register('creating-client-class', on_client_create)

        creator = self.create_client_creator(event_emitter=event_emitter)
        creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        self.assertEqual(len(called), 1)
        self.assertIn('test_operation', called[0])

    def test_client_method_called_event(self):
        event_emitter = hooks.HierarchicalEmitter()

        def inject_params(params, **kwargs):
            new_params = params.copy()
            new_params['Foo'] = 'zero'
            return new_params

        event_emitter.register(
            'provide-client-params.myservice.TestOperation', inject_params)

        wrapped_emitter = mock.Mock(wraps=event_emitter)
        creator = self.create_client_creator(event_emitter=wrapped_emitter)
        service_client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        params = {'Foo': 'one', 'Bar': 'two'}
        service_client.test_operation(**params)

        # Ensure that the initial params were not modified in the handler
        self.assertEqual(params, {'Foo': 'one', 'Bar': 'two'})

        # Ensure the handler passed on the correct param values.
        body = self.endpoint.make_request.call_args[0][1]['body']
        self.assertEqual(body['Foo'], 'zero')

    def test_client_default_for_s3_addressing_style(self):
        creator = self.create_client_creator()
        client = creator.create_client('myservice', 'us-west-2')
        self.assertEqual(client.meta.config.s3, None)

    def test_client_s3_addressing_style_with_config(self):
        creator = self.create_client_creator()
        my_client = creator.create_client(
            'myservice', 'us-west-2',
            client_config=botocore.config.Config(s3={'addressing_style': 'auto'})
        )
        self.assertEqual(
            my_client.meta.config.s3['addressing_style'], 'auto')

    def test_client_s3_addressing_style_with_bad_value(self):
        creator = self.create_client_creator()
        client = creator.create_client(
            'myservice', 'us-west-2',
            scoped_config={'s3': ''},
        )
        self.assertIsNone(client.meta.config.s3)

    def test_client_s3_addressing_style_with_config_store(self):
        self.config_store.set_config_variable(
            's3', {'addressing_style': 'virtual'}
        )
        creator = self.create_client_creator()
        client = creator.create_client('myservice', 'us-west-2')
        self.assertEqual(
            client.meta.config.s3['addressing_style'], 'virtual')

    def test_client_s3_addressing_style_with_incorrect_style(self):
        with self.assertRaises(InvalidS3AddressingStyleError):
            botocore.config.Config(s3={'addressing_style': 'foo'})

    def test_client_s3_addressing_style_config_overrides_config_store(self):
        self.config_store.set_config_variable(
            's3', {'addressing_style': 'virtual'})
        creator = self.create_client_creator()
        my_client = creator.create_client(
            'myservice', 'us-west-2',
            client_config=botocore.config.Config(
                s3={'addressing_style': 'auto'})
        )
        self.assertEqual(
            my_client.meta.config.s3['addressing_style'], 'auto')

    def test_client_payload_signing_from_config_store(self):
        self.config_store.set_config_variable(
            's3', {'payload_signing_enabled': True}
        )
        creator = self.create_client_creator()
        my_client = creator.create_client('myservice', 'us-west-2')
        self.assertEqual(
            my_client.meta.config.s3['payload_signing_enabled'], True)

    def test_client_payload_signing_from_client_config(self):
        creator = self.create_client_creator()
        my_client = creator.create_client(
            'myservice', 'us-west-2',
            client_config=client.Config(s3={'payload_signing_enabled': True})

        )
        self.assertEqual(
            my_client.meta.config.s3['payload_signing_enabled'], True)

    def test_client_payload_signing_client_config_overrides_scoped(self):
        creator = self.create_client_creator()
        my_client = creator.create_client(
            'myservice', 'us-west-2',
            scoped_config={'s3': {'payload_signing_enabled': False}},
            client_config=client.Config(s3={'payload_signing_enabled': True})

        )
        self.assertEqual(
            my_client.meta.config.s3['payload_signing_enabled'], True)

    def test_client_s3_accelerate_from_config_store(self):
        self.config_store.set_config_variable(
            's3', {'use_accelerate_endpoint': True}
        )
        creator = self.create_client_creator()
        my_client = creator.create_client('myservice', 'us-west-2')
        self.assertEqual(
            my_client.meta.config.s3['use_accelerate_endpoint'], True)

    def test_client_s3_accelerate_from_client_config(self):
        creator = self.create_client_creator()
        my_client = creator.create_client(
            'myservice', 'us-west-2',
            client_config=client.Config(s3={'use_accelerate_endpoint': True})

        )
        self.assertEqual(
            my_client.meta.config.s3['use_accelerate_endpoint'], True)

    def test_client_s3_accelerate_client_config_overrides_config_store(self):
        self.config_store.set_config_variable(
            's3', {'use_accelerate_endpoint': False}
        )
        creator = self.create_client_creator()
        my_client = creator.create_client(
            'myservice', 'us-west-2',
            client_config=client.Config(s3={'use_accelerate_endpoint': True})

        )
        self.assertEqual(
            my_client.meta.config.s3['use_accelerate_endpoint'], True)

    def test_before_call_short_circuits_request(self):
        def return_mock_tuple(**kwargs):
            http_mock = mock.Mock()
            http_mock.status_code = 200
            return http_mock, mock.Mock()
        emitter = hooks.HierarchicalEmitter()
        emitter.register_last('before-call.*.*', return_mock_tuple)
        creator = self.create_client_creator(event_emitter=emitter)
        service_client = creator.create_client(
            'myservice', 'us-west-2')

        service_client.test_operation(Foo='one')
        self.assertFalse(self.endpoint.make_request.called)

    def test_getattr_emits_event(self):
        emitter = self.create_mock_emitter()
        emitter.emit_until_response.return_value = (None, None)

        creator = self.create_client_creator(event_emitter=emitter)
        service_client = creator.create_client('myservice', 'us-west-2')

        # Assert that the event hasn't fired yet
        emitter.emit_until_response.assert_not_called()

        with self.assertRaises(AttributeError):
            service_client.attribute_that_does_not_exist

        emitter.emit_until_response.assert_called_once_with(
            'getattr.myservice.attribute_that_does_not_exist',
            client=service_client
        )

    def test_getattr_event_returns_response(self):
        emitter = self.create_mock_emitter()
        emitter.emit_until_response.return_value = (None, 'success')

        creator = self.create_client_creator(event_emitter=emitter)
        service_client = creator.create_client('myservice', 'us-west-2')

        value = service_client.attribute_that_does_not_exist

        self.assertEqual(value, 'success')

    def _create_hostname_binding_client(self, *args, **kwargs):
        test_operation = self.service_description['operations']['TestOperation']
        test_operation['endpoint'] = {'hostPrefix': '{Foo}.'}
        test_shape = self.service_description['shapes']['TestOperationRequest']
        test_shape['members']['Foo']['hostLabel'] = True

        creator = self.create_client_creator()
        return creator.create_client('myservice', *args, **kwargs)

    def test_client_operation_hostname_binding(self):
        client = self._create_hostname_binding_client('us-west-2')
        client.test_operation(Foo='bound')

        expected_url = 'https://bound.myservice.amazonaws.com/'
        self.assertTrue(self.endpoint.make_request.called)
        request_dict = self.endpoint.make_request.call_args[0][1]
        self.assertEqual(request_dict['url'], expected_url)

    def test_client_operation_hostname_binding_validation(self):
        client = self._create_hostname_binding_client('us-west-2')
        with self.assertRaises(ParamValidationError):
            client.test_operation(Foo='')

    def test_client_operation_hostname_binding_configuration(self):
        config = botocore.config.Config(inject_host_prefix=False)
        client = self._create_hostname_binding_client(
            'us-west-2', client_config=config,
        )

        client.test_operation(Foo='baz')
        expected_url = 'https://myservice.amazonaws.com/'
        self.assertTrue(self.endpoint.make_request.called)
        request_dict = self.endpoint.make_request.call_args[0][1]
        self.assertEqual(request_dict['url'], expected_url)


class TestClientErrors(TestAutoGeneratedClient):
    def add_error_response(self, error_response):
        self.endpoint.make_request.return_value = (
            mock.Mock(status_code=400), error_response)

    def test_client_makes_call_with_error(self):
        creator = self.create_client_creator()
        client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        with Stubber(client) as stub:
            stub.add_client_error(
                'test_operation', 'TestOperationErrorCode', 'error occurred')
            with self.assertRaises(client.exceptions.TestOperationException):
                client.test_operation(Foo='one', Bar='two')

    def test_error_with_no_wire_code(self):
        creator = self.create_client_creator()
        client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        with Stubber(client) as stub:
            stub.add_client_error('test_operation', '404', 'Not Found')
            try:
                client.test_operation(Foo='one', Bar='two')
            except client.exceptions.ClientError as e:
                # This is needed becasue the error could be a subclass of
                # ClientError.
                # We explicitly want it to be a generic ClientError though
                self.assertEqual(e.__class__, exceptions.ClientError)

    def test_error_with_dot_separated_code(self):
        creator = self.create_client_creator()
        client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        with Stubber(client) as stub:
            stub.add_client_error(
                'test_operation', 'InvalidAddress.NotFound', 'Not Found')
            try:
                client.test_operation(Foo='one', Bar='two')
            except client.exceptions.ClientError as e:
                # This is needed becasue the error could be a subclass of
                # ClientError.
                # We explicitly want it to be a generic ClientError though
                self.assertEqual(e.__class__, exceptions.ClientError)

    def test_error_with_empty_message(self):
        creator = self.create_client_creator()
        client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        with Stubber(client) as stub:
            stub.add_client_error(
                'test_operation', 'TestOperationErrorCode')
            with self.assertRaises(client.exceptions.TestOperationException):
                client.test_operation(Foo='one', Bar='two')

    def test_error_with_empty_code(self):
        creator = self.create_client_creator()
        client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        with Stubber(client) as stub:
            stub.add_client_error('test_operation')
            try:
                client.test_operation(Foo='one', Bar='two')
            except client.exceptions.ClientError as e:
                # This is needed becasue the error could be a subclass of
                # ClientError.
                # We explicitly want it to be a generic ClientError though
                self.assertEqual(e.__class__, exceptions.ClientError)

    def test_error_with_missing_code(self):
        error_response = {'Error': {'Message': 'error occurred'}}
        # The stubber is not being used because it will always populate the
        # the message and code.
        self.endpoint.make_request.return_value = (
            mock.Mock(status_code=400), error_response)

        creator = self.create_client_creator()
        client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        try:
            client.test_operation(Foo='one', Bar='two')
        except client.exceptions.ClientError as e:
            # This is needed becasue the error could be a subclass of
            # ClientError.
            # We explicitly want it to be a generic ClientError though
            self.assertEqual(e.__class__, exceptions.ClientError)

    def test_error_with_empty_contents(self):
        error_response = {'Error': {}}
        # The stubber is not being used because it will always populate the
        # the message and code.
        self.endpoint.make_request.return_value = (
            mock.Mock(status_code=400), error_response)

        creator = self.create_client_creator()
        client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        try:
            client.test_operation(Foo='one', Bar='two')
        except client.exceptions.ClientError as e:
            # This is needed becasue the error could be a subclass of
            # ClientError.
            # We explicitly want it to be a generic ClientError though
            self.assertEqual(e.__class__, exceptions.ClientError)

    def test_exception_classes_across_clients_are_the_same(self):
        creator = self.create_client_creator(
            exceptions_factory=ClientExceptionsFactory())
        client = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)
        client2 = creator.create_client(
            'myservice', 'us-west-2', credentials=self.credentials)

        with Stubber(client) as stub:
            stub.add_client_error(
                'test_operation', 'TestOperationErrorCode', 'error occurred')
            try:
                client.test_operation(Foo='one', Bar='two')
            except client2.exceptions.TestOperationException as e:
                # Caught exception should as well be an instance of the
                # other client's TestOperationException
                self.assertIsInstance(
                    e, client.exceptions.TestOperationException)


class TestConfig(unittest.TestCase):
    def test_can_use_args_to_construct(self):
        config = botocore.config.Config(*botocore.config.Config.OPTION_DEFAULTS.values())
        for option, default_value in botocore.config.Config.OPTION_DEFAULTS.items():
            self.assertTrue(hasattr(config, option))
            self.assertEqual(getattr(config, option), default_value)

    def test_can_use_kwargs_to_construct(self):
        config = botocore.config.Config(**botocore.config.Config.OPTION_DEFAULTS)
        for option, default_value in botocore.config.Config.OPTION_DEFAULTS.items():
            self.assertTrue(hasattr(config, option))
            self.assertEqual(getattr(config, option), default_value)

    def test_can_use_mix_of_args_and_kwargs(self):
        config = botocore.config.Config('us-east-1', read_timeout=50)
        self.assertEqual(config.region_name, 'us-east-1')
        self.assertEqual(config.read_timeout, 50)

    def test_invalid_kwargs(self):
        with self.assertRaisesRegexp(TypeError, 'Got unexpected keyword'):
            botocore.config.Config(foo='foo')

    def test_pass_invalid_length_of_args(self):
        with self.assertRaisesRegexp(TypeError, 'Takes at most'):
            botocore.config.Config('foo', *botocore.config.Config.OPTION_DEFAULTS.values())

    def test_create_with_multiple_kwargs(self):
        with self.assertRaisesRegexp(TypeError, 'Got multiple values'):
            botocore.config.Config('us-east-1', region_name='us-east-1')

    def test_merge_returns_new_config_object(self):
        config = botocore.config.Config()
        other_config = botocore.config.Config()
        new_config = config.merge(other_config)
        # Check the type is correct
        self.assertIsInstance(new_config, botocore.config.Config)
        # Make sure the new config is a brand new config object
        self.assertIsNot(new_config, config)
        self.assertIsNot(new_config, other_config)

    def test_general_merge_keeps_default_values(self):
        config = botocore.config.Config()
        other_config = botocore.config.Config()
        config_properties = vars(config)
        new_config = config.merge(other_config)
        # Ensure that the values all stayed the same in the new config
        self.assertEqual(config_properties, vars(new_config))

    def test_merge_overrides_values(self):
        config = botocore.config.Config(region_name='us-east-1')
        other_config = botocore.config.Config(region_name='us-west-2')
        new_config = config.merge(other_config)
        self.assertEqual(new_config.region_name, 'us-west-2')

    def test_merge_overrides_values_even_when_using_default(self):
        config = botocore.config.Config(region_name='us-east-1')
        other_config = botocore.config.Config(region_name=None)
        new_config = config.merge(other_config)
        self.assertEqual(new_config.region_name, None)

    def test_merge_overrides_values_even_when_using_default_timeout(self):
        config = botocore.config.Config(read_timeout=30)
        other_config = botocore.config.Config(read_timeout=DEFAULT_TIMEOUT)
        new_config = config.merge(other_config)
        self.assertEqual(new_config.read_timeout, DEFAULT_TIMEOUT)

    def test_merge_overrides_only_when_user_provided_values(self):
        config = botocore.config.Config(
            region_name='us-east-1', signature_version='s3v4')
        other_config = botocore.config.Config(region_name='us-west-2')
        new_config = config.merge(other_config)
        self.assertEqual(new_config.region_name, 'us-west-2')
        self.assertEqual(new_config.signature_version, 's3v4')

    def test_can_set_retry_max_attempts(self):
        config = botocore.config.Config(retries={'max_attempts': 15})
        self.assertEqual(config.retries['max_attempts'], 15)

    def test_validates_retry_config(self):
        with self.assertRaisesRegexp(
                InvalidRetryConfigurationError,
                'Cannot provide retry configuration for "not-allowed"'):
            botocore.config.Config(retries={'not-allowed': True})

    def test_validates_max_retry_attempts(self):
        with self.assertRaises(InvalidMaxRetryAttemptsError):
            botocore.config.Config(retries={'max_attempts': -1})

    def test_validates_total_max_attempts(self):
        with self.assertRaises(InvalidMaxRetryAttemptsError):
            botocore.config.Config(retries={'total_max_attempts': 0})

    def test_validaties_retry_mode(self):
        with self.assertRaises(InvalidRetryModeError):
            botocore.config.Config(retries={'mode': 'turbo-mode'})


class TestClientEndpointBridge(unittest.TestCase):
    def setUp(self):
        self.resolver = mock.Mock()
        self.boilerplate_response = {
            'endpointName': 'us-east-1',
            'hostname': 's3.amazonaws.com',
            'partition': 'aws',
            'protocols': ['http', 'https'],
            'dnsSuffix': 'amazonaws.com',
            'signatureVersions': ['s3', 's3v4']
        }
        self.resolver.construct_endpoint.return_value = \
                self.boilerplate_response

    def test_guesses_endpoint_as_last_resort(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = None
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('myservice', region_name='guess')
        self.assertEqual('guess', resolved['region_name'])
        self.assertEqual('guess', resolved['signing_region'])
        self.assertEqual('myservice', resolved['signing_name'])
        self.assertEqual('myservice', resolved['service_name'])
        self.assertEqual('v4', resolved['signature_version'])
        self.assertEqual('https://myservice.guess.amazonaws.com',
                          resolved['endpoint_url'])

    def test_uses_us_east_1_by_default_for_s3(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 's3.amazonaws.com',
            'endpointName': 'us-east-1', 'signatureVersions': ['s3', 's3v4'],
            'protocols': ['https']}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('s3')
        self.assertEqual('us-east-1', resolved['region_name'])
        self.assertEqual('us-east-1', resolved['signing_region'])
        self.assertEqual('https://s3.amazonaws.com',
                         resolved['endpoint_url'])

    def test_uses_region_from_client_config_if_available(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = None
        client_config = mock.Mock()
        client_config.region_name = 'us-foo-bar'
        bridge = ClientEndpointBridge(resolver, client_config=client_config)
        resolved = bridge.resolve('test')
        self.assertEqual('us-foo-bar', resolved['region_name'])
        self.assertEqual('us-foo-bar', resolved['signing_region'])
        self.assertEqual('https://test.us-foo-bar.amazonaws.com',
                         resolved['endpoint_url'])

    def test_can_guess_endpoint_and_use_given_endpoint_url(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = None
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve(
            'test', 'guess', endpoint_url='http://test.com')
        self.assertEqual('guess', resolved['region_name'])
        self.assertEqual('guess', resolved['signing_region'])
        self.assertEqual('http://test.com', resolved['endpoint_url'])

    def test_can_use_endpoint_url_with_resolved_endpoint(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'do-not-use-this',
            'endpointName': 'us-west-2', 'signatureVersions': ['v2']}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve(
            'ec2', 'us-west-2', endpoint_url='https://foo')
        self.assertEqual('us-west-2', resolved['region_name'])
        self.assertEqual('us-west-2', resolved['signing_region'])
        self.assertEqual('https://foo', resolved['endpoint_url'])
        self.assertEqual('v2', resolved['signature_version'])

    def test_uses_ssl_common_name_over_hostname_if_present(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'do-not-use-this',
            'signatureVersions': ['v4'], 'sslCommonName': 'common-name.com',
            'endpointName': 'us-west-2', 'protocols': ['https']}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('myservice', 'us-west-2')
        self.assertEqual('us-west-2', resolved['region_name'])
        self.assertEqual('us-west-2', resolved['signing_region'])
        self.assertEqual('https://common-name.com', resolved['endpoint_url'])

    def test_can_create_http_urls(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'host.com',
            'signatureVersions': ['v4'],
            'endpointName': 'us-foo-baz'}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('myservice', 'us-foo-baz', is_secure=False)
        self.assertEqual('http://host.com', resolved['endpoint_url'])

    def test_can_create_http_urls(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'host.com',
            'signatureVersions': ['v4'],
            'endpointName': 'us-foo-baz'}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('myservice', 'us-foo-baz', is_secure=False)
        self.assertEqual('http://host.com', resolved['endpoint_url'])

    def test_credential_scope_overrides_signing_region(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws',
            'hostname': 'host.com',
            'endpointName': 'us-foo-baz',
            'signatureVersions': ['v4'],
            'credentialScope': {'region': 'override'}
        }
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('myservice', 'us-foo-baz')
        self.assertEqual('us-foo-baz', resolved['region_name'])
        self.assertEqual('override', resolved['signing_region'])

    def test_cred_scope_does_not_override_signing_region_if_endpoint_url(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws',
            'hostname': 'will-not-use.com',
            'endpointName': 'us-foo-baz',
            'signatureVersions': ['v4'],
            'credentialScope': {'region': 'override'}
        }
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('myservice', 'us-foo-baz',
                                  endpoint_url='https://override.com')
        self.assertEqual('us-foo-baz', resolved['region_name'])
        self.assertEqual('us-foo-baz', resolved['signing_region'])
        self.assertEqual('https://override.com', resolved['endpoint_url'])

    def test_resolved_region_overrides_region_when_no_endpoint_url(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws',
            'hostname': 'host.com',
            'signatureVersions': ['v4'],
            'endpointName': 'override',
            'protocols': ['https'],
        }
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('myservice', 'will-not-be-there')
        self.assertEqual('override', resolved['region_name'])
        self.assertEqual('override', resolved['signing_region'])
        self.assertEqual('https://host.com', resolved['endpoint_url'])

    def test_does_not_use_https_if_not_available(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws',
            'hostname': 'host.com',
            'signatureVersions': ['v4'],
            'endpointName': 'foo',
            # Note: http, not https
            'protocols': ['http'],
        }
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('myservice')
        # We should resolve to http://, not https://
        self.assertEqual('http://host.com', resolved['endpoint_url'])

    def test_uses_signature_version_from_client_config(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test.com',
            'endpointName': 'us-west-2', 'signatureVersions': ['v2']}
        client_config = mock.Mock()
        client_config.signature_version = 's3'
        bridge = ClientEndpointBridge(resolver, client_config=client_config)
        resolved = bridge.resolve('test', 'us-west-2')
        self.assertEqual('s3', resolved['signature_version'])

    def test_uses_signature_version_from_client_config_when_guessing(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = None
        client_config = mock.Mock()
        client_config.signature_version = 's3v4'
        bridge = ClientEndpointBridge(resolver, client_config=client_config)
        resolved = bridge.resolve('test', 'us-west-2')
        self.assertEqual('s3v4', resolved['signature_version'])

    def test_uses_signature_version_from_scoped_config(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test.com',
            'endpointName': 'us-west-2', 'signatureVersions': ['v2']}
        scoped_config = mock.Mock()
        scoped_config.get.return_value = {'signature_version': 's3'}
        bridge = ClientEndpointBridge(resolver, scoped_config)
        resolved = bridge.resolve('test', 'us-west-2')
        self.assertEqual('s3', resolved['signature_version'])

    def test_uses_s3v4_over_s3_for_s3(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test.com',
            'endpointName': 'us-west-2', 'signatureVersions': ['s3v4', 's3']}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('s3', 'us-west-2')
        self.assertEqual('s3v4', resolved['signature_version'])

    def test_uses_s3v4_over_others_for_s3(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test.com',
            'endpointName': 'us-west-2', 'signatureVersions': ['s3v4', 'v4']}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('s3', 'us-west-2')
        self.assertEqual('s3v4', resolved['signature_version'])

    def test_uses_v4_over_other_signers(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test',
            'signatureVersions': ['v2', 'v4'], 'endpointName': 'us-west-2'}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('test', 'us-west-2')
        self.assertEqual('v4', resolved['signature_version'])

    def test_uses_known_signers_from_list_of_signature_versions(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test',
            'signatureVersions': ['foo', 'baz', 'v3https'],
            'endpointName': 'us-west-2'}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('test', 'us-west-2')
        self.assertEqual('v3https', resolved['signature_version'])

    def test_raises_when_signature_version_is_unknown(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test',
            'endpointName': 'us-west-2', 'signatureVersions': ['foo']}
        bridge = ClientEndpointBridge(resolver)
        with self.assertRaises(UnknownSignatureVersionError):
            bridge.resolve('test', 'us-west-2')

    def test_uses_first_known_signature_version(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test',
            'endpointName': 'us-west-2',
            'signatureVersions': ['foo', 'bar', 'baz', 's3v4', 'v2']}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('test', 'us-west-2')
        self.assertEqual('s3v4', resolved['signature_version'])

    def test_raises_when_signature_version_is_not_found(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test',
            'endpointName': 'us-west-2'}
        bridge = ClientEndpointBridge(resolver)
        with self.assertRaises(UnknownSignatureVersionError):
            bridge.resolve('test', 'us-west-2')

    def test_uses_service_name_as_signing_name(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test',
            'signatureVersions': ['v4'],
            'endpointName': 'us-west-2'}
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('test', 'us-west-2')
        self.assertEqual('test', resolved['signing_name'])

    def test_uses_credential_scope_signing_name(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws',
            'hostname': 'test',
            'endpointName': 'us-west-2',
            'signatureVersions': ['v4'],
            'credentialScope': {'service': 'override'}
        }
        bridge = ClientEndpointBridge(resolver)
        resolved = bridge.resolve('test', 'us-west-2')
        self.assertEqual('override', resolved['signing_name'])

    def test_uses_service_signing_name_when_present_and_no_cred_scope(self):
        resolver = mock.Mock()
        resolver.construct_endpoint.return_value = {
            'partition': 'aws', 'hostname': 'test',
            'signatureVersions': ['v4'],
            'endpointName': 'us-west-2'}
        bridge = ClientEndpointBridge(resolver, service_signing_name='foo')
        resolved = bridge.resolve('test', 'us-west-2')
        self.assertEqual('foo', resolved['signing_name'])

    def test_can_construct_dualstack_endpoint_when_enabled(self):
        scoped_config = {'s3': {'use_dualstack_endpoint': True}}
        bridge = ClientEndpointBridge(self.resolver, scoped_config)
        resolved = bridge.resolve('s3', 'us-east-1')
        self.assertEqual(
            resolved['endpoint_url'],
            'https://s3.dualstack.us-east-1.amazonaws.com')

    def test_dualstack_can_use_client_config(self):
        config = botocore.config.Config(s3={'use_dualstack_endpoint': True})
        bridge = ClientEndpointBridge(self.resolver, client_config=config)
        resolved = bridge.resolve('s3', 'us-east-1')
        self.assertEqual(
            resolved['endpoint_url'],
            'https://s3.dualstack.us-east-1.amazonaws.com')

    def test_dualstack_client_config_beats_scoped_config(self):
        scoped_config = {'s3': {'use_dualstack_endpoint': False}}
        config = botocore.config.Config(s3={'use_dualstack_endpoint': True})
        bridge = ClientEndpointBridge(self.resolver, scoped_config,
                                      client_config=config)
        resolved = bridge.resolve('s3', 'us-east-1')
        self.assertEqual(
            resolved['endpoint_url'],
            'https://s3.dualstack.us-east-1.amazonaws.com')

    def test_disable_dualstack_explicitly(self):
        scoped_config = {'s3': {'use_dualstack_endpoint': True}}
        config = botocore.config.Config(s3={'use_dualstack_endpoint': False})
        bridge = ClientEndpointBridge(self.resolver, scoped_config,
                                      client_config=config)
        resolved = bridge.resolve('s3', 'us-east-1')
        self.assertEqual(
            resolved['endpoint_url'],
            'https://s3.amazonaws.com')

    def test_dualstack_honors_dns_suffix(self):
        scoped_config = {'s3': {'use_dualstack_endpoint': True}}
        self.boilerplate_response['dnsSuffix'] = 'amazonaws.com.cn'
        self.boilerplate_response['endpointName'] = 'cn-north-1'
        bridge = ClientEndpointBridge(self.resolver, scoped_config)
        resolved = bridge.resolve('s3', 'cn-north-1')
        self.assertEqual(
            resolved['endpoint_url'],
            'https://s3.dualstack.cn-north-1.amazonaws.com.cn'
        )
