fix: verify success completion signal + cross-session verify_url restore

_on_verify_success() was missing _nickserv_complete() call, causing
_go_ready() to hang at _nickserv_done.wait() when registration
completed immediately (no email verification needed).

get_pending_registration() was not returning verify_url from the DB,
so _resume_pending_verification() never restored self._verify_url --
breaking cross-session captcha resume for OFTC-style networks.

Four regression tests added.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user
2026-02-21 17:55:33 +01:00
parent 0d762ced49
commit ae8de25b27
3 changed files with 79 additions and 7 deletions

View File

@@ -1058,6 +1058,35 @@ class TestHandleNickserv:
assert net._nickserv_pending == ""
bl.mark_nickserv_verified.assert_awaited_once()
@pytest.mark.asyncio
async def test_verify_success_signals_completion(self) -> None:
"""_on_verify_success must signal _nickserv_done."""
bl = _mock_backlog()
net = _net(backlog=bl)
net.nick = "verified_nick"
net._nickserv_pending = "verify"
net._nickserv_password = "pass"
net._nickserv_done = asyncio.Event()
await net._handle_nickserv("verified_nick has been verified")
assert net._nickserv_pending == ""
assert net._nickserv_done.is_set()
@pytest.mark.asyncio
async def test_registration_immediate_signals_completion(self) -> None:
"""Immediate registration (no email) must signal _nickserv_done."""
bl = _mock_backlog()
net = _net(backlog=bl)
net.nick = "fastnick"
net._nickserv_pending = "register"
net._nickserv_password = "pass"
net._nickserv_done = asyncio.Event()
await net._handle_nickserv("Nickname registered under your account")
assert net._nickserv_pending == ""
assert net._nickserv_done.is_set()
bl.mark_nickserv_verified.assert_awaited_once()
@pytest.mark.asyncio
async def test_verify_failure(self) -> None:
net = _net()
@@ -1068,6 +1097,46 @@ class TestHandleNickserv:
await net._handle_nickserv("Invalid verification code")
assert net._nickserv_pending == ""
@pytest.mark.asyncio
async def test_resume_pending_restores_verify_url(self) -> None:
"""Cross-session resume must restore the verify_url from DB."""
bl = _mock_backlog(
pending=("oldnick", "pass", "e@mail.tm", "host", "https://oftc/verify/abc"),
)
net = _net(backlog=bl)
net.state = State.READY
net._running = True
net.nick = "oldnick"
writer = MagicMock()
writer.is_closing.return_value = False
writer.drain = AsyncMock()
net._writer = writer
result = await net._resume_pending_verification()
assert result is True
assert net._verify_url == "https://oftc/verify/abc"
assert net._nickserv_email == "e@mail.tm"
assert net._nickserv_password == "pass"
@pytest.mark.asyncio
async def test_resume_pending_no_url(self) -> None:
"""Resume works when verify_url is empty."""
bl = _mock_backlog(
pending=("nick", "pass", "e@mail.tm", "host", ""),
)
net = _net(backlog=bl)
net.state = State.READY
net._running = True
net.nick = "nick"
writer = MagicMock()
writer.is_closing.return_value = False
writer.drain = AsyncMock()
net._writer = writer
result = await net._resume_pending_verification()
assert result is True
assert net._verify_url == ""
@pytest.mark.asyncio
async def test_late_registration_confirmation(self) -> None:
"""After timeout clears pending state, late confirmation still works."""