ForcePilot/backend/test/unit/channels/test_framework_pipeline.py
Kris 3264900bc9 test: 新增多渠道单元测试用例并配置测试环境变量
新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块
同时在测试配置中添加了测试用的OpenAI API密钥环境变量
2026-05-12 00:56:47 +08:00

184 lines
5.8 KiB
Python

from __future__ import annotations
from typing import Any
import pytest
from yuxi.channels.pipeline.base import BaseInboundPipeline
from yuxi.channels.pipeline.context import PipelineContext
class _TestPipeline(BaseInboundPipeline):
def __init__(self, stages=None):
super().__init__(adapter=None)
self._custom_stages = stages or []
async def _build_stages(self):
return self._custom_stages
class TestPipelineContext:
def test_default_values(self):
ctx = PipelineContext()
assert ctx.event_type == ""
assert ctx.msg_id == ""
assert ctx.stopped is False
def test_stop_with_default_reason(self):
ctx = PipelineContext()
ctx.stop()
assert ctx.stopped is True
assert ctx._stop_reason == "pipeline_stop"
def test_stop_with_custom_reason(self):
ctx = PipelineContext()
ctx.stop("access_denied")
assert ctx.stopped is True
assert ctx._stop_reason == "access_denied"
def test_debug_summary(self):
ctx = PipelineContext(event_type="C2C_MESSAGE_CREATE", sender_id="user-1", msg_id="msg-1")
summary = ctx.debug_summary()
assert "C2C_MESSAGE_CREATE" in summary
assert "user-1" in summary
assert "msg-1" in summary
assert "stop=None" in summary
def test_debug_summary_stopped(self):
ctx = PipelineContext(event_type="C2C_MESSAGE_CREATE")
ctx.stop("access_denied")
summary = ctx.debug_summary()
assert "stop=access_denied" in summary
def test_metadata_persists(self):
ctx = PipelineContext(metadata={"key": "value"})
assert ctx.metadata["key"] == "value"
class TestBaseInboundPipeline:
@pytest.mark.asyncio
async def test_empty_pipeline_returns_context(self):
pipeline = _TestPipeline(stages=[])
await pipeline.initialize()
result = await pipeline.process("test_event", {"data": "value"})
assert result is not None
assert result.event_type == "test_event"
@pytest.mark.asyncio
async def test_single_stage_success(self):
async def stage_pass(pipe, ctx):
ctx.sender_id = "user-1"
return ctx
pipeline = _TestPipeline(stages=[stage_pass])
await pipeline.initialize()
result = await pipeline.process("test_event", {})
assert result is not None
assert result.sender_id == "user-1"
@pytest.mark.asyncio
async def test_stage_returns_none_short_circuits(self):
async def stage_reject(pipe, ctx):
return None
pipeline = _TestPipeline(stages=[stage_reject])
await pipeline.initialize()
result = await pipeline.process("test_event", {})
assert result is None
@pytest.mark.asyncio
async def test_stage_exception_short_circuits(self):
async def stage_error(pipe, ctx):
raise RuntimeError("stage failure")
pipeline = _TestPipeline(stages=[stage_error])
await pipeline.initialize()
result = await pipeline.process("test_event", {})
assert result is None
@pytest.mark.asyncio
async def test_ctx_stop_short_circuits(self):
async def stage_with_stop(pipe, ctx):
ctx.stop("test_stop")
return ctx
pipeline = _TestPipeline(stages=[stage_with_stop])
await pipeline.initialize()
result = await pipeline.process("test_event", {})
assert result is None
@pytest.mark.asyncio
async def test_multiple_stages_execute_in_order(self):
order: list[str] = []
async def stage_1(pipe, ctx):
order.append("stage_1")
return ctx
async def stage_2(pipe, ctx):
order.append("stage_2")
return ctx
async def stage_3(pipe, ctx):
order.append("stage_3")
return ctx
pipeline = _TestPipeline(stages=[stage_1, stage_2, stage_3])
await pipeline.initialize()
await pipeline.process("test_event", {})
assert order == ["stage_1", "stage_2", "stage_3"]
@pytest.mark.asyncio
async def test_stage_short_circuit_stops_remaining(self):
order: list[str] = []
async def stage_1(pipe, ctx):
order.append("stage_1")
return ctx
async def stage_2(pipe, ctx):
order.append("stage_2")
return None
async def stage_3(pipe, ctx):
order.append("stage_3")
return ctx
pipeline = _TestPipeline(stages=[stage_1, stage_2, stage_3])
await pipeline.initialize()
result = await pipeline.process("test_event", {})
assert result is None
assert order == ["stage_1", "stage_2"]
@pytest.mark.asyncio
async def test_stages_share_same_context(self):
async def stage_set(pipe, ctx):
ctx.sender_id = "user-1"
ctx.metadata["first"] = True
return ctx
async def stage_read(pipe, ctx):
assert ctx.sender_id == "user-1"
assert ctx.metadata["first"] is True
ctx.chat_id = "chat-1"
return ctx
pipeline = _TestPipeline(stages=[stage_set, stage_read])
await pipeline.initialize()
result = await pipeline.process("test_event", {})
assert result is not None
assert result.sender_id == "user-1"
assert result.chat_id == "chat-1"
@pytest.mark.asyncio
async def test_adapter_property(self):
adapter = object()
pipeline = BaseInboundPipeline(adapter)
assert pipeline.adapter is adapter
class TestPipelineImport:
def test_import_from_package(self):
from yuxi.channels.pipeline import BaseInboundPipeline, PipelineContext
assert BaseInboundPipeline is not None
assert PipelineContext is not None