from __future__ import annotations from contextlib import contextmanager from typing import TYPE_CHECKING, Any import pytest from twisted.conch.telnet import ITelnetProtocol from twisted.cred import credentials from scrapy.extensions.telnet import TelnetConsole from scrapy.utils.defer import maybe_deferred_to_future from scrapy.utils.test import get_crawler from tests.utils.decorators import coroutine_test if TYPE_CHECKING: from collections.abc import Generator from scrapy.crawler import Crawler pytestmark = pytest.mark.requires_reactor # TelnetConsole requires a reactor def _get_crawler(settings_dict: dict[str, Any] | None = None) -> Crawler: settings = { "TELNETCONSOLE_ENABLED": True, **(settings_dict or {}), } return get_crawler(settings_dict=settings) @contextmanager def _get_console_and_portal( settings: dict[str, Any] | None = None, ) -> Generator[tuple[TelnetConsole, Any]]: crawler = _get_crawler(settings_dict=settings) console = TelnetConsole(crawler) # This function has some side effects we don't need for this test console._get_telnet_vars = dict # type: ignore[method-assign] console.start_listening() protocol = console.protocol() portal = protocol.protocolArgs[0] try: yield console, portal finally: console.stop_listening() @coroutine_test async def test_bad_credentials() -> None: with _get_console_and_portal() as (_, portal): creds = credentials.UsernamePassword(b"username", b"password") d = portal.login(creds, None, ITelnetProtocol) with pytest.raises(ValueError, match="Invalid credentials"): await maybe_deferred_to_future(d) @coroutine_test async def test_good_credentials() -> None: with _get_console_and_portal() as (console, portal): creds = credentials.UsernamePassword( console.username.encode("utf8"), console.password.encode("utf8") ) d = portal.login(creds, None, ITelnetProtocol) await maybe_deferred_to_future(d) @coroutine_test async def test_custom_credentials() -> None: settings = { "TELNETCONSOLE_USERNAME": "user", "TELNETCONSOLE_PASSWORD": "pass", } with _get_console_and_portal(settings=settings) as (_, portal): creds = credentials.UsernamePassword(b"user", b"pass") d = portal.login(creds, None, ITelnetProtocol) await maybe_deferred_to_future(d) def test_invalid_reversed_portrange() -> None: settings = {"TELNETCONSOLE_PORT": [2, 1]} console = TelnetConsole(_get_crawler(settings_dict=settings)) with pytest.raises(ValueError, match=r"invalid portrange: \[2, 1\]"): console.start_listening()