mirror of
https://github.com/Hopiu/django-notifications.git
synced 2026-05-05 20:14:46 +00:00
Added tests to the base model
This commit is contained in:
parent
bb41d39a63
commit
5de134e355
8 changed files with 227 additions and 74 deletions
|
|
@ -115,7 +115,7 @@ class AbstractNotification(models.Model):
|
|||
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 @@ class AbstractNotification(models.Model):
|
|||
self.unread = True
|
||||
self.save()
|
||||
|
||||
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:{app_label}_{model}_change",
|
||||
args=(obj_id,),
|
||||
)
|
||||
return format_html("<a href='{url}'>{id}</a>", url=url, id=obj_id)
|
||||
except NoReverseMatch:
|
||||
return obj_id
|
||||
|
||||
def actor_object_url(self) -> str:
|
||||
try:
|
||||
url = reverse(
|
||||
f"admin:{self.actor_content_type.app_label}_{self.actor_content_type.model}_change",
|
||||
args=(self.actor_object_id,),
|
||||
)
|
||||
return format_html("<a href='{url}'>{id}</a>", url=url, id=self.actor_object_id)
|
||||
except NoReverseMatch:
|
||||
return self.actor_object_id
|
||||
return self._build_url("actor")
|
||||
|
||||
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("<a href='{url}'>{id}</a>", url=url, id=self.action_object_object_id)
|
||||
except NoReverseMatch:
|
||||
return self.action_object_object_id
|
||||
def action_object_url(self) -> Union[str, None]:
|
||||
return self._build_url("action_object")
|
||||
|
||||
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("<a href='{url}'>{id}</a>", url=url, id=self.target_object_id)
|
||||
except NoReverseMatch:
|
||||
return self.target_object_id
|
||||
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 @@ class AbstractNotification(models.Model):
|
|||
|
||||
return naturalday(self.timestamp)
|
||||
|
||||
def naturaltime(self):
|
||||
def naturaltime(self) -> str:
|
||||
return naturaltime(self.timestamp)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
VERB_LIST = (
|
||||
"commented",
|
||||
"liked",
|
||||
"deleted",
|
||||
from notifications.tests.factories.users import (
|
||||
ActorFactory,
|
||||
RecipientFactory,
|
||||
TargetFactory,
|
||||
)
|
||||
|
||||
VERB_LIST_SHORT = ("reached level 60", "joined to site")
|
||||
|
||||
class NotificationFactory(factory.django.DjangoModelFactory):
|
||||
recipient = factory.SubFactory(Recipient)
|
||||
VERB_LIST_WITH_TARGET = (
|
||||
"commented on",
|
||||
"started follow",
|
||||
"liked",
|
||||
)
|
||||
|
||||
actor = factory.SubFactory(Actor)
|
||||
VERB_LIST_FULL = (
|
||||
"closed",
|
||||
"opened",
|
||||
"liked",
|
||||
)
|
||||
|
||||
Notification = load_model("notifications", "Notification")
|
||||
|
||||
|
||||
class NotificationShortFactory(factory.django.DjangoModelFactory):
|
||||
recipient = factory.SubFactory(RecipientFactory)
|
||||
|
||||
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)
|
||||
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)
|
||||
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 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))
|
||||
|
||||
|
||||
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 NotificationFullFactory(NotificationWithTargetFactory, NotificationWithActionObjectFactory):
|
||||
verb = factory.Iterator(VERB_LIST_FULL)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import factory
|
|||
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 Recipient(factory.django.DjangoModelFactory):
|
|||
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 Actor(factory.django.DjangoModelFactory):
|
|||
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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
119
notifications/tests/test_models.py
Normal file
119
notifications/tests/test_models.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
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
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
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 "<a href=" in url
|
||||
assert str(notification.actor.id) in url
|
||||
|
||||
with patch("notifications.models.base.reverse") as mock:
|
||||
mock.side_effect = NoReverseMatch
|
||||
url = notification.actor_object_url()
|
||||
assert notification.actor.id == url
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"increase,expected_result",
|
||||
(
|
||||
({"minutes": 10}, "today"),
|
||||
({"days": 1}, "yesterday"),
|
||||
),
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_natural_day(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.naturalday() == expected_result
|
||||
|
|
@ -4,8 +4,8 @@ from django.core.exceptions import ImproperlyConfigured
|
|||
from django.test import override_settings
|
||||
from swapper import load_model
|
||||
|
||||
from notifications.tests.factories.notifications import NotificationFactory
|
||||
from notifications.tests.factories.users import Recipient
|
||||
from notifications.tests.factories.notifications import NotificationFullFactory
|
||||
from notifications.tests.factories.users import RecipientFactory
|
||||
|
||||
Notification = load_model("notifications", "Notification")
|
||||
User = get_user_model()
|
||||
|
|
@ -20,7 +20,7 @@ User = get_user_model()
|
|||
)
|
||||
@pytest.mark.django_db
|
||||
def test_sent_unsent_methods(emailed, method):
|
||||
NotificationFactory.create_batch(3, emailed=emailed)
|
||||
NotificationFullFactory.create_batch(3, emailed=emailed)
|
||||
assert method().count() == 3
|
||||
|
||||
first_notification = Notification.objects.first()
|
||||
|
|
@ -45,7 +45,7 @@ def test_sent_unsent_methods(emailed, method):
|
|||
)
|
||||
@pytest.mark.django_db
|
||||
def test_read_unread_methods(read, method):
|
||||
NotificationFactory.create_batch(3, unread=read)
|
||||
NotificationFullFactory.create_batch(3, unread=read)
|
||||
assert method().count() == 3
|
||||
|
||||
first_notification = Notification.objects.first()
|
||||
|
|
@ -71,7 +71,7 @@ def test_read_unread_methods(read, method):
|
|||
@override_settings(DJANGO_NOTIFICATIONS_CONFIG={"SOFT_DELETE": True})
|
||||
@pytest.mark.django_db
|
||||
def test_read_unread_with_deleted_notifications(read, method):
|
||||
NotificationFactory.create_batch(3, unread=read)
|
||||
NotificationFullFactory.create_batch(3, unread=read)
|
||||
assert method().count() == 3
|
||||
|
||||
first_notification = Notification.objects.first()
|
||||
|
|
@ -91,7 +91,7 @@ def test_read_unread_with_deleted_notifications(read, method):
|
|||
)
|
||||
@pytest.mark.django_db
|
||||
def test_mark_all_as_read_unread(status, method, check_method):
|
||||
NotificationFactory.create_batch(3, unread=status)
|
||||
NotificationFullFactory.create_batch(3, unread=status)
|
||||
assert check_method().count() == 0
|
||||
|
||||
method()
|
||||
|
|
@ -107,9 +107,9 @@ def test_mark_all_as_read_unread(status, method, check_method):
|
|||
)
|
||||
@pytest.mark.django_db
|
||||
def test_mark_all_as_read_unread_with_recipient(status, method, check_method):
|
||||
recipient = Recipient()
|
||||
NotificationFactory.create_batch(2, unread=status, recipient=recipient)
|
||||
NotificationFactory.create_batch(1, unread=status)
|
||||
recipient = RecipientFactory()
|
||||
NotificationFullFactory.create_batch(2, unread=status, recipient=recipient)
|
||||
NotificationFullFactory.create_batch(1, unread=status)
|
||||
assert Notification.objects.count() == 3
|
||||
assert check_method().count() == 0
|
||||
|
||||
|
|
@ -127,7 +127,7 @@ def test_mark_all_as_read_unread_with_recipient(status, method, check_method):
|
|||
)
|
||||
@pytest.mark.django_db
|
||||
def test_deleted_active_methods(deleted, method):
|
||||
NotificationFactory.create_batch(3, deleted=deleted)
|
||||
NotificationFullFactory.create_batch(3, deleted=deleted)
|
||||
assert method().count() == 3
|
||||
|
||||
first_notification = Notification.objects.first()
|
||||
|
|
@ -166,7 +166,7 @@ def test_deleted_active_methods_without_soft_delete(method):
|
|||
)
|
||||
@pytest.mark.django_db
|
||||
def test_mark_all_as_deleted_active(status, method, check_method):
|
||||
NotificationFactory.create_batch(3, deleted=status)
|
||||
NotificationFullFactory.create_batch(3, deleted=status)
|
||||
assert Notification.objects.count() == 3
|
||||
assert check_method().count() == 0
|
||||
|
||||
|
|
@ -184,9 +184,9 @@ def test_mark_all_as_deleted_active(status, method, check_method):
|
|||
)
|
||||
@pytest.mark.django_db
|
||||
def test_mark_all_as_deleted_active_with_recipient(status, method, check_method):
|
||||
recipient = Recipient()
|
||||
NotificationFactory.create_batch(2, deleted=status, recipient=recipient)
|
||||
NotificationFactory.create_batch(1, deleted=status)
|
||||
recipient = RecipientFactory()
|
||||
NotificationFullFactory.create_batch(2, deleted=status, recipient=recipient)
|
||||
NotificationFullFactory.create_batch(1, deleted=status)
|
||||
assert Notification.objects.count() == 3
|
||||
assert check_method().count() == 0
|
||||
|
||||
|
|
@ -216,7 +216,7 @@ def test_mark_all_as_deleted_active_without_soft_delete(method):
|
|||
)
|
||||
@pytest.mark.django_db
|
||||
def test_mark_as_sent_unsent_method(status, method, check_method):
|
||||
NotificationFactory.create_batch(3, emailed=status)
|
||||
NotificationFullFactory.create_batch(3, emailed=status)
|
||||
assert Notification.objects.count() == 3
|
||||
assert check_method().count() == 0
|
||||
|
||||
|
|
@ -233,9 +233,9 @@ def test_mark_as_sent_unsent_method(status, method, check_method):
|
|||
)
|
||||
@pytest.mark.django_db
|
||||
def test_mark_as_sent_unsent_with_recipient(status, method, check_method):
|
||||
recipient = Recipient()
|
||||
NotificationFactory.create_batch(2, emailed=status, recipient=recipient)
|
||||
NotificationFactory.create_batch(1, emailed=status)
|
||||
recipient = RecipientFactory()
|
||||
NotificationFullFactory.create_batch(2, emailed=status, recipient=recipient)
|
||||
NotificationFullFactory.create_batch(1, emailed=status)
|
||||
assert Notification.objects.count() == 3
|
||||
assert check_method().count() == 0
|
||||
|
||||
|
|
|
|||
18
poetry.lock
generated
18
poetry.lock
generated
|
|
@ -390,6 +390,20 @@ docs = ["furo (>=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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
@ -170,6 +172,7 @@ max-line-length = 120
|
|||
ignored-modules = [
|
||||
"pytest",
|
||||
"factory",
|
||||
"freezegun"
|
||||
]
|
||||
|
||||
[tool.black]
|
||||
|
|
|
|||
Loading…
Reference in a new issue