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

219 lines
7.7 KiB
Python
Raw Normal View History

from __future__ import annotations
import time
from unittest.mock import AsyncMock, MagicMock
import pytest
from yuxi.channels.adapters.mattermost.adapter import MattermostAdapter
from yuxi.channels.adapters.mattermost.cache import LRUCache, TTLCache
@pytest.fixture
def adapter():
config = {
"bot_token": "test-token",
"server_url": "https://mattermost.example.com",
}
return MattermostAdapter(config=config)
@pytest.fixture
def mock_driver():
driver = MagicMock()
driver.posts = MagicMock()
driver.posts.create_post = MagicMock(return_value={"id": "msg_001"})
driver.posts.update_post = AsyncMock()
driver.posts.delete_post = AsyncMock()
driver.posts.pin_post = AsyncMock()
driver.posts.unpin_post = AsyncMock()
driver.posts.get_pinned_posts = AsyncMock(return_value=[])
driver.posts.search_posts = AsyncMock(return_value={"posts": []})
driver.posts.get_thread = AsyncMock(return_value={"posts": []})
driver.reactions = MagicMock()
driver.reactions.create_reaction = AsyncMock()
driver.reactions.remove_reaction = AsyncMock()
driver.reactions.get_reactions = AsyncMock(return_value=[])
driver.users = MagicMock()
driver.users.create_user_typing = AsyncMock()
return driver
@pytest.fixture
def connected_adapter(adapter, mock_driver):
adapter._status = adapter._status.__class__.CONNECTED
adapter._drivers["default"] = mock_driver
adapter._bot_user_id = "bot_001"
adapter._bot_username = "testbot"
adapter._server_url = "https://mattermost.example.com"
return adapter
class TestMattermostOperations:
@pytest.mark.asyncio
async def test_pin_message(self, connected_adapter):
result = await connected_adapter.pin_message("post_001")
assert result is True
@pytest.mark.asyncio
async def test_unpin_message(self, connected_adapter):
result = await connected_adapter.unpin_message("post_001")
assert result is True
@pytest.mark.asyncio
async def test_list_pinned_messages(self, connected_adapter, mock_driver):
mock_driver.posts.get_pinned_posts.return_value = [
{"id": "p1", "message": "pinned post"}
]
result = await connected_adapter.list_pinned_messages("channel_001")
assert len(result) == 1
assert result[0]["id"] == "p1"
@pytest.mark.asyncio
async def test_get_reactions(self, connected_adapter, mock_driver):
mock_driver.reactions.get_reactions.return_value = [
{"emoji_name": "thumbsup", "user_id": "u1"}
]
result = await connected_adapter.get_reactions("post_001")
assert len(result) == 1
assert result[0]["emoji_name"] == "thumbsup"
@pytest.mark.asyncio
async def test_search_messages(self, connected_adapter, mock_driver):
mock_driver.posts.search_posts.return_value = {
"posts": [{"id": "p1", "message": "hello world"}]
}
result = await connected_adapter.search_messages("team_001", "hello")
assert len(result) == 1
@pytest.mark.asyncio
async def test_get_thread_replies(self, connected_adapter, mock_driver):
mock_driver.posts.get_thread.return_value = {
"posts": [{"id": "p1"}, {"id": "p2"}]
}
result = await connected_adapter.get_thread_replies("post_001")
assert len(result) == 2
@pytest.mark.asyncio
async def test_pin_message_not_connected(self, adapter):
assert await adapter.pin_message("post_001") is False
@pytest.mark.asyncio
async def test_unpin_message_not_connected(self, adapter):
assert await adapter.unpin_message("post_001") is False
class TestMattermostSupportsAction:
def test_supports_pin_unpin(self, adapter):
assert adapter.supports_action("pin") is True
assert adapter.supports_action("unpin") is True
def test_supports_existing_actions(self, adapter):
assert adapter.supports_action("send") is True
assert adapter.supports_action("react") is True
assert adapter.supports_action("edit") is True
assert adapter.supports_action("delete") is True
def test_does_not_support_unknown(self, adapter):
assert adapter.supports_action("unknown_action") is False
class TestCache:
def test_ttlcache_get_set(self):
cache = TTLCache(ttl_s=300, max_size=100)
cache.set("key1", "value1")
assert cache.get("key1") == "value1"
def test_ttlcache_delete(self):
cache = TTLCache(ttl_s=300, max_size=100)
cache.set("key1", "value1")
assert cache.delete("key1") is True
assert cache.get("key1") is None
assert cache.delete("nonexistent") is False
def test_lrucache_eviction(self):
cache = LRUCache(max_size=2, ttl_s=300)
cache.set("a", 1)
cache.set("b", 2)
cache.set("c", 3)
assert cache.size() == 2
assert cache.get("a") is None
assert cache.get("b") == 2
assert cache.get("c") == 3
class TestMattermostCapabilities:
def test_pin_capabilities_enabled(self, adapter):
assert adapter.capabilities.pin is True
assert adapter.capabilities.unpin is True
assert adapter.capabilities.list_pins is True
def test_ephemeral_capability_enabled(self, adapter):
assert adapter.capabilities.send_ephemeral is True
def test_typing_capability_enabled(self, adapter):
assert adapter.capabilities.typing is True
class TestMattermostWSConfig:
def test_default_ws_config(self):
adapter = MattermostAdapter(config={})
assert adapter._ws_ping_interval_s == 30.0
assert adapter._ws_pong_timeout_s == 10.0
assert adapter._ws_reconnect_jitter == 0.2
assert adapter._ws_max_reconnect_attempts == 10
assert adapter._ws_reconnect_max_delay_s == 120.0
def test_custom_ws_config(self):
adapter = MattermostAdapter(
config={
"bot_token": "test-token",
"server_url": "https://mattermost.example.com",
"ws_ping_interval_s": 15.0,
"ws_pong_timeout_s": 5.0,
"ws_reconnect_jitter": 0.5,
"ws_max_reconnect_attempts": 5,
"ws_reconnect_max_delay_s": 60.0,
}
)
assert adapter._ws_ping_interval_s == 15.0
assert adapter._ws_pong_timeout_s == 5.0
assert adapter._ws_reconnect_jitter == 0.5
assert adapter._ws_max_reconnect_attempts == 5
assert adapter._ws_reconnect_max_delay_s == 60.0
class TestDedupObservability:
def test_dedup_counters_initialized(self, adapter):
assert adapter._dedup_hit_count == 0
assert adapter._dedup_total_count == 0
def test_dedup_total_increments(self, adapter):
adapter._seen_posts["post_1"] = time.monotonic()
adapter._dedup_check("post_2")
assert adapter._dedup_total_count == 1
def test_dedup_hit_increments(self, adapter):
adapter._seen_posts["post_1"] = time.monotonic()
adapter._dedup_check("post_1")
assert adapter._dedup_hit_count == 1
assert adapter._dedup_total_count == 1
class TestDedupSnapshot:
def test_snapshot_includes_dedup_stats(self, adapter):
adapter._dedup_total_count = 10
adapter._dedup_hit_count = 3
snapshot = adapter.snapshot()
assert snapshot["dedup_hit_count"] == 3
assert snapshot["dedup_total_count"] == 10
assert snapshot["dedup_hit_rate"] == 0.3
def test_snapshot_dedup_zero_division(self, adapter):
snapshot = adapter.snapshot()
assert snapshot["dedup_hit_rate"] == 0.0
def test_snapshot_includes_ws_config(self, adapter):
snapshot = adapter.snapshot()
assert "ws_ping_interval_s" in snapshot
assert "ws_pong_timeout_s" in snapshot