From 0ae1ca7e0269708018c7c4c83f801ccbcc21a0cb Mon Sep 17 00:00:00 2001 From: Corbin Robb <31329271+corbinrobb@users.noreply.github.com> Date: Wed, 23 Feb 2022 10:11:10 -0700 Subject: [PATCH] chore(tests-backend): migrate snowflake and sqlite to unit tests (#18854) * migrate snowflake and sqlite * change structure to better match the other unit tests Co-authored-by: Corbin Robb --- .../db_engine_specs/snowflake_tests.py | 106 --------------- .../db_engine_specs/sqlite_tests.py | 78 ----------- .../db_engine_specs/test_snowflake.py | 127 ++++++++++++++++++ .../unit_tests/db_engine_specs/test_sqlite.py | 90 +++++++++++++ 4 files changed, 217 insertions(+), 184 deletions(-) delete mode 100644 tests/integration_tests/db_engine_specs/snowflake_tests.py delete mode 100644 tests/integration_tests/db_engine_specs/sqlite_tests.py create mode 100644 tests/unit_tests/db_engine_specs/test_snowflake.py create mode 100644 tests/unit_tests/db_engine_specs/test_sqlite.py diff --git a/tests/integration_tests/db_engine_specs/snowflake_tests.py b/tests/integration_tests/db_engine_specs/snowflake_tests.py deleted file mode 100644 index 7478056027d93..0000000000000 --- a/tests/integration_tests/db_engine_specs/snowflake_tests.py +++ /dev/null @@ -1,106 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -import json -from unittest import mock - -from superset.db_engine_specs.snowflake import SnowflakeEngineSpec -from superset.errors import ErrorLevel, SupersetError, SupersetErrorType -from superset.models.core import Database -from superset.models.sql_lab import Query -from tests.integration_tests.db_engine_specs.base_tests import TestDbEngineSpec - - -class TestSnowflakeDbEngineSpec(TestDbEngineSpec): - def test_convert_dttm(self): - dttm = self.get_dttm() - - test_cases = { - "DATE": "TO_DATE('2019-01-02')", - "DATETIME": "CAST('2019-01-02T03:04:05.678900' AS DATETIME)", - "TIMESTAMP": "TO_TIMESTAMP('2019-01-02T03:04:05.678900')", - } - - for type_, expected in test_cases.items(): - self.assertEqual(SnowflakeEngineSpec.convert_dttm(type_, dttm), expected) - - def test_database_connection_test_mutator(self): - database = Database(sqlalchemy_uri="snowflake://abc") - SnowflakeEngineSpec.mutate_db_for_connection_test(database) - engine_params = json.loads(database.extra or "{}") - - self.assertDictEqual( - {"engine_params": {"connect_args": {"validate_default_parameters": True}}}, - engine_params, - ) - - def test_extract_errors(self): - msg = "Object dumbBrick does not exist or not authorized." - result = SnowflakeEngineSpec.extract_errors(Exception(msg)) - assert result == [ - SupersetError( - message="dumbBrick does not exist in this database.", - error_type=SupersetErrorType.OBJECT_DOES_NOT_EXIST_ERROR, - level=ErrorLevel.ERROR, - extra={ - "engine_name": "Snowflake", - "issue_codes": [ - { - "code": 1029, - "message": "Issue 1029 - The object does not exist in the given database.", - } - ], - }, - ) - ] - - msg = "syntax error line 1 at position 10 unexpected 'limmmited'." - result = SnowflakeEngineSpec.extract_errors(Exception(msg)) - assert result == [ - SupersetError( - message='Please check your query for syntax errors at or near "limmmited". Then, try running your query again.', - error_type=SupersetErrorType.SYNTAX_ERROR, - level=ErrorLevel.ERROR, - extra={ - "engine_name": "Snowflake", - "issue_codes": [ - { - "code": 1030, - "message": "Issue 1030 - The query has a syntax error.", - } - ], - }, - ) - ] - - @mock.patch("sqlalchemy.engine.Engine.connect") - def test_get_cancel_query_id(self, engine_mock): - query = Query() - cursor_mock = engine_mock.return_value.__enter__.return_value - cursor_mock.fetchone.return_value = [123] - assert SnowflakeEngineSpec.get_cancel_query_id(cursor_mock, query) == 123 - - @mock.patch("sqlalchemy.engine.Engine.connect") - def test_cancel_query(self, engine_mock): - query = Query() - cursor_mock = engine_mock.return_value.__enter__.return_value - assert SnowflakeEngineSpec.cancel_query(cursor_mock, query, 123) is True - - @mock.patch("sqlalchemy.engine.Engine.connect") - def test_cancel_query_failed(self, engine_mock): - query = Query() - cursor_mock = engine_mock.raiseError.side_effect = Exception() - assert SnowflakeEngineSpec.cancel_query(cursor_mock, query, 123) is False diff --git a/tests/integration_tests/db_engine_specs/sqlite_tests.py b/tests/integration_tests/db_engine_specs/sqlite_tests.py deleted file mode 100644 index 7d98b6b6fa78b..0000000000000 --- a/tests/integration_tests/db_engine_specs/sqlite_tests.py +++ /dev/null @@ -1,78 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -from unittest import mock - -from superset.db_engine_specs.sqlite import SqliteEngineSpec -from tests.integration_tests.db_engine_specs.base_tests import TestDbEngineSpec - - -class TestSQliteDbEngineSpec(TestDbEngineSpec): - def test_convert_dttm(self): - dttm = self.get_dttm() - - self.assertEqual( - SqliteEngineSpec.convert_dttm("TEXT", dttm), "'2019-01-02 03:04:05.678900'" - ) - - def test_convert_dttm_lower(self): - dttm = self.get_dttm() - - self.assertEqual( - SqliteEngineSpec.convert_dttm("text", dttm), "'2019-01-02 03:04:05.678900'" - ) - - def test_convert_dttm_invalid_type(self): - dttm = self.get_dttm() - - self.assertEqual(SqliteEngineSpec.convert_dttm("other", dttm), None) - - def test_get_all_datasource_names_table(self): - database = mock.MagicMock() - database.get_all_schema_names.return_value = ["schema1"] - table_names = ["table1", "table2"] - get_tables = mock.MagicMock(return_value=table_names) - database.get_all_table_names_in_schema = get_tables - result = SqliteEngineSpec.get_all_datasource_names(database, "table") - assert result == table_names - get_tables.assert_called_once_with( - schema="schema1", - force=True, - cache=database.table_cache_enabled, - cache_timeout=database.table_cache_timeout, - ) - - def test_get_all_datasource_names_view(self): - database = mock.MagicMock() - database.get_all_schema_names.return_value = ["schema1"] - views_names = ["view1", "view2"] - get_views = mock.MagicMock(return_value=views_names) - database.get_all_view_names_in_schema = get_views - result = SqliteEngineSpec.get_all_datasource_names(database, "view") - assert result == views_names - get_views.assert_called_once_with( - schema="schema1", - force=True, - cache=database.table_cache_enabled, - cache_timeout=database.table_cache_timeout, - ) - - def test_get_all_datasource_names_invalid_type(self): - database = mock.MagicMock() - database.get_all_schema_names.return_value = ["schema1"] - invalid_type = "asdf" - with self.assertRaises(Exception): - SqliteEngineSpec.get_all_datasource_names(database, invalid_type) diff --git a/tests/unit_tests/db_engine_specs/test_snowflake.py b/tests/unit_tests/db_engine_specs/test_snowflake.py new file mode 100644 index 0000000000000..961b92f626147 --- /dev/null +++ b/tests/unit_tests/db_engine_specs/test_snowflake.py @@ -0,0 +1,127 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +import json +from datetime import datetime +from unittest import mock + +import pytest +from flask.ctx import AppContext + +from superset.errors import ErrorLevel, SupersetError, SupersetErrorType +from tests.unit_tests.fixtures.common import dttm + + +@pytest.mark.parametrize( + "actual,expected", + [ + ("DATE", "TO_DATE('2019-01-02')"), + ("DATETIME", "CAST('2019-01-02T03:04:05.678900' AS DATETIME)"), + ("TIMESTAMP", "TO_TIMESTAMP('2019-01-02T03:04:05.678900')"), + ], +) +def test_convert_dttm( + app_context: AppContext, actual: str, expected: str, dttm: datetime +) -> None: + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + + assert SnowflakeEngineSpec.convert_dttm(actual, dttm) == expected + + +def test_database_connection_test_mutator(app_context: AppContext) -> None: + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + from superset.models.core import Database + + database = Database(sqlalchemy_uri="snowflake://abc") + SnowflakeEngineSpec.mutate_db_for_connection_test(database) + engine_params = json.loads(database.extra or "{}") + + assert { + "engine_params": {"connect_args": {"validate_default_parameters": True}} + } == engine_params + + +def test_extract_errors(app_context: AppContext) -> None: + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + + msg = "Object dumbBrick does not exist or not authorized." + result = SnowflakeEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + message="dumbBrick does not exist in this database.", + error_type=SupersetErrorType.OBJECT_DOES_NOT_EXIST_ERROR, + level=ErrorLevel.ERROR, + extra={ + "engine_name": "Snowflake", + "issue_codes": [ + { + "code": 1029, + "message": "Issue 1029 - The object does not exist in the given database.", + } + ], + }, + ) + ] + + msg = "syntax error line 1 at position 10 unexpected 'limmmited'." + result = SnowflakeEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + message='Please check your query for syntax errors at or near "limmmited". Then, try running your query again.', + error_type=SupersetErrorType.SYNTAX_ERROR, + level=ErrorLevel.ERROR, + extra={ + "engine_name": "Snowflake", + "issue_codes": [ + { + "code": 1030, + "message": "Issue 1030 - The query has a syntax error.", + } + ], + }, + ) + ] + + +@mock.patch("sqlalchemy.engine.Engine.connect") +def test_get_cancel_query_id(engine_mock: mock.Mock) -> None: + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + from superset.models.sql_lab import Query + + query = Query() + cursor_mock = engine_mock.return_value.__enter__.return_value + cursor_mock.fetchone.return_value = [123] + assert SnowflakeEngineSpec.get_cancel_query_id(cursor_mock, query) == 123 + + +@mock.patch("sqlalchemy.engine.Engine.connect") +def test_cancel_query(engine_mock: mock.Mock) -> None: + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + from superset.models.sql_lab import Query + + query = Query() + cursor_mock = engine_mock.return_value.__enter__.return_value + assert SnowflakeEngineSpec.cancel_query(cursor_mock, query, "123") is True + + +@mock.patch("sqlalchemy.engine.Engine.connect") +def test_cancel_query_failed(engine_mock: mock.Mock) -> None: + from superset.db_engine_specs.snowflake import SnowflakeEngineSpec + from superset.models.sql_lab import Query + + query = Query() + cursor_mock = engine_mock.raiseError.side_effect = Exception() + assert SnowflakeEngineSpec.cancel_query(cursor_mock, query, "123") is False diff --git a/tests/unit_tests/db_engine_specs/test_sqlite.py b/tests/unit_tests/db_engine_specs/test_sqlite.py new file mode 100644 index 0000000000000..2ee8ea9a2c603 --- /dev/null +++ b/tests/unit_tests/db_engine_specs/test_sqlite.py @@ -0,0 +1,90 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from datetime import datetime +from unittest import mock + +import pytest +from flask.ctx import AppContext + +from tests.unit_tests.fixtures.common import dttm + + +def test_convert_dttm(app_context: AppContext, dttm: datetime) -> None: + from superset.db_engine_specs.sqlite import SqliteEngineSpec + + assert SqliteEngineSpec.convert_dttm("TEXT", dttm) == "'2019-01-02 03:04:05.678900'" + + +def test_convert_dttm_lower(app_context: AppContext, dttm: datetime) -> None: + from superset.db_engine_specs.sqlite import SqliteEngineSpec + + assert SqliteEngineSpec.convert_dttm("text", dttm) == "'2019-01-02 03:04:05.678900'" + + +def test_convert_dttm_invalid_type(app_context: AppContext, dttm: datetime) -> None: + from superset.db_engine_specs.sqlite import SqliteEngineSpec + + assert SqliteEngineSpec.convert_dttm("other", dttm) == None + + +def test_get_all_datasource_names_table(app_context: AppContext) -> None: + from superset.db_engine_specs.sqlite import SqliteEngineSpec + + database = mock.MagicMock() + database.get_all_schema_names.return_value = ["schema1"] + table_names = ["table1", "table2"] + get_tables = mock.MagicMock(return_value=table_names) + database.get_all_table_names_in_schema = get_tables + result = SqliteEngineSpec.get_all_datasource_names(database, "table") + + assert result == table_names + get_tables.assert_called_once_with( + schema="schema1", + force=True, + cache=database.table_cache_enabled, + cache_timeout=database.table_cache_timeout, + ) + + +def test_get_all_datasource_names_view(app_context: AppContext) -> None: + from superset.db_engine_specs.sqlite import SqliteEngineSpec + + database = mock.MagicMock() + database.get_all_schema_names.return_value = ["schema1"] + views_names = ["view1", "view2"] + get_views = mock.MagicMock(return_value=views_names) + database.get_all_view_names_in_schema = get_views + result = SqliteEngineSpec.get_all_datasource_names(database, "view") + + assert result == views_names + get_views.assert_called_once_with( + schema="schema1", + force=True, + cache=database.table_cache_enabled, + cache_timeout=database.table_cache_timeout, + ) + + +def test_get_all_datasource_names_invalid_type(app_context: AppContext) -> None: + from superset.db_engine_specs.sqlite import SqliteEngineSpec + + database = mock.MagicMock() + database.get_all_schema_names.return_value = ["schema1"] + invalid_type = "asdf" + + with pytest.raises(Exception): + SqliteEngineSpec.get_all_datasource_names(database, invalid_type)