"""Tests for the AareException hierarchy introduced in Phase 1 of the exception-handling redesign. These cover: - class-level ``critical`` default - instance-level override via ``critical=...`` kwarg - correct parent/family relationships so watchers can match by family """ from __future__ import annotations import pytest from aarecommon.errors.exception_handler import ( AareAuthError, AareDBCommunicationError, AareException, AareUserError, AerotechCommunicationError, AerotechException, AuthenticationException, AutomationError, AutoRasterSampleSkipped, AXCFailed, BeamlineBusyException, BeamlineBusyTimeoutException, BeamlineStateException, BECCommunicationError, BECException, CriticalTellException, DataCollectionException, JFJochCommunicationError, JFJochException, LoopCenteringFailed, MagnetPositionSensorErorr, MaintenanceStateException, ManualMountException, MountingFailed, RasterScanException, SampleException, SmargonCommunicationError, SmargonException, SmartMagnetFaultException, StateTransitionFailed, TellCommandWhileBusyException, TellCommunicationError, TellConnectionException, TellException, TransformationInvalidException, UnmountingFailed, UserRightsException, WarningTellException, ) # --------------------------------------------------------------------------- # Class-level criticality defaults # --------------------------------------------------------------------------- def test_aare_exception_class_critical_default_false(): assert AareException.critical is False def test_phase3_classes_have_critical_default_true(): for cls in ( TellCommunicationError, TellConnectionException, CriticalTellException, SmargonCommunicationError, AerotechCommunicationError, JFJochCommunicationError, BECCommunicationError, StateTransitionFailed, MaintenanceStateException, BeamlineBusyException, BeamlineBusyTimeoutException, MagnetPositionSensorErorr, SmartMagnetFaultException, TransformationInvalidException, ): assert cls.critical is True, f"{cls.__name__} should default to critical=True in Phase 3" def test_other_core_classes_keep_critical_default_false(): for cls in ( AutomationError, AareUserError, AareAuthError, TellException, SmargonException, AerotechException, JFJochException, BECException, BeamlineStateException, MountingFailed, UnmountingFailed, LoopCenteringFailed, TellCommandWhileBusyException, WarningTellException, AareDBCommunicationError, ): assert cls.critical is False, f"{cls.__name__} should remain critical=False" # --------------------------------------------------------------------------- # Instance-level critical override # --------------------------------------------------------------------------- def test_instance_critical_override_true(): exc = MountingFailed("mount fail", critical=True) assert exc.critical is True # class default unchanged assert MountingFailed.critical is False def test_instance_critical_override_false(): exc = MountingFailed("mount fail", critical=False) assert exc.critical is False def test_instance_critical_none_keeps_class_default(): # Omitting critical uses class default exc = MountingFailed("mount fail") assert exc.critical is False # class default def test_aaredb_communication_error_instance_critical_override(): exc = AareDBCommunicationError("db down", critical=True, operation="GET") assert exc.critical is True assert exc.operation == "GET" # --------------------------------------------------------------------------- # Routing-root membership # --------------------------------------------------------------------------- def test_automation_errors_are_automation_error(): for cls in ( LoopCenteringFailed, MountingFailed, UnmountingFailed, AXCFailed, TellCommunicationError, SmargonCommunicationError, AerotechCommunicationError, JFJochCommunicationError, BECCommunicationError, AareDBCommunicationError, BeamlineBusyException, RasterScanException, MagnetPositionSensorErorr, SmartMagnetFaultException, TransformationInvalidException, AutoRasterSampleSkipped, ): exc = cls() if cls is not AutoRasterSampleSkipped else cls("skipped") assert isinstance(exc, AutomationError), f"{cls.__name__} should be AutomationError" assert isinstance(exc, AareException), f"{cls.__name__} should be AareException" def test_auth_errors_are_aare_auth_error(): for cls in (AuthenticationException, UserRightsException): exc = cls() assert isinstance(exc, AareAuthError) assert isinstance(exc, AareException) def test_user_errors_are_aare_user_error(): for cls in (ManualMountException, SampleException): exc = cls() assert isinstance(exc, AareUserError) assert isinstance(exc, AareException) # --------------------------------------------------------------------------- # Family-base membership (drives watcher matching) # --------------------------------------------------------------------------- def test_tell_family_membership(): for cls in ( TellCommunicationError, TellConnectionException, CriticalTellException, TellCommandWhileBusyException, WarningTellException, MountingFailed, UnmountingFailed, ): exc = cls() assert isinstance(exc, TellException), f"{cls.__name__} should be in Tell family" assert isinstance(exc, AutomationError) def test_smargon_family_membership(): exc = SmargonCommunicationError() assert isinstance(exc, SmargonException) assert isinstance(exc, AutomationError) def test_aerotech_family_membership(): exc = AerotechCommunicationError() assert isinstance(exc, AerotechException) assert isinstance(exc, AutomationError) def test_jfjoch_family_membership(): exc = JFJochCommunicationError() assert isinstance(exc, JFJochException) assert isinstance(exc, AutomationError) def test_bec_family_membership(): exc = BECCommunicationError() assert isinstance(exc, BECException) assert isinstance(exc, AutomationError) def test_beamline_state_family_membership(): for cls in ( StateTransitionFailed, MaintenanceStateException, BeamlineBusyException, BeamlineBusyTimeoutException, ): exc = cls() assert isinstance(exc, BeamlineStateException) assert isinstance(exc, AutomationError) def test_data_collection_family_membership(): exc = RasterScanException() assert isinstance(exc, DataCollectionException) assert isinstance(exc, AutomationError) # --------------------------------------------------------------------------- # Backward compat: existing __str__/.message contracts preserved # --------------------------------------------------------------------------- def test_message_attribute_preserved(): exc = LoopCenteringFailed("custom message") assert exc.message == "custom message" assert str(exc) == "custom message" def test_default_messages_preserved(): assert str(MountingFailed()) == "A sample was not mounted" assert str(UnmountingFailed()) == "A sample was not unmounted" assert str(LoopCenteringFailed()) == "Loop Centering did not detect a sample"