From 81c4346f103a96564b45b51b762c8ac385bdcb13 Mon Sep 17 00:00:00 2001 From: Joakim Soderlund Date: Sat, 4 Nov 2017 16:30:41 +0100 Subject: [PATCH] Add update stamper --- fimfarchive/stampers.py | 65 +++++++++++++++++++- tests/test_stampers.py | 127 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 190 insertions(+), 2 deletions(-) diff --git a/fimfarchive/stampers.py b/fimfarchive/stampers.py index ba7f4bb..00a1e23 100644 --- a/fimfarchive/stampers.py +++ b/fimfarchive/stampers.py @@ -22,16 +22,23 @@ Stampers for Fimfarchive. # -from typing import Any, Dict +from typing import Any, Dict, Optional, Set, Type, TypeVar +import arrow + +from fimfarchive.flavors import Flavor, UpdateStatus from fimfarchive.stories import Story __all__ = ( 'Stamper', + 'UpdateStamper', ) +F = TypeVar('F', bound=Flavor) + + class Stamper: """ Adds archive-related information to stories. @@ -62,3 +69,59 @@ class Stamper: story: The story to stamp. """ raise NotImplementedError() + + +class UpdateStamper(Stamper): + """ + Adds modification dates to stories. + """ + spec: Dict[str, Set[UpdateStatus]] = { + 'date_created': { + UpdateStatus.CREATED, + }, + 'date_fetched': { + UpdateStatus.CREATED, + UpdateStatus.REVIVED, + UpdateStatus.UPDATED, + }, + 'date_updated': { + UpdateStatus.CREATED, + UpdateStatus.UPDATED, + }, + } + + def find_flavor(self, story: Story, flavor: Type[F]) -> Optional[F]: + """ + Searches for a flavor of a specific type. + + Args: + story: The story to search in. + flavor: The flavor type to find. + + Returns: + A flavor of the desired type, or None. + """ + for current in story.flavors: + if isinstance(current, flavor): + return current + + return None + + def __call__(self, story: Story) -> None: + """ + Applies modification dates to a story. + + Args: + story: The story to stamp. + """ + timestamp = arrow.utcnow().isoformat() + flavor = self.find_flavor(story, UpdateStatus) + archive = self.get_archive(story) + + archive['date_checked'] = timestamp + + for key, value in self.spec.items(): + if flavor in value: + archive[key] = timestamp + elif key not in archive: + archive[key] = None diff --git a/tests/test_stampers.py b/tests/test_stampers.py index f5d67df..e9cf058 100644 --- a/tests/test_stampers.py +++ b/tests/test_stampers.py @@ -22,11 +22,14 @@ Stamper tests. # +from unittest.mock import patch from typing import Dict +import arrow import pytest -from fimfarchive.stampers import Stamper +from fimfarchive.flavors import UpdateStatus +from fimfarchive.stampers import Stamper, UpdateStamper class TestStamper: @@ -62,3 +65,125 @@ class TestStamper: assert archive is original assert meta['archive'] is original + + +class TestUpdateStamper: + """ + UpdateStamper tests. + """ + + @pytest.fixture + def time(self): + """ + Returns a timestamp for the mocked utcnow. + """ + stamp = arrow.get(1) + + with patch('arrow.utcnow') as m: + m.return_value = stamp + yield stamp.isoformat() + + @pytest.fixture + def stamper(self, time): + """ + Returns an update stamper instance. + """ + return UpdateStamper() + + def test_created_story(self, stamper, story, time): + """ + Tests timestamps are added for created stories. + """ + story.flavors.add(UpdateStatus.CREATED) + stamper(story) + + archive = story.meta['archive'] + assert archive['date_checked'] == time + assert archive['date_created'] == time + assert archive['date_fetched'] == time + assert archive['date_updated'] == time + + def test_updated_story(self, stamper, story, time): + """ + Tests timestamps are added for updated stories. + """ + story.flavors.add(UpdateStatus.UPDATED) + stamper(story) + + archive = story.meta['archive'] + assert archive['date_checked'] == time + assert archive['date_created'] is None + assert archive['date_fetched'] == time + assert archive['date_updated'] == time + + def test_revived_story(self, stamper, story, time): + """ + Tests timestamps are added for revived stories. + """ + story.flavors.add(UpdateStatus.REVIVED) + stamper(story) + + archive = story.meta['archive'] + assert archive['date_checked'] == time + assert archive['date_created'] is None + assert archive['date_fetched'] == time + assert archive['date_updated'] is None + + def test_deleted_story(self, stamper, story, time): + """ + Tests timestamps are added for deleted stories. + """ + story.flavors.add(UpdateStatus.DELETED) + stamper(story) + + archive = story.meta['archive'] + assert archive['date_checked'] == time + assert archive['date_created'] is None + assert archive['date_fetched'] is None + assert archive['date_updated'] is None + + def test_created_modification(self, stamper, story, time): + """ + Tests existing timestamps are replaced for created stories. + """ + story.flavors.add(UpdateStatus.CREATED) + prev = arrow.get(-1).isoformat() + + story.meta['archive'] = { + 'date_checked': prev, + 'date_created': prev, + 'date_fetched': prev, + 'date_updated': prev, + } + + stamper(story) + assert prev != time + + archive = story.meta['archive'] + assert archive['date_checked'] == time + assert archive['date_created'] == time + assert archive['date_fetched'] == time + assert archive['date_updated'] == time + + def test_deleted_modification(self, stamper, story, time): + """ + Tests existing timestamps are kept for deleted stories. + """ + story.flavors.add(UpdateStatus.DELETED) + prev = arrow.get(-1).isoformat() + + story.meta['archive'] = { + 'date_checked': prev, + 'date_created': prev, + 'date_fetched': prev, + 'date_updated': prev, + } + + stamper(story) + assert prev != time + + archive = story.meta['archive'] + assert archive['date_checked'] == time + assert archive['date_created'] == prev + assert archive['date_fetched'] == prev + assert archive['date_updated'] == prev