Updated statuses management, tests (#41)

* Updated statuses management, tests

* storage: query: Generalize event loading logic.

* reformat

* storage: query: Rename load_events to prefetch_event_relations.

Co-authored-by: Giacomo Leidi <goodoldpaul@autistici.org>
This commit is contained in:
SlyK182 2021-07-15 18:13:11 +02:00 committed by GitHub
parent 9578f18078
commit b75f0ff057
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 100 additions and 51 deletions

View File

@ -1,5 +1,6 @@
from dataclasses import dataclass, asdict
from typing import Optional
from enum import IntEnum
from typing import Optional, Set
import arrow
import tortoise.timezone
@ -9,6 +10,13 @@ from mobilizon_bots.models.event import Event
from mobilizon_bots.models.publication import PublicationStatus, Publication
class EventPublicationStatus(IntEnum):
WAITING = 1
FAILED = 2
COMPLETED = 3
PARTIAL = 4
@dataclass
class MobilizonEvent:
"""Class representing an event retrieved from Mobilizon."""
@ -22,15 +30,15 @@ class MobilizonEvent:
thumbnail_link: Optional[str] = None
location: Optional[str] = None
publication_time: Optional[dict[str, arrow.Arrow]] = None
publication_status: PublicationStatus = PublicationStatus.WAITING
status: EventPublicationStatus = EventPublicationStatus.WAITING
def __post_init__(self):
assert self.begin_datetime.tzinfo == self.end_datetime.tzinfo
assert self.begin_datetime < self.end_datetime
if self.publication_time:
assert self.publication_status in [
PublicationStatus.COMPLETED,
PublicationStatus.PARTIAL,
assert self.status in [
EventPublicationStatus.COMPLETED,
EventPublicationStatus.PARTIAL,
]
def _fill_template(self, pattern: Template) -> str:
@ -52,19 +60,20 @@ class MobilizonEvent:
)
@staticmethod
def compute_status(publications: list[Publication]):
unique_statuses = set(pub.status for pub in publications)
assert PublicationStatus.PARTIAL not in unique_statuses
def compute_status(publications: list[Publication]) -> EventPublicationStatus:
unique_statuses: Set[PublicationStatus] = set(
pub.status for pub in publications
)
if PublicationStatus.FAILED in unique_statuses:
return PublicationStatus.FAILED
return EventPublicationStatus.FAILED
elif unique_statuses == {
PublicationStatus.COMPLETED,
PublicationStatus.WAITING,
}:
return PublicationStatus.PARTIAL
return EventPublicationStatus.PARTIAL
elif len(unique_statuses) == 1:
return unique_statuses.pop()
return EventPublicationStatus[unique_statuses.pop().name]
raise ValueError(f"Illegal combination of PublicationStatus: {unique_statuses}")
@ -84,7 +93,6 @@ class MobilizonEvent:
mobilizon_id=event.mobilizon_id,
thumbnail_link=event.thumbnail_link,
location=event.location,
# TODO: Discuss publications (both time and status)
publication_time={
pub.publisher.name: arrow.get(
tortoise.timezone.localtime(value=pub.timestamp, timezone=tz)
@ -93,5 +101,5 @@ class MobilizonEvent:
}
if publication_status != PublicationStatus.WAITING
else None,
publication_status=publication_status,
status=publication_status,
)

View File

@ -110,7 +110,8 @@ STRATEGY_NAME_TO_STRATEGY_CLASS = {"next_event": SelectNextEventStrategy}
def select_event_to_publish(
published_events: List[MobilizonEvent], unpublished_events: List[MobilizonEvent],
published_events: List[MobilizonEvent],
unpublished_events: List[MobilizonEvent],
):
strategy = STRATEGY_NAME_TO_STRATEGY_CLASS[

View File

@ -39,7 +39,7 @@ def parse_event(data):
thumbnail_link=parse_picture(data),
location=parse_location(data),
publication_time=None,
publication_status=PublicationStatus.WAITING,
status=PublicationStatus.WAITING,
)

View File

@ -7,8 +7,7 @@ from tortoise.models import Model
class PublicationStatus(IntEnum):
WAITING = 1
FAILED = 2
PARTIAL = 3
COMPLETED = 4
COMPLETED = 3
class Publication(Model):

View File

@ -1,7 +1,8 @@
from dataclasses import dataclass, field
from enum import IntEnum
from typing import List
from mobilizon_bots.event.event import MobilizonEvent, PublicationStatus
from mobilizon_bots.event.event import MobilizonEvent
from mobilizon_bots.publishers import get_active_publishers
from mobilizon_bots.publishers.abstract import AbstractPublisher
from mobilizon_bots.publishers.exceptions import PublisherError
@ -10,9 +11,15 @@ from mobilizon_bots.publishers.telegram import TelegramPublisher
KEY2CLS = {"telegram": TelegramPublisher}
class PublisherStatus(IntEnum):
WAITING = 1
FAILED = 2
COMPLETED = 3
@dataclass
class PublisherReport:
status: PublicationStatus
status: PublisherStatus
reason: str
publisher: AbstractPublisher
@ -23,7 +30,7 @@ class PublisherCoordinatorReport:
@property
def successful(self):
return all(r.status == PublicationStatus.COMPLETED for r in self.reports)
return all(r.status == PublisherStatus.COMPLETED for r in self.reports)
def __iter__(self):
return self.reports.__iter__()
@ -43,7 +50,11 @@ class PublisherCoordinator:
def _make_successful_report(self):
return [
PublisherReport(status=PublicationStatus.COMPLETED, reason="", publisher=p,)
PublisherReport(
status=PublisherStatus.COMPLETED,
reason="",
publisher=p,
)
for p in self.publishers
]
@ -55,7 +66,9 @@ class PublisherCoordinator:
except PublisherError as e:
failed_publishers_reports.append(
PublisherReport(
status=PublicationStatus.FAILED, reason=repr(e), publisher=p,
status=PublisherStatus.FAILED,
reason=repr(e),
publisher=p,
)
)
reports = failed_publishers_reports or self._make_successful_report()
@ -67,7 +80,7 @@ class PublisherCoordinator:
if not p.are_credentials_valid():
invalid_credentials.append(
PublisherReport(
status=PublicationStatus.FAILED,
status=PublisherStatus.FAILED,
reason="Invalid credentials",
publisher=p,
)
@ -75,7 +88,7 @@ class PublisherCoordinator:
if not p.is_event_valid():
invalid_event.append(
PublisherReport(
status=PublicationStatus.FAILED,
status=PublisherStatus.FAILED,
reason="Invalid event",
publisher=p,
)
@ -83,7 +96,7 @@ class PublisherCoordinator:
if not p.is_message_valid():
invalid_msg.append(
PublisherReport(
status=PublicationStatus.FAILED,
status=PublisherStatus.FAILED,
reason="Invalid message",
publisher=p,
)

View File

@ -42,7 +42,8 @@ class TelegramPublisher(AbstractPublisher):
err.append("username")
if err:
self._log_error(
", ".join(err) + " is/are missing", raise_error=InvalidCredentials,
", ".join(err) + " is/are missing",
raise_error=InvalidCredentials,
)
res = requests.get(f"https://api.telegram.org/bot{token}/getMe")
@ -50,7 +51,8 @@ class TelegramPublisher(AbstractPublisher):
if not username == data.get("result", {}).get("username"):
self._log_error(
"Found a different bot than the expected one", raise_error=InvalidBot,
"Found a different bot than the expected one",
raise_error=InvalidBot,
)
def validate_event(self) -> None:
@ -63,7 +65,8 @@ class TelegramPublisher(AbstractPublisher):
res.raise_for_status()
except requests.exceptions.HTTPError as e:
self._log_error(
f"Server returned invalid data: {str(e)}", raise_error=InvalidResponse,
f"Server returned invalid data: {str(e)}",
raise_error=InvalidResponse,
)
try:
@ -76,7 +79,8 @@ class TelegramPublisher(AbstractPublisher):
if not data.get("ok"):
self._log_error(
f"Invalid request (response: {data})", raise_error=InvalidResponse,
f"Invalid request (response: {data})",
raise_error=InvalidResponse,
)
return data

View File

@ -2,6 +2,7 @@ import sys
from typing import Iterable, Optional
from tortoise.queryset import QuerySet
from tortoise.transactions import atomic
from mobilizon_bots.event.event import MobilizonEvent
@ -17,22 +18,29 @@ from mobilizon_bots.publishers.coordinator import PublisherCoordinatorReport
CONNECTION_NAME = "models" if "pytest" in sys.modules else None
async def prefetch_event_relations(queryset: QuerySet[Event]) -> list[Event]:
return (
await queryset.prefetch_related("publications__publisher")
.order_by("begin_datetime")
.distinct()
)
async def events_with_status(
statuses: list[PublicationStatus],
) -> Iterable[MobilizonEvent]:
return map(
MobilizonEvent.from_model,
await Event.filter(publications__status__in=statuses)
.prefetch_related("publications")
.prefetch_related("publications__publisher")
.order_by("begin_datetime")
.distinct(),
await prefetch_event_relations(Event.filter(publications__status__in=statuses)),
)
async def get_published_events() -> Iterable[MobilizonEvent]:
return await events_with_status(
[PublicationStatus.COMPLETED, PublicationStatus.PARTIAL]
return map(
MobilizonEvent.from_model,
await prefetch_event_relations(
Event.filter(publications__status=PublicationStatus.COMPLETED)
),
)

View File

@ -5,7 +5,7 @@ import arrow
import pytest
from tortoise.contrib.test import finalizer, initializer
from mobilizon_bots.event.event import MobilizonEvent
from mobilizon_bots.event.event import MobilizonEvent, EventPublicationStatus
from mobilizon_bots.models.event import Event
from mobilizon_bots.models.notification import Notification, NotificationStatus
from mobilizon_bots.models.publication import Publication, PublicationStatus
@ -16,6 +16,14 @@ def generate_publication_status(published):
return PublicationStatus.COMPLETED if published else PublicationStatus.WAITING
def generate_event_status(published):
return (
EventPublicationStatus.COMPLETED
if published
else EventPublicationStatus.WAITING
)
def generate_notification_status(published):
return NotificationStatus.COMPLETED if published else NotificationStatus.WAITING
@ -38,7 +46,7 @@ def event_generator():
mobilizon_id=mobilizon_id,
thumbnail_link="http://some_link.com/123.jpg",
location="location",
publication_status=generate_publication_status(published),
status=generate_event_status(published),
publication_time=publication_time
or (begin_date.shift(days=-1) if published else None),
)
@ -115,7 +123,9 @@ def event_model_generator():
@pytest.fixture()
def publisher_model_generator():
def _publisher_model_generator(idx=1,):
def _publisher_model_generator(
idx=1,
):
return Publisher(name=f"publisher_{idx}", account_ref=f"account_ref_{idx}")
return _publisher_model_generator

View File

@ -15,7 +15,10 @@ def mock_mobilizon_success_answer(mobilizon_answer, mobilizon_url):
with responses.RequestsMock() as rsps:
rsps.add(
responses.POST, mobilizon_url, json=mobilizon_answer, status=200,
responses.POST,
mobilizon_url,
json=mobilizon_answer,
status=200,
)
yield
@ -26,6 +29,8 @@ def mock_mobilizon_failure_answer(mobilizon_url):
with responses.RequestsMock() as rsps:
rsps.add(
responses.POST, mobilizon_url, status=500,
responses.POST,
mobilizon_url,
status=500,
)
yield

View File

@ -7,6 +7,7 @@ import tortoise.timezone
from mobilizon_bots.event.event import MobilizonEvent
from mobilizon_bots.models.event import Event
from mobilizon_bots.models.publication import PublicationStatus
from mobilizon_bots.event.event import EventPublicationStatus
@pytest.mark.asyncio
@ -148,7 +149,7 @@ async def test_mobilizon_event_from_model(
assert event.thumbnail_link == "thumblink_1"
assert event.location == "loc_1"
assert event.publication_time[publisher_model.name] == publication.timestamp
assert event.publication_status == PublicationStatus.PARTIAL
assert event.status == EventPublicationStatus.PARTIAL
@pytest.mark.asyncio
@ -209,7 +210,7 @@ async def test_mobilizon_event_compute_status_partial(
assert (
MobilizonEvent.compute_status([publication, publication_2])
== PublicationStatus.PARTIAL
== EventPublicationStatus.PARTIAL
)
@ -240,5 +241,5 @@ async def test_mobilizon_event_compute_status_waiting(
assert (
MobilizonEvent.compute_status([publication, publication_2])
== PublicationStatus.WAITING
== EventPublicationStatus.WAITING
)

View File

@ -81,7 +81,7 @@ async def test_get_published_events(
published_events = list(await get_published_events())
assert len(published_events) == 1
assert published_events[0].name == events[0].name
assert published_events[0].mobilizon_id == events[0].mobilizon_id
assert published_events[0].begin_datetime == arrow.get(today)
@ -94,13 +94,13 @@ async def test_get_unpublished_events(
publisher_model_generator, publication_model_generator, event_model_generator
)
published_events = list(await get_unpublished_events())
assert len(published_events) == 2
unpublished_events = list(await get_unpublished_events())
assert len(unpublished_events) == 2
assert published_events[0].name == events[2].name
assert published_events[1].name == events[0].name
assert published_events[0].begin_datetime == events[2].begin_datetime
assert published_events[1].begin_datetime == events[0].begin_datetime
assert unpublished_events[0].mobilizon_id == events[2].mobilizon_id
assert unpublished_events[1].mobilizon_id == events[0].mobilizon_id
assert unpublished_events[0].begin_datetime == events[2].begin_datetime
assert unpublished_events[1].begin_datetime == events[0].begin_datetime
@pytest.mark.asyncio