mirror of
https://github.com/apache/superset.git
synced 2026-09-05 06:51:48 +00:00
fix(sqllab): default PostgreSQL port to 5432 in the dynamic connection form
This commit is contained in:
@@ -31,6 +31,7 @@ from superset.db_engine_specs.postgres import (
|
||||
_check_not_redshift,
|
||||
PostgresEngineSpec as spec, # noqa: N813
|
||||
)
|
||||
from superset.errors import SupersetErrorType
|
||||
from superset.exceptions import SupersetSecurityException
|
||||
from superset.sql.parse import Table
|
||||
from superset.utils.core import GenericDataType
|
||||
@@ -501,3 +502,238 @@ def test_get_schema_names_excludes_only_actual_system_schemas(
|
||||
"pgstats",
|
||||
"information_schema",
|
||||
}
|
||||
|
||||
|
||||
def _basic_parameters(**overrides: Any) -> dict[str, Any]:
|
||||
parameters: dict[str, Any] = {
|
||||
"username": "user",
|
||||
"password": "pwd",
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"database": "db",
|
||||
"query": {},
|
||||
}
|
||||
parameters.update(overrides)
|
||||
return parameters
|
||||
|
||||
|
||||
def test_build_sqlalchemy_uri_defaults_missing_port_to_5432() -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): ``build_sqlalchemy_uri`` defaults a missing
|
||||
``port`` key to the class's own declared default (5432) instead of
|
||||
raising a ``KeyError``, so the dynamic form can connect without a port.
|
||||
"""
|
||||
parameters = _basic_parameters()
|
||||
del parameters["port"]
|
||||
|
||||
uri = spec.build_sqlalchemy_uri(parameters) # type: ignore[arg-type]
|
||||
|
||||
assert make_url(uri).port == 5432
|
||||
assert spec.metadata["default_port"] == 5432
|
||||
|
||||
|
||||
def test_build_sqlalchemy_uri_defaults_blank_port_to_5432() -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): ``build_sqlalchemy_uri`` defaults a blank
|
||||
(``None``) ``port`` value to 5432 rather than emitting ``port=None``.
|
||||
"""
|
||||
parameters = _basic_parameters(port=None)
|
||||
|
||||
uri = spec.build_sqlalchemy_uri(parameters) # type: ignore[arg-type]
|
||||
|
||||
assert make_url(uri).port == 5432
|
||||
|
||||
|
||||
def test_build_sqlalchemy_uri_respects_explicit_port() -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): an explicitly provided port is still honored
|
||||
and not overridden by the default.
|
||||
"""
|
||||
parameters = _basic_parameters(port=5433)
|
||||
|
||||
uri = spec.build_sqlalchemy_uri(parameters) # type: ignore[arg-type]
|
||||
|
||||
assert make_url(uri).port == 5433
|
||||
|
||||
|
||||
def test_build_sqlalchemy_uri_preserves_explicit_port_zero() -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): an explicitly supplied port of ``0`` (a value
|
||||
the schema's ``Range(min=0, ...)`` validator accepts) must not be
|
||||
silently overwritten by the default port. A truthiness check like
|
||||
``port or default`` would incorrectly replace ``0`` with 5432.
|
||||
"""
|
||||
parameters = _basic_parameters(port=0)
|
||||
|
||||
uri = spec.build_sqlalchemy_uri(parameters) # type: ignore[arg-type]
|
||||
|
||||
assert make_url(uri).port == 0
|
||||
|
||||
|
||||
def test_build_sqlalchemy_uri_defaults_empty_string_port_to_5432() -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): ``build_sqlalchemy_uri`` may be called directly
|
||||
with raw, non-schema-loaded parameters (see
|
||||
``ValidateDatabaseParametersCommand``), where a cleared number input
|
||||
submits ``""`` rather than ``null``. That must default to 5432 rather
|
||||
than raising when SQLAlchemy tries to parse ``""`` as a port.
|
||||
"""
|
||||
parameters = _basic_parameters(port="")
|
||||
|
||||
uri = spec.build_sqlalchemy_uri(parameters) # type: ignore[arg-type]
|
||||
|
||||
assert make_url(uri).port == 5432
|
||||
|
||||
|
||||
def test_parameters_schema_blank_port_string_loads_as_none() -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): the Connect Database form's Port field is a
|
||||
number input; clearing it submits ``""`` (HTML input values are always
|
||||
strings), not ``null``. The schema must normalize that to ``None``
|
||||
instead of rejecting it with "Not a valid integer.", so the dynamic
|
||||
form's CONNECT flow (which loads through ``parameters_schema`` before
|
||||
calling ``build_sqlalchemy_uri``) succeeds with a blank port.
|
||||
"""
|
||||
loaded = spec.parameters_schema.load(_basic_parameters(port=""))
|
||||
|
||||
assert loaded["port"] is None
|
||||
|
||||
|
||||
def test_validate_parameters_blank_port_is_not_a_missing_parameter(
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): a blank/missing ``port`` must not trigger
|
||||
``CONNECTION_MISSING_PARAMETERS_ERROR``, since ``build_sqlalchemy_uri``
|
||||
falls back to the default Postgres port.
|
||||
"""
|
||||
mocker.patch(
|
||||
"superset.db_engine_specs.postgres.is_hostname_valid", return_value=True
|
||||
)
|
||||
|
||||
properties = {"parameters": _basic_parameters(port=None)}
|
||||
errors = spec.validate_parameters(properties) # type: ignore[arg-type]
|
||||
|
||||
for error in errors:
|
||||
assert "port" not in (error.extra or {}).get("missing", [])
|
||||
assert error.error_type != SupersetErrorType.CONNECTION_MISSING_PARAMETERS_ERROR
|
||||
|
||||
|
||||
def test_validate_parameters_missing_host_still_errors(
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): omitting ``host`` still reports it as missing;
|
||||
only ``port`` was made optional.
|
||||
"""
|
||||
properties = {"parameters": _basic_parameters(host="", port=None)}
|
||||
errors = spec.validate_parameters(properties) # type: ignore[arg-type]
|
||||
|
||||
assert len(errors) == 1
|
||||
assert errors[0].error_type == SupersetErrorType.CONNECTION_MISSING_PARAMETERS_ERROR
|
||||
assert (errors[0].extra or {})["missing"] == ["host"]
|
||||
|
||||
|
||||
def test_validate_parameters_missing_other_required_field_still_errors(
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): omitting a still-required field (``database``)
|
||||
continues to be reported, even though ``port`` is blank too.
|
||||
"""
|
||||
mocker.patch(
|
||||
"superset.db_engine_specs.postgres.is_hostname_valid", return_value=True
|
||||
)
|
||||
|
||||
properties = {"parameters": _basic_parameters(database="", port=None)}
|
||||
errors = spec.validate_parameters(properties) # type: ignore[arg-type]
|
||||
|
||||
missing_errors = [
|
||||
error
|
||||
for error in errors
|
||||
if error.error_type == SupersetErrorType.CONNECTION_MISSING_PARAMETERS_ERROR
|
||||
]
|
||||
assert len(missing_errors) == 1
|
||||
assert (missing_errors[0].extra or {})["missing"] == ["database"]
|
||||
|
||||
|
||||
def test_validate_parameters_explicit_valid_port_checks_open(
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): when a port IS supplied, format/range/open
|
||||
validation is preserved unchanged.
|
||||
"""
|
||||
mocker.patch(
|
||||
"superset.db_engine_specs.postgres.is_hostname_valid", return_value=True
|
||||
)
|
||||
is_port_open = mocker.patch(
|
||||
"superset.db_engine_specs.postgres.is_port_open", return_value=True
|
||||
)
|
||||
|
||||
properties = {"parameters": _basic_parameters(port=5432)}
|
||||
errors = spec.validate_parameters(properties) # type: ignore[arg-type]
|
||||
|
||||
assert errors == []
|
||||
is_port_open.assert_called_once_with("localhost", 5432)
|
||||
|
||||
|
||||
def test_validate_parameters_invalid_port_still_errors(
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): an out-of-range port supplied by the user
|
||||
still produces ``CONNECTION_INVALID_PORT_ERROR``, exactly as before.
|
||||
"""
|
||||
mocker.patch(
|
||||
"superset.db_engine_specs.postgres.is_hostname_valid", return_value=True
|
||||
)
|
||||
|
||||
properties = {"parameters": _basic_parameters(port=70000)}
|
||||
errors = spec.validate_parameters(properties) # type: ignore[arg-type]
|
||||
|
||||
assert len(errors) == 1
|
||||
assert errors[0].error_type == SupersetErrorType.CONNECTION_INVALID_PORT_ERROR
|
||||
|
||||
|
||||
def test_validate_parameters_non_integer_port_matches_base_parity(
|
||||
mocker: MockerFixture,
|
||||
) -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): a non-integer port must produce BOTH errors
|
||||
that ``BasicParametersMixin.validate_parameters`` produces -- the
|
||||
"Port must be a valid integer." error from the failed ``int()``
|
||||
conversion, AND the "must be an integer between 0 and 65535" range
|
||||
error, since the base method does not return early after the former
|
||||
and falls through to the range check (which is also False for a
|
||||
non-int value). A Postgres-specific override that stops after the
|
||||
first error would silently narrow this API's error-reporting contract.
|
||||
"""
|
||||
mocker.patch(
|
||||
"superset.db_engine_specs.postgres.is_hostname_valid", return_value=True
|
||||
)
|
||||
|
||||
properties = {"parameters": _basic_parameters(port="not-a-port")}
|
||||
errors = spec.validate_parameters(properties) # type: ignore[arg-type]
|
||||
|
||||
assert len(errors) == 2
|
||||
assert errors[0].message == "Port must be a valid integer."
|
||||
assert errors[0].error_type == SupersetErrorType.CONNECTION_INVALID_PORT_ERROR
|
||||
assert (
|
||||
errors[1].message
|
||||
== "The port must be an integer between 0 and 65535 (inclusive)."
|
||||
)
|
||||
assert errors[1].error_type == SupersetErrorType.CONNECTION_INVALID_PORT_ERROR
|
||||
|
||||
|
||||
def test_parameters_schema_port_is_not_required() -> None:
|
||||
"""
|
||||
DB Eng Specs (postgres): the JSON schema exposed to the frontend for the
|
||||
Connect Database dynamic form must not mark ``port`` as required, so the
|
||||
modal doesn't block client-side submission when the field is left blank.
|
||||
"""
|
||||
json_schema = spec.parameters_json_schema()
|
||||
|
||||
assert "port" not in json_schema.get("required", [])
|
||||
assert "host" in json_schema.get("required", [])
|
||||
assert "database" in json_schema.get("required", [])
|
||||
|
||||
Reference in New Issue
Block a user