Skip to content

Commit 1f2841c

Browse files
committed
feat: add verification clearance notifications to warning topic
- Add new VERIFICATION_CLEARANCE_MESSAGE constant to announce user verification - Update verify handler to send notification to warning topic when user has warnings - Include user mention in clearance message using mention_markdown - Add new test fixtures and helper methods to verify command tests - Update fixture to use autouse=True for proper database setup - Add comprehensive test coverage for clearance notifications - Test notification sent when user has previous warnings - Test no notification sent when user has no warnings - Update README test statistics (208 tests, 700 statements, 100% coverage)
1 parent 6b3dd93 commit 1f2841c

4 files changed

Lines changed: 140 additions & 22 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,9 @@ uv run pytest -v
135135
### Test Coverage
136136

137137
The project maintains comprehensive test coverage:
138-
- **Coverage**: 100% across all modules (692 statements, 0 missed)
139-
- **Tests**: 206 total
140-
- **Pass Rate**: 100% (206/206 passed)
138+
- **Coverage**: 100% across all modules (700 statements, 0 missed)
139+
- **Tests**: 208 total
140+
- **Pass Rate**: 100% (208/208 passed)
141141
- **All modules**: 100% coverage including JobQueue scheduler integration and captcha verification
142142
- Services: `bot_info.py`, `scheduler.py`, `user_checker.py`, `telegram_utils.py`, `captcha_recovery.py`
143143
- Handlers: `captcha.py`, `dm.py`, `message.py`, `topic_guard.py`, `verify.py`

src/bot/constants.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,7 @@ def format_threshold_display(threshold_minutes: int) -> str:
130130
"✅ Selamat! Kamu sudah memenuhi persyaratan.\n"
131131
"Pembatasan kamu di grup telah dicabut. Silakan bergabung kembali!"
132132
)
133+
134+
VERIFICATION_CLEARANCE_MESSAGE = (
135+
"✅ {user_mention} telah diverifikasi oleh admin. Silakan berdiskusi kembali."
136+
)

src/bot/handlers/verify.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
from telegram import Update
1212
from telegram.error import BadRequest
1313
from telegram.ext import ContextTypes
14+
from telegram.helpers import mention_markdown
1415

1516
from bot.config import get_settings
17+
from bot.constants import VERIFICATION_CLEARANCE_MESSAGE
1618
from bot.database.service import get_database
1719
from bot.services.telegram_utils import unrestrict_user
1820

@@ -85,7 +87,24 @@ async def handle_verify_command(
8587

8688
# Delete all warning records for this user
8789
deleted_count = db.delete_user_warnings(target_user_id, settings.group_id)
90+
91+
# Send notification to warning topic if user had previous warnings
8892
if deleted_count > 0:
93+
# Get user info for proper mention
94+
user_info = await context.bot.get_chat(target_user_id)
95+
user_mention = mention_markdown(target_user_id, user_info.full_name, version=2)
96+
97+
# Send clearance message to warning topic
98+
clearance_message = VERIFICATION_CLEARANCE_MESSAGE.format(
99+
user_mention=user_mention
100+
)
101+
await context.bot.send_message(
102+
chat_id=settings.group_id,
103+
message_thread_id=settings.warning_topic_id,
104+
text=clearance_message,
105+
parse_mode="Markdown"
106+
)
107+
logger.info(f"Sent clearance notification to warning topic for user {target_user_id}")
89108
logger.info(f"Deleted {deleted_count} warning record(s) for user {target_user_id}")
90109

91110
await update.message.reply_text(

tests/test_verify_handler.py

Lines changed: 114 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
from bot.handlers.verify import handle_unverify_command, handle_verify_command
99

1010

11-
@pytest.fixture
11+
@pytest.fixture(autouse=True)
1212
def temp_db():
1313
with tempfile.TemporaryDirectory() as tmpdir:
1414
db_path = Path(tmpdir) / "test.db"
15+
reset_database() # Reset before init
1516
init_database(str(db_path))
1617
yield db_path
1718
reset_database()
@@ -36,8 +37,9 @@ def mock_context():
3637
context.bot = MagicMock()
3738
context.bot.get_chat = AsyncMock()
3839
context.bot.restrict_chat_member = AsyncMock()
40+
context.bot.send_message = AsyncMock()
3941

40-
# Mock get_chat to return a chat with default permissions
42+
# Mock get_chat to return both chat permissions and user info
4143
mock_chat = MagicMock()
4244
mock_permissions = MagicMock()
4345
mock_permissions.can_send_messages = True
@@ -48,6 +50,7 @@ def mock_context():
4850
mock_permissions.can_invite_users = True
4951
mock_permissions.can_pin_messages = False
5052
mock_chat.permissions = mock_permissions
53+
mock_chat.full_name = "Test User"
5154
context.bot.get_chat.return_value = mock_chat
5255

5356
context.bot_data = {"admin_ids": [12345]}
@@ -112,8 +115,16 @@ async def test_invalid_user_id_format(self, mock_update, mock_context):
112115
call_args = mock_update.message.reply_text.call_args
113116
assert "angka" in call_args.args[0]
114117

115-
async def test_successful_verify_new_user(self, mock_update, mock_context, temp_db):
116-
target_user_id = 555666
118+
async def test_successful_verify_new_user(self, mock_update, mock_context, temp_db, monkeypatch):
119+
# Mock the settings
120+
class MockSettings:
121+
group_id = -1001234567890
122+
warning_topic_id = 12345
123+
telegram_bot_token = "fake_token"
124+
125+
monkeypatch.setattr("bot.handlers.verify.get_settings", lambda: MockSettings())
126+
127+
target_user_id = 11111111 # Use unique ID
117128
mock_context.args = [str(target_user_id)]
118129

119130
await handle_verify_command(mock_update, mock_context)
@@ -173,8 +184,17 @@ async def test_verify_respects_admin_ids(self, mock_update, mock_context):
173184
call_args = mock_update.message.reply_text.call_args
174185
assert "izin" in call_args.args[0]
175186

176-
async def test_verify_with_extra_args_uses_first(self, mock_update, mock_context, temp_db):
177-
mock_context.args = ["555666", "extra", "args"]
187+
async def test_verify_with_extra_args_uses_first(self, mock_update, mock_context, temp_db, monkeypatch):
188+
# Mock the settings
189+
class MockSettings:
190+
group_id = -1001234567890
191+
warning_topic_id = 12345
192+
telegram_bot_token = "fake_token"
193+
194+
monkeypatch.setattr("bot.handlers.verify.get_settings", lambda: MockSettings())
195+
196+
target_user_id = 22222222 # Use unique ID
197+
mock_context.args = [str(target_user_id), "extra", "args"]
178198

179199
await handle_verify_command(mock_update, mock_context)
180200

@@ -183,7 +203,7 @@ async def test_verify_with_extra_args_uses_first(self, mock_update, mock_context
183203
assert "diverifikasi" in call_args.args[0]
184204

185205
db = get_database()
186-
assert db.is_user_photo_whitelisted(555666)
206+
assert db.is_user_photo_whitelisted(target_user_id)
187207

188208
async def test_verify_large_user_id(self, mock_update, mock_context, temp_db):
189209
large_id = 9999999999
@@ -194,9 +214,17 @@ async def test_verify_large_user_id(self, mock_update, mock_context, temp_db):
194214
db = get_database()
195215
assert db.is_user_photo_whitelisted(large_id)
196216

197-
async def test_verify_unrestricts_user(self, mock_update, mock_context, temp_db):
217+
async def test_verify_unrestricts_user(self, mock_update, mock_context, temp_db, monkeypatch):
198218
"""Test that verify command unrestricts the user."""
199-
target_user_id = 555666
219+
# Mock the settings
220+
class MockSettings:
221+
group_id = -1001234567890
222+
warning_topic_id = 12345
223+
telegram_bot_token = "fake_token"
224+
225+
monkeypatch.setattr("bot.handlers.verify.get_settings", lambda: MockSettings())
226+
227+
target_user_id = 33333333 # Use unique ID
200228
mock_context.args = [str(target_user_id)]
201229

202230
await handle_verify_command(mock_update, mock_context)
@@ -207,37 +235,50 @@ async def test_verify_unrestricts_user(self, mock_update, mock_context, temp_db)
207235
assert call_args.kwargs["user_id"] == target_user_id
208236
assert call_args.kwargs["permissions"].can_send_messages is True
209237

210-
async def test_verify_deletes_warnings(self, mock_update, mock_context, temp_db):
238+
async def test_verify_deletes_warnings(self, mock_update, mock_context, temp_db, monkeypatch):
211239
"""Test that verify command deletes all warning records."""
212-
from bot.config import get_settings
240+
# Mock the settings
241+
class MockSettings:
242+
group_id = -1001234567890
243+
warning_topic_id = 12345
244+
telegram_bot_token = "fake_token"
213245

214-
target_user_id = 555666
215-
settings = get_settings()
246+
monkeypatch.setattr("bot.handlers.verify.get_settings", lambda: MockSettings())
247+
248+
target_user_id = 66666666 # Use unique ID
216249
db = get_database()
217250

218251
# Create some warning records for the user
219-
db.get_or_create_user_warning(target_user_id, settings.group_id)
220-
db.increment_message_count(target_user_id, settings.group_id)
252+
db.get_or_create_user_warning(target_user_id, MockSettings.group_id)
253+
db.increment_message_count(target_user_id, MockSettings.group_id)
221254

222255
# Verify there's at least one warning
223-
warning = db.get_or_create_user_warning(target_user_id, settings.group_id)
256+
warning = db.get_or_create_user_warning(target_user_id, MockSettings.group_id)
224257
assert warning.message_count >= 1
225258

226259
# Now verify the user
227260
mock_context.args = [str(target_user_id)]
228261
await handle_verify_command(mock_update, mock_context)
229262

230263
# Warnings should be deleted - trying to get warnings should create a new one
231-
new_warning = db.get_or_create_user_warning(target_user_id, settings.group_id)
264+
new_warning = db.get_or_create_user_warning(target_user_id, MockSettings.group_id)
232265
assert new_warning.message_count == 1 # Fresh start
233266

234267
async def test_verify_handles_non_restricted_user_gracefully(
235-
self, mock_update, mock_context, temp_db
268+
self, mock_update, mock_context, temp_db, monkeypatch
236269
):
237270
"""Test that verify doesn't fail if user is not restricted."""
238271
from telegram.error import BadRequest
239272

240-
target_user_id = 555666
273+
# Mock the settings
274+
class MockSettings:
275+
group_id = -1001234567890
276+
warning_topic_id = 12345
277+
telegram_bot_token = "fake_token"
278+
279+
monkeypatch.setattr("bot.handlers.verify.get_settings", lambda: MockSettings())
280+
281+
target_user_id = 44444444 # Use unique ID
241282
mock_context.args = [str(target_user_id)]
242283

243284
# Simulate BadRequest when trying to unrestrict a non-restricted user
@@ -255,6 +296,60 @@ async def test_verify_handles_non_restricted_user_gracefully(
255296
call_args = mock_update.message.reply_text.call_args
256297
assert "diverifikasi" in call_args.args[0]
257298

299+
async def test_verify_with_warnings_sends_notification_to_topic(
300+
self, mock_update, mock_context, temp_db, monkeypatch
301+
):
302+
"""Test that verify sends notification to warning topic when user has warnings."""
303+
# Mock the settings
304+
class MockSettings:
305+
group_id = -1001234567890
306+
warning_topic_id = 12345
307+
telegram_bot_token = "fake_token"
308+
309+
monkeypatch.setattr("bot.handlers.verify.get_settings", lambda: MockSettings())
310+
311+
target_user_id = 77777777 # Use unique ID
312+
db = get_database()
313+
314+
# Create warning records for the user
315+
db.get_or_create_user_warning(target_user_id, MockSettings.group_id)
316+
db.increment_message_count(target_user_id, MockSettings.group_id)
317+
db.increment_message_count(target_user_id, MockSettings.group_id)
318+
319+
# Now verify the user
320+
mock_context.args = [str(target_user_id)]
321+
await handle_verify_command(mock_update, mock_context)
322+
323+
# Should send notification to warning topic
324+
mock_context.bot.send_message.assert_called_once()
325+
call_args = mock_context.bot.send_message.call_args
326+
assert call_args.kwargs["chat_id"] == MockSettings.group_id
327+
assert call_args.kwargs["message_thread_id"] == MockSettings.warning_topic_id
328+
assert call_args.kwargs["parse_mode"] == "Markdown"
329+
# Check the message contains user mention
330+
assert "Test User" in call_args.kwargs["text"] or str(target_user_id) in call_args.kwargs["text"]
331+
332+
async def test_verify_without_warnings_no_notification(
333+
self, mock_update, mock_context, temp_db, monkeypatch
334+
):
335+
"""Test that verify doesn't send notification when user has no warnings."""
336+
# Mock the settings
337+
class MockSettings:
338+
group_id = -1001234567890
339+
warning_topic_id = 12345
340+
telegram_bot_token = "fake_token"
341+
342+
monkeypatch.setattr("bot.handlers.verify.get_settings", lambda: MockSettings())
343+
344+
target_user_id = 88888888 # Use unique ID
345+
mock_context.args = [str(target_user_id)]
346+
347+
# Verify user without any warnings
348+
await handle_verify_command(mock_update, mock_context)
349+
350+
# Should NOT send notification to warning topic
351+
mock_context.bot.send_message.assert_not_called()
352+
258353

259354
class TestHandleUnverifyCommand:
260355
async def test_no_message(self, mock_context):

0 commit comments

Comments
 (0)