diff --git a/mobilizon_reshare/cli/__init__.py b/mobilizon_reshare/cli/__init__.py index bd78ca2..54b40cb 100644 --- a/mobilizon_reshare/cli/__init__.py +++ b/mobilizon_reshare/cli/__init__.py @@ -3,6 +3,7 @@ import logging import traceback from logging.config import dictConfig from pathlib import Path +import sys from mobilizon_reshare.config.config import get_settings from mobilizon_reshare.storage.db import tear_down, MoReDB @@ -38,4 +39,4 @@ async def _safe_execution(f): def safe_execution(f): code = asyncio.run(_safe_execution(f)) - exit(code) + sys.exit(code) diff --git a/mobilizon_reshare/cli/commands/list/list_event.py b/mobilizon_reshare/cli/commands/list/list_event.py index 68444f1..27395f6 100644 --- a/mobilizon_reshare/cli/commands/list/list_event.py +++ b/mobilizon_reshare/cli/commands/list/list_event.py @@ -1,4 +1,5 @@ -from typing import Iterable +from datetime import datetime +from typing import Iterable, Optional import click from arrow import Arrow @@ -42,8 +43,13 @@ async def list_unpublished_events(frm: Arrow = None, to: Arrow = None): async def list_events( - status: EventPublicationStatus = None, frm: Arrow = None, to: Arrow = None + status: EventPublicationStatus = None, + frm: Optional[datetime] = None, + to: Optional[datetime] = None, ): + + frm = Arrow.fromdatetime(frm) if frm else None + to = Arrow.fromdatetime(to) if to else None if status is None: events = await get_all_events(from_date=frm, to_date=to) elif status == EventPublicationStatus.WAITING: diff --git a/mobilizon_reshare/cli/commands/list/list_publication.py b/mobilizon_reshare/cli/commands/list/list_publication.py index bcf03ff..1d8878d 100644 --- a/mobilizon_reshare/cli/commands/list/list_publication.py +++ b/mobilizon_reshare/cli/commands/list/list_publication.py @@ -1,4 +1,5 @@ -from typing import Iterable +from datetime import datetime +from typing import Iterable, Optional import click from arrow import Arrow @@ -28,8 +29,13 @@ def pretty(publication: Publication): async def list_publications( - status: PublicationStatus = None, frm: Arrow = None, to: Arrow = None + status: PublicationStatus = None, + frm: Optional[datetime] = None, + to: Optional[datetime] = None, ): + + frm = Arrow.fromdatetime(frm) if frm else None + to = Arrow.fromdatetime(to) if to else None if status is None: publications = await get_all_publications(from_date=frm, to_date=to) else: diff --git a/mobilizon_reshare/config/config.py b/mobilizon_reshare/config/config.py index fe7069e..50c3d73 100644 --- a/mobilizon_reshare/config/config.py +++ b/mobilizon_reshare/config/config.py @@ -36,7 +36,7 @@ def current_version() -> str: return fp.read() -def get_settings_files_paths(): +def get_settings_files_paths() -> Optional[str]: dirs = AppDirs(appname="mobilizon-reshare", version=current_version()) bundled_settings_path = pkg_resources.resource_filename( @@ -111,7 +111,7 @@ def build_and_validate_settings(): # better in the future. -class CustomConfig(object): +class CustomConfig: @classmethod def get_instance(cls): if not hasattr(cls, "_instance") or cls._instance is None: diff --git a/mobilizon_reshare/event/event_selection_strategies.py b/mobilizon_reshare/event/event_selection_strategies.py index ecd038c..9ad32c8 100644 --- a/mobilizon_reshare/event/event_selection_strategies.py +++ b/mobilizon_reshare/event/event_selection_strategies.py @@ -19,8 +19,7 @@ class EventSelectionStrategy(ABC): selected = self._select(published_events, unpublished_events) if selected: return selected[0] - else: - return None + return None @abstractmethod def _select( diff --git a/mobilizon_reshare/formatting/description.py b/mobilizon_reshare/formatting/description.py index 8a9d262..b906dc3 100644 --- a/mobilizon_reshare/formatting/description.py +++ b/mobilizon_reshare/formatting/description.py @@ -14,12 +14,12 @@ def html_to_plaintext(content) -> str: :return: """ # TODO: support links and quotes - soup = BeautifulSoup(content) + soup = BeautifulSoup(content, features="html.parser") p_list = get_bottom_paragraphs(soup) if p_list: return "\n".join(" ".join(tag.stripped_strings) for tag in p_list) - else: - return soup.text + + return soup.text def html_to_markdown(content) -> str: diff --git a/mobilizon_reshare/main/retry.py b/mobilizon_reshare/main/retry.py index 6cb77d8..5b98b99 100644 --- a/mobilizon_reshare/main/retry.py +++ b/mobilizon_reshare/main/retry.py @@ -1,9 +1,11 @@ import logging +from typing import Optional from uuid import UUID from mobilizon_reshare.publishers.coordinator import ( PublisherCoordinator, - PublicationFailureNotifiersCoordinator, + PublisherCoordinatorReport, + PublicationFailureLoggerCoordinator, ) from mobilizon_reshare.storage.query.exceptions import EventNotFound from mobilizon_reshare.storage.query.read import ( @@ -26,7 +28,7 @@ async def retry_event_publications(event_id): return PublisherCoordinator(failed_publications).run() -async def retry_publication(publication_id): +async def retry_publication(publication_id) -> Optional[PublisherCoordinatorReport]: # TODO test this function publication = await get_publication(publication_id) if not publication: @@ -34,10 +36,18 @@ async def retry_publication(publication_id): return logger.info(f"Publication {publication_id} found.") - return PublisherCoordinator([publication]).run() + reports = PublisherCoordinator([publication]).run() + + await save_publication_report(reports) + + for report in reports.reports: + if not report.succesful: + PublicationFailureLoggerCoordinator(report,).notify_failure() -async def retry_event(mobilizon_event_id: UUID = None): +async def retry_event( + mobilizon_event_id: UUID = None, +) -> Optional[PublisherCoordinatorReport]: if mobilizon_event_id is None: raise NotImplementedError( "Autonomous retry not implemented yet, please specify an event_id" @@ -54,4 +64,4 @@ async def retry_event(mobilizon_event_id: UUID = None): await save_publication_report(reports) for report in reports.reports: if not report.succesful: - PublicationFailureNotifiersCoordinator(report,).notify_failure() + PublicationFailureLoggerCoordinator(report,).notify_failure() diff --git a/mobilizon_reshare/mobilizon/events.py b/mobilizon_reshare/mobilizon/events.py index a4383f8..1a41b85 100644 --- a/mobilizon_reshare/mobilizon/events.py +++ b/mobilizon_reshare/mobilizon/events.py @@ -24,8 +24,8 @@ def parse_location(data): return f"{addr['description']}, {addr['locality']}, {addr['region']}" elif "onlineAddress" in data and data["onlineAddress"]: return data["onlineAddress"] - else: - return None + + return None def parse_picture(data): diff --git a/mobilizon_reshare/models/event.py b/mobilizon_reshare/models/event.py index feca5c1..170265e 100644 --- a/mobilizon_reshare/models/event.py +++ b/mobilizon_reshare/models/event.py @@ -23,7 +23,7 @@ class Event(Model): publications: fields.ReverseRelation["Publication"] def __str__(self): - return self.name + return str(self.name) def __repr__(self): return f"{self.id} - {self.name}" diff --git a/mobilizon_reshare/publishers/abstract.py b/mobilizon_reshare/publishers/abstract.py index 684fd2e..a152084 100644 --- a/mobilizon_reshare/publishers/abstract.py +++ b/mobilizon_reshare/publishers/abstract.py @@ -5,7 +5,6 @@ from dataclasses import dataclass from typing import List, Optional from uuid import UUID -import arrow from dynaconf.utils.boxing import DynaBox from jinja2 import Environment, FileSystemLoader, Template @@ -131,6 +130,7 @@ class AbstractEventFormatter(LoggerMixin, ConfLoaderMixin): self._validate_event(event) self._validate_message(self.get_message_from_event(event)) + @abstractmethod def _preprocess_event(self, event): """ Allows publishers to preprocess events before feeding them to the template diff --git a/mobilizon_reshare/publishers/coordinator.py b/mobilizon_reshare/publishers/coordinator.py index 367bca6..6239631 100644 --- a/mobilizon_reshare/publishers/coordinator.py +++ b/mobilizon_reshare/publishers/coordinator.py @@ -1,4 +1,5 @@ import logging +from abc import abstractmethod, ABC from dataclasses import dataclass from typing import List, Optional @@ -77,7 +78,7 @@ class PublisherCoordinator: return self._post() - def _post(self): + def _post(self) -> PublisherCoordinatorReport: reports = [] for publication in self.publications: @@ -122,8 +123,7 @@ class PublisherCoordinator: for publication in self.publications: reasons = [] reasons = self._safe_run( - reasons, - publication.publisher.validate_credentials, + reasons, publication.publisher.validate_credentials, ) reasons = self._safe_run( reasons, publication.formatter.validate_event, publication.event @@ -141,7 +141,7 @@ class PublisherCoordinator: return errors -class AbstractCoordinator: +class Sender: def __init__(self, message: str, platforms: List[AbstractPlatform] = None): self.message = message self.platforms = platforms @@ -151,29 +151,41 @@ class AbstractCoordinator: try: platform.send(self.message) except Exception as e: - logger.critical(f"Notifier failed to send message:\n{self.message}") + logger.critical(f"Failed to send message:\n{self.message}") logger.exception(e) -class AbstractNotifiersCoordinator(AbstractCoordinator): - def __init__(self, message: str, notifiers: List[AbstractPlatform] = None): - platforms = notifiers or [ +class AbstractNotifiersCoordinator(ABC): + def __init__(self, report, notifiers: List[AbstractPlatform] = None): + self.platforms = notifiers or [ get_notifier_class(notifier)() for notifier in get_active_notifiers() ] - super(AbstractNotifiersCoordinator, self).__init__(message, platforms) + self.report = report + + @abstractmethod + def notify_failure(self): + pass class PublicationFailureNotifiersCoordinator(AbstractNotifiersCoordinator): - def __init__(self, report: BasePublicationReport, platforms=None): - self.report = report - super(PublicationFailureNotifiersCoordinator, self).__init__( - message=report.get_failure_message(), notifiers=platforms - ) + """ + Sends a notification of a failure report to the active platforms + """ def notify_failure(self): logger.info("Sending failure notifications") if self.report.status == PublicationStatus.FAILED: - self.send_to_all() + Sender(self.report.get_failure_message(), self.platforms).send_to_all() + + +class PublicationFailureLoggerCoordinator(PublicationFailureNotifiersCoordinator): + """ + Logs a report to console + """ + + def notify_failure(self): + if self.report.status == PublicationStatus.FAILED: + logger.error(self.report.get_failure_message()) class RecapCoordinator: @@ -194,15 +206,13 @@ class RecapCoordinator: recap_publication.publisher.send(message) reports.append( BasePublicationReport( - status=PublicationStatus.COMPLETED, - reason=None, + status=PublicationStatus.COMPLETED, reason=None, ) ) except PublisherError as e: reports.append( BasePublicationReport( - status=PublicationStatus.FAILED, - reason=str(e), + status=PublicationStatus.FAILED, reason=str(e), ) ) diff --git a/mobilizon_reshare/publishers/exceptions.py b/mobilizon_reshare/publishers/exceptions.py index f23eb22..038f739 100644 --- a/mobilizon_reshare/publishers/exceptions.py +++ b/mobilizon_reshare/publishers/exceptions.py @@ -1,8 +1,6 @@ class PublisherError(Exception): """Generic publisher error""" - pass - class InvalidAttribute(PublisherError): """Publisher defined with invalid or missing attribute""" diff --git a/mobilizon_reshare/publishers/platforms/facebook.py b/mobilizon_reshare/publishers/platforms/facebook.py index b703039..92532de 100644 --- a/mobilizon_reshare/publishers/platforms/facebook.py +++ b/mobilizon_reshare/publishers/platforms/facebook.py @@ -56,9 +56,7 @@ class FacebookPlatform(AbstractPlatform): name = "facebook" def _get_api(self) -> facebook.GraphAPI: - return facebook.GraphAPI( - access_token=self.conf["page_access_token"], version="8.0" - ) + return facebook.GraphAPI(access_token=self.conf["page_access_token"]) def _send(self, message: str, event: Optional[MobilizonEvent] = None): try: diff --git a/mobilizon_reshare/publishers/platforms/zulip.py b/mobilizon_reshare/publishers/platforms/zulip.py index 619e4ed..80e50c3 100644 --- a/mobilizon_reshare/publishers/platforms/zulip.py +++ b/mobilizon_reshare/publishers/platforms/zulip.py @@ -61,18 +61,6 @@ class ZulipPlatform(AbstractPlatform): api_uri = "api/v1/" name = "zulip" - def _send_private( - self, message: str, event: Optional[MobilizonEvent] = None - ) -> Response: - """ - Send private messages - """ - return requests.post( - url=urljoin(self.conf.instance, self.api_uri) + "messages", - auth=HTTPBasicAuth(self.conf.bot_email, self.conf.bot_token), - data={"type": "private", "to": f"[{self.user_id}]", "content": message}, - ) - def _send(self, message: str, event: Optional[MobilizonEvent] = None) -> Response: """ Send stream messages @@ -110,18 +98,18 @@ class ZulipPlatform(AbstractPlatform): raise_error=InvalidBot, ) - def _validate_response(self, res: Response) -> dict: + def _validate_response(self, response: Response) -> dict: try: - res.raise_for_status() + response.raise_for_status() except requests.exceptions.HTTPError as e: - self._log_debug(str(res)) + self._log_debug(str(response)) self._log_error( str(e), raise_error=HTTPResponseError, ) # See https://zulip.com/api/rest-error-handling try: - data = res.json() + data = response.json() except Exception as e: self._log_error( f"Server returned invalid json data: {str(e)}", @@ -130,7 +118,7 @@ class ZulipPlatform(AbstractPlatform): if data["result"] == "error": self._log_error( - f"{res.status_code} Error - {data['msg']}", raise_error=ZulipError, + f"{response.status_code} Error - {data['msg']}", raise_error=ZulipError, ) return data diff --git a/mobilizon_reshare/publishers/templates/facebook.tmpl.j2 b/mobilizon_reshare/publishers/templates/facebook.tmpl.j2 index e1636b7..6e261d0 100644 --- a/mobilizon_reshare/publishers/templates/facebook.tmpl.j2 +++ b/mobilizon_reshare/publishers/templates/facebook.tmpl.j2 @@ -1,4 +1,4 @@ -# {{ name }} +{{ name }} 🕒 {{ begin_datetime.to('local').format('DD MMMM, HH:mm') }} - {{ end_datetime.to('local').format('DD MMMM, HH:mm') }} diff --git a/mobilizon_reshare/storage/query/converter.py b/mobilizon_reshare/storage/query/converter.py index e569c3a..87b5292 100644 --- a/mobilizon_reshare/storage/query/converter.py +++ b/mobilizon_reshare/storage/query/converter.py @@ -78,7 +78,7 @@ def compute_event_status(publications: list[Publication]) -> EventPublicationSta raise ValueError(f"Illegal combination of PublicationStatus: {unique_statuses}") -def publication_from_orm(model: Publication, event: MobilizonEvent): +def publication_from_orm(model: Publication, event: MobilizonEvent) -> EventPublication: # imported here to avoid circular dependencies from mobilizon_reshare.publishers.platforms.platform_mapping import ( get_publisher_class, @@ -87,9 +87,4 @@ def publication_from_orm(model: Publication, event: MobilizonEvent): publisher = get_publisher_class(model.publisher.name)() formatter = get_formatter_class(model.publisher.name)() - return EventPublication( - publisher, - formatter, - event, - model.id, - ) + return EventPublication(publisher, formatter, event, model.id,) diff --git a/mobilizon_reshare/storage/query/read.py b/mobilizon_reshare/storage/query/read.py index cee2901..13c9c00 100644 --- a/mobilizon_reshare/storage/query/read.py +++ b/mobilizon_reshare/storage/query/read.py @@ -119,17 +119,11 @@ def _add_date_window( @atomic(CONNECTION_NAME) async def publications_with_status( status: PublicationStatus, - event_mobilizon_id: Optional[UUID] = None, from_date: Optional[Arrow] = None, to_date: Optional[Arrow] = None, -) -> Iterable[EventPublication]: +) -> Iterable[Publication]: query = Publication.filter(status=status) - if event_mobilizon_id: - query = query.prefetch_related("event").filter( - event__mobilizon_id=event_mobilizon_id - ) - return await prefetch_publication_relations( _add_date_window(query, "timestamp", from_date, to_date) ) @@ -180,7 +174,7 @@ async def build_publications(event: MobilizonEvent) -> list[EventPublication]: @atomic(CONNECTION_NAME) async def get_failed_publications_for_event( event_mobilizon_id: UUID, -) -> list[MobilizonEvent]: +) -> list[EventPublication]: event = await get_event(event_mobilizon_id) failed_publications = list( filter( @@ -190,7 +184,10 @@ async def get_failed_publications_for_event( ) for p in failed_publications: await p.fetch_related("publisher") - return list(map(partial(publication_from_orm, event=event), failed_publications)) + mobilizon_event = event_from_model(event) + return list( + map(partial(publication_from_orm, event=mobilizon_event), failed_publications) + ) @atomic(CONNECTION_NAME) @@ -201,6 +198,8 @@ async def get_publication(publication_id): ) # TODO: this is redundant but there's some prefetch problem otherwise publication.event = await get_event(publication.event.mobilizon_id) - return publication_from_orm(event=event_from_model(publication.event)) + return publication_from_orm( + event=event_from_model(publication.event), model=publication + ) except DoesNotExist: return None diff --git a/tests/commands/test_list.py b/tests/commands/test_list.py index 3574443..6abfedc 100644 --- a/tests/commands/test_list.py +++ b/tests/commands/test_list.py @@ -1,4 +1,5 @@ import pytest +from arrow import arrow from mobilizon_reshare.cli.commands.list.list_event import list_events from mobilizon_reshare.cli.commands.list.list_publication import list_publications @@ -60,6 +61,32 @@ async def test_list_publications(capsys, generate_models): ] +@pytest.mark.asyncio +async def test_list_publications_from_window(capsys, generate_models): + await generate_models(spec) + await list_publications( + frm=arrow.Arrow(year=2021, month=6, day=6, hour=3, minute=30) + ) + output = capsys.readouterr() + assert clean_output(output) == [ + "00000000-0000-0000-0000-000000000001 2021-06-06T04:00:00+00:00 " + "FAILED zulip 00000000-0000-0000-0000-000000000002", + ] + + +@pytest.mark.asyncio +async def test_list_publications_to_window(capsys, generate_models): + await generate_models(spec) + await list_publications( + to=arrow.Arrow(year=2021, month=6, day=6, hour=3, minute=30) + ) + output = capsys.readouterr() + assert clean_output(output) == [ + "00000000-0000-0000-0000-000000000000 2021-06-06T03:00:00+00:00 " + "COMPLETED zulip 00000000-0000-0000-0000-000000000001", + ] + + @pytest.mark.asyncio async def test_list_publications_with_status(capsys, generate_models): await generate_models(spec) diff --git a/tests/commands/test_retry.py b/tests/commands/test_retry.py index 8badbf9..f9d5b48 100644 --- a/tests/commands/test_retry.py +++ b/tests/commands/test_retry.py @@ -1,9 +1,9 @@ import uuid -from logging import INFO +from logging import INFO, ERROR import pytest -from mobilizon_reshare.main.retry import retry_event +from mobilizon_reshare.main.retry import retry_event, retry_publication from mobilizon_reshare.models.publication import PublicationStatus, Publication @@ -17,13 +17,12 @@ async def test_retry_decision(): "publisher_class", [pytest.lazy_fixture("mock_publisher_class")] ) @pytest.mark.asyncio -async def test_retry( +async def test_retry_event( event_with_failed_publication, mock_publisher_config, message_collector, failed_publication, ): - assert failed_publication.status == PublicationStatus.FAILED await retry_event(event_with_failed_publication.mobilizon_id) p = await Publication.filter(id=failed_publication.id).first() assert p.status == PublicationStatus.COMPLETED, p.id @@ -35,7 +34,7 @@ async def test_retry( "publisher_class", [pytest.lazy_fixture("mock_publisher_class")] ) @pytest.mark.asyncio -async def test_retry_no_publications( +async def test_retry_event_no_publications( stored_event, mock_publisher_config, message_collector, caplog ): with caplog.at_level(INFO): @@ -48,7 +47,9 @@ async def test_retry_no_publications( "publisher_class", [pytest.lazy_fixture("mock_publisher_class")] ) @pytest.mark.asyncio -async def test_retry_missing_event(mock_publisher_config, message_collector, caplog): +async def test_retry_event_missing_event( + mock_publisher_config, message_collector, caplog +): event_id = uuid.uuid4() with caplog.at_level(INFO): await retry_event(event_id) @@ -61,7 +62,7 @@ async def test_retry_missing_event(mock_publisher_config, message_collector, cap "publisher_class", [pytest.lazy_fixture("mock_publisher_class")] ) @pytest.mark.asyncio -async def test_retry_mixed_publications( +async def test_retry_event_mixed_publications( event_with_failed_publication, mock_publisher_config, message_collector, @@ -81,3 +82,77 @@ async def test_retry_mixed_publications( assert p.status == PublicationStatus.COMPLETED, p.id assert len(message_collector) == 1 assert message_collector[0] == "test event|description of the event" + + +@pytest.mark.parametrize( + "publisher_class", [pytest.lazy_fixture("mock_publisher_class")] +) +@pytest.mark.asyncio +async def test_retry_publication( + event_with_failed_publication, + mock_publisher_config, + message_collector, + failed_publication: Publication, +): + await retry_publication(failed_publication.id) + p = await Publication.filter(id=failed_publication.id).first() + assert p.status == PublicationStatus.COMPLETED, p.id + assert len(message_collector) == 1 + assert message_collector[0] == "test event|description of the event" + + +@pytest.mark.parametrize( + "publisher_class", [pytest.lazy_fixture("mock_publisher_class")] +) +@pytest.mark.asyncio +async def test_retry_publication_missing( + mock_publisher_config, message_collector, caplog +): + publication_id = uuid.uuid4() + with caplog.at_level(INFO): + await retry_publication(publication_id) + assert f"Publication {publication_id} not found.\n" in caplog.text + assert len(message_collector) == 0 + + +@pytest.mark.parametrize( + "publisher_class", [pytest.lazy_fixture("mock_publisher_invalid_class")] +) +@pytest.mark.asyncio +async def test_event_retry_failure( + event_with_failed_publication, + mock_publisher_config, + failed_publication: Publication, + caplog, +): + + with caplog.at_level(ERROR): + await retry_event(event_with_failed_publication.mobilizon_id) + assert ( + f"Publication {failed_publication.id} failed with status: 0.\nReason: credentials error" + in caplog.text + ) + + p = await Publication.filter(id=failed_publication.id).first() + assert p.status == PublicationStatus.FAILED, p.id + + +@pytest.mark.parametrize( + "publisher_class", [pytest.lazy_fixture("mock_publisher_invalid_class")] +) +@pytest.mark.asyncio +async def test_publication_retry_failure( + event_with_failed_publication, + mock_publisher_config, + failed_publication: Publication, + caplog, +): + + with caplog.at_level(ERROR): + await retry_publication(failed_publication.id) + assert ( + f"Publication {failed_publication.id} failed with status: 0.\nReason: credentials error" + in caplog.text + ) + p = await Publication.filter(id=failed_publication.id).first() + assert p.status == PublicationStatus.FAILED, p.id diff --git a/tests/conftest.py b/tests/conftest.py index ae3933a..efffaa5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,8 +33,8 @@ with importlib.resources.path( os.environ["SECRETS_FOR_DYNACONF"] = str(bundled_secrets_path) -def generate_publication_status(published): - return PublicationStatus.COMPLETED if published else PublicationStatus.WAITING +def generate_publication_status(published) -> PublicationStatus: + return PublicationStatus.COMPLETED if published else PublicationStatus.FAILED @pytest.fixture(autouse=True) @@ -56,7 +56,7 @@ def generate_notification_status(published): @pytest.fixture(scope="session", autouse=True) -def set_dynaconf_environment(request) -> None: +def set_dynaconf_environment() -> None: os.environ["ENV_FOR_DYNACONF"] = "testing" os.environ["FORCE_ENV_FOR_DYNACONF"] = "testing" @@ -120,7 +120,7 @@ def event() -> MobilizonEvent: @pytest.fixture -async def stored_event(event): +async def stored_event(event) -> Event: model = event_to_model(event) await model.save() await model.fetch_related("publications") @@ -379,7 +379,7 @@ async def event_with_failed_publication( @pytest.fixture -async def failed_publication(stored_event): +async def failed_publication(stored_event) -> Publication: p = Publication( event=stored_event, diff --git a/tests/formatting/test_output_format.py b/tests/formatting/test_output_format.py index d903c4b..17f52a9 100644 --- a/tests/formatting/test_output_format.py +++ b/tests/formatting/test_output_format.py @@ -13,11 +13,10 @@ end_date = begin_date.shift(hours=1) @pytest.fixture() -def event() -> MobilizonEvent: - return MobilizonEvent( +def event_to_format() -> MobilizonEvent: + event = MobilizonEvent( name="test event", description="

description of the event

another header

", - # "", begin_datetime=begin_date, end_datetime=end_date, mobilizon_link="http://some_link.com/123", @@ -26,6 +25,8 @@ def event() -> MobilizonEvent: location="location", last_update_time=begin_date, ) + print("in fix", event.description, id(event)) + return event @pytest.mark.parametrize( @@ -33,7 +34,7 @@ def event() -> MobilizonEvent: [ [ "facebook", - f"""# test event + f"""test event 🕒 01 January, {begin_date.format('HH:mm')} - 01 January, {end_date.format('HH:mm')} @@ -61,8 +62,12 @@ Link: http://some_link.com/123 ], ], ) -def test_output_format(event, publisher_name, expected_output): +def test_output_format( + event_to_format: MobilizonEvent, publisher_name, expected_output +): assert ( - get_formatter_class(publisher_name)().get_message_from_event(event).strip() + get_formatter_class(publisher_name)() + .get_message_from_event(event_to_format) + .strip() == expected_output.strip() ) diff --git a/tests/publishers/conftest.py b/tests/publishers/conftest.py index c507419..0db76cb 100644 --- a/tests/publishers/conftest.py +++ b/tests/publishers/conftest.py @@ -84,16 +84,14 @@ def mock_publisher_invalid_response(message_collector): @pytest.fixture() async def mock_active_publishers_config(monkeypatch): - p = Publisher(name="zulip") - await p.save() + publisher = Publisher(name="zulip") + await publisher.save() def _mock_active_pub(): return ["zulip"] monkeypatch.setattr( - mobilizon_reshare.storage.query.read, - "get_active_publishers", - _mock_active_pub + mobilizon_reshare.storage.query.read, "get_active_publishers", _mock_active_pub ) - return p + return publisher diff --git a/tests/publishers/test_coordinator.py b/tests/publishers/test_coordinator.py index a9847d1..58f37c1 100644 --- a/tests/publishers/test_coordinator.py +++ b/tests/publishers/test_coordinator.py @@ -51,7 +51,7 @@ def failure_report(mock_publisher_invalid, event): ) def test_publication_report_successful(statuses, successful): reports = [] - for i, status in enumerate(statuses): + for _, status in enumerate(statuses): reports.append( EventPublicationReport(reason=None, publication=None, status=status) ) @@ -112,12 +112,8 @@ async def mock_publications( @pytest.mark.parametrize("num_publications", [2]) @pytest.mark.asyncio -async def test_publication_coordinator_run_success( - mock_publications, -): - coordinator = PublisherCoordinator( - publications=mock_publications, - ) +async def test_publication_coordinator_run_success(mock_publications,): + coordinator = PublisherCoordinator(publications=mock_publications,) report = coordinator.run() assert len(report.reports) == 2 assert report.successful, "\n".join(map(lambda rep: rep.reason, report.reports)) @@ -182,7 +178,7 @@ async def test_notifier_coordinator_error( ) with caplog.at_level(logging.CRITICAL): coordinator.notify_failure() - assert "Notifier failed to send" in caplog.text + assert "Failed to send" in caplog.text assert failure_report.get_failure_message() in caplog.text # 4 = 2 reports * 2 notifiers assert mock_send.call_count == 2 diff --git a/tests/storage/test_query.py b/tests/storage/test_query.py index 94f3fe7..4551407 100644 --- a/tests/storage/test_query.py +++ b/tests/storage/test_query.py @@ -1,10 +1,9 @@ from datetime import timedelta -from uuid import UUID import arrow import pytest -from mobilizon_reshare.event.event import MobilizonEvent, EventPublicationStatus +from mobilizon_reshare.event.event import EventPublicationStatus from mobilizon_reshare.models.publication import PublicationStatus from mobilizon_reshare.storage.query.read import ( get_published_events, @@ -13,9 +12,9 @@ from mobilizon_reshare.storage.query.read import ( events_without_publications, build_publications, ) +from tests import today from tests.storage import complete_specification, event_0, event_1, event_3 from tests.storage import result_publication -from tests import today @pytest.mark.asyncio @@ -54,19 +53,11 @@ async def test_get_published_events(generate_models): ], ) async def test_publications_with_status( - status, - mobilizon_id, - from_date, - to_date, - expected_result, - generate_models, + status, mobilizon_id, from_date, to_date, expected_result, generate_models, ): await generate_models(complete_specification) publications = await publications_with_status( - status=status, - event_mobilizon_id=mobilizon_id, - from_date=from_date, - to_date=to_date, + status=status, from_date=from_date, to_date=to_date, ) assert publications == expected_result @@ -129,13 +120,7 @@ async def test_event_with_status_window( @pytest.mark.parametrize( "spec, expected_events", [ - ( - {"event": 2, "publications": [], "publisher": ["zulip"]}, - [ - event_0, - event_1, - ], - ), + ({"event": 2, "publications": [], "publisher": ["zulip"]}, [event_0, event_1],), ( { "event": 3, @@ -155,12 +140,7 @@ async def test_event_with_status_window( }, [event_0], ), - ( - complete_specification, - [ - event_3, - ], - ), + (complete_specification, [event_3],), ], ) async def test_events_without_publications(spec, expected_events, generate_models): @@ -174,12 +154,7 @@ async def test_events_without_publications(spec, expected_events, generate_model @pytest.mark.parametrize( "mock_active_publishers, spec, event, n_publications", [ - ( - [], - {"event": 2, "publications": [], "publisher": ["zulip"]}, - event_0, - 0, - ), + ([], {"event": 2, "publications": [], "publisher": ["zulip"]}, event_0, 0,), ( ["zulip"], {"event": 2, "publications": [], "publisher": ["zulip"]},