fimfarchive/tests/tasks/test_update.py

366 lines
10 KiB
Python
Raw Permalink Normal View History

2017-08-01 22:20:45 +02:00
"""
Update task tests.
"""
#
# Fimfarchive, preserves stories from Fimfiction.
# Copyright (C) 2015 Joakim Soderlund
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
2018-01-27 00:01:52 +01:00
from typing import Dict
2017-08-01 22:20:45 +02:00
from unittest.mock import MagicMock, call, patch
import pytest
from fimfarchive.exceptions import InvalidStoryError, StorySourceError
from fimfarchive.fetchers import Fetcher
from fimfarchive.flavors import DataFormat, StorySource, UpdateStatus
2017-10-25 21:09:50 +02:00
from fimfarchive.selectors import RefetchSelector, UpdateSelector
2017-08-01 22:20:45 +02:00
from fimfarchive.stories import Story
from fimfarchive.tasks.update import (
UpdateTask, SUCCESS_DELAY, SKIPPED_DELAY, FAILURE_DELAY,
)
class DummyFetcher(Fetcher):
"""
Fetcher with local instance storage.
"""
def __init__(self):
"""
Constructor.
"""
2018-01-27 00:01:52 +01:00
self.stories: Dict[int, Story] = dict()
2017-08-01 22:20:45 +02:00
def add(self, key, date, flavors=()):
"""
Adds a story to the fetcher.
"""
story = Story(
key=key,
flavors=flavors,
data=f'Story {key}',
meta={
'id': key,
'date_modified': date,
'chapters': [
{'id': key},
],
},
)
self.stories[key] = story
return story
def fetch(self, key):
"""
Returns a previously stored story.
"""
try:
return self.stories[key]
except KeyError:
raise InvalidStoryError()
class TestUpdateTask:
"""
Tests update task.
"""
@pytest.fixture
def fimfiction(self):
"""
Returns a `Fetcher` simulating Fimfiction.
"""
return DummyFetcher()
@pytest.fixture
def fimfarchive(self):
"""
Returns a `Fetcher` simulating Fimfarchive.
"""
return DummyFetcher()
@pytest.fixture
2017-10-25 21:09:50 +02:00
def selector(self):
"""
Returns an `UpdateSelector` instance.
"""
return UpdateSelector()
@pytest.fixture
2017-11-04 20:41:22 +01:00
def stamper(self):
"""
Returns a stamper mock.
"""
return MagicMock()
2018-01-27 00:01:52 +01:00
@pytest.fixture
def archive(self):
"""
Returns an archive meta dictionary.
"""
return {'key': 'value'}
2017-11-04 20:41:22 +01:00
@pytest.fixture
def task(self, fimfarchive, fimfiction, selector, stamper, tmpdir):
2017-08-01 22:20:45 +02:00
"""
Returns an `UpdateTask` instance.
"""
return UpdateTask(
fimfiction=fimfiction,
fimfarchive=fimfarchive,
2017-10-25 21:09:50 +02:00
selector=selector,
2017-11-04 20:41:22 +01:00
stamper=stamper,
2017-08-01 22:20:45 +02:00
workdir=str(tmpdir),
retries=2,
skips=2,
)
def verify_run(self, task, delays):
"""
Runs the task and verifies delays.
"""
calls = [call(delay) for delay in delays]
with patch('time.sleep') as m:
task.run()
m.assert_has_calls(calls)
def verify_fetch(self, task, target, status):
"""
Runs the task and verifies a regular fetch.
"""
task.write = MagicMock(side_effect=lambda story: story)
delays = (
SKIPPED_DELAY,
SUCCESS_DELAY,
SKIPPED_DELAY,
SKIPPED_DELAY,
)
self.verify_run(task, delays)
2017-11-04 20:41:22 +01:00
task.stamp.assert_called_once_with(target)
2017-08-01 22:20:45 +02:00
task.write.assert_called_once_with(target)
assert status in target.flavors
assert task.state['key'] == 4
def verify_empty(self, task, fetcher):
"""
Runs the task and verifies an empty fetch.
"""
2017-11-04 20:41:22 +01:00
task.write = MagicMock()
2017-08-01 22:20:45 +02:00
task.skip_writer.write = MagicMock()
target = fetcher.add(key=1, date=1)
target.meta['chapters'].clear()
delays = (
SKIPPED_DELAY,
SKIPPED_DELAY,
)
self.verify_run(task, delays)
2017-11-04 20:41:22 +01:00
task.stamp.assert_not_called()
task.write.assert_not_called()
2017-08-01 22:20:45 +02:00
task.skip_writer.write.assert_called_once_with(target)
def verify_failure(self, task, fetcher):
"""
Runs the task and verifies a failed fetch.
"""
task.write = MagicMock(side_effect=lambda story: story)
fetcher.fetch = MagicMock(side_effect=StorySourceError)
delays = (
FAILURE_DELAY,
FAILURE_DELAY,
)
self.verify_run(task, delays)
2017-11-04 20:41:22 +01:00
task.stamp.assert_not_called()
2017-08-01 22:20:45 +02:00
task.write.assert_not_called()
def test_created_story(self, task, fimfiction):
"""
Tests updating for a created story.
"""
target = fimfiction.add(key=1, date=1)
self.verify_fetch(task, target, UpdateStatus.CREATED)
2018-01-27 00:01:52 +01:00
def test_revived_story(self, task, fimfarchive, fimfiction, archive):
2017-08-01 22:20:45 +02:00
"""
Tests updating for a revived story.
"""
target = fimfarchive.add(key=1, date=1)
other = fimfiction.add(key=1, date=1)
2018-01-27 00:01:52 +01:00
target.meta['archive'] = archive
2017-08-01 22:20:45 +02:00
target.merge = MagicMock(return_value=target)
self.verify_fetch(task, target, UpdateStatus.REVIVED)
target.merge.assert_called_once_with(meta=other.meta)
2018-01-27 00:01:52 +01:00
assert other.meta['archive'] is not archive
assert other.meta['archive'] == archive
2017-08-01 22:20:45 +02:00
2018-01-27 00:01:52 +01:00
def test_updated_story(self, task, fimfarchive, fimfiction, archive):
2017-08-01 22:20:45 +02:00
"""
Tests updating for an updated story.
"""
2018-01-27 00:01:52 +01:00
other = fimfarchive.add(key=1, date=0)
2017-08-01 22:20:45 +02:00
target = fimfiction.add(key=1, date=1)
2018-01-27 00:01:52 +01:00
other.meta['archive'] = archive
2017-08-01 22:20:45 +02:00
self.verify_fetch(task, target, UpdateStatus.UPDATED)
2018-01-27 00:01:52 +01:00
assert target.meta['archive'] is not archive
assert target.meta['archive'] == archive
2017-08-01 22:20:45 +02:00
def test_deleted_story(self, task, fimfarchive):
"""
Test updating for a deleted story.
"""
target = fimfarchive.add(key=1, date=1)
self.verify_fetch(task, target, UpdateStatus.DELETED)
def test_cleared_story(self, task, fimfarchive, fimfiction):
"""
Tests updating for a cleared story.
"""
target = fimfarchive.add(key=1, date=0)
other = fimfiction.add(key=1, date=1)
other.meta['chapters'].clear()
self.verify_fetch(task, target, UpdateStatus.DELETED)
def test_empty_fimfiction_story(self, task, fimfiction):
"""
Tests updating for an empty story from fimfiction.
"""
self.verify_empty(task, fimfiction)
def test_empty_fimfarchive_story(self, task, fimfarchive):
"""
Tests updating for an empty story from fimfarchive.
"""
self.verify_empty(task, fimfarchive)
def test_fimfarchive_failure(self, task, fimfarchive):
"""
Tests handling of a failure in Fimfarchive.
"""
self.verify_failure(task, fimfarchive)
def test_fimfiction_failure(self, task, fimfiction):
"""
Tests handling of a failure in Fimfiction.
"""
self.verify_failure(task, fimfiction)
def test_write_meta(self, task, story):
"""
Tests writing of meta for stories from Fimfarchive.
"""
story = story.merge(flavors=[
DataFormat.JSON,
StorySource.FIMFARCHIVE,
])
task.meta_writer.write = MagicMock()
task.json_writer.write = MagicMock()
task.write(story)
task.meta_writer.write.assert_called_once_with(story)
task.json_writer.write.assert_not_called()
2017-08-01 22:20:45 +02:00
def test_write_epub(self, task, story):
"""
Tests writing of a story in EPUB format.
"""
story = story.merge(flavors=[DataFormat.EPUB])
task.epub_writer.write = MagicMock()
task.write(story)
task.epub_writer.write.assert_called_once_with(story)
def test_write_html(self, task, story):
"""
Tests writing of a story in HTML format.
"""
story = story.merge(flavors=[DataFormat.HTML])
task.html_writer.write = MagicMock()
task.write(story)
task.html_writer.write.assert_called_once_with(story)
2017-11-08 18:41:48 +01:00
def test_write_json(self, task, story):
"""
Tests writing of a story in JSON format.
"""
story = story.merge(flavors=[DataFormat.JSON])
task.json_writer.write = MagicMock()
task.write(story)
task.json_writer.write.assert_called_once_with(story)
2017-08-01 22:20:45 +02:00
def test_write_unsupported(self, task, story):
"""
Tests `ValueError` is raised for unknown data formats.
"""
story = story.merge(flavors=[DataFormat.FPUB])
with pytest.raises(ValueError):
task.write(story)
2017-10-25 21:09:50 +02:00
2018-01-27 00:01:52 +01:00
def test_remote_archive(self, task, fimfarchive, fimfiction, archive):
"""
Tests `ValueError` is raised if Fimfiction returns archive meta.
"""
old = fimfarchive.add(key=1, date=0)
new = fimfiction.add(key=1, date=1)
new.meta['archive'] = archive
with pytest.raises(ValueError):
task.copy_archive_meta(old, new)
2017-10-25 21:09:50 +02:00
class TestRefetchingUpdateTask(TestUpdateTask):
"""
Tests update task with a refetch selector.
"""
@pytest.fixture
def selector(self):
"""
Returns a `RefetchSelector` instance.
"""
return RefetchSelector()
def test_revived_story(self, task, fimfarchive, fimfiction):
"""
Tests updating for a revived story.
"""
fimfarchive.add(key=1, date=1)
target = fimfiction.add(key=1, date=1)
self.verify_fetch(task, target, UpdateStatus.UPDATED)