1. 删除了 test/unit/external_systems/framework/ 下的废弃空测试目录 2. 修复多处测试断言逻辑、参数传递与测试数据构造 3. 新增日志级别、缓存令牌、流事件等DTO单元测试 4. 补充路由绑定、会话仓储、outbox仓储的测试覆盖 5. 更新测试用例中的异常类型、参数校验与业务逻辑断言
182 lines
5.8 KiB
Python
182 lines
5.8 KiB
Python
"""fence.py DTO 单元测试。
|
|
|
|
覆盖 ``FenceVerdict`` / ``FenceGeneration`` / ``FenceContext`` 的字段赋值、
|
|
默认值、不可变语义与 ``__post_init__`` 校验逻辑。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
from datetime import datetime
|
|
|
|
import pytest
|
|
from yuxi.channels.contract.dtos.fence import (
|
|
FenceContext,
|
|
FenceGeneration,
|
|
FenceVerdict,
|
|
)
|
|
from yuxi.channels.contract.errors import ValidationError
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestFenceVerdict:
|
|
"""栅栏裁决 DTO 测试。"""
|
|
|
|
def test_fields_are_assigned(self):
|
|
# Arrange
|
|
# Act
|
|
verdict = FenceVerdict(is_stale=True, current_generation=3, stale_generation=1)
|
|
# Assert
|
|
assert verdict.is_stale is True
|
|
assert verdict.current_generation == 3
|
|
assert verdict.stale_generation == 1
|
|
|
|
def test_stale_generation_defaults_to_none(self):
|
|
# Arrange
|
|
# Act
|
|
verdict = FenceVerdict(is_stale=False, current_generation=2)
|
|
# Assert
|
|
assert verdict.stale_generation is None
|
|
|
|
def test_is_frozen(self):
|
|
# Arrange
|
|
verdict = FenceVerdict(is_stale=False, current_generation=0)
|
|
# Act / Assert
|
|
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
verdict.is_stale = True # type: ignore[misc]
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestFenceGeneration:
|
|
"""栅栏代际 DTO 测试。"""
|
|
|
|
def test_fields_are_assigned(self):
|
|
# Arrange
|
|
# Act
|
|
gen = FenceGeneration(
|
|
conversation_id="conv-1",
|
|
current_generation=3,
|
|
visible_generation=2,
|
|
)
|
|
# Assert
|
|
assert gen.conversation_id == "conv-1"
|
|
assert gen.current_generation == 3
|
|
assert gen.visible_generation == 2
|
|
|
|
def test_zero_generations_are_valid(self):
|
|
# Arrange
|
|
# Act
|
|
gen = FenceGeneration(
|
|
conversation_id="conv-1",
|
|
current_generation=0,
|
|
visible_generation=0,
|
|
)
|
|
# Assert - 代际为 0 是合法的(非负)
|
|
assert gen.current_generation == 0
|
|
assert gen.visible_generation == 0
|
|
|
|
def test_is_frozen(self):
|
|
# Arrange
|
|
gen = FenceGeneration(
|
|
conversation_id="conv-1", current_generation=0, visible_generation=0
|
|
)
|
|
# Act / Assert
|
|
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
gen.current_generation = 1 # type: ignore[misc]
|
|
|
|
def test_empty_conversation_id_raises_validation_error(self):
|
|
# Arrange
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
FenceGeneration(conversation_id="", current_generation=0, visible_generation=0)
|
|
assert exc_info.value.field == "conversation_id"
|
|
|
|
def test_negative_current_generation_raises_validation_error(self):
|
|
# Arrange
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
FenceGeneration(conversation_id="conv-1", current_generation=-1, visible_generation=0)
|
|
assert exc_info.value.field == "current_generation"
|
|
|
|
def test_negative_visible_generation_raises_validation_error(self):
|
|
# Arrange
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
FenceGeneration(conversation_id="conv-1", current_generation=0, visible_generation=-1)
|
|
assert exc_info.value.field == "visible_generation"
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestFenceContext:
|
|
"""栅栏上下文 DTO 测试。"""
|
|
|
|
def test_fields_are_assigned(self):
|
|
# Arrange
|
|
now = datetime(2026, 1, 1, 12, 0, 0)
|
|
# Act
|
|
ctx = FenceContext(
|
|
conversation_id="conv-1",
|
|
generation=1,
|
|
started_at=now,
|
|
)
|
|
# Assert
|
|
assert ctx.conversation_id == "conv-1"
|
|
assert ctx.generation == 1
|
|
assert ctx.started_at is now
|
|
assert ctx.ttl_seconds == 120
|
|
|
|
def test_custom_ttl(self):
|
|
# Arrange
|
|
now = datetime(2026, 1, 1, 12, 0, 0)
|
|
# Act
|
|
ctx = FenceContext(
|
|
conversation_id="conv-1",
|
|
generation=1,
|
|
started_at=now,
|
|
ttl_seconds=300,
|
|
)
|
|
# Assert
|
|
assert ctx.ttl_seconds == 300
|
|
|
|
def test_zero_generation_is_valid(self):
|
|
# Arrange
|
|
now = datetime(2026, 1, 1, 12, 0, 0)
|
|
# Act
|
|
ctx = FenceContext(conversation_id="conv-1", generation=0, started_at=now)
|
|
# Assert
|
|
assert ctx.generation == 0
|
|
|
|
def test_is_frozen(self):
|
|
# Arrange
|
|
now = datetime(2026, 1, 1, 12, 0, 0)
|
|
ctx = FenceContext(conversation_id="conv-1", generation=0, started_at=now)
|
|
# Act / Assert
|
|
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
ctx.generation = 1 # type: ignore[misc]
|
|
|
|
def test_empty_conversation_id_raises_validation_error(self):
|
|
# Arrange
|
|
now = datetime(2026, 1, 1, 12, 0, 0)
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
FenceContext(conversation_id="", generation=0, started_at=now)
|
|
assert exc_info.value.field == "conversation_id"
|
|
|
|
def test_negative_generation_raises_validation_error(self):
|
|
# Arrange
|
|
now = datetime(2026, 1, 1, 12, 0, 0)
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
FenceContext(conversation_id="conv-1", generation=-1, started_at=now)
|
|
assert exc_info.value.field == "generation"
|
|
|
|
def test_non_positive_ttl_raises_validation_error(self):
|
|
# Arrange
|
|
now = datetime(2026, 1, 1, 12, 0, 0)
|
|
# Act / Assert
|
|
with pytest.raises(ValidationError) as exc_info:
|
|
FenceContext(conversation_id="conv-1", generation=0, started_at=now, ttl_seconds=0)
|
|
assert exc_info.value.field == "ttl_seconds"
|