From b32eac95dc69d4822f3d2bb262ccbc2abc6dd3dd Mon Sep 17 00:00:00 2001 From: Alvaro Leonel Date: Sun, 14 Apr 2024 12:40:54 -0300 Subject: [PATCH] Added tests to the base model --- notifications/models/base.py | 44 +++---- .../tests/factories/notifications.py | 51 ++++++-- notifications/tests/factories/users.py | 6 +- .../test_migrations/test_level_migration.py | 6 +- notifications/tests/test_models.py | 118 ++++++++++++++++++ notifications/tests/test_querysets.py | 36 +++--- poetry.lock | 18 ++- pyproject.toml | 2 + 8 files changed, 216 insertions(+), 65 deletions(-) create mode 100644 notifications/tests/test_models.py diff --git a/notifications/models/base.py b/notifications/models/base.py index 2d0a3b36..76c96ddd 100644 --- a/notifications/models/base.py +++ b/notifications/models/base.py @@ -115,7 +115,7 @@ class Meta: verbose_name = _("Notification") verbose_name_plural = _("Notifications") - def __str__(self): + def __str__(self) -> str: ctx = { "actor": self.actor, "verb": self.verb, @@ -153,37 +153,29 @@ def mark_as_unread(self) -> None: self.unread = True self.save() - def actor_object_url(self) -> str: + def _build_url(self, field_name: str) -> str: + app_label = getattr(getattr(self, f"{field_name}_content_type"), "app_label") + model = getattr(getattr(self, f"{field_name}_content_type"), "model") + obj_id = getattr(self, f"{field_name}_object_id") try: url = reverse( - f"admin:{self.actor_content_type.app_label}_{self.actor_content_type.model}_change", - args=(self.actor_object_id,), + f"admin:{app_label}_{model}_change", + args=(obj_id,), ) - return format_html("{id}", url=url, id=self.actor_object_id) + return format_html("{id}", url=url, id=obj_id) except NoReverseMatch: - return self.actor_object_id + return obj_id - def action_object_url(self) -> str: - try: - url = reverse( - f"admin:{self.action_object_content_type.app_label}_{self.action_object_content_type.model}_change", - args=(self.action_object_id,), - ) - return format_html("{id}", url=url, id=self.action_object_object_id) - except NoReverseMatch: - return self.action_object_object_id + def actor_object_url(self) -> str: + return self._build_url("actor") - def target_object_url(self) -> str: - try: - url = reverse( - f"admin:{self.target_content_type.app_label}_{self.target_content_type.model}_change", - args=(self.target_object_id,), - ) - return format_html("{id}", url=url, id=self.target_object_id) - except NoReverseMatch: - return self.target_object_id + def action_object_url(self) -> Union[str, None]: + return self._build_url("action_object") + + def target_object_url(self) -> Union[str, None]: + return self._build_url("target") - def naturalday(self): + def naturalday(self) -> Union[str, None]: """ Shortcut for the ``humanize``. Take a parameter humanize_type. This parameter control the which humanize method use. @@ -192,5 +184,5 @@ def naturalday(self): return naturalday(self.timestamp) - def naturaltime(self): + def naturaltime(self) -> str: return naturaltime(self.timestamp) diff --git a/notifications/tests/factories/notifications.py b/notifications/tests/factories/notifications.py index 0ff720c4..bc4320b3 100644 --- a/notifications/tests/factories/notifications.py +++ b/notifications/tests/factories/notifications.py @@ -1,33 +1,58 @@ import factory from django.contrib.contenttypes.models import ContentType +from swapper import load_model -from notifications.models import Notification -from notifications.tests.factories.users import Actor, Recipient, Target +from notifications.tests.factories.users import ( + ActorFactory, + RecipientFactory, + TargetFactory, +) + +VERB_LIST_SHORT = ("reached level 60", "joined to site") + +VERB_LIST_WITH_TARGET = ( + "commented on", + "started follow", + "liked", +) -VERB_LIST = ( - "commented", +VERB_LIST_FULL = ( + "closed", + "opened", "liked", - "deleted", ) +Notification = load_model("notifications", "Notification") + -class NotificationFactory(factory.django.DjangoModelFactory): - recipient = factory.SubFactory(Recipient) +class NotificationShortFactory(factory.django.DjangoModelFactory): + recipient = factory.SubFactory(RecipientFactory) - actor = factory.SubFactory(Actor) + actor = factory.SubFactory(ActorFactory) actor_object_id = factory.SelfAttribute("actor.id") actor_content_type = factory.LazyAttribute(lambda obj: ContentType.objects.get_for_model(obj.actor)) - verb = factory.Iterator(VERB_LIST) + verb = factory.Iterator(VERB_LIST_SHORT) description = factory.Faker("catch_phrase") - target = factory.SubFactory(Target) + class Meta: + model = Notification + + +class NotificationWithTargetFactory(NotificationShortFactory): + verb = factory.Iterator(VERB_LIST_WITH_TARGET) + + target = factory.SubFactory(TargetFactory) target_object_id = factory.SelfAttribute("target.id") target_content_type = factory.LazyAttribute(lambda obj: ContentType.objects.get_for_model(obj.target)) - action_object = factory.SubFactory(Target) + +class NotificationWithActionObjectFactory(NotificationShortFactory): + verb = factory.Iterator(VERB_LIST_WITH_TARGET) + action_object = factory.SubFactory(TargetFactory) action_object_object_id = factory.SelfAttribute("action_object.id") action_object_content_type = factory.LazyAttribute(lambda obj: ContentType.objects.get_for_model(obj.action_object)) - class Meta: - model = Notification + +class NotificationFullFactory(NotificationWithTargetFactory, NotificationWithActionObjectFactory): + verb = factory.Iterator(VERB_LIST_FULL) diff --git a/notifications/tests/factories/users.py b/notifications/tests/factories/users.py index 0a82ba36..34129ea6 100644 --- a/notifications/tests/factories/users.py +++ b/notifications/tests/factories/users.py @@ -2,7 +2,7 @@ from django.conf import settings -class Recipient(factory.django.DjangoModelFactory): +class RecipientFactory(factory.django.DjangoModelFactory): username = factory.Sequence(lambda n: f"recipient-{n}") first_name = factory.SelfAttribute("username") @@ -10,7 +10,7 @@ class Meta: model = settings.AUTH_USER_MODEL -class Actor(factory.django.DjangoModelFactory): +class ActorFactory(factory.django.DjangoModelFactory): username = factory.Sequence(lambda n: f"actor-{n}") first_name = factory.SelfAttribute("username") @@ -18,7 +18,7 @@ class Meta: model = settings.AUTH_USER_MODEL -class Target(factory.django.DjangoModelFactory): +class TargetFactory(factory.django.DjangoModelFactory): username = factory.Sequence(lambda n: f"target-{n}") first_name = factory.SelfAttribute("username") diff --git a/notifications/tests/test_migrations/test_level_migration.py b/notifications/tests/test_migrations/test_level_migration.py index ef402a06..917a0f37 100644 --- a/notifications/tests/test_migrations/test_level_migration.py +++ b/notifications/tests/test_migrations/test_level_migration.py @@ -12,9 +12,9 @@ def test_main_migration0002(migrator): OldNotification = old_state.apps.get_model("notifications", "Notification") # pylint: disable=invalid-name OldContentType = old_state.apps.get_model("contenttypes", "ContentType") # pylint: disable=invalid-name - mark_follower = factory.create(OldUser, FACTORY_CLASS=user_factory.Recipient) - guido = factory.create(OldUser, FACTORY_CLASS=user_factory.Target) - mark = factory.create(OldUser, FACTORY_CLASS=user_factory.Actor) + mark_follower = factory.create(OldUser, FACTORY_CLASS=user_factory.RecipientFactory) + guido = factory.create(OldUser, FACTORY_CLASS=user_factory.TargetFactory) + mark = factory.create(OldUser, FACTORY_CLASS=user_factory.ActorFactory) user_type = OldContentType.objects.get_for_model(mark) notification_base = { diff --git a/notifications/tests/test_models.py b/notifications/tests/test_models.py new file mode 100644 index 00000000..3ce33e96 --- /dev/null +++ b/notifications/tests/test_models.py @@ -0,0 +1,118 @@ +from datetime import datetime, timedelta +from unittest.mock import patch + +import pytest +from django.urls import NoReverseMatch +from freezegun import freeze_time +from swapper import load_model + +from notifications.tests.factories.notifications import ( + NotificationFullFactory, + NotificationShortFactory, + NotificationWithActionObjectFactory, + NotificationWithTargetFactory, +) + +Notification = load_model("notifications", "Notification") + + +@pytest.mark.django_db +def test__str__(): + notification = NotificationShortFactory() + + notification_str = str(notification) + assert str(notification.actor) in notification_str + assert str(notification.verb) in notification_str + assert str(notification.action_object) not in notification_str + assert str(notification.target) not in notification_str + + notification = NotificationWithTargetFactory() + notification_str = str(notification) + assert str(notification.actor) in notification_str + assert str(notification.verb) in notification_str + assert str(notification.target) in notification_str + assert str(notification.action_object) not in notification_str + + notification = NotificationWithActionObjectFactory() + notification_str = str(notification) + assert str(notification.actor) in notification_str + assert str(notification.verb) in notification_str + assert str(notification.action_object) in notification_str + assert str(notification.target) not in notification_str + + notification = NotificationFullFactory() + notification_str = str(notification) + assert str(notification.actor) in notification_str + assert str(notification.verb) in notification_str + assert str(notification.target) in notification_str + assert str(notification.action_object) in notification_str + + +@pytest.mark.parametrize( + "increase,expected_result", + ( + ({"minutes": 10}, "10\xa0minutes"), + ({"days": 2}, "2\xa0days"), + ), +) +@pytest.mark.django_db +def test_timesince(increase, expected_result): + initial_date = datetime(2023, 1, 1, 0, 0, 0) + with freeze_time(initial_date): + notification = NotificationShortFactory() + with freeze_time(initial_date + timedelta(**increase)): + assert notification.timesince() == expected_result + + +def test_slug(): + notification = NotificationShortFactory() + assert notification.id == notification.slug + + +@pytest.mark.parametrize( + "before,method", + ( + (True, "mark_as_read"), + (False, "mark_as_unread"), + ), +) +@pytest.mark.django_db +def test_mark_as_read_unread(before, method): + notification = NotificationShortFactory(unread=before) + + assert Notification.objects.filter(unread=before).count() == 1 + func = getattr(notification, method) + func() + assert Notification.objects.filter(unread=before).count() == 0 + assert Notification.objects.filter(unread=not before).count() == 1 + + +@pytest.mark.django_db +def test_build_url(): + notification = NotificationShortFactory() + + url = notification.actor_object_url() + + assert "=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] +[[package]] +name = "freezegun" +version = "1.4.0" +description = "Let your Python tests travel through time" +optional = true +python-versions = ">=3.7" +files = [ + {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"}, + {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + [[package]] name = "identify" version = "2.5.35" @@ -1215,9 +1229,9 @@ files = [ [extras] dev = ["django-debug-toolbar", "pre-commit", "psycopg2-binary"] lint = ["bandit", "black", "isort", "mypy", "pylint", "pylint-django"] -test = ["coverage", "django-test-migrations", "factory-boy", "pytest", "pytest-cov", "pytest-django", "pytest-xdist"] +test = ["coverage", "django-test-migrations", "factory-boy", "freezegun", "pytest", "pytest-cov", "pytest-django", "pytest-xdist"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "c05e64d20b9253c2d60188553b6a88be71d8a40ccd9caff588ae06bfd42931fc" +content-hash = "7934a50f2a5ec5aa8f522e683839042fa165ec26367ab4767f194c1455f313bd" diff --git a/pyproject.toml b/pyproject.toml index 753ed98d..78813dcd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -101,6 +101,7 @@ pytest = {version = "^7", optional = true } pytest-cov = {version = "^4", optional = true } pytest-django = {version = "^4", optional = true } pytest-xdist = {version = "^3.3.1", optional = true } +freezegun = {version = "^1.4.0", optional = true} [tool.poetry.extras] dev = [ @@ -121,6 +122,7 @@ test = [ "django-test", "django-test-migrations", "factory-boy", + "freezegun", "pytest", "pytest-cov", "pytest-django",