You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by be...@apache.org on 2025/10/27 18:57:00 UTC
(superset) branch explorable updated (5ba6db46a7 -> 0cc1f46516)
This is an automated email from the ASF dual-hosted git repository.
beto pushed a change to branch explorable
in repository https://gitbox.apache.org/repos/asf/superset.git
from 5ba6db46a7 Add unit tests
new 7aa9c63b66 More tests
new 0cc1f46516 WIP Snowflake tests
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
superset/semantic_layers/snowflake_.py | 9 +-
tests/unit_tests/semantic_layers/test_mapper.py | 325 +++++++++++----------
tests/unit_tests/semantic_layers/test_snowflake.py | 286 ++++++++++++++++++
3 files changed, 462 insertions(+), 158 deletions(-)
create mode 100644 tests/unit_tests/semantic_layers/test_snowflake.py
(superset) 01/02: More tests
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch explorable
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 7aa9c63b668018b4ffcce4e1e181c36fd9e91f68
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Mon Oct 27 13:42:47 2025 -0400
More tests
---
tests/unit_tests/semantic_layers/test_mapper.py | 323 +++++++++++++-----------
1 file changed, 169 insertions(+), 154 deletions(-)
diff --git a/tests/unit_tests/semantic_layers/test_mapper.py b/tests/unit_tests/semantic_layers/test_mapper.py
index abe9e08096..c8a3e49eb7 100644
--- a/tests/unit_tests/semantic_layers/test_mapper.py
+++ b/tests/unit_tests/semantic_layers/test_mapper.py
@@ -32,6 +32,7 @@ from superset.semantic_layers.mapper import (
_get_time_bounds,
_get_time_filter,
map_query_object,
+ validate_query_object,
)
from superset.semantic_layers.types import (
AdhocExpression,
@@ -53,6 +54,9 @@ from superset.semantic_layers.types import (
)
from superset.utils.core import FilterOperator
+# Alias for convenience
+Feature = SemanticViewFeature
+
class MockSemanticViewImplementation:
"""
@@ -143,31 +147,29 @@ def mock_datasource() -> Mock:
return datasource
-def test_convert_time_grain_time() -> None:
+@pytest.mark.parametrize(
+ "input_grain, expected_grain",
+ [
+ ("PT1S", TimeGrain.PT1S),
+ ("PT1M", TimeGrain.PT1M),
+ ("PT1H", TimeGrain.PT1H),
+ ("P1D", DateGrain.P1D),
+ ("P1W", DateGrain.P1W),
+ ("P1M", DateGrain.P1M),
+ ("P1Y", DateGrain.P1Y),
+ ("P3M", DateGrain.P3M),
+ ("INVALID", None),
+ ("", None),
+ ],
+)
+def test_convert_date_time_grain(
+ input_grain: str,
+ expected_grain: TimeGrain | DateGrain,
+) -> None:
"""
Test conversion of time grains (hour, minute, second).
"""
- assert _convert_time_grain("PT1S") == TimeGrain.PT1S
- assert _convert_time_grain("PT1M") == TimeGrain.PT1M
- assert _convert_time_grain("PT1H") == TimeGrain.PT1H
-
-
-def test_convert_time_grain_date() -> None:
- """
- Test conversion of date grains (day, week, month, year).
- """
- assert _convert_time_grain("P1D") == DateGrain.P1D
- assert _convert_time_grain("P1W") == DateGrain.P1W
- assert _convert_time_grain("P1M") == DateGrain.P1M
- assert _convert_time_grain("P1Y") == DateGrain.P1Y
-
-
-def test_convert_time_grain_invalid() -> None:
- """
- Test that invalid grain returns None.
- """
- assert _convert_time_grain("INVALID") is None
- assert _convert_time_grain("") is None
+ assert _convert_time_grain(input_grain) == expected_grain
def test_get_filters_from_extras_empty() -> None:
@@ -335,30 +337,6 @@ def test_convert_query_object_filter_temporal_range() -> None:
assert result is None
-def test_convert_query_object_filter_equals(mock_datasource):
- """
- Test conversion of EQUALS filter.
- """
- all_dimensions = {
- dim.name: dim for dim in mock_datasource.implementation.dimensions
- }
-
- filter_ = {
- "op": FilterOperator.EQUALS.value,
- "col": "region",
- "val": "US",
- }
-
- result = _convert_query_object_filter(filter_, all_dimensions)
-
- assert result == Filter(
- type=PredicateType.WHERE,
- column=all_dimensions["region"],
- operator=Operator.EQUALS,
- value="US",
- )
-
-
def test_convert_query_object_filter_in(mock_datasource):
"""
Test conversion of IN filter.
@@ -1007,147 +985,184 @@ def test_map_query_object_with_time_offsets(mock_datasource):
}
-def test_map_query_object_with_group_limit(mock_datasource):
+def test_convert_query_object_filter_unknown_operator(mock_datasource):
+ """
+ Test filter with unknown operator returns None.
+ """
+ all_dimensions = {
+ dim.name: dim for dim in mock_datasource.implementation.dimensions
+ }
+
+ filter_ = {
+ "op": "UNKNOWN_OPERATOR",
+ "col": "category",
+ "val": "Electronics",
+ }
+
+ result = _convert_query_object_filter(filter_, all_dimensions)
+
+ assert result is None
+
+
+def test_validate_query_object_undefined_metric_error(mock_datasource):
"""
- Test mapping with group limit.
+ Test validation error for undefined metrics.
"""
query_object = QueryObject(
datasource=mock_datasource,
- from_dttm=datetime(2025, 10, 15),
- to_dttm=datetime(2025, 10, 22),
- metrics=["total_sales"],
- columns=["category", "region"],
- series_columns=["category"],
- series_limit=10,
- series_limit_metric="total_sales",
- granularity="order_date",
+ metrics=["undefined_metric"],
+ columns=["order_date"],
)
- result = map_query_object(query_object)
+ with pytest.raises(ValueError, match="All metrics must be defined"):
+ validate_query_object(query_object, mock_datasource.implementation)
- assert len(result) == 1
- semantic_query = result[0]
- print(semantic_query.group_limit.dimensions)
- assert semantic_query.group_limit == GroupLimit(
- top=10,
- dimensions=[
- Dimension(
- id="products.category",
- name="category",
- type=STRING,
- definition="category",
- description="Product category",
- grain=None,
- )
- ],
- metric=Metric(
- id="orders.total_sales",
- name="total_sales",
- type=NUMBER,
- definition="SUM(amount)",
- description="Total sales",
- ),
- direction=OrderDirection.DESC,
- group_others=False,
- filters=None,
+def test_validate_query_object_undefined_dimension_error(mock_datasource):
+ """
+ Test validation error for undefined dimensions.
+ """
+ query_object = QueryObject(
+ datasource=mock_datasource,
+ metrics=["total_sales"],
+ columns=["undefined_dimension"],
)
+ with pytest.raises(ValueError, match="All dimensions must be defined"):
+ validate_query_object(query_object, mock_datasource.implementation)
-def test_map_query_object_with_order(mock_datasource):
+
+def test_validate_query_object_time_grain_without_column_error(mock_datasource):
"""
- Test mapping with order by (testing _get_order directly).
+ Test validation error when time grain provided without time column.
"""
- all_metrics = {
- metric.name: metric for metric in mock_datasource.implementation.metrics
- }
- all_dimensions = {
- dim.name: dim for dim in mock_datasource.implementation.dimensions
- }
+ query_object = QueryObject(
+ datasource=mock_datasource,
+ metrics=["total_sales"],
+ columns=["order_date", "category"],
+ granularity=None, # No time column
+ extras={"time_grain_sqla": "P1D"},
+ )
+
+ with pytest.raises(ValueError, match="time column must be specified"):
+ validate_query_object(query_object, mock_datasource.implementation)
+
+def test_validate_query_object_unsupported_time_grain_error(mock_datasource):
+ """
+ Test validation error for unsupported time grain.
+ """
query_object = QueryObject(
datasource=mock_datasource,
- from_dttm=datetime(2025, 10, 15),
- to_dttm=datetime(2025, 10, 22),
metrics=["total_sales"],
columns=["order_date", "category"],
granularity="order_date",
- orderby=[("total_sales", False)],
+ extras={"time_grain_sqla": "P1Y"}, # Year grain not supported
)
- # Test _get_order_from_query_object directly to avoid validation
- order = _get_order_from_query_object(query_object, all_metrics, all_dimensions)
-
- assert order == [(all_metrics["total_sales"], OrderDirection.DESC)]
+ with pytest.raises(
+ ValueError,
+ match=(
+ "The time grain is not supported for the time column in the Semantic View."
+ ),
+ ):
+ validate_query_object(query_object, mock_datasource.implementation)
-def test_map_query_object_filters_all_sources(mock_datasource):
+def test_validate_query_object_group_limit_not_supported_error():
"""
- Test that filters from all sources are combined.
+ Test validation error when group limit not supported.
"""
- mock_datasource.fetch_values_predicate = "tenant_id = 123"
+ mock_datasource = Mock()
+ time_dim = Dimension("order_date", "order_date", STRING, "order_date", "Date")
+ category_dim = Dimension("category", "category", STRING, "category", "Category")
+ sales_metric = Metric("total_sales", "total_sales", NUMBER, "SUM(amount)", "Sales")
+
+ mock_datasource.implementation.dimensions = {time_dim, category_dim}
+ mock_datasource.implementation.metrics = {sales_metric}
+ mock_datasource.implementation.features = frozenset() # No GROUP_LIMIT feature
query_object = QueryObject(
datasource=mock_datasource,
- from_dttm=datetime(2025, 10, 15),
- to_dttm=datetime(2025, 10, 22),
metrics=["total_sales"],
columns=["order_date", "category"],
- granularity="order_date",
- apply_fetch_values_predicate=True,
- extras={
- "where": "customer_id > 100",
- "having": "SUM(amount) > 1000",
- },
- filter=[
- {
- "op": FilterOperator.EQUALS.value,
- "col": "region",
- "val": "US",
- }
- ],
+ series_columns=["category"],
+ series_limit=10,
)
- result = map_query_object(query_object)
+ with pytest.raises(ValueError, match="Group limit is not supported"):
+ validate_query_object(query_object, mock_datasource.implementation)
- assert len(result) == 1
- semantic_query = result[0]
- filters = semantic_query.filters
+def test_validate_query_object_undefined_series_column_error(mock_datasource):
+ """
+ Test validation error for undefined series columns.
+ """
+ query_object = QueryObject(
+ datasource=mock_datasource,
+ metrics=["total_sales"],
+ columns=["order_date", "category"],
+ series_columns=["undefined_column"],
+ series_limit=10,
+ )
- # Should have at least:
- # - 2 time filters (>= and <)
- # - 1 fetch values filter
- # - 2 extras filters (where and having)
- # May also have region filter if it gets converted
- assert filters == {
- Filter(
- type=PredicateType.WHERE,
- column=Dimension(
- id="orders.order_date",
- name="order_date",
- type=STRING,
- definition="order_date",
- description="Order date",
- grain=None,
- ),
- operator=Operator.LESS_THAN,
- value=datetime(2025, 10, 22, 0, 0),
- ),
- AdhocFilter(type=PredicateType.WHERE, definition="tenant_id = 123"),
- Filter(
- type=PredicateType.WHERE,
- column=Dimension(
- id="orders.order_date",
- name="order_date",
- type=STRING,
- definition="order_date",
- description="Order date",
- grain=None,
- ),
- operator=Operator.GREATER_THAN_OR_EQUAL,
- value=datetime(2025, 10, 15, 0, 0),
- ),
- AdhocFilter(type=PredicateType.HAVING, definition="SUM(amount) > 1000"),
- AdhocFilter(type=PredicateType.WHERE, definition="customer_id > 100"),
+ with pytest.raises(ValueError, match="All series columns must be defined"):
+ validate_query_object(query_object, mock_datasource.implementation)
+
+
+@pytest.mark.parametrize(
+ "filter_op, expected_operator",
+ [
+ ("==", Operator.EQUALS),
+ ("!=", Operator.NOT_EQUALS),
+ ("<", Operator.LESS_THAN),
+ (">", Operator.GREATER_THAN),
+ ("<=", Operator.LESS_THAN_OR_EQUAL),
+ (">=", Operator.GREATER_THAN_OR_EQUAL),
+ ],
+)
+def test_convert_query_object_filter(
+ filter_op: str,
+ expected_operator: Operator,
+) -> None:
+ """
+ Test filter with different operators.
+ """
+ all_dimensions = {
+ "category": Dimension("category", "category", STRING, "category", "Category")
+ }
+
+ filter_ = {
+ "op": filter_op,
+ "col": "category",
+ "val": "Electronics",
+ }
+
+ result = _convert_query_object_filter(filter_, all_dimensions)
+
+ assert result == Filter(
+ type=PredicateType.WHERE,
+ column=all_dimensions["category"],
+ operator=expected_operator,
+ value="Electronics",
+ )
+
+
+def test_convert_query_object_filter_like():
+ """Test filter with LIKE operator."""
+ all_dimensions = {"name": Dimension("name", "name", STRING, "name", "Name")}
+
+ filter_ = {
+ "op": "LIKE",
+ "col": "name",
+ "val": "%test%",
}
+
+ result = _convert_query_object_filter(filter_, all_dimensions)
+
+ assert result == Filter(
+ type=PredicateType.WHERE,
+ column=all_dimensions["name"],
+ operator=Operator.LIKE,
+ value="%test%",
+ )
(superset) 02/02: WIP Snowflake tests
Posted by be...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch explorable
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 0cc1f46516c2b52a93dacf98e80dd3a3d8168666
Author: Beto Dealmeida <ro...@dealmeida.net>
AuthorDate: Mon Oct 27 14:56:52 2025 -0400
WIP Snowflake tests
---
superset/semantic_layers/snowflake_.py | 9 +-
tests/unit_tests/semantic_layers/test_mapper.py | 4 +-
tests/unit_tests/semantic_layers/test_snowflake.py | 286 +++++++++++++++++++++
3 files changed, 294 insertions(+), 5 deletions(-)
diff --git a/superset/semantic_layers/snowflake_.py b/superset/semantic_layers/snowflake_.py
index 2f8b5b9169..176cc36e3d 100644
--- a/superset/semantic_layers/snowflake_.py
+++ b/superset/semantic_layers/snowflake_.py
@@ -82,10 +82,11 @@ def substitute_parameters(query: str, parameters: Sequence[Any] | None) -> str:
for parameter in parameters:
if parameter is None:
replacement = "NULL"
- elif isinstance(parameter, (int, float)):
- replacement = str(parameter)
elif isinstance(parameter, bool):
+ # Check bool before int/float since bool is a subclass of int
replacement = str(parameter).upper()
+ elif isinstance(parameter, (int, float)):
+ replacement = str(parameter)
else:
# String - escape single quotes
quoted = str(parameter).replace("'", "''")
@@ -244,9 +245,9 @@ def get_connection_parameters(configuration: SnowflakeConfiguration) -> dict[str
params["password"] = auth.password.get_secret_value()
elif isinstance(auth, PrivateKeyAuth):
pem_private_key = serialization.load_pem_private_key(
- auth.private_key.encode(),
+ auth.private_key.get_secret_value().encode(),
password=(
- auth.private_key_password.encode()
+ auth.private_key_password.get_secret_value().encode()
if auth.private_key_password
else None
),
diff --git a/tests/unit_tests/semantic_layers/test_mapper.py b/tests/unit_tests/semantic_layers/test_mapper.py
index c8a3e49eb7..f9f8b0d17a 100644
--- a/tests/unit_tests/semantic_layers/test_mapper.py
+++ b/tests/unit_tests/semantic_layers/test_mapper.py
@@ -1149,7 +1149,9 @@ def test_convert_query_object_filter(
def test_convert_query_object_filter_like():
- """Test filter with LIKE operator."""
+ """
+ Test filter with LIKE operator.
+ """
all_dimensions = {"name": Dimension("name", "name", STRING, "name", "Name")}
filter_ = {
diff --git a/tests/unit_tests/semantic_layers/test_snowflake.py b/tests/unit_tests/semantic_layers/test_snowflake.py
new file mode 100644
index 0000000000..43aef22110
--- /dev/null
+++ b/tests/unit_tests/semantic_layers/test_snowflake.py
@@ -0,0 +1,286 @@
+# 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 contextlib import nullcontext
+
+import pytest
+
+from superset.semantic_layers.snowflake_ import (
+ get_connection_parameters,
+ SnowflakeConfiguration,
+ substitute_parameters,
+ validate_order_by,
+)
+
+
+@pytest.mark.parametrize(
+ "query, parameters, expected",
+ [
+ # No parameters
+ (
+ "SELECT * FROM table",
+ None,
+ "SELECT * FROM table",
+ ),
+ (
+ "SELECT * FROM table",
+ [],
+ "SELECT * FROM table",
+ ),
+ # NULL values
+ (
+ "SELECT * FROM table WHERE id = ?",
+ [None],
+ "SELECT * FROM table WHERE id = NULL",
+ ),
+ # Integer values
+ (
+ "SELECT * FROM table WHERE id = ?",
+ [123],
+ "SELECT * FROM table WHERE id = 123",
+ ),
+ (
+ "SELECT * FROM table WHERE id = ? AND status = ?",
+ [123, 456],
+ "SELECT * FROM table WHERE id = 123 AND status = 456",
+ ),
+ # Float values
+ (
+ "SELECT * FROM table WHERE price = ?",
+ [99.99],
+ "SELECT * FROM table WHERE price = 99.99",
+ ),
+ (
+ "SELECT * FROM table WHERE price BETWEEN ? AND ?",
+ [10.5, 99.99],
+ "SELECT * FROM table WHERE price BETWEEN 10.5 AND 99.99",
+ ),
+ # Boolean values
+ (
+ "SELECT * FROM table WHERE active = ?",
+ [True],
+ "SELECT * FROM table WHERE active = TRUE",
+ ),
+ (
+ "SELECT * FROM table WHERE active = ? AND deleted = ?",
+ [True, False],
+ "SELECT * FROM table WHERE active = TRUE AND deleted = FALSE",
+ ),
+ # String values
+ (
+ "SELECT * FROM table WHERE name = ?",
+ ["John"],
+ "SELECT * FROM table WHERE name = 'John'",
+ ),
+ (
+ "SELECT * FROM table WHERE name = ? OR name = ?",
+ ["John", "Jane"],
+ "SELECT * FROM table WHERE name = 'John' OR name = 'Jane'",
+ ),
+ # String with single quotes (should be escaped)
+ (
+ "SELECT * FROM table WHERE name = ?",
+ ["O'Brien"],
+ "SELECT * FROM table WHERE name = 'O''Brien'",
+ ),
+ (
+ "SELECT * FROM table WHERE text = ?",
+ ["It's a test"],
+ "SELECT * FROM table WHERE text = 'It''s a test'",
+ ),
+ # Mixed types
+ (
+ (
+ "SELECT * FROM table WHERE name = ? "
+ "AND age = ? AND active = ? AND salary = ?"
+ ),
+ ["John", 30, True, 50000.5],
+ (
+ "SELECT * FROM table WHERE name = 'John' "
+ "AND age = 30 AND active = TRUE AND salary = 50000.5"
+ ),
+ ),
+ (
+ "SELECT * FROM table WHERE col1 = ? AND col2 = ? AND col3 = ?",
+ [None, "test", 42],
+ "SELECT * FROM table WHERE col1 = NULL AND col2 = 'test' AND col3 = 42",
+ ),
+ ],
+)
+def test_substitute_parameters(
+ query: str,
+ parameters: list | None,
+ expected: str,
+) -> None:
+ """
+ Test parameter substitution for various types and combinations.
+ """
+ assert substitute_parameters(query, parameters) == expected
+
+
+@pytest.mark.parametrize(
+ "definition, should_raise",
+ [
+ # Valid simple cases
+ ("column_name", False),
+ ("COUNT(*)", False),
+ ("SUM(amount)", False),
+ ("table.column", False),
+ ("schema.table.column", False),
+ # Valid with direction
+ ("column_name ASC", False),
+ ("column_name DESC", False),
+ ("COUNT(*) DESC", False),
+ ("SUM(revenue) ASC", False),
+ # Valid with NULLS handling
+ ("column_name NULLS FIRST", False),
+ ("column_name NULLS LAST", False),
+ ("column_name ASC NULLS FIRST", False),
+ ("column_name DESC NULLS LAST", False),
+ ("COUNT(*) DESC NULLS FIRST", False),
+ # Valid complex expressions
+ ("gender ASC, COUNT(*)", False),
+ ("gender ASC, COUNT(*) DESC", False),
+ ("col1 ASC, col2 DESC, col3", False),
+ ("CASE WHEN x > 0 THEN 1 ELSE 0 END", False),
+ ("CAST(column AS INTEGER)", False),
+ ("UPPER(name)", False),
+ ("CONCAT(first_name, ' ', last_name)", False),
+ # Valid with mixed complexity
+ ("table.column ASC NULLS FIRST, COUNT(*) DESC", False),
+ ("schema.table.col1, func(col2) DESC NULLS LAST", False),
+ # Invalid - SQL injection attempts with semicolons
+ ("column_name; DROP TABLE users;", True),
+ ("column_name; DELETE FROM data; --", True),
+ ("name; UPDATE users SET admin=1; --", True),
+ # Invalid - SQL injection with multiple statements
+ ("col1; SELECT * FROM passwords", True),
+ ("col1; INSERT INTO logs VALUES(1)", True),
+ # Edge cases - incomplete syntax
+ ("column/*", True),
+ ],
+)
+def test_validate_order_by(definition: str, should_raise: bool) -> None:
+ """
+ Test ORDER BY validation for valid expressions and SQL injection prevention.
+ """
+ context = (
+ pytest.raises(ValueError, match="Invalid ORDER BY")
+ if should_raise
+ else nullcontext()
+ )
+ with context:
+ validate_order_by(definition)
+
+
+@pytest.mark.parametrize(
+ "config_params, expected_keys",
+ [
+ # Minimal UserPasswordAuth configuration
+ (
+ {
+ "account_identifier": "test_account",
+ "auth": {
+ "auth_type": "user_password",
+ "username": "test_user",
+ "password": "test_password",
+ },
+ "allow_changing_database": True,
+ "allow_changing_schema": True,
+ },
+ {
+ "account": "test_account",
+ "application": "Apache Superset",
+ "paramstyle": "qmark",
+ "insecure_mode": True,
+ "user": "test_user",
+ "password": "test_password",
+ },
+ ),
+ # Full UserPasswordAuth configuration
+ (
+ {
+ "account_identifier": "test_account",
+ "role": "ACCOUNTADMIN",
+ "warehouse": "COMPUTE_WH",
+ "database": "TEST_DB",
+ "schema": "PUBLIC",
+ "auth": {
+ "auth_type": "user_password",
+ "username": "admin",
+ "password": "secret123",
+ },
+ },
+ {
+ "account": "test_account",
+ "application": "Apache Superset",
+ "paramstyle": "qmark",
+ "insecure_mode": True,
+ "role": "ACCOUNTADMIN",
+ "warehouse": "COMPUTE_WH",
+ "database": "TEST_DB",
+ "schema": "PUBLIC",
+ "user": "admin",
+ "password": "secret123",
+ },
+ ),
+ # UserPasswordAuth with some optional fields
+ (
+ {
+ "account_identifier": "mycompany.us-east-1",
+ "warehouse": "ETL_WH",
+ "database": "ANALYTICS",
+ "auth": {
+ "auth_type": "user_password",
+ "username": "analyst",
+ "password": "p@ssw0rd",
+ },
+ "allow_changing_schema": True,
+ },
+ {
+ "account": "mycompany.us-east-1",
+ "application": "Apache Superset",
+ "paramstyle": "qmark",
+ "insecure_mode": True,
+ "warehouse": "ETL_WH",
+ "database": "ANALYTICS",
+ "user": "analyst",
+ "password": "p@ssw0rd",
+ },
+ ),
+ ],
+)
+def test_get_connection_parameters(
+ config_params: dict,
+ expected_keys: dict,
+) -> None:
+ """
+ Test connection parameter generation for various configurations.
+ """
+ # Create configuration from params
+ config = SnowflakeConfiguration(**config_params)
+
+ # Get connection parameters
+ result = get_connection_parameters(config)
+
+ # Check that all expected keys are present with correct values
+ for key, value in expected_keys.items():
+ assert key in result, f"Expected key '{key}' not found in result"
+ assert result[key] == value, f"Expected {key}={value}, got {result[key]}"
+
+ # Verify no unexpected keys
+ assert set(result.keys()) == set(expected_keys.keys())