From c6c5e50a70544f275daf9b05168e7dcbfa2a2c23 Mon Sep 17 00:00:00 2001 From: Arthur Deierlein Date: Thu, 3 Aug 2023 11:43:03 +0200 Subject: [PATCH] chore(api/notifications): added tests --- api/outdated/conftest.py | 13 +++++ api/outdated/notifications/tests/__init__.py | 0 .../notifications/tests/templates/base.txt | 8 +++ .../tests/templates/test-bar.txt | 9 +++ .../tests/templates/test-baz.txt | 9 +++ .../tests/templates/test-foo.txt | 9 +++ .../tests/test_build_notification_queue.py | 47 +++++++++++++++ .../tests/test_management_commands.py | 29 ++++++++++ .../notifications/tests/test_notifier.py | 58 +++++++++++++++++++ api/poetry.lock | 19 +++++- api/pyproject.toml | 1 + 11 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 api/outdated/notifications/tests/__init__.py create mode 100644 api/outdated/notifications/tests/templates/base.txt create mode 100644 api/outdated/notifications/tests/templates/test-bar.txt create mode 100644 api/outdated/notifications/tests/templates/test-baz.txt create mode 100644 api/outdated/notifications/tests/templates/test-foo.txt create mode 100644 api/outdated/notifications/tests/test_build_notification_queue.py create mode 100644 api/outdated/notifications/tests/test_management_commands.py create mode 100644 api/outdated/notifications/tests/test_notifier.py diff --git a/api/outdated/conftest.py b/api/outdated/conftest.py index 3d3a1831..91973d6d 100644 --- a/api/outdated/conftest.py +++ b/api/outdated/conftest.py @@ -1,6 +1,7 @@ from functools import partial import pytest +from django.core.management import call_command from pytest_factoryboy import register from rest_framework.test import APIClient @@ -59,6 +60,18 @@ def client(db, settings, get_claims): return client +@pytest.fixture +def setup_notifications(transactional_db, settings): + settings.NOTIFICATIONS = [ + ("test-foo", 60), + ("test-bar", 10), + ("test-baz", -20), + ] + call_command("update-notifications") + settings.TEMPLATES[0]["DIRS"] = ["outdated/notifications/tests/templates"] + settings.TEMPLATES[0]["APP_DIRS"] = False + + @pytest.fixture(scope="module") def vcr_config(): return { diff --git a/api/outdated/notifications/tests/__init__.py b/api/outdated/notifications/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/outdated/notifications/tests/templates/base.txt b/api/outdated/notifications/tests/templates/base.txt new file mode 100644 index 00000000..55f1e8b5 --- /dev/null +++ b/api/outdated/notifications/tests/templates/base.txt @@ -0,0 +1,8 @@ +{% block subject -%} +{% endblock -%} + +Project: {{project.name}} +Repo: {{project.repo}} + +{% block content -%} +{% endblock %} diff --git a/api/outdated/notifications/tests/templates/test-bar.txt b/api/outdated/notifications/tests/templates/test-bar.txt new file mode 100644 index 00000000..07eba94d --- /dev/null +++ b/api/outdated/notifications/tests/templates/test-bar.txt @@ -0,0 +1,9 @@ +{% extends 'base.txt' %} + +{% block subject %} +bar +{% endblock %} + +{% block content %} +test-bar.txt contents +{% endblock %} diff --git a/api/outdated/notifications/tests/templates/test-baz.txt b/api/outdated/notifications/tests/templates/test-baz.txt new file mode 100644 index 00000000..182a49bc --- /dev/null +++ b/api/outdated/notifications/tests/templates/test-baz.txt @@ -0,0 +1,9 @@ +{% extends 'base.txt' %} + +{% block subject %} +baz +{% endblock %} + +{% block content %} +test-baz.txt contents +{% endblock %} diff --git a/api/outdated/notifications/tests/templates/test-foo.txt b/api/outdated/notifications/tests/templates/test-foo.txt new file mode 100644 index 00000000..85c1ecc9 --- /dev/null +++ b/api/outdated/notifications/tests/templates/test-foo.txt @@ -0,0 +1,9 @@ +{% extends 'base.txt' %} + +{% block subject %} +foo +{% endblock %} + +{% block content %} +test-foo.txt contents +{% endblock %} diff --git a/api/outdated/notifications/tests/test_build_notification_queue.py b/api/outdated/notifications/tests/test_build_notification_queue.py new file mode 100644 index 00000000..0c639c30 --- /dev/null +++ b/api/outdated/notifications/tests/test_build_notification_queue.py @@ -0,0 +1,47 @@ +from datetime import date, timedelta +from unittest.mock import MagicMock + +import pytest +from pytest_mock import MockerFixture + +from .. import models + + +@pytest.mark.parametrize( + "status,called", + (["UNDEFINED", False], ["OUTDATED", True], ["WARNING", True], ["UP-TO-DATE", True]), +) +def test_build_notification_queue_called( + transactional_db, + mocker: MockerFixture, + status, + called, + project_factory, + release_version_factory, + version_factory, +): + build_notification_queue_mock: MagicMock = mocker.patch.object( + models, "build_notification_queue" + ) + release_version = release_version_factory( + undefined=status == "UNDEFINED", + outdated=status == "OUTDATED", + warning=status == "WARNING", + up_to_date=status == "UP-TO-DATE", + ) + version = version_factory(release_version=release_version) + project = project_factory() + assert build_notification_queue_mock.call_count == 0 + project.versioned_dependencies.add(version) + + if called: + build_notification_queue_mock.assert_called_with(project) + other_project = project_factory(versioned_dependencies=[version]) + build_notification_queue_mock.assert_called_with(other_project) + + release_version.end_of_life = date.today() + timedelta(days=2000) + release_version.save() + + assert build_notification_queue_mock.call_count == 4 + else: + assert build_notification_queue_mock.call_count == 0 diff --git a/api/outdated/notifications/tests/test_management_commands.py b/api/outdated/notifications/tests/test_management_commands.py new file mode 100644 index 00000000..e229e39f --- /dev/null +++ b/api/outdated/notifications/tests/test_management_commands.py @@ -0,0 +1,29 @@ +from django.core.management import call_command + +from outdated.notifications.notifier import Notifier + + +def test_notify( + db, + project, + maintainer_factory, + version_factory, + release_version_factory, + capsys, + mocker, +): + call_command("notify", project.name) + stdout, _ = capsys.readouterr() + assert stdout == f"Skipped {project.name} (no-maintainers)\n" + maintainer_factory(project=project) + call_command("notify", project.name) + notify_mocker = mocker.spy(Notifier, "__init__") + stdout, _ = capsys.readouterr() + assert stdout == "" + notify_mocker.assert_not_called() + project.versioned_dependencies.set( + [version_factory(release_version=release_version_factory(warning=True))] + ) + project.save() + call_command("notify", project.name) + notify_mocker.assert_called_once() diff --git a/api/outdated/notifications/tests/test_notifier.py b/api/outdated/notifications/tests/test_notifier.py new file mode 100644 index 00000000..7181fb20 --- /dev/null +++ b/api/outdated/notifications/tests/test_notifier.py @@ -0,0 +1,58 @@ +from datetime import date, timedelta +from typing import Optional + +import pytest + +from outdated.notifications.notifier import Notifier +from outdated.outdated.models import Maintainer + + +@pytest.mark.parametrize("nonprimary_maintainers", [False, True]) +@pytest.mark.parametrize( + "days_until_outdated,template,sent", + [ + (200, None, False), + (60, "test-foo", True), + (50, "test-foo", True), + (10, "test-bar", True), + (-20, "test-baz", True), + ], +) +def test_send_notification( + setup_notifications, + days_until_outdated: int, + template: Optional[str], + sent: bool, + nonprimary_maintainers: bool, + maintainer, + maintainer_factory, + mailoutbox, + version_factory, + release_version_factory, +): + project = maintainer.project + release_version = release_version_factory( + end_of_life=date.today() + timedelta(days=days_until_outdated) + ) + version = version_factory(release_version=release_version) + project.versioned_dependencies.add(version) + project.save() + if nonprimary_maintainers: + maintainer_factory(project=project) + maintainer_factory(project=project) + maintainer_factory(project=project) + nonprimary_maintainers = list(Maintainer.objects.filter(is_primary=False)) + notification_queue = list(project.notification_queue.all()) + Notifier(project).notify() + if sent: + mail = mailoutbox[0] + assert mail.subject == template.replace("test-", "") + assert ( + mail.body + == f"Project: {project.name}\nRepo: {project.repo}\n\n{template}.txt contents\n" + ) + assert mail.to[0] == maintainer.user.email + assert mail.cc == [m.user.email for m in nonprimary_maintainers] + assert notification_queue[1:] == list(project.notification_queue.all()) + else: + assert not mailoutbox diff --git a/api/poetry.lock b/api/poetry.lock index 881cd663..2527ab20 100644 --- a/api/poetry.lock +++ b/api/poetry.lock @@ -1604,6 +1604,23 @@ inflection = "*" pytest = ">=5.0.0" typing_extensions = "*" +[[package]] +name = "pytest-mock" +version = "3.11.1" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"}, + {file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pytest-vcr" version = "1.0.2" @@ -2037,4 +2054,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "950cea0c2ac9452b963b3b436057080266cf809d1911ea6100948e65d5f5a71a" +content-hash = "aee986e288a271b27dea28ad39ae41176af2186110b0e8e6066d7f91351102a9" diff --git a/api/pyproject.toml b/api/pyproject.toml index d39eaa27..fe801228 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -39,6 +39,7 @@ pytest-cov = "^4.0.0" pytest-vcr = "^1.0.2" pdbpp = "^0.10.3" requests-mock = "^1.10.0" +pytest-mock = "^3.11.1" [tool.isort]