"""degradation.py DTO 单元测试。 覆盖 ``DegradationLevel`` 枚举与 ``RecoveryStatus`` 的字段赋值、默认值、 不可变语义与 ``__post_init__`` 校验逻辑。 """ from __future__ import annotations import dataclasses from datetime import datetime import pytest from yuxi.channels.contract.dtos.degradation import ( DegradationLevel, RecoveryStatus, ) from yuxi.channels.contract.errors import ValidationError pytestmark = pytest.mark.unit @pytest.mark.unit class TestDegradationLevel: """降级等级枚举测试。""" def test_enum_values_are_correct(self): # Arrange # Act # Assert assert DegradationLevel.HEALTHY == "healthy" assert DegradationLevel.DEGRADED == "degraded" assert DegradationLevel.UNHEALTHY == "unhealthy" def test_enum_inherits_from_str(self): # Arrange # Act # Assert assert issubclass(DegradationLevel, str) assert isinstance(DegradationLevel.HEALTHY, str) def test_enum_has_three_members(self): # Arrange # Act members = list(DegradationLevel) # Assert assert len(members) == 3 @pytest.mark.unit class TestRecoveryStatus: """恢复状态 DTO 测试。""" def test_required_fields_are_assigned(self): # Arrange # Act status = RecoveryStatus(plugin_id="plugin-1") # Assert assert status.plugin_id == "plugin-1" def test_defaults_are_correct(self): # Arrange # Act status = RecoveryStatus(plugin_id="plugin-1") # Assert assert status.retry_count == 0 assert status.last_retry_at is None assert status.next_retry_at is None assert status.recovered is False assert status.failure_reason is None def test_custom_values_are_assigned(self): # Arrange now = datetime(2026, 1, 1, 12, 0, 0) later = datetime(2026, 1, 1, 12, 5, 0) # Act status = RecoveryStatus( plugin_id="plugin-1", retry_count=2, last_retry_at=now, next_retry_at=later, recovered=False, failure_reason="connection refused", ) # Assert assert status.retry_count == 2 assert status.last_retry_at is now assert status.next_retry_at is later assert status.recovered is False assert status.failure_reason == "connection refused" def test_max_retry_count_is_valid(self): # Arrange # Act status = RecoveryStatus(plugin_id="plugin-1", retry_count=3) # Assert - retry_count 上限 3 assert status.retry_count == 3 def test_is_frozen(self): # Arrange status = RecoveryStatus(plugin_id="plugin-1") # Act / Assert with pytest.raises(dataclasses.FrozenInstanceError): status.plugin_id = "other" # type: ignore[misc] def test_empty_plugin_id_raises_validation_error(self): # Arrange # Act / Assert with pytest.raises(ValidationError) as exc_info: RecoveryStatus(plugin_id="") assert exc_info.value.field == "plugin_id" def test_negative_retry_count_raises_validation_error(self): # Arrange # Act / Assert with pytest.raises(ValidationError) as exc_info: RecoveryStatus(plugin_id="plugin-1", retry_count=-1) assert exc_info.value.field == "retry_count" def test_exceeding_retry_count_raises_validation_error(self): # Arrange # Act / Assert - retry_count 超过 3 应抛出 with pytest.raises(ValidationError) as exc_info: RecoveryStatus(plugin_id="plugin-1", retry_count=4) assert exc_info.value.field == "retry_count"