Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notifications model and goods report notification #1036

Merged
merged 17 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,5 @@ _dumped_cache.pkl

# Vim
.swp

tamato_*.sql
31 changes: 31 additions & 0 deletions common/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -1426,3 +1426,34 @@ class Meta:
commodity = factory.SubFactory(GoodsNomenclatureFactory)
duty_sentence = factory.Faker("text", max_nb_chars=24)
valid_between = date_ranges("no_end")


class SucceededImportBatchFactory(ImportBatchFactory):
status = ImportBatchStatus.SUCCEEDED
goods_import = True
taric_file = "goods.xml"


class GoodsSuccessfulImportNotificationFactory(factory.django.DjangoModelFactory):
"""This is a factory for a goods report notification, requires an import id
passed by notified_object_id."""

class Meta:
model = "notifications.GoodsSuccessfulImportNotification"


class EnvelopeReadyForProcessingNotificationFactory(factory.django.DjangoModelFactory):
"""This is a factory for an envelope ready for processing notificaiton."""

class Meta:
model = "notifications.EnvelopeReadyForProcessingNotification"


class CrownDependenciesEnvelopeSuccessNotificationFactory(
factory.django.DjangoModelFactory,
):
"""This is a factory for a crown dependencies envelope success
notification."""

class Meta:
model = "notifications.CrownDependenciesEnvelopeSuccessNotification"
139 changes: 139 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Sequence
from typing import Tuple
from typing import Type
from unittest.mock import MagicMock
from unittest.mock import patch

import boto3
Expand Down Expand Up @@ -66,6 +67,7 @@
from measures.models import MeasurementUnitQualifier
from measures.models import MonetaryUnit
from measures.parsers import DutySentenceParser
from publishing.models import PackagedWorkBasket
from workbaskets.models import WorkBasket
from workbaskets.models import get_partition_scheme
from workbaskets.validators import WorkflowStatus
Expand Down Expand Up @@ -1820,3 +1822,140 @@ def duty_sentence_x_2_data(request, get_component_data):
(expected, [get_component_data(*args) for args in component_data]),
)
return history


@pytest.fixture()
def mocked_send_emails_apply_async():
with patch(
"notifications.tasks.send_emails_task.apply_async",
return_value=MagicMock(id=factory.Faker("uuid4")),
) as mocked_delay:
yield mocked_delay


@pytest.fixture()
def mocked_send_emails():
with patch(
"notifications.tasks.send_emails_task",
return_value=MagicMock(id=factory.Faker("uuid4")),
) as mocked_delay:
yield mocked_delay


@pytest.fixture(scope="function")
def packaged_workbasket_factory(queued_workbasket_factory):
"""
Factory fixture to create a packaged workbasket.
params:
workbasket defaults to queued_workbasket_factory() which creates a
Workbasket in the state QUEUED with an approved transaction and tracked models
"""

def factory_method(workbasket=None, **kwargs):
if not workbasket:
workbasket = queued_workbasket_factory()
with patch(
"publishing.tasks.create_xml_envelope_file.apply_async",
return_value=MagicMock(id=factory.Faker("uuid4")),
):
packaged_workbasket = factories.QueuedPackagedWorkBasketFactory(
workbasket=workbasket, **kwargs
)
return packaged_workbasket

return factory_method


@pytest.fixture(scope="function")
def published_envelope_factory(packaged_workbasket_factory, envelope_storage):
"""
Factory fixture to create an envelope and update the packaged_workbasket
envelope field.
params:
packaged_workbasket defaults to packaged_workbasket_factory() which creates a
Packaged workbasket with a Workbasket in the state QUEUED
with an approved transaction and tracked models
"""

def factory_method(packaged_workbasket=None, **kwargs):
if not packaged_workbasket:
packaged_workbasket = packaged_workbasket_factory()

with patch(
"publishing.storages.EnvelopeStorage.save",
wraps=MagicMock(side_effect=envelope_storage.save),
) as mock_save:
envelope = factories.PublishedEnvelopeFactory(
packaged_work_basket=packaged_workbasket,
**kwargs,
)
mock_save.assert_called_once()

packaged_workbasket.envelope = envelope
packaged_workbasket.save()
return envelope

return factory_method


@pytest.fixture(scope="function")
def successful_envelope_factory(
published_envelope_factory,
mocked_send_emails_apply_async,
):
"""
Factory fixture to create a successfully processed envelope and update the
packaged_workbasket envelope field.
params:
packaged_workbasket defaults to packaged_workbasket_factory() which creates a
Packaged workbasket with a Workbasket in the state QUEUED
with an approved transaction and tracked models
"""

def factory_method(**kwargs):
envelope = published_envelope_factory(**kwargs)

packaged_workbasket = PackagedWorkBasket.objects.get(
envelope=envelope,
)

packaged_workbasket.begin_processing()
assert packaged_workbasket.position == 0
assert (
packaged_workbasket.pk
== PackagedWorkBasket.objects.currently_processing().pk
)
factories.LoadingReportFactory.create(packaged_workbasket=packaged_workbasket)
packaged_workbasket.processing_succeeded()
packaged_workbasket.save()
assert packaged_workbasket.position == 0
return envelope

return factory_method


@pytest.fixture(scope="function")
def crown_dependencies_envelope_factory(successful_envelope_factory):
"""
Factory fixture to create a crown dependencies envelope.
params:
packaged_workbasket defaults to packaged_workbasket_factory() which creates a
Packaged workbasket with a Workbasket in the state QUEUED
with an approved transaction and tracked models
"""

def factory_method(**kwargs):
envelope = successful_envelope_factory(**kwargs)

packaged_workbasket = PackagedWorkBasket.objects.get(
envelope=envelope,
)
return factories.CrownDependenciesEnvelopeFactory(
packaged_work_basket=packaged_workbasket,
)

return factory_method
40 changes: 40 additions & 0 deletions importer/jinja2/eu-importer/notify-success.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{% extends "layouts/layout.jinja" %}
{% from "components/breadcrumbs/macro.njk" import govukBreadcrumbs %}
{% from "components/button/macro.njk" import govukButton %}
{% from "components/panel/macro.njk" import govukPanel %}


{% set page_title = "Notify Goods Report" %}


{% block breadcrumb %}
{{ govukBreadcrumbs({
"items": [
{"text": "Home", "href": url("home")},
{"text": "Edit an existing workbasket", "href": url("workbaskets:workbasket-ui-list")},
{"text": "Workbasket " ~ request.session.workbasket.id ~ " - Review goods",
"href": url("workbaskets:workbasket-ui-review-goods")},
{"text": page_title}
]
}) }}
{% endblock %}


{% block content %}
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
{{ govukPanel({
"titleText": "Notification email sent",
"text": "An email notification has been successfully sent to Channel Islands.",
"classes": "govuk-!-margin-bottom-7"
}) }}

<div class="govuk-button-group">
<a class="govuk-link" href="{{ url('workbaskets:workbasket-ui-review-goods') }}">
Back to Review goods list
</a>
</div>

</div>
</div>
{% endblock %}
43 changes: 43 additions & 0 deletions importer/management/commands/send_goods_report_notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from django.core.management import BaseCommand
from django.core.management.base import CommandError

from importer.models import ImportBatch
from notifications.models import GoodsSuccessfulImportNotification


def send_notifcation(
import_id: int,
):
try:
import_batch = ImportBatch.objects.get(
pk=import_id,
)
except ImportBatch.DoesNotExist:
raise CommandError(
f"No ImportBatch instance found with pk={import_id}",
)

notification = GoodsSuccessfulImportNotification(
notified_object_pk=import_batch.id,
)
notification.save()
notification.synchronous_send_emails()


class Command(BaseCommand):
help = "Send a good report notifcation for a give Id"

def add_arguments(self, parser):
parser.add_argument(
"--import-batch-id",
help=(
"The primary key ID of ImportBatch instance for which a report "
"should be generated."
),
type=int,
)

def handle(self, *args, **options):
send_notifcation(
import_id=options["import_batch_id"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from unittest.mock import MagicMock
from unittest.mock import patch

import pytest
from django.core.management import call_command
from django.core.management.base import CommandError

pytestmark = pytest.mark.django_db


@pytest.mark.parametrize(
"args, exception_type, error_msg",
[
(
[""],
CommandError,
"Error: unrecognized arguments:",
),
(
["--import-batch-id", "1234"],
CommandError,
"No ImportBatch instance found with pk=1234",
),
],
)
def test_send_goods_report_notification_required_arguments(
args,
exception_type,
error_msg,
):
"""Test that `send_goods_report_notification` command raises errors when
invalid arguments are provided."""
with pytest.raises(exception_type, match=error_msg):
call_command("send_goods_report_notification", *args)


def test_send_goods_report_notification(
completed_goods_import_batch,
):
"""Test that `send_goods_report_notification` command triggers an email
notification."""

with patch(
"notifications.models.send_emails_task",
return_value=MagicMock(),
) as mocked_email_task:
call_command(
"send_goods_report_notification",
"--import-batch-id",
str(completed_goods_import_batch.id),
)
mocked_email_task.assert_called_once()
27 changes: 27 additions & 0 deletions importer/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from os import path
from unittest.mock import MagicMock
from unittest.mock import patch

import pytest
Expand Down Expand Up @@ -282,3 +283,29 @@ def test_import_list_filters_return_correct_imports(
assert len(page.find_all(class_="status-badge")) == 1
assert page.find(class_="status-badge", text=expected_status_text)
assert page.find("tbody").find("td", text=import_batch.name)


def test_notify_channel_islands_redirects(
valid_user_client,
completed_goods_import_batch,
):
"""Tests that, when the notify button is clicked that it redirects to the
conformation page on successful notification trigger."""

with patch(
"notifications.models.send_emails_task",
return_value=MagicMock(),
) as mocked_email_task:
response = valid_user_client.get(
reverse(
"goods-report-notify",
kwargs={"pk": completed_goods_import_batch.pk},
),
)

mocked_email_task.assert_called_once()
assert response.status_code == 302
assert response.url == reverse(
"goods-report-notify-success",
kwargs={"pk": completed_goods_import_batch.pk},
)
10 changes: 10 additions & 0 deletions importer/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@
views.DownloadGoodsReportView.as_view(),
name="goods-report-ui-download",
),
path(
"notify-goods-report/<pk>/",
views.NotifyGoodsReportView.as_view(),
name="goods-report-notify",
),
path(
"notify-goods-report-confirm/<pk>/",
views.NotifyGoodsReportSuccessView.as_view(),
name="goods-report-notify-success",
),
]

urlpatterns = general_importer_urlpatterns + commodity_importer_urlpatterns
Loading