mirror of https://github.com/scrapy/scrapy.git
Split out test_engine_download.py.
This commit is contained in:
parent
273951902a
commit
bd88cba406
|
|
@ -7,7 +7,7 @@ import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import TYPE_CHECKING, Any, cast
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
from unittest.mock import Mock, call
|
from unittest.mock import Mock
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
@ -515,98 +515,6 @@ class TestEngine(TestEngineBase):
|
||||||
assert "AssertionError" not in stderr_str, stderr_str
|
assert "AssertionError" not in stderr_str, stderr_str
|
||||||
|
|
||||||
|
|
||||||
class TestEngineDownloadAsync:
|
|
||||||
"""Test cases for ExecutionEngine.download_async()."""
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def engine(self) -> ExecutionEngine:
|
|
||||||
crawler = get_crawler(MySpider)
|
|
||||||
engine = ExecutionEngine(crawler, lambda _: None)
|
|
||||||
engine.downloader.close()
|
|
||||||
engine.downloader = Mock()
|
|
||||||
engine._slot = Mock()
|
|
||||||
engine._slot.inprogress = set()
|
|
||||||
return engine
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _download(engine: ExecutionEngine, request: Request) -> Response:
|
|
||||||
return await engine.download_async(request)
|
|
||||||
|
|
||||||
@coroutine_test
|
|
||||||
async def test_download_async_success(self, engine):
|
|
||||||
"""Test basic successful async download of a request."""
|
|
||||||
request = Request("http://example.com")
|
|
||||||
response = Response("http://example.com", body=b"test body")
|
|
||||||
engine.spider = Mock()
|
|
||||||
engine.downloader.fetch.return_value = defer.succeed(response)
|
|
||||||
engine._slot.add_request = Mock()
|
|
||||||
engine._slot.remove_request = Mock()
|
|
||||||
|
|
||||||
result = await self._download(engine, request)
|
|
||||||
assert result == response
|
|
||||||
engine._slot.add_request.assert_called_once_with(request)
|
|
||||||
engine._slot.remove_request.assert_called_once_with(request)
|
|
||||||
engine.downloader.fetch.assert_called_once_with(request)
|
|
||||||
|
|
||||||
@coroutine_test
|
|
||||||
async def test_download_async_redirect(self, engine):
|
|
||||||
"""Test async download with a redirect request."""
|
|
||||||
original_request = Request("http://example.com")
|
|
||||||
redirect_request = Request("http://example.com/redirect")
|
|
||||||
final_response = Response("http://example.com/redirect", body=b"redirected")
|
|
||||||
|
|
||||||
# First call returns redirect request, second call returns final response
|
|
||||||
engine.downloader.fetch.side_effect = [
|
|
||||||
defer.succeed(redirect_request),
|
|
||||||
defer.succeed(final_response),
|
|
||||||
]
|
|
||||||
engine.spider = Mock()
|
|
||||||
engine._slot.add_request = Mock()
|
|
||||||
engine._slot.remove_request = Mock()
|
|
||||||
|
|
||||||
result = await self._download(engine, original_request)
|
|
||||||
assert result == final_response
|
|
||||||
assert engine.downloader.fetch.call_count == 2
|
|
||||||
engine._slot.add_request.assert_has_calls(
|
|
||||||
[call(original_request), call(redirect_request)]
|
|
||||||
)
|
|
||||||
engine._slot.remove_request.assert_has_calls(
|
|
||||||
[call(original_request), call(redirect_request)]
|
|
||||||
)
|
|
||||||
|
|
||||||
@coroutine_test
|
|
||||||
async def test_download_async_no_spider(self, engine):
|
|
||||||
"""Test async download attempt when no spider is available."""
|
|
||||||
request = Request("http://example.com")
|
|
||||||
engine.spider = None
|
|
||||||
with pytest.raises(RuntimeError, match="No open spider to crawl:"):
|
|
||||||
await self._download(engine, request)
|
|
||||||
|
|
||||||
@coroutine_test
|
|
||||||
async def test_download_async_failure(self, engine):
|
|
||||||
"""Test async download when the downloader raises an exception."""
|
|
||||||
request = Request("http://example.com")
|
|
||||||
error = RuntimeError("Download failed")
|
|
||||||
engine.spider = Mock()
|
|
||||||
engine.downloader.fetch.return_value = defer.fail(error)
|
|
||||||
engine._slot.add_request = Mock()
|
|
||||||
engine._slot.remove_request = Mock()
|
|
||||||
|
|
||||||
with pytest.raises(RuntimeError, match="Download failed"):
|
|
||||||
await self._download(engine, request)
|
|
||||||
engine._slot.add_request.assert_called_once_with(request)
|
|
||||||
engine._slot.remove_request.assert_called_once_with(request)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.filterwarnings("ignore::scrapy.exceptions.ScrapyDeprecationWarning")
|
|
||||||
class TestEngineDownload(TestEngineDownloadAsync):
|
|
||||||
"""Test cases for ExecutionEngine.download()."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def _download(engine: ExecutionEngine, request: Request) -> Response:
|
|
||||||
return await maybe_deferred_to_future(engine.download(request))
|
|
||||||
|
|
||||||
|
|
||||||
@coroutine_test
|
@coroutine_test
|
||||||
async def test_request_scheduled_signal():
|
async def test_request_scheduled_signal():
|
||||||
class TestScheduler(BaseScheduler):
|
class TestScheduler(BaseScheduler):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from unittest.mock import Mock, call
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
from scrapy.core.engine import ExecutionEngine
|
||||||
|
from scrapy.http import Request, Response
|
||||||
|
from scrapy.utils.defer import maybe_deferred_to_future
|
||||||
|
from scrapy.utils.spider import DefaultSpider
|
||||||
|
from scrapy.utils.test import get_crawler
|
||||||
|
from tests.utils.decorators import coroutine_test
|
||||||
|
|
||||||
|
|
||||||
|
class TestEngineDownloadAsync:
|
||||||
|
"""Test cases for ExecutionEngine.download_async()."""
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def engine(self) -> ExecutionEngine:
|
||||||
|
# crawler = get_crawler(MySpider)
|
||||||
|
crawler = get_crawler(DefaultSpider)
|
||||||
|
engine = ExecutionEngine(crawler, lambda _: None)
|
||||||
|
engine.downloader.close()
|
||||||
|
engine.downloader = Mock()
|
||||||
|
engine._slot = Mock()
|
||||||
|
engine._slot.inprogress = set()
|
||||||
|
return engine
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _download(engine: ExecutionEngine, request: Request) -> Response:
|
||||||
|
return await engine.download_async(request)
|
||||||
|
|
||||||
|
@coroutine_test
|
||||||
|
async def test_download_async_success(self, engine):
|
||||||
|
"""Test basic successful async download of a request."""
|
||||||
|
request = Request("http://example.com")
|
||||||
|
response = Response("http://example.com", body=b"test body")
|
||||||
|
engine.spider = Mock()
|
||||||
|
engine.downloader.fetch.return_value = defer.succeed(response)
|
||||||
|
engine._slot.add_request = Mock()
|
||||||
|
engine._slot.remove_request = Mock()
|
||||||
|
|
||||||
|
result = await self._download(engine, request)
|
||||||
|
assert result == response
|
||||||
|
engine._slot.add_request.assert_called_once_with(request)
|
||||||
|
engine._slot.remove_request.assert_called_once_with(request)
|
||||||
|
engine.downloader.fetch.assert_called_once_with(request)
|
||||||
|
|
||||||
|
@coroutine_test
|
||||||
|
async def test_download_async_redirect(self, engine):
|
||||||
|
"""Test async download with a redirect request."""
|
||||||
|
original_request = Request("http://example.com")
|
||||||
|
redirect_request = Request("http://example.com/redirect")
|
||||||
|
final_response = Response("http://example.com/redirect", body=b"redirected")
|
||||||
|
|
||||||
|
# First call returns redirect request, second call returns final response
|
||||||
|
engine.downloader.fetch.side_effect = [
|
||||||
|
defer.succeed(redirect_request),
|
||||||
|
defer.succeed(final_response),
|
||||||
|
]
|
||||||
|
engine.spider = Mock()
|
||||||
|
engine._slot.add_request = Mock()
|
||||||
|
engine._slot.remove_request = Mock()
|
||||||
|
|
||||||
|
result = await self._download(engine, original_request)
|
||||||
|
assert result == final_response
|
||||||
|
assert engine.downloader.fetch.call_count == 2
|
||||||
|
engine._slot.add_request.assert_has_calls(
|
||||||
|
[call(original_request), call(redirect_request)]
|
||||||
|
)
|
||||||
|
engine._slot.remove_request.assert_has_calls(
|
||||||
|
[call(original_request), call(redirect_request)]
|
||||||
|
)
|
||||||
|
|
||||||
|
@coroutine_test
|
||||||
|
async def test_download_async_no_spider(self, engine):
|
||||||
|
"""Test async download attempt when no spider is available."""
|
||||||
|
request = Request("http://example.com")
|
||||||
|
engine.spider = None
|
||||||
|
with pytest.raises(RuntimeError, match="No open spider to crawl:"):
|
||||||
|
await self._download(engine, request)
|
||||||
|
|
||||||
|
@coroutine_test
|
||||||
|
async def test_download_async_failure(self, engine):
|
||||||
|
"""Test async download when the downloader raises an exception."""
|
||||||
|
request = Request("http://example.com")
|
||||||
|
error = RuntimeError("Download failed")
|
||||||
|
engine.spider = Mock()
|
||||||
|
engine.downloader.fetch.return_value = defer.fail(error)
|
||||||
|
engine._slot.add_request = Mock()
|
||||||
|
engine._slot.remove_request = Mock()
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match="Download failed"):
|
||||||
|
await self._download(engine, request)
|
||||||
|
engine._slot.add_request.assert_called_once_with(request)
|
||||||
|
engine._slot.remove_request.assert_called_once_with(request)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings("ignore::scrapy.exceptions.ScrapyDeprecationWarning")
|
||||||
|
class TestEngineDownload(TestEngineDownloadAsync):
|
||||||
|
"""Test cases for ExecutionEngine.download()."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _download(engine: ExecutionEngine, request: Request) -> Response:
|
||||||
|
return await maybe_deferred_to_future(engine.download(request))
|
||||||
Loading…
Reference in New Issue