Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions backend/community_manager/actions/chat.py
Original file line number Diff line number Diff line change
Expand Up @@ -998,6 +998,10 @@ async def refresh_external_sources(self) -> None:
logger.warning(f"Validation for {source.url!r} failed. Continue...")
continue

# Update content before the eligibility check so that
# is_whitelisted reads the current list, not the stale one
telegram_chat_external_source_service.set_content(source, diff.current)

if diff.removed:
logger.info(
f"Found {len(diff.removed)} removed members from the source {source.chat_id!r}"
Comment on lines 1005 to 1007
Expand All @@ -1009,9 +1013,6 @@ async def refresh_external_sources(self) -> None:
await community_user_action.kick_ineligible_chat_members(
chat_members=chat_members
)
# Set content only after the source was refreshed to ensure
# no new attempts to kick users that are already kicked will be made
telegram_chat_external_source_service.set_content(source, diff.current)

logger.info("All enabled chat sources refreshed.")

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import pytest
from unittest.mock import AsyncMock, patch

from sqlalchemy.orm import Session

from community_manager.actions.chat import (
CommunityManagerTaskChatAction,
CommunityManagerUserChatAction,
)
from core.dtos.chat.rule.whitelist import WhitelistRuleItemsDifferenceDTO
from core.services.chat.rule.whitelist import TelegramChatExternalSourceService
from tests.factories.chat import TelegramChatFactory, TelegramChatUserFactory
from tests.factories.rule.external_source import (
TelegramChatWhitelistExternalSourceFactory,
)
from tests.factories.rule.group import TelegramChatRuleGroupFactory
from tests.factories.user import UserFactory


@pytest.mark.asyncio
async def test_refresh_external_sources__removed_user_is_kicked(
db_session: Session,
):
chat = TelegramChatFactory.create(is_full_control=True)
group = TelegramChatRuleGroupFactory.create(chat=chat)

user_stays = UserFactory.create(telegram_id=1001)
user_removed = UserFactory.create(telegram_id=1002)

TelegramChatUserFactory.create(chat=chat, user=user_stays, is_managed=True)
TelegramChatUserFactory.create(chat=chat, user=user_removed, is_managed=True)

source = TelegramChatWhitelistExternalSourceFactory.create(
chat=chat,
group=group,
content=[1001, 1002],
is_enabled=True,
url="https://example.com/api/whitelist",
)
db_session.flush()

mock_validate = AsyncMock(
return_value=WhitelistRuleItemsDifferenceDTO(
previous=[1001, 1002],
current=[1001],
)
)

action = CommunityManagerTaskChatAction(db_session)

with patch.object(
TelegramChatExternalSourceService,
"validate_external_source",
mock_validate,
), patch.object(
CommunityManagerUserChatAction,
"kick_chat_member",
new_callable=AsyncMock,
) as mock_kick:
await action.refresh_external_sources()

# User 1002 was removed from the API response and should be kicked
mock_kick.assert_awaited_once()
kicked_member = mock_kick.await_args.args[0]
assert kicked_member.user.telegram_id == 1002
assert kicked_member.chat_id == chat.id

db_session.refresh(source)
assert source.content == [1001]


@pytest.mark.asyncio
async def test_refresh_external_sources__no_removed_users__no_kicks(
db_session: Session,
):
chat = TelegramChatFactory.create(is_full_control=True)
group = TelegramChatRuleGroupFactory.create(chat=chat)

user = UserFactory.create(telegram_id=1001)
TelegramChatUserFactory.create(chat=chat, user=user, is_managed=True)

TelegramChatWhitelistExternalSourceFactory.create(
chat=chat,
group=group,
content=[1001],
is_enabled=True,
url="https://example.com/api/whitelist",
)
db_session.flush()

mock_validate = AsyncMock(
return_value=WhitelistRuleItemsDifferenceDTO(
previous=[1001],
current=[1001],
)
)

action = CommunityManagerTaskChatAction(db_session)

with patch.object(
TelegramChatExternalSourceService,
"validate_external_source",
mock_validate,
), patch.object(
CommunityManagerUserChatAction,
"kick_chat_member",
new_callable=AsyncMock,
) as mock_kick:
await action.refresh_external_sources()

mock_kick.assert_not_awaited()