@@ -1559,182 +1559,166 @@ def test_mssql_pymssql_connection_factory():
15591559 del sys .modules ["pymssql" ]
15601560
15611561
1562- def test_mssql_cursor_init_datetimeoffset_handling ():
1563- """Test that the MSSQL cursor init properly handles DATETIMEOFFSET conversion."""
1562+ def test_mssql_pyodbc_connection_datetimeoffset_handling ():
1563+ """Test that the MSSQL pyodbc connection properly handles DATETIMEOFFSET conversion."""
15641564 from datetime import datetime , timezone , timedelta
15651565 import struct
1566- from unittest .mock import Mock
1566+ from unittest .mock import Mock , patch
15671567
1568- config = MSSQLConnectionConfig (
1569- host = "localhost" ,
1570- driver = "pyodbc" , # DATETIMEOFFSET handling is pyodbc-specific
1571- check_import = False ,
1572- )
1568+ with patch ("pyodbc.connect" ) as mock_pyodbc_connect :
1569+ # Track calls to add_output_converter
1570+ converter_calls = []
15731571
1574- # Get the cursor init function
1575- cursor_init = config ._cursor_init
1576- assert cursor_init is not None
1577-
1578- # Create a mock cursor and connection
1579- mock_connection = Mock ()
1580- mock_cursor = Mock ()
1581- mock_cursor .connection = mock_connection
1582-
1583- # Track calls to add_output_converter
1584- converter_calls = []
1585-
1586- def mock_add_output_converter (sql_type , converter_func ):
1587- converter_calls .append ((sql_type , converter_func ))
1588-
1589- mock_connection .add_output_converter = mock_add_output_converter
1590-
1591- # Call the cursor init function
1592- cursor_init (mock_cursor )
1593-
1594- # Verify that add_output_converter was called for SQL type -155 (DATETIMEOFFSET)
1595- assert len (converter_calls ) == 1
1596- sql_type , converter_func = converter_calls [0 ]
1597- assert sql_type == - 155
1598-
1599- # Test the converter function with actual DATETIMEOFFSET binary data
1600- # Create a test DATETIMEOFFSET value: 2023-12-25 15:30:45.123456789 +05:30
1601- year , month , day = 2023 , 12 , 25
1602- hour , minute , second = 15 , 30 , 45
1603- nanoseconds = 123456789
1604- tz_hour_offset , tz_minute_offset = 5 , 30
1605-
1606- # Pack the binary data according to the DATETIMEOFFSET format
1607- binary_data = struct .pack (
1608- "<6hI2h" ,
1609- year ,
1610- month ,
1611- day ,
1612- hour ,
1613- minute ,
1614- second ,
1615- nanoseconds ,
1616- tz_hour_offset ,
1617- tz_minute_offset ,
1618- )
1619-
1620- # Convert using the registered converter
1621- result = converter_func (binary_data )
1622-
1623- # Verify the result
1624- expected_dt = datetime (
1625- 2023 ,
1626- 12 ,
1627- 25 ,
1628- 15 ,
1629- 30 ,
1630- 45 ,
1631- 123456 , # microseconds = nanoseconds // 1000
1632- timezone (timedelta (hours = 5 , minutes = 30 )),
1633- )
1634- assert result == expected_dt
1635- assert result .tzinfo == timezone (timedelta (hours = 5 , minutes = 30 ))
1636-
1637-
1638- def test_mssql_cursor_init_negative_timezone_offset ():
1639- """Test DATETIMEOFFSET handling with negative timezone offset."""
1640- from datetime import datetime , timezone , timedelta
1641- import struct
1642- from unittest .mock import Mock
1572+ def mock_add_output_converter (sql_type , converter_func ):
1573+ converter_calls .append ((sql_type , converter_func ))
16431574
1644- config = MSSQLConnectionConfig (
1645- host = "localhost" ,
1646- driver = "pyodbc" , # DATETIMEOFFSET handling is pyodbc-specific
1647- check_import = False ,
1648- )
1575+ # Create a mock connection that will be returned by pyodbc.connect
1576+ mock_connection = Mock ()
1577+ mock_connection .add_output_converter = mock_add_output_converter
1578+ mock_pyodbc_connect .return_value = mock_connection
16491579
1650- cursor_init = config ._cursor_init
1651- mock_connection = Mock ()
1652- mock_cursor = Mock ()
1653- mock_cursor .connection = mock_connection
1580+ config = MSSQLConnectionConfig (
1581+ host = "localhost" ,
1582+ driver = "pyodbc" , # DATETIMEOFFSET handling is pyodbc-specific
1583+ check_import = False ,
1584+ )
16541585
1655- converter_calls = []
1586+ # Get the connection factory and call it
1587+ factory_with_kwargs = config ._connection_factory_with_kwargs
1588+ connection = factory_with_kwargs ()
16561589
1657- def mock_add_output_converter (sql_type , converter_func ):
1658- converter_calls .append ((sql_type , converter_func ))
1590+ # Verify that add_output_converter was called for SQL type -155 (DATETIMEOFFSET)
1591+ assert len (converter_calls ) == 1
1592+ sql_type , converter_func = converter_calls [0 ]
1593+ assert sql_type == - 155
1594+
1595+ # Test the converter function with actual DATETIMEOFFSET binary data
1596+ # Create a test DATETIMEOFFSET value: 2023-12-25 15:30:45.123456789 +05:30
1597+ year , month , day = 2023 , 12 , 25
1598+ hour , minute , second = 15 , 30 , 45
1599+ nanoseconds = 123456789
1600+ tz_hour_offset , tz_minute_offset = 5 , 30
1601+
1602+ # Pack the binary data according to the DATETIMEOFFSET format
1603+ binary_data = struct .pack (
1604+ "<6hI2h" ,
1605+ year ,
1606+ month ,
1607+ day ,
1608+ hour ,
1609+ minute ,
1610+ second ,
1611+ nanoseconds ,
1612+ tz_hour_offset ,
1613+ tz_minute_offset ,
1614+ )
16591615
1660- mock_connection .add_output_converter = mock_add_output_converter
1661- cursor_init (mock_cursor )
1616+ # Convert using the registered converter
1617+ result = converter_func (binary_data )
1618+
1619+ # Verify the result
1620+ expected_dt = datetime (
1621+ 2023 ,
1622+ 12 ,
1623+ 25 ,
1624+ 15 ,
1625+ 30 ,
1626+ 45 ,
1627+ 123456 , # microseconds = nanoseconds // 1000
1628+ timezone (timedelta (hours = 5 , minutes = 30 )),
1629+ )
1630+ assert result == expected_dt
1631+ assert result .tzinfo == timezone (timedelta (hours = 5 , minutes = 30 ))
16621632
1663- # Get the converter function
1664- _ , converter_func = converter_calls [0 ]
16651633
1666- # Test with negative timezone offset: 2023-01-01 12:00:00.0 -08:00
1667- year , month , day = 2023 , 1 , 1
1668- hour , minute , second = 12 , 0 , 0
1669- nanoseconds = 0
1670- tz_hour_offset , tz_minute_offset = - 8 , 0
1634+ def test_mssql_pyodbc_connection_negative_timezone_offset ():
1635+ """Test DATETIMEOFFSET handling with negative timezone offset at connection level."""
1636+ from datetime import datetime , timezone , timedelta
1637+ import struct
1638+ from unittest . mock import Mock , patch
16711639
1672- binary_data = struct .pack (
1673- "<6hI2h" ,
1674- year ,
1675- month ,
1676- day ,
1677- hour ,
1678- minute ,
1679- second ,
1680- nanoseconds ,
1681- tz_hour_offset ,
1682- tz_minute_offset ,
1683- )
1640+ with patch ("pyodbc.connect" ) as mock_pyodbc_connect :
1641+ converter_calls = []
16841642
1685- result = converter_func (binary_data )
1643+ def mock_add_output_converter (sql_type , converter_func ):
1644+ converter_calls .append ((sql_type , converter_func ))
16861645
1687- expected_dt = datetime ( 2023 , 1 , 1 , 12 , 0 , 0 , 0 , timezone ( timedelta ( hours = - 8 , minutes = 0 )) )
1688- assert result == expected_dt
1689- assert result . tzinfo == timezone ( timedelta ( hours = - 8 ))
1646+ mock_connection = Mock ( )
1647+ mock_connection . add_output_converter = mock_add_output_converter
1648+ mock_pyodbc_connect . return_value = mock_connection
16901649
1650+ config = MSSQLConnectionConfig (
1651+ host = "localhost" ,
1652+ driver = "pyodbc" , # DATETIMEOFFSET handling is pyodbc-specific
1653+ check_import = False ,
1654+ )
16911655
1692- def test_mssql_cursor_init_no_add_output_converter ():
1693- """Test that cursor init gracefully handles connections without add_output_converter."""
1694- from unittest .mock import Mock
1656+ factory_with_kwargs = config ._connection_factory_with_kwargs
1657+ connection = factory_with_kwargs ()
16951658
1696- config = MSSQLConnectionConfig (
1697- host = "localhost" ,
1698- driver = "pyodbc" , # DATETIMEOFFSET handling is pyodbc-specific
1699- check_import = False ,
1700- )
1659+ # Get the converter function
1660+ _ , converter_func = converter_calls [0 ]
1661+
1662+ # Test with negative timezone offset: 2023-01-01 12:00:00.0 -08:00
1663+ year , month , day = 2023 , 1 , 1
1664+ hour , minute , second = 12 , 0 , 0
1665+ nanoseconds = 0
1666+ tz_hour_offset , tz_minute_offset = - 8 , 0
1667+
1668+ binary_data = struct .pack (
1669+ "<6hI2h" ,
1670+ year ,
1671+ month ,
1672+ day ,
1673+ hour ,
1674+ minute ,
1675+ second ,
1676+ nanoseconds ,
1677+ tz_hour_offset ,
1678+ tz_minute_offset ,
1679+ )
17011680
1702- cursor_init = config ._cursor_init
1703- assert cursor_init is not None
1681+ result = converter_func (binary_data )
17041682
1705- # Create a mock cursor and connection without add_output_converter
1706- mock_connection = Mock ()
1707- mock_cursor = Mock ()
1708- mock_cursor .connection = mock_connection
1683+ expected_dt = datetime (2023 , 1 , 1 , 12 , 0 , 0 , 0 , timezone (timedelta (hours = - 8 , minutes = 0 )))
1684+ assert result == expected_dt
1685+ assert result .tzinfo == timezone (timedelta (hours = - 8 ))
17091686
1710- # Remove the add_output_converter attribute
1711- if hasattr (mock_connection , "add_output_converter" ):
1712- delattr (mock_connection , "add_output_converter" )
17131687
1714- # This should not raise an exception
1715- cursor_init (mock_cursor )
1688+ def test_mssql_pyodbc_connection_no_add_output_converter ():
1689+ """Test that connection gracefully handles pyodbc without add_output_converter."""
1690+ from unittest .mock import Mock , patch
17161691
1692+ with patch ("pyodbc.connect" ) as mock_pyodbc_connect :
1693+ # Create a mock connection without add_output_converter
1694+ mock_connection = Mock ()
1695+ # Remove the add_output_converter attribute
1696+ if hasattr (mock_connection , "add_output_converter" ):
1697+ delattr (mock_connection , "add_output_converter" )
1698+ mock_pyodbc_connect .return_value = mock_connection
17171699
1718- def test_mssql_cursor_init_returns_callable_for_pyodbc ():
1719- """Test that _cursor_init returns a callable function for pyodbc driver."""
1720- config = MSSQLConnectionConfig (
1721- host = "localhost" ,
1722- driver = "pyodbc" ,
1723- check_import = False ,
1724- )
1700+ config = MSSQLConnectionConfig (
1701+ host = "localhost" ,
1702+ driver = "pyodbc" , # DATETIMEOFFSET handling is pyodbc-specific
1703+ check_import = False ,
1704+ )
1705+
1706+ # This should not raise an exception
1707+ factory_with_kwargs = config ._connection_factory_with_kwargs
1708+ connection = factory_with_kwargs ()
17251709
1726- cursor_init = config ._cursor_init
1727- assert cursor_init is not None
1728- assert callable (cursor_init )
1710+ # Verify we get the connection back
1711+ assert connection is mock_connection
17291712
17301713
1731- def test_mssql_cursor_init_returns_none_for_pymssql ():
1732- """Test that _cursor_init returns None for pymssql driver."""
1714+ def test_mssql_no_cursor_init_for_pymssql ():
1715+ """Test that _cursor_init is not needed for pymssql driver."""
17331716 config = MSSQLConnectionConfig (
17341717 host = "localhost" ,
17351718 driver = "pymssql" ,
17361719 check_import = False ,
17371720 )
17381721
1739- cursor_init = config ._cursor_init
1740- assert cursor_init is None
1722+ # Since we moved output converter setup to connection level,
1723+ # there's no cursor init needed for any driver
1724+ assert not hasattr (config , "_cursor_init" ) or config ._cursor_init is None
0 commit comments