ForcePilot/backend/test/unit/channels/test_msteams_chunking.py

250 lines
9.1 KiB
Python
Raw Normal View History

"""MSTeams chunking / commands / command_gate 单元测试。"""
from __future__ import annotations
import pytest
from yuxi.channels.adapters.msteams.chunking import chunk_text
from yuxi.channels.adapters.msteams.commands import extract_command, build_command_help_card
from yuxi.channels.adapters.msteams.command_gate import CommandGate
class TestChunkText:
def test_short_text_returns_single_chunk(self):
result = chunk_text("hello", limit=4000)
assert result == ["hello"]
def test_exact_limit_text_returns_single_chunk(self):
text = "x" * 100
result = chunk_text(text, limit=100)
assert result == [text]
def test_length_mode_short(self):
result = chunk_text("hi", limit=10, mode="length")
assert result == ["hi"]
def test_length_mode_splits_evenly(self):
text = "abcdefghij"
result = chunk_text(text, limit=3, mode="length")
assert len(result) == 4
assert "".join(result) == text
def test_length_mode_exact_chunks(self):
text = "abcabc"
result = chunk_text(text, limit=3, mode="length")
assert result == ["abc", "abc"]
def test_newline_mode_basic(self):
text = "line1\nline2\nline3"
result = chunk_text(text, limit=20, mode="newline")
assert result == ["line1\nline2\nline3"]
def test_newline_mode_splits_long_lines(self):
text = "\n".join(["A" * 10, "B" * 10, "C" * 10])
result = chunk_text(text, limit=15, mode="newline")
assert len(result) == 3
def test_newline_mode_merges_short_lines(self):
text = "\n".join(["a", "b", "c"])
result = chunk_text(text, limit=100, mode="newline")
assert len(result) == 1
assert "a\nb\nc" in result[0]
def test_paragraph_mode_basic(self):
text = "Short paragraph."
result = chunk_text(text, limit=100)
assert len(result) == 1
assert "Short paragraph." in result[0]
def test_paragraph_mode_splits_long(self):
text = "Sentence A. Sentence B. Sentence C."
result = chunk_text(text, limit=20)
assert len(result) > 1
def test_paragraph_mode_multi_paragraph(self):
text = "Para1.\n\nPara2."
result = chunk_text(text, limit=10)
assert len(result) >= 2
def test_empty_text(self):
result = chunk_text("", limit=100)
assert result == [""]
def test_default_mode_is_paragraph(self):
text = "A" * 5000
result = chunk_text(text, limit=4000)
assert len(result) >= 1
def test_paragraph_merges_small_chunks(self):
text = "a.\n\nb."
result = chunk_text(text, limit=200)
assert len(result) == 1
class TestExtractCommand:
def test_valid_command(self):
cmd, args = extract_command("/help")
assert cmd == "/help"
assert args == ""
def test_valid_command_with_args(self):
cmd, args = extract_command("/reset confirm")
assert cmd == "/reset"
assert args == "confirm"
def test_unknown_command(self):
cmd, content = extract_command("/unknown")
assert cmd is None
assert content == "/unknown"
def test_non_command_text(self):
cmd, content = extract_command("hello world")
assert cmd is None
assert content == "hello world"
def test_empty_text(self):
cmd, content = extract_command("")
assert cmd is None
assert content == ""
def test_command_case_insensitive(self):
cmd, args = extract_command("/HELP")
assert cmd == "/help"
def test_command_with_extra_whitespace(self):
cmd, args = extract_command(" /help test ")
assert cmd == "/help"
assert args == "test"
def test_all_known_commands(self):
for cmd_name in ["/reset", "/history", "/context", "/summary", "/help", "/status"]:
cmd, _ = extract_command(cmd_name)
assert cmd == cmd_name
class TestBuildCommandHelpCard:
def test_returns_adaptive_card(self):
card = build_command_help_card()
assert card["type"] == "AdaptiveCard"
assert card["version"] == "1.5"
def test_card_contains_fact_set(self):
card = build_command_help_card()
body_types = [b["type"] for b in card["body"]]
assert "FactSet" in body_types
class TestCommandGateBasic:
@pytest.fixture
def gate(self):
return CommandGate()
def test_default_command_enabled_is_true(self, gate):
assert gate._command_enabled is True
def test_basic_command_authorized_direct(self, gate):
assert gate.is_command_authorized("help", "direct") is True
def test_basic_command_authorized_group(self, gate):
assert gate.is_command_authorized("status", "group") is True
def test_command_normalized(self, gate):
assert gate.is_command_authorized("/help", "direct") is True
def test_commands_disabled(self):
gate = CommandGate({"command_enabled": False})
assert gate.is_command_authorized("help", "direct") is False
assert gate.is_command_authorized("help", "group") is False
class TestCommandGateDM:
def test_dm_blocked_command(self):
gate = CommandGate({"dm_blocked_commands": ["reset"]})
assert gate.is_command_authorized("reset", "direct") is False
assert gate.is_command_authorized("help", "direct") is True
def test_dm_allowed_only(self):
gate = CommandGate({"dm_allowed_commands": ["help", "status"]})
assert gate.is_command_authorized("help", "direct") is True
assert gate.is_command_authorized("status", "direct") is True
assert gate.is_command_authorized("reset", "direct") is False
def test_dm_blocked_takes_priority(self):
gate = CommandGate({
"dm_allowed_commands": ["help", "reset"],
"dm_blocked_commands": ["reset"],
})
assert gate.is_command_authorized("help", "direct") is True
assert gate.is_command_authorized("reset", "direct") is False
class TestCommandGateGroup:
def test_group_blocked_command(self):
gate = CommandGate({"group_blocked_commands": ["summary"]})
assert gate.is_command_authorized("summary", "group") is False
assert gate.is_command_authorized("help", "group") is True
def test_channel_uses_group_policy(self):
gate = CommandGate({"group_blocked_commands": ["summary"]})
assert gate.is_command_authorized("summary", "channel") is False
def test_group_allowed_only(self):
gate = CommandGate({"group_allowed_commands": ["help"]})
assert gate.is_command_authorized("help", "group") is True
assert gate.is_command_authorized("status", "group") is False
class TestCommandGateAdmin:
def test_admin_with_empty_admin_ids(self):
gate = CommandGate()
assert gate.is_admin("any-user") is False
def test_admin_recognized(self):
gate = CommandGate({"admin_user_ids": ["admin-001", "admin-002"]})
assert gate.is_admin("admin-001") is True
assert gate.is_admin("admin-002") is True
assert gate.is_admin("user-001") is False
class TestCommandGateResolve:
def test_resolve_with_team_config(self):
gate = CommandGate({"dm_allowed_commands": ["help"]})
team_config = {"dm_allowed_commands": ["help", "reset"]}
result = gate.resolve_command_gate(team_config, "reset", "direct")
assert result is True
def test_resolve_falls_back_to_self(self):
gate = CommandGate({"dm_allowed_commands": ["help"]})
result = gate.resolve_command_gate(None, "help", "direct")
assert result is True
def test_resolve_with_no_team_overrides(self):
gate = CommandGate({"dm_allowed_commands": ["help"]})
team_config = {"group_allowed_commands": ["status"]}
result = gate.resolve_command_gate(team_config, "help", "direct")
assert result is True
class TestCommandGateDual:
def test_dual_gate_dm(self):
gate = CommandGate()
dm_config = {"dm_allowed_commands": ["help", "reset"]}
assert gate.resolve_dual_text_control_command_gate(dm_config, None, "help", "direct") is True
assert gate.resolve_dual_text_control_command_gate(dm_config, None, "status", "direct") is False
def test_dual_gate_group(self):
gate = CommandGate()
group_config = {"group_allowed_commands": ["help"]}
assert gate.resolve_dual_text_control_command_gate(None, group_config, "help", "group") is True
assert gate.resolve_dual_text_control_command_gate(None, group_config, "status", "group") is False
def test_dual_gate_both(self):
gate = CommandGate()
dm_config = {"dm_allowed_commands": ["help"]}
group_config = {"group_allowed_commands": ["status"]}
assert gate.resolve_dual_text_control_command_gate(dm_config, group_config, "help", "direct") is True
assert gate.resolve_dual_text_control_command_gate(dm_config, group_config, "status", "group") is True
def test_dual_gate_blocks_disabled(self):
gate = CommandGate({"command_enabled": False})
assert gate.resolve_dual_text_control_command_gate(None, None, "help", "direct") is False