Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
"publicly, use the SSH tunnel option."
)

_GENERIC_CONNECTION_ERROR = "Could not connect to MS SQL. Please check all connection details are valid."

MSSQLErrors = {
# SQL Server error 18456 is an authentication failure (wrong username/password, or the login is
# disabled), not a problem with the database field. Surface the same wording the sibling SQL
Expand All @@ -68,6 +70,11 @@
# message echoes the server name and client IP, so match the stable, distinctive phrase instead.
"is not allowed to access the server": _FIREWALL_BLOCKED_ERROR,
"connection timed out": _CONNECTION_TIMED_OUT_ERROR,
# DB-Lib error 20002, the generic connect-time failure `get_non_retryable_errors` below also
# matches (and leaves unmapped there since the cause varies). Map it here too so a credential
# check that hits it returns the same generic guidance as an unmatched error already gets,
# instead of falling through to `capture_exception` as a bug.
"Adaptive Server connection failed": _GENERIC_CONNECTION_ERROR,
}

_MSSQL_IMPLEMENTATION = MSSQLImplementation()
Expand Down Expand Up @@ -372,7 +379,7 @@ def validate_credentials(
return False, value

capture_exception(e)
return False, "Could not connect to MS SQL. Please check all connection details are valid."
return False, _GENERIC_CONNECTION_ERROR
except BaseSSHTunnelForwarderError as e:
return (
False,
Expand All @@ -381,6 +388,6 @@ def validate_credentials(
)
except Exception as e:
capture_exception(e)
return False, "Could not connect to MS SQL. Please check all connection details are valid."
return False, _GENERIC_CONNECTION_ERROR

return True, None
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
)
from products.warehouse_sources.backend.temporal.data_imports.sources.mssql.source import (
_FIREWALL_BLOCKED_ERROR,
_GENERIC_CONNECTION_ERROR,
MSSQLSource,
)
from products.warehouse_sources.backend.types import IncrementalFieldType
Expand Down Expand Up @@ -674,6 +675,26 @@ def test_firewall_block_returns_actionable_message_without_capturing(self, sourc
assert error == _FIREWALL_BLOCKED_ERROR
capture.assert_not_called()

def test_adaptive_server_connection_failed_returns_generic_message_without_capturing(self, source, mocker):
capture = mocker.patch(
"products.warehouse_sources.backend.temporal.data_imports.sources.mssql.source.capture_exception"
)
mocker.patch.object(source, "is_database_host_valid", return_value=(True, None))
# Real DB-Lib 20017/20002 pair: the server closed the connection mid-handshake, which
# FreeTDS reports as both an EOF and a generic "connection failed".
dropped_connection_error = pymssql.OperationalError(
20017,
b"DB-Lib error message 20017, severity 9:\nUnexpected EOF from the server (127.0.0.1)\n"
b"DB-Lib error message 20002, severity 9:\nAdaptive Server connection failed (127.0.0.1)\n",
)
mocker.patch.object(source, "get_schemas", side_effect=dropped_connection_error)

valid, error = source.validate_credentials(_make_config(), team_id=1)

assert valid is False
assert error == _GENERIC_CONNECTION_ERROR
capture.assert_not_called()

@pytest.mark.parametrize(
("driver_error", "expected_guidance"),
[
Expand Down
Loading