<reponame>HarperDB/harperdb-sdk-python
import responses
import unittest

import harperdb
import harperdb_testcase


class TestHarperDBBase(harperdb_testcase.HarperDBTestCase):

    def setUp(self):
        """ This method is called before each test.
        """
        self.db = harperdb.HarperDBBase(self.URL)

    @unittest.mock.patch('base64.b64encode')
    def test_create_harperdb_base_with_kwargs(self, mock_b64encode):
        """ Create an instance of HarperDBBase with keyword args.
        """
        # by mocking the base64 module we can define what it returns,
        # so it's very easy to check the value stored in db.token
        mock_b64encode.return_value = b'anArbitraryBase64String'

        db = harperdb.HarperDBBase(
            self.URL,
            self.USERNAME,
            self.PASSWORD,
            timeout=3)
        mock_b64encode.assert_called_once_with(
            '{}:{}'.format(self.USERNAME, self.PASSWORD).encode('utf-8'))
        self.assertEqual(db.url, self.URL)
        self.assertEqual(
            db.token,
            'Basic {}'.format(mock_b64encode.return_value.decode('utf-8')))
        self.assertEqual(db.timeout, 3)

    @responses.activate
    def test_create_schema(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'create_schema',
            'schema': 'test_schema',
        }
        # mock the server response to create_schema
        responses.add(
            'POST',
            self.URL,
            json=self.SCHEMA_CREATED,
            status=200)

        self.assertEqual(
            self.db._create_schema(spec['schema']),
            self.SCHEMA_CREATED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_schema(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'drop_schema',
            'schema': 'test_schema',
        }
        # mock the server response to drop_schema
        responses.add(
            'POST',
            self.URL,
            json=self.SCHEMA_DROPPED,
            status=200)

        self.assertEqual(
            self.db._drop_schema(spec['schema']),
            self.SCHEMA_DROPPED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_describe_schema(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'describe_schema',
            'schema': 'test_schema',
        }
        # mock the server response to describe_schema
        responses.add(
            'POST',
            self.URL,
            json=self.DESCRIBE_SCHEMA_1,
            status=200)

        self.assertEqual(
            self.db._describe_schema(spec['schema']),
            self.DESCRIBE_SCHEMA_1)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_create_table(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'create_table',
            'schema': 'test_schema',
            'table': 'test_table',
            'hash_attribute': 'id',
        }
        # mock the server response to drop_schema
        responses.add(
            'POST',
            self.URL,
            json=self.TABLE_CREATED,
            status=200)

        self.assertEqual(
            self.db._create_table(
                schema=spec['schema'],
                table=spec['table'],
                hash_attribute=spec['hash_attribute']),
            self.TABLE_CREATED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_describe_table(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'describe_table',
            'schema': 'test_schema',
            'table': 'test_table',
        }
        # mock the server response to describe_table
        responses.add(
            'POST',
            self.URL,
            json=self.DESCRIBE_TABLE,
            status=200)

        self.assertEqual(
            self.db._describe_table(spec['schema'], spec['table']),
            self.DESCRIBE_TABLE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_describe_all(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'describe_all'
        }
        # mock the server response to describe_all
        responses.add(
            'POST',
            self.URL,
            json=self.DESCRIBE_ALL,
            status=200)

        self.assertEqual(self.db._describe_all(), self.DESCRIBE_ALL)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_table(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'drop_table',
            'schema': 'test_schema',
            'table': 'test_table',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.TABLE_DROPPED,
            status=200)

        self.assertEqual(
            self.db._drop_table(schema=spec['schema'], table=spec['table']),
            self.TABLE_DROPPED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_attribute(self):
        """ Drop an attribute from a table.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'drop_attribute',
            'schema': 'test_schema',
            'table': 'test_table',
            'attribute': 'test_attribute',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.DROP_ATTRIBUTE,
            status=200)

        self.assertEqual(
            self.db._drop_attribute(
                schema=spec['schema'],
                table=spec['table'],
                attribute=spec['attribute']),
            self.DROP_ATTRIBUTE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_insert(self):
        """ Insert a list of records.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'insert',
            'schema': 'test_schema',
            'table': 'test_table',
            'records': [
                {
                    'id': 1,
                    'name': 'foo',
                },
            ],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORD_INSERTED,
            status=200)

        self.assertEqual(
            self.db._insert(
                schema=spec['schema'],
                table=spec['table'],
                records=spec['records']),
            self.RECORD_INSERTED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_update(self):
        """ Update a list of records by hash.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'update',
            'schema': 'test_schema',
            'table': 'test_table',
            'records': [
                {
                    'id': 1,
                    'name': 'foo',
                },
            ],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORD_UPSERTED,
            status=200)

        self.assertEqual(
            self.db._update(
                schema=spec['schema'],
                table=spec['table'],
                records=spec['records']),
            self.RECORD_UPSERTED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_delete(self):
        """ Delete a list of records by hash.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'delete',
            'schema': 'test_schema',
            'table': 'test_table',
            'hash_values': ['1'],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORDS_DELETED,
            status=200)

        self.assertEqual(
            self.db._delete(
                schema=spec['schema'],
                table=spec['table'],
                hash_values=spec['hash_values']),
            self.RECORDS_DELETED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_search_by_hash(self):
        """ Search records by hash value.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'search_by_hash',
            'schema': 'test_schema',
            'table': 'test_table',
            'hash_values': ['1'],
            'get_attributes': ['*'],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORDS,
            status=200)

        self.assertEqual(
            self.db._search_by_hash(
                schema=spec['schema'],
                table=spec['table'],
                hash_values=spec['hash_values']),
            self.RECORDS)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        # test optional get_attributes
        spec['get_attributes'] = ['foo', 'bar']
        self.db._search_by_hash(
            schema=spec['schema'],
            table=spec['table'],
            hash_values=spec['hash_values'],
            get_attributes=spec['get_attributes'])

        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)

    @responses.activate
    def test_search_by_value(self):
        """ Search records by value.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'search_by_value',
            'schema': 'test_schema',
            'table': 'test_table',
            'search_attribute': 'name',
            'search_value': 'foo',
            'get_attributes': ['*'],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORDS,
            status=200)

        self.assertEqual(
            self.db._search_by_value(
                schema=spec['schema'],
                table=spec['table'],
                search_attribute=spec['search_attribute'],
                search_value=spec['search_value']),
            self.RECORDS)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        # test optional get_attributes
        spec['get_attributes'] = ['foo', 'bar']
        self.db._search_by_value(
            schema=spec['schema'],
            table=spec['table'],
            search_attribute=spec['search_attribute'],
            search_value=spec['search_value'],
            get_attributes=spec['get_attributes'])

        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)

    @responses.activate
    def test_sql(self):
        """ Accept SQL strings.
        """
        sql_string = \
            'INSERT INTO test_schema.test_table (id, name) VALUES(1, \"foo\")'
        # define the expected JSON body in POST request
        spec = {
            'operation': 'sql',
            'sql': sql_string,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORD_INSERTED,
            status=200)

        self.assertEqual(self.db._sql(sql_string), self.RECORD_INSERTED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_data_load(self):
        """ Records are inserted from a CSV file path.
        """
        spec = {
            'operation': 'csv_data_load',
            'action': 'insert',
            'schema': 'test_schema',
            'table': 'test_table',
            'data': self.CSV_STRING,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_data_load(
                schema=spec['schema'],
                table=spec['table'],
                path='tests/test.csv'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_data_load_update(self):
        """ Records are updated from a CSV file path.
        """
        spec = {
            'operation': 'csv_data_load',
            'action': 'update',
            'schema': 'test_schema',
            'table': 'test_table',
            'data': self.CSV_STRING,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_data_load(
                schema=spec['schema'],
                table=spec['table'],
                path='tests/test.csv',
                action='update'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_file_load(self):
        """ Records are inserted from a CSV file path on the HarperDB host.
        """
        spec = {
            'operation': 'csv_file_load',
            'action': 'insert',
            'schema': 'test_schema',
            'table': 'test_table',
            'file_path': 'path/to/file/on/host.csv',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_file_load(
                schema=spec['schema'],
                table=spec['table'],
                file_path=spec['file_path']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_file_load_update(self):
        """ Records are updated from a CSV file path on the HarperDB host.
        """
        spec = {
            'operation': 'csv_file_load',
            'action': 'update',
            'schema': 'test_schema',
            'table': 'test_table',
            'file_path': 'path/to/file/on/host.csv',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_file_load(
                schema=spec['schema'],
                table=spec['table'],
                file_path=spec['file_path'],
                action='update'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_url_load(self):
        """ Records are inserted from a CSV file at a URL.
        """
        spec = {
            'operation': 'csv_url_load',
            'action': 'insert',
            'schema': 'test_schema',
            'table': 'test_table',
            'csv_url': 'example.com/test.csv',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_url_load(
                schema=spec['schema'],
                table=spec['table'],
                csv_url=spec['csv_url']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_url_load_update(self):
        """ Records are updated from a CSV file at a URL.
        """
        spec = {
            'operation': 'csv_url_load',
            'action': 'update',
            'schema': 'test_schema',
            'table': 'test_table',
            'csv_url': 'example.com/test.csv',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_url_load(
                schema=spec['schema'],
                table=spec['table'],
                csv_url=spec['csv_url'],
                action='update'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_get_job(self):
        """ Returns a job dictionary from an id.
        """
        spec = {
            'operation': 'get_job',
            'id': 'aUniqueID',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.GET_JOB,
            status=200)

        self.assertEqual(
            self.db._get_job(spec['id']),
            self.GET_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_search_jobs_by_start_date(self):
        """ Get an array of jobs by date range.
        """
        spec = {
            'operation': 'search_jobs_by_start_date',
            'from_date': '2020-01-01',
            'to_date': '2020-02-01',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.SEARCH_JOB,
            status=200)

        self.assertEqual(
            self.db._search_jobs_by_start_date(
                from_date=spec['from_date'],
                to_date=spec['to_date']
            ),
            self.SEARCH_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_add_user(self):
        """ Add a user.
        """
        spec = {
            'operation': 'add_user',
            'role': 'aUniqueID',
            'username': 'user',
            'password': '<PASSWORD>',
            'active': True,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.USER_ADDED,
            status=200)

        self.assertEqual(
            self.db._add_user(
                role=spec['role'],
                username=spec['username'],
                password=spec['password']),
            self.USER_ADDED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_alter_user(self):
        """ Alter a user.
        """
        spec = {
            'operation': 'alter_user',
            'role': 'aUniqueID',
            'username': 'user',
            'password': '<PASSWORD>',
            'active': False,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.USER_ALTERED,
            status=200)

        self.assertEqual(
            self.db._alter_user(
                role=spec['role'],
                username=spec['username'],
                password=spec['password'],
                active=False),
            self.USER_ALTERED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_user(self):
        """ Drop a user.
        """
        spec = {
            'operation': 'drop_user',
            'username': 'user',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.USER_DROPPED,
            status=200)

        self.assertEqual(
            self.db._drop_user(username=spec['username']),
            self.USER_DROPPED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_user_info(self):
        """ Get user info.
        """
        spec = {
            'operation': 'user_info',
            'username': 'user',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.USER_INFO,
            status=200)

        self.assertEqual(
            self.db._user_info(username=spec['username']),
            self.USER_INFO)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_list_users(self):
        """ List users.
        """
        spec = {
            'operation': 'list_users',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.LIST_USERS,
            status=200)

        self.assertEqual(
            self.db._list_users(),
            self.LIST_USERS)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_add_role(self):
        """ Add a role.
        """
        spec = {
            'operation': 'add_role',
            'role': 'developer',
            'permission': {
                'super_user': False,
                'dev': {
                    'tables': {
                        'dog': {
                            'read': True,
                            'insert': True,
                            'update': True,
                            'delete': False,
                            'attribute_restrictions': [],
                        }
                    }
                }
            }
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.ADD_ROLE,
            status=200)

        self.assertEqual(
            self.db._add_role(role='developer', permission=spec['permission']),
            self.ADD_ROLE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_alter_role(self):
        """ Add a role.
        """
        spec = {
            'operation': 'alter_role',
            'id': 'aUniqueID',
            'permission': {
                'super_user': False,
                'dev': {
                    'tables': {
                        'dog': {
                            'read': True,
                            'insert': True,
                            'update': True,
                            'delete': False,
                            'attribute_restrictions': []
                        }
                    }
                }
            }
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.ALTER_ROLE,
            status=200)

        self.assertEqual(
            self.db._alter_role(id='aUniqueID', permission=spec['permission']),
            self.ALTER_ROLE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_role(self):
        """ Drop a role.
        """
        spec = {
            'operation': 'drop_role',
            'id': 'aUniqueID',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.DROP_ROLE,
            status=200)

        self.assertEqual(
            self.db._drop_role(id='aUniqueID'),
            self.DROP_ROLE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_list_roles(self):
        """List Roles.
        """
        spec = {
            'operation': 'list_roles',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.LIST_ROLES,
            status=200)

        self.assertEqual(
            self.db._list_roles(),
            self.LIST_ROLES)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_add_node(self):
        """ Add a node to a cluster.
        """
        spec = {
            'operation': 'add_node',
            'name': 'anotherNode',
            'host': 'hostname',
            'port': 31415,
            'subscriptions': [
                {
                    'channel': 'dev:dog',
                    'subscribe': False,
                    'publish': True,
                },
            ]
        }
        responses.add(
            'POST',
            self.URL,
            json=self.NODE_ADDED,
            status=200)

        self.assertEqual(
            self.db._add_node(
                name=spec['name'],
                host=spec['host'],
                port=spec['port'],
                subscriptions=spec['subscriptions'],),
            self.NODE_ADDED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_update_node(self):
        """ Update a node in a cluster.
        """
        spec = {
            'operation': 'update_node',
            'name': 'anotherNode',
            'host': 'hostname',
            'port': 31415,
            'subscriptions': [
                {
                    'channel': 'dev:dog',
                    'subscribe': False,
                    'publish': True,
                },
            ]
        }
        responses.add(
            'POST',
            self.URL,
            json=self.NODE_ADDED,
            status=200)

        self.assertEqual(
            self.db._update_node(
                name=spec['name'],
                host=spec['host'],
                port=spec['port'],
                subscriptions=spec['subscriptions'],),
            self.NODE_ADDED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_remove_node(self):
        """ Remove a node from a cluster.
        """
        spec = {
            'operation': 'remove_node',
            'name': 'anotherNode',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.NODE_REMOVED,
            status=200)

        self.assertEqual(
            self.db._remove_node(name=spec['name']),
            self.NODE_REMOVED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_cluster_status(self):
        """ Retrieve cluster status.
        """
        spec = {
            'operation': 'cluster_status',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.CLUSTER_STATUS,
            status=200)

        self.assertEqual(self.db._cluster_status(), self.CLUSTER_STATUS)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_registration_info(self):
        """ Retrieve registration info.
        """
        spec = {
            'operation': 'registration_info',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.REGISTRATION,
            status=200)

        self.assertEqual(self.db._registration_info(), self.REGISTRATION)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_get_fingerprint(self):
        """ Retrieve fingerprint.
        """
        spec = {
            'operation': 'get_fingerprint',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.FINGERPRINT,
            status=200)

        self.assertEqual(self.db._get_fingerprint(), self.FINGERPRINT)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_set_license(self):
        """ Set license.
        """
        spec = {
            'operation': 'set_license',
            'key': 'myLicenseKey',
            'company': 'myCompany',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.SET_LICENSE,
            status=200)

        self.assertEqual(
            self.db._set_license(spec['key'], spec['company']),
            self.SET_LICENSE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_delete_files_before(self):
        """ Delete records before a given date.
        """
        spec = {
            'operation': 'delete_files_before',
            'date': '2018-07-10',
            'schema': 'dev',
            'table': 'dog',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORDS_DELETED,
            status=200)

        self.assertEqual(
            self.db._delete_files_before(
                date=spec['date'],
                schema=spec['schema'],
                table=spec['table']),
            self.RECORDS_DELETED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_export_local(self):
        """ Export records to server file system.
        """
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        spec = {
            'operation': 'export_local',
            'format': 'json',
            'path': 'path/to/data',
            'search_operation': {
                'operation': 'search_by_hash',
                'hash_values': ['uniqueHash'],
            },
        }
        self.assertEqual(
            self.db._export_local(
                path=spec['path'],
                hash_values=['uniqueHash']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        # test keyword args
        spec['format'] = 'csv'
        spec['search_operation'] = {
            'operation': 'search_by_value',
            'search_attribute': 'pi',
            'search_value': 3.14,
        }
        self.assertEqual(
            self.db._export_local(
                path=spec['path'],
                search_attribute='pi',
                search_value=3.14,
                format='csv'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)
        # more keyword args
        spec['format'] = 'json'
        spec['search_operation'] = {
            'operation': 'sql',
            'sql': 'my SQL string',
        }
        self.assertEqual(
            self.db._export_local(
                path=spec['path'],
                sql=spec['search_operation']['sql']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 3)

    @responses.activate
    def test_export_to_s3(self):
        """ Export records to an Amazon S3 bucket.
        """
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        spec = {
            'operation': 'export_to_s3',
            'format': 'json',
            'search_operation': {
                'operation': 'search_by_hash',
                'hash_values': ['uniqueHash'],
            },
            's3': {
                'aws_access_key_id': 'myKey',
                'aws_secret_access_key': 'mySecretKey',
                'bucket': 'myBucket',
                'key': 'KEY',
            },
        }
        self.assertEqual(
            self.db._export_to_s3(
                aws_access_key='myKey',
                aws_secret_access_key='mySecretKey',
                bucket='myBucket',
                key='KEY',
                hash_values=['uniqueHash']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        spec = {
            'operation': 'export_to_s3',
            'format': 'csv',
            'search_operation': {
                'operation': 'search_by_value',
                'search_attribute': 'pi',
                'search_value': 3.14,
            },
            's3': {
                'aws_access_key_id': 'myKey',
                'aws_secret_access_key': 'mySecretKey',
                'bucket': 'myBucket',
                'key': 'KEY',
            },
        }
        self.assertEqual(
            self.db._export_to_s3(
                aws_access_key='myKey',
                aws_secret_access_key='mySecretKey',
                bucket='myBucket',
                key='KEY',
                search_attribute='pi',
                search_value=3.14,
                format='csv'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)

        spec = {
            'operation': 'export_to_s3',
            'format': 'json',
            'search_operation': {
                'operation': 'sql',
                'sql': 'my SQL string',
            },
            's3': {
                'aws_access_key_id': 'myKey',
                'aws_secret_access_key': 'mySecretKey',
                'bucket': 'myBucket',
                'key': 'KEY',
            },
        }
        self.assertEqual(
            self.db._export_to_s3(
                aws_access_key='myKey',
                aws_secret_access_key='mySecretKey',
                bucket='myBucket',
                key='KEY',
                sql=spec['search_operation']['sql']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 3)

    @responses.activate
    def test_read_log(self):
        """ Read the server log.
        """
        spec = {
            'operation': 'read_log',
            'limit': 1000,
            'start': 0,
            'from': None,
            'until': None,
            'order': 'desc'
        }
        responses.add(
            'POST',
            self.URL,
            json=self.READ_LOG,
            status=200)

        self.assertEqual(self.db._read_log(), self.READ_LOG)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        # test keyword args
        spec = {
            'operation': 'read_log',
            'limit': 1001,
            'start': 1,
            'from': "2020-01-01",
            'until': "2020-02-01",
            'order': 'asc'
        }

        self.assertEqual(
            self.db._read_log(
                limit=spec['limit'],
                start=spec['start'],
                from_date=spec['from'],
                to_date=spec['until'],
                order=spec['order']),
            self.READ_LOG)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)

    @responses.activate
    def test_system_information(self):
        """ Retrieve system information.
        """
        spec = {
            'operation': 'system_information',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.SYSTEM_INFORMATION,
            status=200)

        self.assertEqual(
            self.db._system_information(),
            self.SYSTEM_INFORMATION)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

import responses
import unittest

import harperdb
import harperdb_testcase


class TestHarperDBBase(harperdb_testcase.HarperDBTestCase):

    def setUp(self):
        """ This method is called before each test.
        """
        self.db = harperdb.HarperDBBase(self.URL)

    @unittest.mock.patch('base64.b64encode')
    def test_create_harperdb_base_with_kwargs(self, mock_b64encode):
        """ Create an instance of HarperDBBase with keyword args.
        """
        # by mocking the base64 module we can define what it returns,
        # so it's very easy to check the value stored in db.token
        mock_b64encode.return_value = b'anArbitraryBase64String'

        db = harperdb.HarperDBBase(
            self.URL,
            self.USERNAME,
            self.PASSWORD,
            timeout=3)
        mock_b64encode.assert_called_once_with(
            '{}:{}'.format(self.USERNAME, self.PASSWORD).encode('utf-8'))
        self.assertEqual(db.url, self.URL)
        self.assertEqual(
            db.token,
            'Basic {}'.format(mock_b64encode.return_value.decode('utf-8')))
        self.assertEqual(db.timeout, 3)

    @responses.activate
    def test_create_schema(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'create_schema',
            'schema': 'test_schema',
        }
        # mock the server response to create_schema
        responses.add(
            'POST',
            self.URL,
            json=self.SCHEMA_CREATED,
            status=200)

        self.assertEqual(
            self.db._create_schema(spec['schema']),
            self.SCHEMA_CREATED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_schema(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'drop_schema',
            'schema': 'test_schema',
        }
        # mock the server response to drop_schema
        responses.add(
            'POST',
            self.URL,
            json=self.SCHEMA_DROPPED,
            status=200)

        self.assertEqual(
            self.db._drop_schema(spec['schema']),
            self.SCHEMA_DROPPED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_describe_schema(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'describe_schema',
            'schema': 'test_schema',
        }
        # mock the server response to describe_schema
        responses.add(
            'POST',
            self.URL,
            json=self.DESCRIBE_SCHEMA_1,
            status=200)

        self.assertEqual(
            self.db._describe_schema(spec['schema']),
            self.DESCRIBE_SCHEMA_1)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_create_table(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'create_table',
            'schema': 'test_schema',
            'table': 'test_table',
            'hash_attribute': 'id',
        }
        # mock the server response to drop_schema
        responses.add(
            'POST',
            self.URL,
            json=self.TABLE_CREATED,
            status=200)

        self.assertEqual(
            self.db._create_table(
                schema=spec['schema'],
                table=spec['table'],
                hash_attribute=spec['hash_attribute']),
            self.TABLE_CREATED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_describe_table(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'describe_table',
            'schema': 'test_schema',
            'table': 'test_table',
        }
        # mock the server response to describe_table
        responses.add(
            'POST',
            self.URL,
            json=self.DESCRIBE_TABLE,
            status=200)

        self.assertEqual(
            self.db._describe_table(spec['schema'], spec['table']),
            self.DESCRIBE_TABLE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_describe_all(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'describe_all'
        }
        # mock the server response to describe_all
        responses.add(
            'POST',
            self.URL,
            json=self.DESCRIBE_ALL,
            status=200)

        self.assertEqual(self.db._describe_all(), self.DESCRIBE_ALL)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_table(self):
        # define the expected JSON body in POST request
        spec = {
            'operation': 'drop_table',
            'schema': 'test_schema',
            'table': 'test_table',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.TABLE_DROPPED,
            status=200)

        self.assertEqual(
            self.db._drop_table(schema=spec['schema'], table=spec['table']),
            self.TABLE_DROPPED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_attribute(self):
        """ Drop an attribute from a table.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'drop_attribute',
            'schema': 'test_schema',
            'table': 'test_table',
            'attribute': 'test_attribute',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.DROP_ATTRIBUTE,
            status=200)

        self.assertEqual(
            self.db._drop_attribute(
                schema=spec['schema'],
                table=spec['table'],
                attribute=spec['attribute']),
            self.DROP_ATTRIBUTE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_insert(self):
        """ Insert a list of records.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'insert',
            'schema': 'test_schema',
            'table': 'test_table',
            'records': [
                {
                    'id': 1,
                    'name': 'foo',
                },
            ],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORD_INSERTED,
            status=200)

        self.assertEqual(
            self.db._insert(
                schema=spec['schema'],
                table=spec['table'],
                records=spec['records']),
            self.RECORD_INSERTED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_update(self):
        """ Update a list of records by hash.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'update',
            'schema': 'test_schema',
            'table': 'test_table',
            'records': [
                {
                    'id': 1,
                    'name': 'foo',
                },
            ],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORD_UPSERTED,
            status=200)

        self.assertEqual(
            self.db._update(
                schema=spec['schema'],
                table=spec['table'],
                records=spec['records']),
            self.RECORD_UPSERTED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_delete(self):
        """ Delete a list of records by hash.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'delete',
            'schema': 'test_schema',
            'table': 'test_table',
            'hash_values': ['1'],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORDS_DELETED,
            status=200)

        self.assertEqual(
            self.db._delete(
                schema=spec['schema'],
                table=spec['table'],
                hash_values=spec['hash_values']),
            self.RECORDS_DELETED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_search_by_hash(self):
        """ Search records by hash value.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'search_by_hash',
            'schema': 'test_schema',
            'table': 'test_table',
            'hash_values': ['1'],
            'get_attributes': ['*'],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORDS,
            status=200)

        self.assertEqual(
            self.db._search_by_hash(
                schema=spec['schema'],
                table=spec['table'],
                hash_values=spec['hash_values']),
            self.RECORDS)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        # test optional get_attributes
        spec['get_attributes'] = ['foo', 'bar']
        self.db._search_by_hash(
            schema=spec['schema'],
            table=spec['table'],
            hash_values=spec['hash_values'],
            get_attributes=spec['get_attributes'])

        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)

    @responses.activate
    def test_search_by_value(self):
        """ Search records by value.
        """
        # define the expected JSON body in POST request
        spec = {
            'operation': 'search_by_value',
            'schema': 'test_schema',
            'table': 'test_table',
            'search_attribute': 'name',
            'search_value': 'foo',
            'get_attributes': ['*'],
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORDS,
            status=200)

        self.assertEqual(
            self.db._search_by_value(
                schema=spec['schema'],
                table=spec['table'],
                search_attribute=spec['search_attribute'],
                search_value=spec['search_value']),
            self.RECORDS)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        # test optional get_attributes
        spec['get_attributes'] = ['foo', 'bar']
        self.db._search_by_value(
            schema=spec['schema'],
            table=spec['table'],
            search_attribute=spec['search_attribute'],
            search_value=spec['search_value'],
            get_attributes=spec['get_attributes'])

        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)

    @responses.activate
    def test_sql(self):
        """ Accept SQL strings.
        """
        sql_string = \
            'INSERT INTO test_schema.test_table (id, name) VALUES(1, \"foo\")'
        # define the expected JSON body in POST request
        spec = {
            'operation': 'sql',
            'sql': sql_string,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORD_INSERTED,
            status=200)

        self.assertEqual(self.db._sql(sql_string), self.RECORD_INSERTED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_data_load(self):
        """ Records are inserted from a CSV file path.
        """
        spec = {
            'operation': 'csv_data_load',
            'action': 'insert',
            'schema': 'test_schema',
            'table': 'test_table',
            'data': self.CSV_STRING,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_data_load(
                schema=spec['schema'],
                table=spec['table'],
                path='tests/test.csv'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_data_load_update(self):
        """ Records are updated from a CSV file path.
        """
        spec = {
            'operation': 'csv_data_load',
            'action': 'update',
            'schema': 'test_schema',
            'table': 'test_table',
            'data': self.CSV_STRING,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_data_load(
                schema=spec['schema'],
                table=spec['table'],
                path='tests/test.csv',
                action='update'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_file_load(self):
        """ Records are inserted from a CSV file path on the HarperDB host.
        """
        spec = {
            'operation': 'csv_file_load',
            'action': 'insert',
            'schema': 'test_schema',
            'table': 'test_table',
            'file_path': 'path/to/file/on/host.csv',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_file_load(
                schema=spec['schema'],
                table=spec['table'],
                file_path=spec['file_path']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_file_load_update(self):
        """ Records are updated from a CSV file path on the HarperDB host.
        """
        spec = {
            'operation': 'csv_file_load',
            'action': 'update',
            'schema': 'test_schema',
            'table': 'test_table',
            'file_path': 'path/to/file/on/host.csv',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_file_load(
                schema=spec['schema'],
                table=spec['table'],
                file_path=spec['file_path'],
                action='update'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_url_load(self):
        """ Records are inserted from a CSV file at a URL.
        """
        spec = {
            'operation': 'csv_url_load',
            'action': 'insert',
            'schema': 'test_schema',
            'table': 'test_table',
            'csv_url': 'example.com/test.csv',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_url_load(
                schema=spec['schema'],
                table=spec['table'],
                csv_url=spec['csv_url']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_csv_url_load_update(self):
        """ Records are updated from a CSV file at a URL.
        """
        spec = {
            'operation': 'csv_url_load',
            'action': 'update',
            'schema': 'test_schema',
            'table': 'test_table',
            'csv_url': 'example.com/test.csv',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        self.assertEqual(
            self.db._csv_url_load(
                schema=spec['schema'],
                table=spec['table'],
                csv_url=spec['csv_url'],
                action='update'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_get_job(self):
        """ Returns a job dictionary from an id.
        """
        spec = {
            'operation': 'get_job',
            'id': 'aUniqueID',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.GET_JOB,
            status=200)

        self.assertEqual(
            self.db._get_job(spec['id']),
            self.GET_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_search_jobs_by_start_date(self):
        """ Get an array of jobs by date range.
        """
        spec = {
            'operation': 'search_jobs_by_start_date',
            'from_date': '2020-01-01',
            'to_date': '2020-02-01',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.SEARCH_JOB,
            status=200)

        self.assertEqual(
            self.db._search_jobs_by_start_date(
                from_date=spec['from_date'],
                to_date=spec['to_date']
            ),
            self.SEARCH_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_add_user(self):
        """ Add a user.
        """
        spec = {
            'operation': 'add_user',
            'role': 'aUniqueID',
            'username': 'user',
            'password': '<PASSWORD>',
            'active': True,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.USER_ADDED,
            status=200)

        self.assertEqual(
            self.db._add_user(
                role=spec['role'],
                username=spec['username'],
                password=spec['password']),
            self.USER_ADDED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_alter_user(self):
        """ Alter a user.
        """
        spec = {
            'operation': 'alter_user',
            'role': 'aUniqueID',
            'username': 'user',
            'password': '<PASSWORD>',
            'active': False,
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.USER_ALTERED,
            status=200)

        self.assertEqual(
            self.db._alter_user(
                role=spec['role'],
                username=spec['username'],
                password=spec['password'],
                active=False),
            self.USER_ALTERED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_user(self):
        """ Drop a user.
        """
        spec = {
            'operation': 'drop_user',
            'username': 'user',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.USER_DROPPED,
            status=200)

        self.assertEqual(
            self.db._drop_user(username=spec['username']),
            self.USER_DROPPED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_user_info(self):
        """ Get user info.
        """
        spec = {
            'operation': 'user_info',
            'username': 'user',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.USER_INFO,
            status=200)

        self.assertEqual(
            self.db._user_info(username=spec['username']),
            self.USER_INFO)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_list_users(self):
        """ List users.
        """
        spec = {
            'operation': 'list_users',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.LIST_USERS,
            status=200)

        self.assertEqual(
            self.db._list_users(),
            self.LIST_USERS)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_add_role(self):
        """ Add a role.
        """
        spec = {
            'operation': 'add_role',
            'role': 'developer',
            'permission': {
                'super_user': False,
                'dev': {
                    'tables': {
                        'dog': {
                            'read': True,
                            'insert': True,
                            'update': True,
                            'delete': False,
                            'attribute_restrictions': [],
                        }
                    }
                }
            }
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.ADD_ROLE,
            status=200)

        self.assertEqual(
            self.db._add_role(role='developer', permission=spec['permission']),
            self.ADD_ROLE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_alter_role(self):
        """ Add a role.
        """
        spec = {
            'operation': 'alter_role',
            'id': 'aUniqueID',
            'permission': {
                'super_user': False,
                'dev': {
                    'tables': {
                        'dog': {
                            'read': True,
                            'insert': True,
                            'update': True,
                            'delete': False,
                            'attribute_restrictions': []
                        }
                    }
                }
            }
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.ALTER_ROLE,
            status=200)

        self.assertEqual(
            self.db._alter_role(id='aUniqueID', permission=spec['permission']),
            self.ALTER_ROLE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_drop_role(self):
        """ Drop a role.
        """
        spec = {
            'operation': 'drop_role',
            'id': 'aUniqueID',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.DROP_ROLE,
            status=200)

        self.assertEqual(
            self.db._drop_role(id='aUniqueID'),
            self.DROP_ROLE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_list_roles(self):
        """List Roles.
        """
        spec = {
            'operation': 'list_roles',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.LIST_ROLES,
            status=200)

        self.assertEqual(
            self.db._list_roles(),
            self.LIST_ROLES)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_add_node(self):
        """ Add a node to a cluster.
        """
        spec = {
            'operation': 'add_node',
            'name': 'anotherNode',
            'host': 'hostname',
            'port': 31415,
            'subscriptions': [
                {
                    'channel': 'dev:dog',
                    'subscribe': False,
                    'publish': True,
                },
            ]
        }
        responses.add(
            'POST',
            self.URL,
            json=self.NODE_ADDED,
            status=200)

        self.assertEqual(
            self.db._add_node(
                name=spec['name'],
                host=spec['host'],
                port=spec['port'],
                subscriptions=spec['subscriptions'],),
            self.NODE_ADDED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_update_node(self):
        """ Update a node in a cluster.
        """
        spec = {
            'operation': 'update_node',
            'name': 'anotherNode',
            'host': 'hostname',
            'port': 31415,
            'subscriptions': [
                {
                    'channel': 'dev:dog',
                    'subscribe': False,
                    'publish': True,
                },
            ]
        }
        responses.add(
            'POST',
            self.URL,
            json=self.NODE_ADDED,
            status=200)

        self.assertEqual(
            self.db._update_node(
                name=spec['name'],
                host=spec['host'],
                port=spec['port'],
                subscriptions=spec['subscriptions'],),
            self.NODE_ADDED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_remove_node(self):
        """ Remove a node from a cluster.
        """
        spec = {
            'operation': 'remove_node',
            'name': 'anotherNode',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.NODE_REMOVED,
            status=200)

        self.assertEqual(
            self.db._remove_node(name=spec['name']),
            self.NODE_REMOVED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_cluster_status(self):
        """ Retrieve cluster status.
        """
        spec = {
            'operation': 'cluster_status',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.CLUSTER_STATUS,
            status=200)

        self.assertEqual(self.db._cluster_status(), self.CLUSTER_STATUS)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_registration_info(self):
        """ Retrieve registration info.
        """
        spec = {
            'operation': 'registration_info',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.REGISTRATION,
            status=200)

        self.assertEqual(self.db._registration_info(), self.REGISTRATION)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_get_fingerprint(self):
        """ Retrieve fingerprint.
        """
        spec = {
            'operation': 'get_fingerprint',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.FINGERPRINT,
            status=200)

        self.assertEqual(self.db._get_fingerprint(), self.FINGERPRINT)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_set_license(self):
        """ Set license.
        """
        spec = {
            'operation': 'set_license',
            'key': 'myLicenseKey',
            'company': 'myCompany',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.SET_LICENSE,
            status=200)

        self.assertEqual(
            self.db._set_license(spec['key'], spec['company']),
            self.SET_LICENSE)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_delete_files_before(self):
        """ Delete records before a given date.
        """
        spec = {
            'operation': 'delete_files_before',
            'date': '2018-07-10',
            'schema': 'dev',
            'table': 'dog',
        }
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.RECORDS_DELETED,
            status=200)

        self.assertEqual(
            self.db._delete_files_before(
                date=spec['date'],
                schema=spec['schema'],
                table=spec['table']),
            self.RECORDS_DELETED)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

    @responses.activate
    def test_export_local(self):
        """ Export records to server file system.
        """
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        spec = {
            'operation': 'export_local',
            'format': 'json',
            'path': 'path/to/data',
            'search_operation': {
                'operation': 'search_by_hash',
                'hash_values': ['uniqueHash'],
            },
        }
        self.assertEqual(
            self.db._export_local(
                path=spec['path'],
                hash_values=['uniqueHash']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        # test keyword args
        spec['format'] = 'csv'
        spec['search_operation'] = {
            'operation': 'search_by_value',
            'search_attribute': 'pi',
            'search_value': 3.14,
        }
        self.assertEqual(
            self.db._export_local(
                path=spec['path'],
                search_attribute='pi',
                search_value=3.14,
                format='csv'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)
        # more keyword args
        spec['format'] = 'json'
        spec['search_operation'] = {
            'operation': 'sql',
            'sql': 'my SQL string',
        }
        self.assertEqual(
            self.db._export_local(
                path=spec['path'],
                sql=spec['search_operation']['sql']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 3)

    @responses.activate
    def test_export_to_s3(self):
        """ Export records to an Amazon S3 bucket.
        """
        # mock the server response
        responses.add(
            'POST',
            self.URL,
            json=self.START_JOB,
            status=200)

        spec = {
            'operation': 'export_to_s3',
            'format': 'json',
            'search_operation': {
                'operation': 'search_by_hash',
                'hash_values': ['uniqueHash'],
            },
            's3': {
                'aws_access_key_id': 'myKey',
                'aws_secret_access_key': 'mySecretKey',
                'bucket': 'myBucket',
                'key': 'KEY',
            },
        }
        self.assertEqual(
            self.db._export_to_s3(
                aws_access_key='myKey',
                aws_secret_access_key='mySecretKey',
                bucket='myBucket',
                key='KEY',
                hash_values=['uniqueHash']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        spec = {
            'operation': 'export_to_s3',
            'format': 'csv',
            'search_operation': {
                'operation': 'search_by_value',
                'search_attribute': 'pi',
                'search_value': 3.14,
            },
            's3': {
                'aws_access_key_id': 'myKey',
                'aws_secret_access_key': 'mySecretKey',
                'bucket': 'myBucket',
                'key': 'KEY',
            },
        }
        self.assertEqual(
            self.db._export_to_s3(
                aws_access_key='myKey',
                aws_secret_access_key='mySecretKey',
                bucket='myBucket',
                key='KEY',
                search_attribute='pi',
                search_value=3.14,
                format='csv'),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)

        spec = {
            'operation': 'export_to_s3',
            'format': 'json',
            'search_operation': {
                'operation': 'sql',
                'sql': 'my SQL string',
            },
            's3': {
                'aws_access_key_id': 'myKey',
                'aws_secret_access_key': 'mySecretKey',
                'bucket': 'myBucket',
                'key': 'KEY',
            },
        }
        self.assertEqual(
            self.db._export_to_s3(
                aws_access_key='myKey',
                aws_secret_access_key='mySecretKey',
                bucket='myBucket',
                key='KEY',
                sql=spec['search_operation']['sql']),
            self.START_JOB)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 3)

    @responses.activate
    def test_read_log(self):
        """ Read the server log.
        """
        spec = {
            'operation': 'read_log',
            'limit': 1000,
            'start': 0,
            'from': None,
            'until': None,
            'order': 'desc'
        }
        responses.add(
            'POST',
            self.URL,
            json=self.READ_LOG,
            status=200)

        self.assertEqual(self.db._read_log(), self.READ_LOG)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)

        # test keyword args
        spec = {
            'operation': 'read_log',
            'limit': 1001,
            'start': 1,
            'from': "2020-01-01",
            'until': "2020-02-01",
            'order': 'asc'
        }

        self.assertEqual(
            self.db._read_log(
                limit=spec['limit'],
                start=spec['start'],
                from_date=spec['from'],
                to_date=spec['until'],
                order=spec['order']),
            self.READ_LOG)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 2)

    @responses.activate
    def test_system_information(self):
        """ Retrieve system information.
        """
        spec = {
            'operation': 'system_information',
        }
        responses.add(
            'POST',
            self.URL,
            json=self.SYSTEM_INFORMATION,
            status=200)

        self.assertEqual(
            self.db._system_information(),
            self.SYSTEM_INFORMATION)
        self.assertLastRequestMatchesSpec(spec)
        self.assertEqual(len(responses.calls), 1)
This method is called before each test. Create an instance of HarperDBBase with keyword args. # by mocking the base64 module we can define what it returns, # so it's very easy to check the value stored in db.token # define the expected JSON body in POST request # mock the server response to create_schema # define the expected JSON body in POST request # mock the server response to drop_schema # define the expected JSON body in POST request # mock the server response to describe_schema # define the expected JSON body in POST request # mock the server response to drop_schema # define the expected JSON body in POST request # mock the server response to describe_table # define the expected JSON body in POST request # mock the server response to describe_all # define the expected JSON body in POST request # mock the server response Drop an attribute from a table. # define the expected JSON body in POST request # mock the server response Insert a list of records. # define the expected JSON body in POST request # mock the server response Update a list of records by hash. # define the expected JSON body in POST request # mock the server response Delete a list of records by hash. # define the expected JSON body in POST request # mock the server response Search records by hash value. # define the expected JSON body in POST request # mock the server response # test optional get_attributes Search records by value. # define the expected JSON body in POST request # mock the server response # test optional get_attributes Accept SQL strings. # define the expected JSON body in POST request # mock the server response Records are inserted from a CSV file path. # mock the server response Records are updated from a CSV file path. # mock the server response Records are inserted from a CSV file path on the HarperDB host. # mock the server response Records are updated from a CSV file path on the HarperDB host. # mock the server response Records are inserted from a CSV file at a URL. # mock the server response Records are updated from a CSV file at a URL. # mock the server response Returns a job dictionary from an id. # mock the server response Get an array of jobs by date range. # mock the server response Add a user. # mock the server response Alter a user. # mock the server response Drop a user. # mock the server response Get user info. # mock the server response List users. # mock the server response Add a role. # mock the server response Add a role. # mock the server response Drop a role. # mock the server response List Roles. # mock the server response Add a node to a cluster. Update a node in a cluster. Remove a node from a cluster. Retrieve cluster status. Retrieve registration info. Retrieve fingerprint. Set license. Delete records before a given date. # mock the server response Export records to server file system. # mock the server response # test keyword args # more keyword args Export records to an Amazon S3 bucket. # mock the server response Read the server log. # test keyword args Retrieve system information.