class CloudantClientTests(UnitTestDbBase):
    """
    Cloudant specific client unit tests
    """
    def test_constructor_with_creds_removed_from_url(self):
        """
        Test instantiating a client object using a URL
        """
        client = Cloudant(
            None,
            None,
            url='https://a9a9a9a9-a9a9-a9a9-a9a9-a9a9a9a9a9a9-bluemix'
            ':a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9'
            'a9a9a9a9a9a9@d8a01891-e4d2-4102-b5f8-751fb735ce31-'
            'bluemix.cloudant.com')
        self.assertEqual(
            client.server_url, 'https://d8a01891-e4d2-4102-b5f8-751fb735ce31-'
            'bluemix.cloudant.com')
        self.assertEqual(client._user,
                         'a9a9a9a9-a9a9-a9a9-a9a9-a9a9a9a9a9a9-bluemix')
        self.assertEqual(
            client._auth_token, 'a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a'
            '9a9a9a9a9a9a9a9a9a9a9a9a9')

    @skip_if_not_cookie_auth
    def test_cloudant_session_login(self):
        """
        Test that the Cloudant client session successfully authenticates.
        """
        self.client.connect()
        old_cookie = self.client.session_cookie()

        sleep(5)  # ensure we get a different cookie back

        self.client.session_login()
        self.assertNotEqual(self.client.session_cookie(), old_cookie)

    @skip_if_not_cookie_auth
    def test_cloudant_session_login_with_new_credentials(self):
        """
        Test that the Cloudant client session fails to authenticate when
        passed incorrect credentials.
        """
        self.client.connect()

        with self.assertRaises(HTTPError) as cm:
            self.client.session_login('invalid-user-123', 'pa$$w0rd01')

        self.assertTrue(
            str(cm.exception).find('Name or password is incorrect'))

    @skip_if_not_cookie_auth
    def test_cloudant_context_helper(self):
        """
        Test that the cloudant context helper works as expected.
        """
        try:
            with cloudant(self.user, self.pwd, account=self.account) as c:
                self.assertIsInstance(c, Cloudant)
                self.assertIsInstance(c.r_session, requests.Session)
        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))

    @skip_if_not_cookie_auth
    def test_cloudant_bluemix_context_helper_with_legacy_creds(self):
        """
        Test that the cloudant_bluemix context helper with legacy creds works as expected.
        """
        instance_name = 'Cloudant NoSQL DB-lv'
        vcap_services = {
            'cloudantNoSQLDB': [{
                'credentials': {
                    'username': self.user,
                    'password': self.pwd,
                    'host': '{0}.cloudant.com'.format(self.account),
                    'port': 443,
                    'url': self.url
                },
                'name': instance_name,
            }]
        }

        try:
            with cloudant_bluemix(vcap_services,
                                  instance_name=instance_name) as c:
                self.assertIsInstance(c, Cloudant)
                self.assertIsInstance(c.r_session, requests.Session)
                self.assertEquals(c.session()['userCtx']['name'], self.user)
        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))

    @unittest.skipUnless(
        os.environ.get('IAM_API_KEY'),
        'Skipping Cloudant Bluemix context helper with IAM test')
    def test_cloudant_bluemix_context_helper_with_iam(self):
        """
        Test that the cloudant_bluemix context helper with IAM works as expected.
        """
        instance_name = 'Cloudant NoSQL DB-lv'
        vcap_services = {
            'cloudantNoSQLDB': [{
                'credentials': {
                    'apikey': self.iam_api_key,
                    'username': self.user,
                    'host': '{0}.cloudant.com'.format(self.account),
                    'port': 443,
                    'url': self.url
                },
                'name': instance_name,
            }]
        }

        try:
            with cloudant_bluemix(vcap_services,
                                  instance_name=instance_name) as c:
                self.assertIsInstance(c, Cloudant)
                self.assertIsInstance(c.r_session, requests.Session)
        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))

    def test_cloudant_bluemix_context_helper_raise_error_for_missing_iam_and_creds(
            self):
        """
        Test that the cloudant_bluemix context helper raises a CloudantClientException
        when the IAM key, username, and password are missing in the VCAP_SERVICES env variable.
        """
        instance_name = 'Cloudant NoSQL DB-lv'
        vcap_services = {
            'cloudantNoSQLDB': [{
                'credentials': {
                    'host': '{0}.cloudant.com'.format(self.account),
                    'port': 443,
                    'url': self.url
                },
                'name': instance_name,
            }]
        }

        try:
            with cloudant_bluemix(vcap_services,
                                  instance_name=instance_name) as c:
                self.assertIsInstance(c, Cloudant)
                self.assertIsInstance(c.r_session, requests.Session)
        except CloudantClientException as err:
            self.assertEqual(
                'Invalid service: IAM API key or username/password credentials are required.',
                str(err))

    def test_cloudant_bluemix_dedicated_context_helper(self):
        """
        Test that the cloudant_bluemix context helper works as expected when
        specifying a service name.
        """
        instance_name = 'Cloudant NoSQL DB-wq'
        service_name = 'cloudantNoSQLDB Dedicated'
        vcap_services = {
            service_name: [{
                'credentials': {
                    'username': self.user,
                    'password': self.pwd,
                    'host': '{0}.cloudant.com'.format(self.account),
                    'port': 443,
                    'url': self.url
                },
                'name': instance_name,
            }]
        }

        try:
            with cloudant_bluemix(vcap_services,
                                  instance_name=instance_name,
                                  service_name=service_name) as c:
                self.assertIsInstance(c, Cloudant)
                self.assertIsInstance(c.r_session, requests.Session)
                self.assertEquals(c.session()['userCtx']['name'], self.user)
        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))

    def test_constructor_with_account(self):
        """
        Test instantiating a client object using an account name
        """
        # Ensure that the client is new
        del self.client
        self.client = Cloudant(self.user, self.pwd, account=self.account)
        self.assertEqual(self.client.server_url,
                         'https://{0}.cloudant.com'.format(self.account))

    @skip_if_not_cookie_auth
    def test_bluemix_constructor_with_legacy_creds(self):
        """
        Test instantiating a client object using a VCAP_SERVICES environment
        variable.
        """
        instance_name = 'Cloudant NoSQL DB-lv'
        vcap_services = {
            'cloudantNoSQLDB': [{
                'credentials': {
                    'username': self.user,
                    'password': self.pwd,
                    'host': '{0}.cloudant.com'.format(self.account),
                    'port': 443,
                    'url': self.url
                },
                'name': instance_name
            }]
        }

        # create Cloudant Bluemix client
        c = Cloudant.bluemix(vcap_services)

        try:
            c.connect()
            self.assertIsInstance(c, Cloudant)
            self.assertIsInstance(c.r_session, requests.Session)
            self.assertEquals(c.session()['userCtx']['name'], self.user)

        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))

        finally:
            c.disconnect()

    @unittest.skipUnless(os.environ.get('IAM_API_KEY'),
                         'Skipping Cloudant Bluemix constructor with IAM test')
    def test_bluemix_constructor_with_iam(self):
        """
        Test instantiating a client object using a VCAP_SERVICES environment
        variable.
        """
        instance_name = 'Cloudant NoSQL DB-lv'
        vcap_services = {
            'cloudantNoSQLDB': [{
                'credentials': {
                    'apikey': self.iam_api_key,
                    'username': self.user,
                    'host': '{0}.cloudant.com'.format(self.account),
                    'port': 443
                },
                'name': instance_name
            }]
        }

        # create Cloudant Bluemix client
        c = Cloudant.bluemix(vcap_services)

        try:
            c.connect()
            self.assertIsInstance(c, Cloudant)
            self.assertIsInstance(c.r_session, requests.Session)

        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))

        finally:
            c.disconnect()

    def test_bluemix_constructor_specify_instance_name(self):
        """
        Test instantiating a client object using a VCAP_SERVICES environment
        variable and specifying which instance name to use.
        """
        instance_name = 'Cloudant NoSQL DB-lv'
        vcap_services = {
            'cloudantNoSQLDB': [{
                'credentials': {
                    'username': self.user,
                    'password': self.pwd,
                    'host': '{0}.cloudant.com'.format(self.account),
                    'port': 443,
                    'url': self.url
                },
                'name': instance_name
            }]
        }

        # create Cloudant Bluemix client
        c = Cloudant.bluemix(vcap_services, instance_name=instance_name)

        try:
            c.connect()
            self.assertIsInstance(c, Cloudant)
            self.assertIsInstance(c.r_session, requests.Session)
            self.assertEquals(c.session()['userCtx']['name'], self.user)

        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))

        finally:
            c.disconnect()

    @skip_if_not_cookie_auth
    def test_bluemix_constructor_with_multiple_services(self):
        """
        Test instantiating a client object using a VCAP_SERVICES environment
        variable that contains multiple services.
        """
        instance_name = 'Cloudant NoSQL DB-lv'
        vcap_services = {
            'cloudantNoSQLDB': [{
                'credentials': {
                    'apikey': '1234api',
                    'host': '{0}.cloudant.com'.format(self.account),
                    'port': 443,
                    'url': self.url
                },
                'name': instance_name
            }, {
                'credentials': {
                    'username': '******',
                    'password': '******',
                    'host': 'baz.com',
                    'port': 1234,
                    'url': 'https://*****:*****@baz.com:1234'
                },
                'name': 'Cloudant NoSQL DB-yu'
            }]
        }

        # create Cloudant Bluemix client
        c = Cloudant.bluemix(vcap_services, instance_name=instance_name)

        try:
            c.connect()
            self.assertIsInstance(c, Cloudant)
            self.assertIsInstance(c.r_session, requests.Session)
            self.assertEquals(c.session()['userCtx']['name'], self.user)

        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))

        finally:
            c.disconnect()

    def test_connect_headers(self):
        """
        Test that the appropriate request headers are set
        """
        try:
            self.client.connect()
            self.assertEqual(self.client.r_session.headers['X-Cloudant-User'],
                             self.account)
            agent = self.client.r_session.headers.get('User-Agent')
            ua_parts = agent.split('/')
            self.assertEqual(len(ua_parts), 6)
            self.assertEqual(ua_parts[0], 'python-cloudant')
            self.assertEqual(ua_parts[1], sys.modules['cloudant'].__version__)
            self.assertEqual(ua_parts[2], 'Python')
            self.assertEqual(
                ua_parts[3],
                '{0}.{1}.{2}'.format(sys.version_info[0], sys.version_info[1],
                                     sys.version_info[2])),
            self.assertEqual(ua_parts[4], os.uname()[0]),
            self.assertEqual(ua_parts[5], os.uname()[4])
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_connect_timeout(self):
        """
        Test that a connect timeout occurs when instantiating
        a client object with a timeout of 10 ms.
        """
        with self.assertRaises(ConnectTimeout) as cm:
            self.set_up_client(auto_connect=True, timeout=.01)
        self.assertTrue(str(cm.exception).find('timed out.'))

    def test_db_updates_infinite_feed_call(self):
        """
        Test that infinite_db_updates() method call constructs and returns an
        InfiniteFeed object
        """
        try:
            self.client.connect()
            db_updates = self.client.infinite_db_updates()
            self.assertIsInstance(db_updates, InfiniteFeed)
            self.assertEqual(db_updates._url,
                             '/'.join([self.client.server_url, '_db_updates']))
            self.assertIsInstance(db_updates._r_session, requests.Session)
            self.assertFalse(db_updates._raw_data)
            self.assertDictEqual(db_updates._options, {'feed': 'continuous'})
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_billing_data(self):
        """
        Test the retrieval of billing data
        """
        try:
            self.client.connect()
            now = datetime.datetime.now()
            expected = [
                'data_volume', 'total', 'start', 'end', 'http_heavy',
                'http_light', 'bill_type'
            ]
            # Test using year and month
            year = now.year
            month = now.month
            data = self.client.bill(year, month)
            self.assertTrue(all(x in expected for x in data.keys()))
            #Test without year and month arguments
            del data
            data = self.client.bill()
            self.assertTrue(all(x in expected for x in data.keys()))
        finally:
            self.client.disconnect()

    def test_set_year_without_month_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with only
        year parameter
        """
        try:
            self.client.connect()
            year = 2016
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(year)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - None')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_month_without_year_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with only
        month parameter
        """
        try:
            self.client.connect()
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(None, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - None, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_invalid_type_year_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with a type
        string for the year parameter
        """
        try:
            self.client.connect()
            year = 'foo'
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - foo, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_year_with_invalid_month_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with an
        invalid month parameter
        """
        try:
            self.client.connect()
            year = 2016
            month = 13
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - 13')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_volume_usage_data(self):
        """
        Test the retrieval of volume usage data
        """
        try:
            self.client.connect()
            now = datetime.datetime.now()
            expected = ['data_vol', 'granularity', 'start', 'end']
            # Test using year and month
            year = now.year
            month = now.month
            data = self.client.volume_usage(year, month)
            self.assertTrue(all(x in expected for x in data.keys()))
            #Test without year and month arguments
            del data
            data = self.client.volume_usage()
            self.assertTrue(all(x in expected for x in data.keys()))
        finally:
            self.client.disconnect()

    def test_set_year_without_month_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with only
        year parameter
        """
        try:
            self.client.connect()
            year = 2016
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(year)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - None')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_month_without_year_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with only
        month parameter
        """
        try:
            self.client.connect()
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(None, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - None, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_invalid_type_year_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with a type
        string for the year parameter
        """
        try:
            self.client.connect()
            year = 'foo'
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - foo, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_year_with_invalid_month_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with an
        invalid month parameter
        """
        try:
            self.client.connect()
            year = 2016
            month = 13
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - 13')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_requests_usage_data(self):
        """
        Test the retrieval of requests usage data
        """
        try:
            self.client.connect()
            now = datetime.datetime.now()
            expected = ['requests', 'granularity', 'start', 'end']
            # Test using year and month
            year = now.year
            month = now.month
            data = self.client.requests_usage(year, month)
            self.assertTrue(all(x in expected for x in data.keys()))
            #Test without year and month arguments
            del data
            data = self.client.requests_usage()
            self.assertTrue(all(x in expected for x in data.keys()))
        finally:
            self.client.disconnect()

    def test_set_year_without_month_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with an
        invalid month parameter
        """
        try:
            self.client.connect()
            year = 2016
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(year)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - None')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_month_without_year_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with only
        month parameter
        """
        try:
            self.client.connect()
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(None, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - None, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_invalid_type_year_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with
        a type string for the year parameter
        """
        try:
            self.client.connect()
            year = 'foo'
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - foo, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_year_with_invalid_month_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with only
        year parameter
        """
        try:
            self.client.connect()
            year = 2016
            month = 13
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - 13')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_shared_databases(self):
        """
        Test the retrieval of shared database list
        """
        try:
            self.client.connect()
            self.assertIsInstance(self.client.shared_databases(), list)
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_generate_api_key(self):
        """
        Test the generation of an API key for this client account
        """
        try:
            self.client.connect()
            expected = ['key', 'password', 'ok']
            api_key = self.client.generate_api_key()
            self.assertTrue(all(x in expected for x in api_key.keys()))
            self.assertTrue(api_key['ok'])
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_cors_configuration(self):
        """
        Test the retrieval of the current CORS configuration for this client
        account
        """
        try:
            self.client.connect()
            expected = ['allow_credentials', 'enable_cors', 'origins']
            cors = self.client.cors_configuration()
            self.assertTrue(all(x in expected for x in cors.keys()))
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_cors_origins(self):
        """
        Test the retrieval of the CORS origins list
        """
        try:
            self.client.connect()
            origins = self.client.cors_origins()
            self.assertIsInstance(origins, list)
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_disable_cors(self):
        """
        Test disabling CORS (assuming CORS is enabled)
        """
        try:
            self.client.connect()
            # Save original CORS settings
            save = self.client.cors_configuration()
            # Test CORS disable
            self.assertEqual(self.client.disable_cors(), {'ok': True})
            # Restore original CORS settings
            self.client.update_cors_configuration(save['enable_cors'],
                                                  save['allow_credentials'],
                                                  save['origins'], True)
        finally:
            self.client.disconnect()

    @skip_if_not_cookie_auth
    def test_update_cors_configuration(self):
        """
        Test updating CORS configuration
        """
        try:
            self.client.connect()
            # Save original CORS settings
            save = self.client.cors_configuration()
            # Test updating CORS settings, overwriting origins
            result = self.client.update_cors_configuration(
                True, True, ['https://ibm.com'], True)
            self.assertEqual(result, {'ok': True})
            updated_cors = self.client.cors_configuration()
            self.assertTrue(updated_cors['enable_cors'])
            self.assertTrue(updated_cors['allow_credentials'])
            expected = ['https://ibm.com']
            self.assertTrue(all(x in expected
                                for x in updated_cors['origins']))
            # Test updating CORS settings, adding to origins
            result = self.client.update_cors_configuration(
                True, True, ['https://ibm.cloudant.com'])
            self.assertEqual(result, {'ok': True})
            del updated_cors
            updated_cors = self.client.cors_configuration()
            self.assertTrue(updated_cors['enable_cors'])
            self.assertTrue(updated_cors['allow_credentials'])
            expected.append('https://ibm.cloudant.com')
            self.assertTrue(all(x in expected
                                for x in updated_cors['origins']))
            # Restore original CORS settings
            self.client.update_cors_configuration(save['enable_cors'],
                                                  save['allow_credentials'],
                                                  save['origins'], True)
        finally:
            self.client.disconnect()
class CloudantClientTests(UnitTestDbBase):
    """
    Cloudant specific client unit tests
    """

    def test_cloudant_context_helper(self):
        """
        Test that the cloudant context helper works as expected.
        """
        try:
            with cloudant(self.user, self.pwd, account=self.account) as c:
                self.assertIsInstance(c, Cloudant)
                self.assertIsInstance(c.r_session, requests.Session)
                self.assertEqual(c.r_session.auth, (self.user, self.pwd))
        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))
    
    def test_constructor_with_account(self):
        """
        Test instantiating a client object using an account name
        """
        # Ensure that the client is new
        del self.client
        self.client = Cloudant(self.user, self.pwd, account=self.account)
        self.assertEqual(
            self.client.server_url,
            'https://{0}.cloudant.com'.format(self.account)
            )

    def test_connect_headers(self):
        """
        Test that the appropriate request headers are set
        """
        try:
            self.client.connect()
            self.assertEqual(
                self.client.r_session.headers['X-Cloudant-User'],
                self.account
                )
            agent = self.client.r_session.headers.get('User-Agent')
            ua_parts = agent.split('/')
            self.assertEqual(len(ua_parts), 6)
            self.assertEqual(ua_parts[0], 'python-cloudant')
            self.assertEqual(ua_parts[1], sys.modules['cloudant'].__version__)
            self.assertEqual(ua_parts[2], 'Python')
            self.assertEqual(ua_parts[3], '{0}.{1}.{2}'.format(
                sys.version_info[0], sys.version_info[1], sys.version_info[2])),
            self.assertEqual(ua_parts[4], os.uname()[0]),
            self.assertEqual(ua_parts[5], os.uname()[4])
        finally:
            self.client.disconnect()

    def test_db_updates_infinite_feed_call(self):
        """
        Test that infinite_db_updates() method call constructs and returns an
        InfiniteFeed object
        """
        try:
            self.client.connect()
            db_updates = self.client.infinite_db_updates()
            self.assertIsInstance(db_updates, InfiniteFeed)
            self.assertEqual(
                db_updates._url, '/'.join([self.client.server_url, '_db_updates']))
            self.assertIsInstance(db_updates._r_session, requests.Session)
            self.assertFalse(db_updates._raw_data)
            self.assertDictEqual(db_updates._options, {'feed': 'continuous'})
        finally:
            self.client.disconnect()

    def test_billing_data(self):
        """
        Test the retrieval of billing data
        """
        try:
            self.client.connect()
            expected = [
                'data_volume',
                'total',
                'start',
                'end',
                'http_heavy',
                'http_light'
                ]
            # Test using year and month
            year = 2016
            month = 1
            data = self.client.bill(year, month)
            self.assertTrue(all(x in expected for x in data.keys()))
            #Test without year and month arguments
            del data
            data = self.client.bill()
            self.assertTrue(all(x in expected for x in data.keys()))
        finally:
            self.client.disconnect()

    def test_set_year_without_month_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with only
        year parameter
        """
        try:
            self.client.connect()
            year = 2016
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(year)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - None')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_month_without_year_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with only
        month parameter
        """
        try:
            self.client.connect()
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(None, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - None, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_invalid_type_year_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with a type
        string for the year parameter
        """
        try:
            self.client.connect()
            year = 'foo'
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - foo, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_year_with_invalid_month_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with an
        invalid month parameter
        """
        try:
            self.client.connect()
            year = 2016
            month = 13
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - 13')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_volume_usage_data(self):
        """
        Test the retrieval of volume usage data
        """
        try:
            self.client.connect()
            expected = [
                'data_vol',
                'granularity',
                'start',
                'end'
                ]
            # Test using year and month
            year = 2016
            month = 12
            data = self.client.volume_usage(year, month)
            self.assertTrue(all(x in expected for x in data.keys()))
            #Test without year and month arguments
            del data
            data = self.client.volume_usage()
            self.assertTrue(all(x in expected for x in data.keys()))
        finally:
            self.client.disconnect()

    def test_set_year_without_month_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with only
        year parameter
        """
        try:
            self.client.connect()
            year = 2016
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(year)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - None')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_month_without_year_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with only
        month parameter
        """
        try:
            self.client.connect()
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(None, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - None, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_invalid_type_year_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with a type
        string for the year parameter
        """
        try:
            self.client.connect()
            year = 'foo'
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - foo, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_year_with_invalid_month_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with an
        invalid month parameter
        """
        try:
            self.client.connect()
            year = 2016
            month = 13
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - 13')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_requests_usage_data(self):
        """
        Test the retrieval of requests usage data
        """
        try:
            self.client.connect()
            expected = [
                'requests',
                'granularity',
                'start',
                'end'
                ]
            # Test using year and month
            year = 2016
            month = 1
            data = self.client.requests_usage(year, month)
            self.assertTrue(all(x in expected for x in data.keys()))
            #Test without year and month arguments
            del data
            data = self.client.requests_usage()
            self.assertTrue(all(x in expected for x in data.keys()))
        finally:
            self.client.disconnect()

    def test_set_year_without_month_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with an
        invalid month parameter
        """
        try:
            self.client.connect()
            year = 2016
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(year)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - None')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_month_without_year_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with only
        month parameter
        """
        try:
            self.client.connect()
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(None, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - None, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_invalid_type_year_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with
        a type string for the year parameter
        """
        try:
            self.client.connect()
            year = 'foo'
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - foo, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_year_with_invalid_month_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with only
        year parameter
        """
        try:
            self.client.connect()
            year = 2016
            month = 13
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - 13')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_shared_databases(self):
        """
        Test the retrieval of shared database list
        """
        try:
            self.client.connect()
            self.assertIsInstance(self.client.shared_databases(), list)
        finally:
            self.client.disconnect()

    def test_generate_api_key(self):
        """
        Test the generation of an API key for this client account
        """
        try:
            self.client.connect()
            expected = ['key', 'password', 'ok']
            api_key = self.client.generate_api_key()
            self.assertTrue(all(x in expected for x in api_key.keys()))
            self.assertTrue(api_key['ok'])
        finally:
            self.client.disconnect()

    def test_cors_configuration(self):
        """
        Test the retrieval of the current CORS configuration for this client
        account
        """
        try:
            self.client.connect()
            expected = ['allow_credentials', 'enable_cors', 'origins']
            cors = self.client.cors_configuration()
            self.assertTrue(all(x in expected for x in cors.keys()))
        finally:
            self.client.disconnect()

    def test_cors_origins(self):
        """
        Test the retrieval of the CORS origins list
        """
        try:
            self.client.connect()
            origins = self.client.cors_origins()
            self.assertIsInstance(origins, list)
        finally:
            self.client.disconnect()

    def test_disable_cors(self):
        """
        Test disabling CORS (assuming CORS is enabled)
        """
        try:
            self.client.connect()
            # Save original CORS settings
            save = self.client.cors_configuration()
            # Test CORS disable
            self.assertEqual(self.client.disable_cors(), {'ok': True})
            # Restore original CORS settings
            self.client.update_cors_configuration(
                save['enable_cors'],
                save['allow_credentials'],
                save['origins'],
                True
                )
        finally:
            self.client.disconnect()

    def test_update_cors_configuration(self):
        """
        Test updating CORS configuration
        """
        try:
            self.client.connect()
            # Save original CORS settings
            save = self.client.cors_configuration()
            # Test updating CORS settings, overwriting origins
            result = self.client.update_cors_configuration(
                True,
                True,
                ['https://ibm.com'],
                True)
            self.assertEqual(result, {'ok': True})
            updated_cors = self.client.cors_configuration()
            self.assertTrue(updated_cors['enable_cors'])
            self.assertTrue(updated_cors['allow_credentials'])
            expected = ['https://ibm.com']
            self.assertTrue(all(x in expected for x in updated_cors['origins']))
            # Test updating CORS settings, adding to origins
            result = self.client.update_cors_configuration(
                True,
                True,
                ['https://ibm.cloudant.com']
                )
            self.assertEqual(result, {'ok': True})
            del updated_cors
            updated_cors = self.client.cors_configuration()
            self.assertTrue(updated_cors['enable_cors'])
            self.assertTrue(updated_cors['allow_credentials'])
            expected.append('https://ibm.cloudant.com')
            self.assertTrue(all(x in expected for x in updated_cors['origins']))
            # Restore original CORS settings
            self.client.update_cors_configuration(
                save['enable_cors'],
                save['allow_credentials'],
                save['origins'],
                True
                )
        finally:
            self.client.disconnect()
class CloudantClientTests(UnitTestDbBase):
    """
    Cloudant specific client unit tests
    """
    def test_cloudant_context_helper(self):
        """
        Test that the cloudant context helper works as expected.
        """
        try:
            with cloudant(self.user, self.pwd, account=self.account) as c:
                self.assertIsInstance(c, Cloudant)
                self.assertIsInstance(c.r_session, requests.Session)
                self.assertEqual(c.r_session.auth, (self.user, self.pwd))
        except Exception as err:
            self.fail('Exception {0} was raised.'.format(str(err)))

    def test_constructor_with_account(self):
        """
        Test instantiating a client object using an account name
        """
        # Ensure that the client is new
        del self.client
        self.client = Cloudant(self.user, self.pwd, account=self.account)
        self.assertEqual(self.client.server_url,
                         'https://{0}.cloudant.com'.format(self.account))

    def test_connect_headers(self):
        """
        Test that the appropriate request headers are set
        """
        try:
            self.client.connect()
            self.assertEqual(self.client.r_session.headers['X-Cloudant-User'],
                             self.account)
            agent = self.client.r_session.headers.get('User-Agent')
            ua_parts = agent.split('/')
            self.assertEqual(len(ua_parts), 6)
            self.assertEqual(ua_parts[0], 'python-cloudant')
            self.assertEqual(ua_parts[1], sys.modules['cloudant'].__version__)
            self.assertEqual(ua_parts[2], 'Python')
            self.assertEqual(
                ua_parts[3],
                '{0}.{1}.{2}'.format(sys.version_info[0], sys.version_info[1],
                                     sys.version_info[2])),
            self.assertEqual(ua_parts[4], os.uname()[0]),
            self.assertEqual(ua_parts[5], os.uname()[4])
        finally:
            self.client.disconnect()

    def test_db_updates_infinite_feed_call(self):
        """
        Test that infinite_db_updates() method call constructs and returns an
        InfiniteFeed object
        """
        try:
            self.client.connect()
            db_updates = self.client.infinite_db_updates()
            self.assertIsInstance(db_updates, InfiniteFeed)
            self.assertEqual(db_updates._url,
                             '/'.join([self.client.server_url, '_db_updates']))
            self.assertIsInstance(db_updates._r_session, requests.Session)
            self.assertFalse(db_updates._raw_data)
            self.assertDictEqual(db_updates._options, {'feed': 'continuous'})
        finally:
            self.client.disconnect()

    def test_billing_data(self):
        """
        Test the retrieval of billing data
        """
        try:
            self.client.connect()
            now = datetime.datetime.now()
            expected = [
                'data_volume', 'total', 'start', 'end', 'http_heavy',
                'http_light', 'bill_type'
            ]
            # Test using year and month
            year = now.year
            month = now.month
            data = self.client.bill(year, month)
            self.assertTrue(all(x in expected for x in data.keys()))
            #Test without year and month arguments
            del data
            data = self.client.bill()
            self.assertTrue(all(x in expected for x in data.keys()))
        finally:
            self.client.disconnect()

    def test_set_year_without_month_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with only
        year parameter
        """
        try:
            self.client.connect()
            year = 2016
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(year)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - None')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_month_without_year_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with only
        month parameter
        """
        try:
            self.client.connect()
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(None, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - None, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_invalid_type_year_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with a type
        string for the year parameter
        """
        try:
            self.client.connect()
            year = 'foo'
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - foo, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_year_with_invalid_month_for_billing_data(self):
        """
        Test raising an exception when retrieving billing data with an
        invalid month parameter
        """
        try:
            self.client.connect()
            year = 2016
            month = 13
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.bill(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - 13')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_volume_usage_data(self):
        """
        Test the retrieval of volume usage data
        """
        try:
            self.client.connect()
            now = datetime.datetime.now()
            expected = ['data_vol', 'granularity', 'start', 'end']
            # Test using year and month
            year = now.year
            month = now.month
            data = self.client.volume_usage(year, month)
            self.assertTrue(all(x in expected for x in data.keys()))
            #Test without year and month arguments
            del data
            data = self.client.volume_usage()
            self.assertTrue(all(x in expected for x in data.keys()))
        finally:
            self.client.disconnect()

    def test_set_year_without_month_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with only
        year parameter
        """
        try:
            self.client.connect()
            year = 2016
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(year)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - None')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_month_without_year_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with only
        month parameter
        """
        try:
            self.client.connect()
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(None, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - None, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_invalid_type_year_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with a type
        string for the year parameter
        """
        try:
            self.client.connect()
            year = 'foo'
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - foo, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_year_with_invalid_month_for_volume_usage_data(self):
        """
        Test raising an exception when retrieving volume usage data with an
        invalid month parameter
        """
        try:
            self.client.connect()
            year = 2016
            month = 13
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.volume_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - 13')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_requests_usage_data(self):
        """
        Test the retrieval of requests usage data
        """
        try:
            self.client.connect()
            now = datetime.datetime.now()
            expected = ['requests', 'granularity', 'start', 'end']
            # Test using year and month
            year = now.year
            month = now.month
            data = self.client.requests_usage(year, month)
            self.assertTrue(all(x in expected for x in data.keys()))
            #Test without year and month arguments
            del data
            data = self.client.requests_usage()
            self.assertTrue(all(x in expected for x in data.keys()))
        finally:
            self.client.disconnect()

    def test_set_year_without_month_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with an
        invalid month parameter
        """
        try:
            self.client.connect()
            year = 2016
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(year)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - None')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_month_without_year_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with only
        month parameter
        """
        try:
            self.client.connect()
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(None, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - None, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_invalid_type_year_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with
        a type string for the year parameter
        """
        try:
            self.client.connect()
            year = 'foo'
            month = 1
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - foo, month - 1')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_set_year_with_invalid_month_for_requests_usage_data(self):
        """
        Test raising an exception when retrieving requests usage data with only
        year parameter
        """
        try:
            self.client.connect()
            year = 2016
            month = 13
            with self.assertRaises(CloudantArgumentError) as cm:
                self.client.requests_usage(year, month)
            expected = ('Invalid year and/or month supplied.  '
                        'Found: year - 2016, month - 13')
            self.assertEqual(str(cm.exception), expected)
        finally:
            self.client.disconnect()

    def test_shared_databases(self):
        """
        Test the retrieval of shared database list
        """
        try:
            self.client.connect()
            self.assertIsInstance(self.client.shared_databases(), list)
        finally:
            self.client.disconnect()

    def test_generate_api_key(self):
        """
        Test the generation of an API key for this client account
        """
        try:
            self.client.connect()
            expected = ['key', 'password', 'ok']
            api_key = self.client.generate_api_key()
            self.assertTrue(all(x in expected for x in api_key.keys()))
            self.assertTrue(api_key['ok'])
        finally:
            self.client.disconnect()

    def test_cors_configuration(self):
        """
        Test the retrieval of the current CORS configuration for this client
        account
        """
        try:
            self.client.connect()
            expected = ['allow_credentials', 'enable_cors', 'origins']
            cors = self.client.cors_configuration()
            self.assertTrue(all(x in expected for x in cors.keys()))
        finally:
            self.client.disconnect()

    def test_cors_origins(self):
        """
        Test the retrieval of the CORS origins list
        """
        try:
            self.client.connect()
            origins = self.client.cors_origins()
            self.assertIsInstance(origins, list)
        finally:
            self.client.disconnect()

    def test_disable_cors(self):
        """
        Test disabling CORS (assuming CORS is enabled)
        """
        try:
            self.client.connect()
            # Save original CORS settings
            save = self.client.cors_configuration()
            # Test CORS disable
            self.assertEqual(self.client.disable_cors(), {'ok': True})
            # Restore original CORS settings
            self.client.update_cors_configuration(save['enable_cors'],
                                                  save['allow_credentials'],
                                                  save['origins'], True)
        finally:
            self.client.disconnect()

    def test_update_cors_configuration(self):
        """
        Test updating CORS configuration
        """
        try:
            self.client.connect()
            # Save original CORS settings
            save = self.client.cors_configuration()
            # Test updating CORS settings, overwriting origins
            result = self.client.update_cors_configuration(
                True, True, ['https://ibm.com'], True)
            self.assertEqual(result, {'ok': True})
            updated_cors = self.client.cors_configuration()
            self.assertTrue(updated_cors['enable_cors'])
            self.assertTrue(updated_cors['allow_credentials'])
            expected = ['https://ibm.com']
            self.assertTrue(all(x in expected
                                for x in updated_cors['origins']))
            # Test updating CORS settings, adding to origins
            result = self.client.update_cors_configuration(
                True, True, ['https://ibm.cloudant.com'])
            self.assertEqual(result, {'ok': True})
            del updated_cors
            updated_cors = self.client.cors_configuration()
            self.assertTrue(updated_cors['enable_cors'])
            self.assertTrue(updated_cors['allow_credentials'])
            expected.append('https://ibm.cloudant.com')
            self.assertTrue(all(x in expected
                                for x in updated_cors['origins']))
            # Restore original CORS settings
            self.client.update_cors_configuration(save['enable_cors'],
                                                  save['allow_credentials'],
                                                  save['origins'], True)
        finally:
            self.client.disconnect()