97 lines
3.4 KiB
Python
97 lines
3.4 KiB
Python
from __future__ import annotations
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
from yuxi.channel.constants import CHANNEL_PROCESSED_TTL_SECONDS, channel_processed_key
|
|
from yuxi.channel.message.dedupe import MessageDeduper
|
|
|
|
|
|
@pytest.fixture
|
|
def redis_mock():
|
|
return MagicMock(
|
|
set=AsyncMock(),
|
|
exists=AsyncMock(),
|
|
setex=AsyncMock(),
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def deduper():
|
|
return MessageDeduper()
|
|
|
|
|
|
async def test_is_processed_new_message(redis_mock, deduper):
|
|
redis_mock.set.return_value = True
|
|
with patch("yuxi.channel.message.dedupe.get_redis_client", AsyncMock(return_value=redis_mock)):
|
|
result = await deduper.is_processed("msg-1")
|
|
|
|
assert result is False
|
|
redis_mock.set.assert_awaited_once_with(
|
|
channel_processed_key("msg-1"), "1", nx=True, ex=CHANNEL_PROCESSED_TTL_SECONDS
|
|
)
|
|
|
|
|
|
async def test_is_processed_duplicate_message(redis_mock, deduper):
|
|
redis_mock.set.return_value = None
|
|
with patch("yuxi.channel.message.dedupe.get_redis_client", AsyncMock(return_value=redis_mock)):
|
|
result = await deduper.is_processed("msg-1")
|
|
|
|
assert result is True
|
|
|
|
|
|
async def test_is_processed_empty_message_id_returns_true(redis_mock, deduper):
|
|
with patch("yuxi.channel.message.dedupe.get_redis_client", AsyncMock(return_value=redis_mock)):
|
|
assert await deduper.is_processed("") is True
|
|
assert await deduper.is_processed(None) is True
|
|
|
|
redis_mock.set.assert_not_called()
|
|
|
|
|
|
async def test_is_already_processed_true(redis_mock, deduper):
|
|
redis_mock.exists.return_value = 1
|
|
with patch("yuxi.channel.message.dedupe.get_redis_client", AsyncMock(return_value=redis_mock)):
|
|
result = await deduper.is_already_processed("msg-1")
|
|
|
|
assert result is True
|
|
redis_mock.exists.assert_awaited_once_with(channel_processed_key("msg-1"))
|
|
|
|
|
|
async def test_is_already_processed_false(redis_mock, deduper):
|
|
redis_mock.exists.return_value = 0
|
|
with patch("yuxi.channel.message.dedupe.get_redis_client", AsyncMock(return_value=redis_mock)):
|
|
result = await deduper.is_already_processed("msg-1")
|
|
|
|
assert result is False
|
|
|
|
|
|
async def test_is_already_processed_empty_message_id_returns_true(redis_mock, deduper):
|
|
with patch("yuxi.channel.message.dedupe.get_redis_client", AsyncMock(return_value=redis_mock)):
|
|
assert await deduper.is_already_processed("") is True
|
|
|
|
redis_mock.exists.assert_not_called()
|
|
|
|
|
|
async def test_mark_processed_sets_key_with_ttl(redis_mock, deduper):
|
|
with patch("yuxi.channel.message.dedupe.get_redis_client", AsyncMock(return_value=redis_mock)):
|
|
await deduper.mark_processed("msg-1")
|
|
|
|
redis_mock.setex.assert_awaited_once_with(channel_processed_key("msg-1"), CHANNEL_PROCESSED_TTL_SECONDS, "1")
|
|
|
|
|
|
async def test_mark_processed_empty_message_id_is_noop(redis_mock, deduper):
|
|
with patch("yuxi.channel.message.dedupe.get_redis_client", AsyncMock(return_value=redis_mock)):
|
|
await deduper.mark_processed("")
|
|
await deduper.mark_processed(None)
|
|
|
|
redis_mock.setex.assert_not_called()
|
|
|
|
|
|
async def test_custom_ttl(redis_mock):
|
|
deduper = MessageDeduper(ttl_seconds=60)
|
|
redis_mock.set.return_value = True
|
|
with patch("yuxi.channel.message.dedupe.get_redis_client", AsyncMock(return_value=redis_mock)):
|
|
await deduper.is_processed("msg-1")
|
|
|
|
redis_mock.set.assert_awaited_once_with(channel_processed_key("msg-1"), "1", nx=True, ex=60)
|