fix: address review issues in PR #2829 webhook intentional deletion guard
5 issues found by review agents and fixed:
1. Intentional deletion guard was at top of process_event, skipping ALL
cleanup (subscription URLs, server counts, expired marking). Moved
check into _handle_user_deleted so cleanup still runs but re-creation
is suppressed via subscription_still_valid=False.
2. Variable shadowing: telegram_id loop var in any() generator shadowed
the outer telegram_id local. Renamed to tid/uid.
3. No hard cap on in-memory dicts: added _MAX_INTENTIONAL_ENTRIES=10000
with early return in mark_intentional_panel_deletion.
4. mark_intentional_panel_deletion called inside for-loop with single
UUID — race window if webhook from first delete arrives before
second UUID is marked. Moved to before the loop with all UUIDs.
5. Tests: @pytest.mark.anyio → @pytest.mark.anyio('asyncio') for
consistency. Replaced process_event(db=None) test with direct
mark+detect unit tests + hard cap test.
This commit is contained in:
@@ -54,7 +54,7 @@ def _signature(body: bytes) -> str:
|
||||
return hmac.new(secret.encode('utf-8'), body, hashlib.sha256).hexdigest()
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.anyio('asyncio')
|
||||
async def test_remnawave_webhook_accepts_event_without_scope(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
bot = AsyncMock()
|
||||
process_event = AsyncMock(return_value=True)
|
||||
@@ -90,7 +90,7 @@ async def test_remnawave_webhook_accepts_event_without_scope(monkeypatch: pytest
|
||||
process_event.assert_awaited_once_with(None, 'user.modified', {'uuid': 'user-123'})
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@pytest.mark.anyio('asyncio')
|
||||
async def test_remnawave_webhook_rejects_payload_without_event() -> None:
|
||||
bot = AsyncMock()
|
||||
payload = {'data': {'uuid': 'user-123'}}
|
||||
@@ -111,20 +111,29 @@ async def test_remnawave_webhook_rejects_payload_without_event() -> None:
|
||||
assert json.loads(response.body.decode('utf-8')) == {'status': 'error', 'reason': 'missing_event'}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_process_event_skips_intentional_admin_user_deleted() -> None:
|
||||
bot = AsyncMock()
|
||||
service = RemnaWaveWebhookService(bot)
|
||||
|
||||
def test_intentional_panel_deletion_guard_marks_and_detects() -> None:
|
||||
"""Verify that mark + is_intentional round-trip works correctly."""
|
||||
RemnaWaveWebhookService.mark_intentional_panel_deletion(
|
||||
panel_uuids=['panel-user-123'],
|
||||
telegram_id=8368498066,
|
||||
)
|
||||
|
||||
processed = await service.process_event(
|
||||
None,
|
||||
'user.deleted',
|
||||
{'uuid': 'panel-user-123', 'telegramId': 8368498066},
|
||||
assert RemnaWaveWebhookService._is_intentional_panel_deletion_event(
|
||||
{'uuid': 'panel-user-123', 'telegramId': 8368498066}
|
||||
)
|
||||
|
||||
assert processed is True
|
||||
# Unknown UUID should not match
|
||||
assert not RemnaWaveWebhookService._is_intentional_panel_deletion_event(
|
||||
{'uuid': 'unknown-uuid', 'telegramId': 99999}
|
||||
)
|
||||
|
||||
|
||||
def test_intentional_panel_deletion_guard_respects_hard_cap(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Verify that the guard stops accepting entries after hitting the cap."""
|
||||
monkeypatch.setattr(RemnaWaveWebhookService, '_MAX_INTENTIONAL_ENTRIES', 3)
|
||||
|
||||
RemnaWaveWebhookService.mark_intentional_panel_deletion(panel_uuids=['a', 'b', 'c'])
|
||||
# 3 entries — at capacity
|
||||
RemnaWaveWebhookService.mark_intentional_panel_deletion(panel_uuids=['d'])
|
||||
# 'd' should NOT be stored (cap reached)
|
||||
assert 'd' not in RemnaWaveWebhookService._intentional_panel_deletions_by_uuid
|
||||
|
||||
Reference in New Issue
Block a user