@@ -1180,3 +1180,238 @@ def test_mssql_engine_import_validator():
11801180 mock_import .return_value = None
11811181 config = MSSQLConnectionConfig (host = "localhost" , driver = "pyodbc" )
11821182 assert config .driver == "pyodbc"
1183+
1184+
1185+ def test_mssql_connection_config_parameter_validation (make_config ):
1186+ """Test MSSQL connection config parameter validation."""
1187+ # Test default driver is pymssql
1188+ config = make_config (type = "mssql" , host = "localhost" , check_import = False )
1189+ assert isinstance (config , MSSQLConnectionConfig )
1190+ assert config .driver == "pymssql"
1191+
1192+ # Test explicit pyodbc driver
1193+ config = make_config (type = "mssql" , host = "localhost" , driver = "pyodbc" , check_import = False )
1194+ assert isinstance (config , MSSQLConnectionConfig )
1195+ assert config .driver == "pyodbc"
1196+
1197+ # Test explicit pymssql driver
1198+ config = make_config (type = "mssql" , host = "localhost" , driver = "pymssql" , check_import = False )
1199+ assert isinstance (config , MSSQLConnectionConfig )
1200+ assert config .driver == "pymssql"
1201+
1202+ # Test pyodbc specific parameters
1203+ config = make_config (
1204+ type = "mssql" ,
1205+ host = "localhost" ,
1206+ driver = "pyodbc" ,
1207+ driver_name = "ODBC Driver 18 for SQL Server" ,
1208+ trust_server_certificate = True ,
1209+ encrypt = False ,
1210+ odbc_properties = {"Authentication" : "ActiveDirectoryServicePrincipal" },
1211+ check_import = False ,
1212+ )
1213+ assert isinstance (config , MSSQLConnectionConfig )
1214+ assert config .driver_name == "ODBC Driver 18 for SQL Server"
1215+ assert config .trust_server_certificate is True
1216+ assert config .encrypt is False
1217+ assert config .odbc_properties == {"Authentication" : "ActiveDirectoryServicePrincipal" }
1218+
1219+ # Test pymssql specific parameters
1220+ config = make_config (
1221+ type = "mssql" ,
1222+ host = "localhost" ,
1223+ driver = "pymssql" ,
1224+ tds_version = "7.4" ,
1225+ conn_properties = ["SET ANSI_NULLS ON" ],
1226+ check_import = False ,
1227+ )
1228+ assert isinstance (config , MSSQLConnectionConfig )
1229+ assert config .tds_version == "7.4"
1230+ assert config .conn_properties == ["SET ANSI_NULLS ON" ]
1231+
1232+
1233+ def test_mssql_connection_kwargs_keys ():
1234+ """Test _connection_kwargs_keys returns correct keys for each driver variant."""
1235+ # Test pymssql driver keys
1236+ config = MSSQLConnectionConfig (host = "localhost" , driver = "pymssql" , check_import = False )
1237+ pymssql_keys = config ._connection_kwargs_keys
1238+ expected_pymssql_keys = {
1239+ "password" ,
1240+ "user" ,
1241+ "database" ,
1242+ "host" ,
1243+ "timeout" ,
1244+ "login_timeout" ,
1245+ "charset" ,
1246+ "appname" ,
1247+ "port" ,
1248+ "tds_version" ,
1249+ "conn_properties" ,
1250+ "autocommit" ,
1251+ }
1252+ assert pymssql_keys == expected_pymssql_keys
1253+
1254+ # Test pyodbc driver keys
1255+ config = MSSQLConnectionConfig (host = "localhost" , driver = "pyodbc" , check_import = False )
1256+ pyodbc_keys = config ._connection_kwargs_keys
1257+ expected_pyodbc_keys = {
1258+ "password" ,
1259+ "user" ,
1260+ "database" ,
1261+ "host" ,
1262+ "timeout" ,
1263+ "login_timeout" ,
1264+ "charset" ,
1265+ "appname" ,
1266+ "port" ,
1267+ "autocommit" ,
1268+ "driver_name" ,
1269+ "trust_server_certificate" ,
1270+ "encrypt" ,
1271+ "odbc_properties" ,
1272+ }
1273+ assert pyodbc_keys == expected_pyodbc_keys
1274+
1275+ # Verify pyodbc keys don't include pymssql-specific parameters
1276+ assert "tds_version" not in pyodbc_keys
1277+ assert "conn_properties" not in pyodbc_keys
1278+
1279+
1280+ def test_mssql_pyodbc_connection_string_generation ():
1281+ """Test pyodbc.connect gets invoked with the correct ODBC connection string."""
1282+ with patch ("pyodbc.connect" ) as mock_pyodbc_connect :
1283+ # Mock the return value to have the methods we need
1284+ mock_connection = mock_pyodbc_connect .return_value
1285+
1286+ # Create a pyodbc config
1287+ config = MSSQLConnectionConfig (
1288+ host = "testserver.database.windows.net" ,
1289+ port = 1433 ,
1290+ database = "testdb" ,
1291+ user = "testuser" ,
1292+ password = "testpass" ,
1293+ driver = "pyodbc" ,
1294+ driver_name = "ODBC Driver 18 for SQL Server" ,
1295+ trust_server_certificate = True ,
1296+ encrypt = True ,
1297+ login_timeout = 30 ,
1298+ check_import = False ,
1299+ )
1300+
1301+ # Get the connection factory with kwargs and call it
1302+ factory_with_kwargs = config ._connection_factory_with_kwargs
1303+ connection = factory_with_kwargs ()
1304+
1305+ # Verify pyodbc.connect was called with the correct connection string
1306+ mock_pyodbc_connect .assert_called_once ()
1307+ call_args = mock_pyodbc_connect .call_args
1308+
1309+ # Check the connection string (first argument)
1310+ conn_str = call_args [0 ][0 ]
1311+ expected_parts = [
1312+ "DRIVER={ODBC Driver 18 for SQL Server}" ,
1313+ "SERVER=testserver.database.windows.net,1433" ,
1314+ "DATABASE=testdb" ,
1315+ "Encrypt=YES" ,
1316+ "TrustServerCertificate=YES" ,
1317+ "Connection Timeout=30" ,
1318+ "UID=testuser" ,
1319+ "PWD=testpass" ,
1320+ ]
1321+
1322+ for part in expected_parts :
1323+ assert part in conn_str
1324+
1325+ # Check autocommit parameter
1326+ assert call_args [1 ]["autocommit" ] is False
1327+
1328+
1329+ def test_mssql_pyodbc_connection_string_with_odbc_properties ():
1330+ """Test pyodbc connection string includes custom ODBC properties."""
1331+ with patch ("pyodbc.connect" ) as mock_pyodbc_connect :
1332+ # Create a pyodbc config with custom ODBC properties
1333+ config = MSSQLConnectionConfig (
1334+ host = "testserver.database.windows.net" ,
1335+ database = "testdb" ,
1336+ user = "client-id" ,
1337+ password = "client-secret" ,
1338+ driver = "pyodbc" ,
1339+ odbc_properties = {
1340+ "Authentication" : "ActiveDirectoryServicePrincipal" ,
1341+ "ClientCertificate" : "/path/to/cert.pem" ,
1342+ "TrustServerCertificate" : "NO" , # This should be ignored since we set it explicitly
1343+ },
1344+ trust_server_certificate = True , # This should take precedence
1345+ check_import = False ,
1346+ )
1347+
1348+ # Get the connection factory with kwargs and call it
1349+ factory_with_kwargs = config ._connection_factory_with_kwargs
1350+ connection = factory_with_kwargs ()
1351+
1352+ # Verify pyodbc.connect was called
1353+ mock_pyodbc_connect .assert_called_once ()
1354+ conn_str = mock_pyodbc_connect .call_args [0 ][0 ]
1355+
1356+ # Check that custom ODBC properties are included
1357+ assert "Authentication=ActiveDirectoryServicePrincipal" in conn_str
1358+ assert "ClientCertificate=/path/to/cert.pem" in conn_str
1359+
1360+ # Verify that explicit trust_server_certificate takes precedence
1361+ assert "TrustServerCertificate=YES" in conn_str
1362+
1363+ # Should not have the conflicting property from odbc_properties
1364+ assert conn_str .count ("TrustServerCertificate" ) == 1
1365+
1366+
1367+ def test_mssql_pyodbc_connection_string_minimal ():
1368+ """Test pyodbc connection string with minimal configuration."""
1369+ with patch ("pyodbc.connect" ) as mock_pyodbc_connect :
1370+ config = MSSQLConnectionConfig (
1371+ host = "localhost" ,
1372+ driver = "pyodbc" ,
1373+ autocommit = True ,
1374+ check_import = False ,
1375+ )
1376+
1377+ factory_with_kwargs = config ._connection_factory_with_kwargs
1378+ connection = factory_with_kwargs ()
1379+
1380+ mock_pyodbc_connect .assert_called_once ()
1381+ conn_str = mock_pyodbc_connect .call_args [0 ][0 ]
1382+
1383+ # Check basic required parts
1384+ assert "DRIVER={ODBC Driver 18 for SQL Server}" in conn_str
1385+ assert "SERVER=localhost,1433" in conn_str
1386+ assert "Encrypt=YES" in conn_str # Default encrypt=True
1387+ assert "Connection Timeout=60" in conn_str # Default timeout
1388+
1389+ # Check autocommit parameter
1390+ assert mock_pyodbc_connect .call_args [1 ]["autocommit" ] is True
1391+
1392+
1393+ def test_mssql_pymssql_connection_factory ():
1394+ """Test pymssql connection factory returns correct function."""
1395+ # Mock the import of pymssql at the module level
1396+ import sys
1397+ from unittest .mock import MagicMock
1398+
1399+ # Create a mock pymssql module
1400+ mock_pymssql = MagicMock ()
1401+ sys .modules ["pymssql" ] = mock_pymssql
1402+
1403+ try :
1404+ config = MSSQLConnectionConfig (
1405+ host = "localhost" ,
1406+ driver = "pymssql" ,
1407+ check_import = False ,
1408+ )
1409+
1410+ factory = config ._connection_factory
1411+
1412+ # Verify the factory returns pymssql.connect
1413+ assert factory is mock_pymssql .connect
1414+ finally :
1415+ # Clean up the mock module
1416+ if "pymssql" in sys .modules :
1417+ del sys .modules ["pymssql" ]
0 commit comments