|
24 | 24 | from mssql_python import Connection, connect, pooling |
25 | 25 | import threading |
26 | 26 |
|
| 27 | +# Import all exception classes for testing |
| 28 | +from mssql_python.exceptions import ( |
| 29 | + Warning, |
| 30 | + Error, |
| 31 | + InterfaceError, |
| 32 | + DatabaseError, |
| 33 | + DataError, |
| 34 | + OperationalError, |
| 35 | + IntegrityError, |
| 36 | + InternalError, |
| 37 | + ProgrammingError, |
| 38 | + NotSupportedError, |
| 39 | +) |
| 40 | + |
27 | 41 | def drop_table_if_exists(cursor, table_name): |
28 | 42 | """Drop the table if it exists""" |
29 | 43 | try: |
@@ -485,3 +499,188 @@ def test_connection_pooling_basic(conn_str): |
485 | 499 |
|
486 | 500 | conn1.close() |
487 | 501 | conn2.close() |
| 502 | + |
| 503 | +# DB-API 2.0 Exception Attribute Tests |
| 504 | +def test_connection_exception_attributes_exist(db_connection): |
| 505 | + """Test that all DB-API 2.0 exception classes are available as Connection attributes""" |
| 506 | + # Test that all required exception attributes exist |
| 507 | + assert hasattr(db_connection, 'Warning'), "Connection should have Warning attribute" |
| 508 | + assert hasattr(db_connection, 'Error'), "Connection should have Error attribute" |
| 509 | + assert hasattr(db_connection, 'InterfaceError'), "Connection should have InterfaceError attribute" |
| 510 | + assert hasattr(db_connection, 'DatabaseError'), "Connection should have DatabaseError attribute" |
| 511 | + assert hasattr(db_connection, 'DataError'), "Connection should have DataError attribute" |
| 512 | + assert hasattr(db_connection, 'OperationalError'), "Connection should have OperationalError attribute" |
| 513 | + assert hasattr(db_connection, 'IntegrityError'), "Connection should have IntegrityError attribute" |
| 514 | + assert hasattr(db_connection, 'InternalError'), "Connection should have InternalError attribute" |
| 515 | + assert hasattr(db_connection, 'ProgrammingError'), "Connection should have ProgrammingError attribute" |
| 516 | + assert hasattr(db_connection, 'NotSupportedError'), "Connection should have NotSupportedError attribute" |
| 517 | + |
| 518 | +def test_connection_exception_attributes_are_classes(db_connection): |
| 519 | + """Test that all exception attributes are actually exception classes""" |
| 520 | + # Test that the attributes are the correct exception classes |
| 521 | + assert db_connection.Warning is Warning, "Connection.Warning should be the Warning class" |
| 522 | + assert db_connection.Error is Error, "Connection.Error should be the Error class" |
| 523 | + assert db_connection.InterfaceError is InterfaceError, "Connection.InterfaceError should be the InterfaceError class" |
| 524 | + assert db_connection.DatabaseError is DatabaseError, "Connection.DatabaseError should be the DatabaseError class" |
| 525 | + assert db_connection.DataError is DataError, "Connection.DataError should be the DataError class" |
| 526 | + assert db_connection.OperationalError is OperationalError, "Connection.OperationalError should be the OperationalError class" |
| 527 | + assert db_connection.IntegrityError is IntegrityError, "Connection.IntegrityError should be the IntegrityError class" |
| 528 | + assert db_connection.InternalError is InternalError, "Connection.InternalError should be the InternalError class" |
| 529 | + assert db_connection.ProgrammingError is ProgrammingError, "Connection.ProgrammingError should be the ProgrammingError class" |
| 530 | + assert db_connection.NotSupportedError is NotSupportedError, "Connection.NotSupportedError should be the NotSupportedError class" |
| 531 | + |
| 532 | +def test_connection_exception_inheritance(db_connection): |
| 533 | + """Test that exception classes have correct inheritance hierarchy""" |
| 534 | + # Test inheritance hierarchy according to DB-API 2.0 |
| 535 | + |
| 536 | + # All exceptions inherit from Error (except Warning) |
| 537 | + assert issubclass(db_connection.InterfaceError, db_connection.Error), "InterfaceError should inherit from Error" |
| 538 | + assert issubclass(db_connection.DatabaseError, db_connection.Error), "DatabaseError should inherit from Error" |
| 539 | + |
| 540 | + # Database exceptions inherit from DatabaseError |
| 541 | + assert issubclass(db_connection.DataError, db_connection.DatabaseError), "DataError should inherit from DatabaseError" |
| 542 | + assert issubclass(db_connection.OperationalError, db_connection.DatabaseError), "OperationalError should inherit from DatabaseError" |
| 543 | + assert issubclass(db_connection.IntegrityError, db_connection.DatabaseError), "IntegrityError should inherit from DatabaseError" |
| 544 | + assert issubclass(db_connection.InternalError, db_connection.DatabaseError), "InternalError should inherit from DatabaseError" |
| 545 | + assert issubclass(db_connection.ProgrammingError, db_connection.DatabaseError), "ProgrammingError should inherit from DatabaseError" |
| 546 | + assert issubclass(db_connection.NotSupportedError, db_connection.DatabaseError), "NotSupportedError should inherit from DatabaseError" |
| 547 | + |
| 548 | +def test_connection_exception_instantiation(db_connection): |
| 549 | + """Test that exception classes can be instantiated from Connection attributes""" |
| 550 | + # Test that we can create instances of exceptions using connection attributes |
| 551 | + warning = db_connection.Warning("Test warning", "DDBC warning") |
| 552 | + assert isinstance(warning, db_connection.Warning), "Should be able to create Warning instance" |
| 553 | + assert "Test warning" in str(warning), "Warning should contain driver error message" |
| 554 | + |
| 555 | + error = db_connection.Error("Test error", "DDBC error") |
| 556 | + assert isinstance(error, db_connection.Error), "Should be able to create Error instance" |
| 557 | + assert "Test error" in str(error), "Error should contain driver error message" |
| 558 | + |
| 559 | + interface_error = db_connection.InterfaceError("Interface error", "DDBC interface error") |
| 560 | + assert isinstance(interface_error, db_connection.InterfaceError), "Should be able to create InterfaceError instance" |
| 561 | + assert "Interface error" in str(interface_error), "InterfaceError should contain driver error message" |
| 562 | + |
| 563 | + db_error = db_connection.DatabaseError("Database error", "DDBC database error") |
| 564 | + assert isinstance(db_error, db_connection.DatabaseError), "Should be able to create DatabaseError instance" |
| 565 | + assert "Database error" in str(db_error), "DatabaseError should contain driver error message" |
| 566 | + |
| 567 | +def test_connection_exception_catching_with_connection_attributes(db_connection): |
| 568 | + """Test that we can catch exceptions using Connection attributes in multi-connection scenarios""" |
| 569 | + cursor = db_connection.cursor() |
| 570 | + |
| 571 | + try: |
| 572 | + # Test catching InterfaceError using connection attribute |
| 573 | + cursor.close() |
| 574 | + cursor.execute("SELECT 1") # Should raise InterfaceError on closed cursor |
| 575 | + pytest.fail("Should have raised an exception") |
| 576 | + except db_connection.InterfaceError as e: |
| 577 | + assert "closed" in str(e).lower(), "Error message should mention closed cursor" |
| 578 | + except Exception as e: |
| 579 | + pytest.fail(f"Should have caught InterfaceError, but got {type(e).__name__}: {e}") |
| 580 | + |
| 581 | +def test_connection_exception_error_handling_example(db_connection): |
| 582 | + """Test real-world error handling example using Connection exception attributes""" |
| 583 | + cursor = db_connection.cursor() |
| 584 | + |
| 585 | + try: |
| 586 | + # Try to create a table with invalid syntax (should raise ProgrammingError) |
| 587 | + cursor.execute("CREATE INVALID TABLE syntax_error") |
| 588 | + pytest.fail("Should have raised ProgrammingError") |
| 589 | + except db_connection.ProgrammingError as e: |
| 590 | + # This is the expected exception for syntax errors |
| 591 | + assert "syntax" in str(e).lower() or "incorrect" in str(e).lower() or "near" in str(e).lower(), "Should be a syntax-related error" |
| 592 | + except db_connection.DatabaseError as e: |
| 593 | + # ProgrammingError inherits from DatabaseError, so this might catch it too |
| 594 | + # This is acceptable according to DB-API 2.0 |
| 595 | + pass |
| 596 | + except Exception as e: |
| 597 | + pytest.fail(f"Expected ProgrammingError or DatabaseError, got {type(e).__name__}: {e}") |
| 598 | + |
| 599 | +def test_connection_exception_multi_connection_scenario(conn_str): |
| 600 | + """Test exception handling in multi-connection environment""" |
| 601 | + # Create two separate connections |
| 602 | + conn1 = connect(conn_str) |
| 603 | + conn2 = connect(conn_str) |
| 604 | + |
| 605 | + try: |
| 606 | + cursor1 = conn1.cursor() |
| 607 | + cursor2 = conn2.cursor() |
| 608 | + |
| 609 | + # Close first connection but try to use its cursor |
| 610 | + conn1.close() |
| 611 | + |
| 612 | + try: |
| 613 | + cursor1.execute("SELECT 1") |
| 614 | + pytest.fail("Should have raised an exception") |
| 615 | + except conn1.InterfaceError as e: |
| 616 | + # Using conn1.InterfaceError even though conn1 is closed |
| 617 | + # The exception class attribute should still be accessible |
| 618 | + assert "closed" in str(e).lower(), "Should mention closed cursor" |
| 619 | + except Exception as e: |
| 620 | + pytest.fail(f"Expected InterfaceError from conn1 attributes, got {type(e).__name__}: {e}") |
| 621 | + |
| 622 | + # Second connection should still work |
| 623 | + cursor2.execute("SELECT 1") |
| 624 | + result = cursor2.fetchone() |
| 625 | + assert result[0] == 1, "Second connection should still work" |
| 626 | + |
| 627 | + # Test using conn2 exception attributes |
| 628 | + try: |
| 629 | + cursor2.execute("SELECT * FROM nonexistent_table_12345") |
| 630 | + pytest.fail("Should have raised an exception") |
| 631 | + except conn2.ProgrammingError as e: |
| 632 | + # Using conn2.ProgrammingError for table not found |
| 633 | + assert "nonexistent_table_12345" in str(e) or "object" in str(e).lower() or "not" in str(e).lower(), "Should mention the missing table" |
| 634 | + except conn2.DatabaseError as e: |
| 635 | + # Acceptable since ProgrammingError inherits from DatabaseError |
| 636 | + pass |
| 637 | + except Exception as e: |
| 638 | + pytest.fail(f"Expected ProgrammingError or DatabaseError from conn2, got {type(e).__name__}: {e}") |
| 639 | + |
| 640 | + finally: |
| 641 | + try: |
| 642 | + if not conn1._closed: |
| 643 | + conn1.close() |
| 644 | + except: |
| 645 | + pass |
| 646 | + try: |
| 647 | + if not conn2._closed: |
| 648 | + conn2.close() |
| 649 | + except: |
| 650 | + pass |
| 651 | + |
| 652 | +def test_connection_exception_attributes_consistency(conn_str): |
| 653 | + """Test that exception attributes are consistent across multiple Connection instances""" |
| 654 | + conn1 = connect(conn_str) |
| 655 | + conn2 = connect(conn_str) |
| 656 | + |
| 657 | + try: |
| 658 | + # Test that the same exception classes are referenced by different connections |
| 659 | + assert conn1.Error is conn2.Error, "All connections should reference the same Error class" |
| 660 | + assert conn1.InterfaceError is conn2.InterfaceError, "All connections should reference the same InterfaceError class" |
| 661 | + assert conn1.DatabaseError is conn2.DatabaseError, "All connections should reference the same DatabaseError class" |
| 662 | + assert conn1.ProgrammingError is conn2.ProgrammingError, "All connections should reference the same ProgrammingError class" |
| 663 | + |
| 664 | + # Test that the classes are the same as module-level imports |
| 665 | + assert conn1.Error is Error, "Connection.Error should be the same as module-level Error" |
| 666 | + assert conn1.InterfaceError is InterfaceError, "Connection.InterfaceError should be the same as module-level InterfaceError" |
| 667 | + assert conn1.DatabaseError is DatabaseError, "Connection.DatabaseError should be the same as module-level DatabaseError" |
| 668 | + |
| 669 | + finally: |
| 670 | + conn1.close() |
| 671 | + conn2.close() |
| 672 | + |
| 673 | +def test_connection_exception_attributes_comprehensive_list(): |
| 674 | + """Test that all DB-API 2.0 required exception attributes are present on Connection class""" |
| 675 | + # Test at the class level (before instantiation) |
| 676 | + required_exceptions = [ |
| 677 | + 'Warning', 'Error', 'InterfaceError', 'DatabaseError', |
| 678 | + 'DataError', 'OperationalError', 'IntegrityError', |
| 679 | + 'InternalError', 'ProgrammingError', 'NotSupportedError' |
| 680 | + ] |
| 681 | + |
| 682 | + for exc_name in required_exceptions: |
| 683 | + assert hasattr(Connection, exc_name), f"Connection class should have {exc_name} attribute" |
| 684 | + exc_class = getattr(Connection, exc_name) |
| 685 | + assert isinstance(exc_class, type), f"Connection.{exc_name} should be a class" |
| 686 | + assert issubclass(exc_class, Exception), f"Connection.{exc_name} should be an Exception subclass" |
0 commit comments