ForcePilot/backend/test/unit/channels/contract/dtos/test_fence.py
Kris f9f08221fc chore: 整理代码风格与优化细节
本次提交包含多类代码优化:
1. 修复多处单行代码换行格式,统一代码排版
2. 为外部系统模块新增快捷菜单配置
3. 优化前端API请求参数命名一致性
4. 补充媒体下载超限错误码与领域异常类
5. 完善仓储层更新逻辑,支持显式清空字段
6. 优化部分测试用例与工具函数代码结构
7. 为微信插件白名单缓存增加过期时间
2026-07-14 15:13:29 +08:00

180 lines
5.7 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"