Skip to content

Commit

Permalink
Add (failing) tests for non-ParseErrors raised during update_feeds()
Browse files Browse the repository at this point in the history
(update hooks, session hooks, retriever/parser unexpected exceptions).

For #218.
  • Loading branch information
lemon24 committed Aug 3, 2023
1 parent 05aefc1 commit 669fb03
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 18 deletions.
10 changes: 10 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,16 @@ def update_feed(request):
return request.param


@pytest.fixture(
params=[
m if 'workers' not in m.__name__ else slow(m)
for m in reader_methods.update_feeds_iter_methods
]
)
def update_feeds_iter(request):
return request.param


def feed_arg_as_str(feed):
return feed.url

Expand Down
27 changes: 27 additions & 0 deletions tests/reader_methods.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from reader import ParseError
from reader import UpdateResult


def do_nothing(reader):
pass

Expand Down Expand Up @@ -100,6 +104,8 @@ def search_entry_counts(reader, **kwargs):


class _update_feed_methods:
# TODO: we can remove the update_feeds() variant if we add a test to confirm update_feeds is a thin wrapper over update_feeds_iter

def update_feeds(reader, _):
reader.update_feeds()

Expand All @@ -122,3 +128,24 @@ def update_feed(reader, url):
update_feed_methods = [
v for k, v in _update_feed_methods.__dict__.items() if not k.startswith('_')
]


class _update_feeds_iter_methods:
def update_feeds_iter(reader):
return reader.update_feeds_iter()

def update_feeds_iter_workers(reader):
return reader.update_feeds_iter(workers=2)

def update_feeds_iter_simulated(reader):
for feed in reader.get_feeds(updates_enabled=True):
try:
yield UpdateResult(feed.url, reader.update_feed(feed))
except ParseError as e:
yield UpdateResult(feed.url, e)


# update_feeds(reader) -> (url, result), ...
update_feeds_iter_methods = [
v for k, v in _update_feeds_iter_methods.__dict__.items() if not k.startswith('_')
]
114 changes: 96 additions & 18 deletions tests/test_reader_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
from fakeparser import FailingParser
from fakeparser import NotModifiedParser
from fakeparser import Parser
from test_reader_private import CustomParser
from test_reader_private import CustomRetriever

from reader import EntryUpdateStatus
from reader import ParseError
from reader._types import EntryData


Expand Down Expand Up @@ -234,24 +237,27 @@ def after_feeds_plugin(r):
# TODO: test relative order of different hooks


HOOK_NAMES = """
after_entry_update_hooks
before_feed_update_hooks
after_feed_update_hooks
before_feeds_update_hooks
after_feeds_update_hooks
""".split()


@pytest.mark.parametrize('hook_name', HOOK_NAMES)
def test_update_hook_unexpected_exception(reader, update_feed, hook_name):
if update_feed.__name__ == 'update_feed' and '_feeds_' in hook_name:
@pytest.mark.parametrize(
'hook_name',
[
'after_entry_update_hooks',
'before_feed_update_hooks',
'after_feed_update_hooks',
'before_feeds_update_hooks',
'after_feeds_update_hooks',
],
)
@pytest.mark.xfail(raises=RuntimeError, strict=True)
def test_update_hook_unexpected_exception(reader, update_feeds_iter, hook_name):
if 'simulated' in update_feeds_iter.__name__ and '_feeds_' in hook_name:
pytest.skip("does not apply")

reader._parser = parser = Parser()
for feed_id in 1, 2, 3:
reader.add_feed(parser.feed(feed_id))
parser.entry(2, 1)
parser.entry(1, 1)

exc = RuntimeError('error')

def hook(reader, obj=None, *_):
if '_entry_' in hook_name:
Expand All @@ -262,11 +268,83 @@ def hook(reader, obj=None, *_):
feed_url = None
else:
assert False, hook_name
if not feed_url or feed_url == '2':
raise Exception('error')
if not feed_url or feed_url == '1':
raise exc

getattr(reader, hook_name).append(hook)

with pytest.raises(Exception) as excinfo:
update_feed(reader, '2')
assert excinfo.value.args[0] == 'error'
rv = {int(r.url): r for r in update_feeds_iter(reader)}

assert rv[1].error.__cause__ is exc
assert isinstance(rv[1].error, ParseError)
assert rv[1].error.__cause__ is exc
assert rv[2].updated_feed
assert rv[3].updated_feed


@pytest.mark.parametrize(
'target_name, method_name',
[
('retriever', '__call__'),
('retriever', 'process_feed_for_update'),
('parser', '__call__'),
('parser', 'process_entry_pairs'),
],
)
@pytest.mark.xfail(raises=RuntimeError, strict=True)
def test_retriever_parser_unexpected_exception(
reader, update_feeds_iter, target_name, method_name
):
retriever = CustomRetriever()
reader._parser.mount_retriever('test:', retriever)
parser = CustomParser()
reader._parser.mount_parser_by_mime_type(parser)

for feed_id in 1, 2, 3:
reader.add_feed(f'test:{feed_id}')

exc = RuntimeError('error')

def raise_exc(name, url):
if name == method_name and '1' in url:
raise exc

locals()[target_name].raise_exc = raise_exc

rv = {int(r.url.rpartition(':')[2]): r for r in update_feeds_iter(reader)}

assert rv[1].error.__cause__ is exc
assert isinstance(rv[1].error, ParseError)
assert rv[1].error.__cause__ is exc
assert rv[2].updated_feed
assert rv[3].updated_feed


@pytest.mark.parametrize('hook_name', ['request_hooks', 'response_hooks'])
def test_session_hook_unexpected_exception(
reader, data_dir, update_feeds_iter, requests_mock, hook_name
):
for feed_id in 1, 2, 3:
url = f'http://example.com/{feed_id}'
requests_mock.get(
url,
text=data_dir.joinpath('full.atom').read_text(),
headers={'content-type': 'application/atom+xml'},
)
reader.add_feed(url)

exc = RuntimeError('error')

def hook(session, obj, *_, **__):
if '1' in obj.url:
raise exc

getattr(reader._parser.session_factory, hook_name).append(hook)

rv = {int(r.url.rpartition('/')[2]): r for r in update_feeds_iter(reader)}

assert rv[1].error.__cause__ is exc
assert isinstance(rv[1].error, ParseError)
assert rv[1].error.__cause__ is exc
assert rv[2].updated_feed
assert rv[3].updated_feed
13 changes: 13 additions & 0 deletions tests/test_reader_private.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,33 +152,46 @@ def get_entry_ids():
assert get_entry_ids() == ['1, 1']


# TODO: move CustomRetriever and CustomParser to fakes.py


class CustomRetriever:
slow_to_read = False

@contextmanager
def __call__(self, url, http_etag, *_, **__):
self.raise_exc('__call__', url)
yield RetrieveResult('file', 'x.test', http_etag=http_etag.upper())

def validate_url(self, url):
pass

def process_feed_for_update(self, feed):
self.raise_exc('process_feed_for_update', feed.url)
assert feed.http_etag is None
return feed._replace(http_etag='etag')

def raise_exc(self, method_name, feed_url):
pass


class CustomParser:
http_accept = 'x.test'

def __call__(self, url, file, headers):
self.raise_exc('__call__', url)
feed = FeedData(url, title=file.upper())
entries = [EntryData(url, 'id', title='entry')]
return feed, entries

def process_entry_pairs(self, url, pairs):
self.raise_exc('process_entry_pairs', url)
for new, old in pairs:
yield new._replace(title=new.title.upper()), old

def raise_exc(self, method_name, feed_url):
pass


def test_retriever_parser_process_hooks(reader):
"""Test retriever.process_feed_for_update() and
Expand Down

0 comments on commit 669fb03

Please sign in to comment.