diff --git a/passbook/stages/email/forms.py b/passbook/stages/email/forms.py
index 1ff829215..bdd865fb1 100644
--- a/passbook/stages/email/forms.py
+++ b/passbook/stages/email/forms.py
@@ -12,7 +12,7 @@ class EmailStageSendForm(forms.Form):
class EmailStageForm(forms.ModelForm):
- """Form to create/edit Dummy Stage"""
+ """Form to create/edit E-Mail Stage"""
class Meta:
diff --git a/passbook/stages/email/models.py b/passbook/stages/email/models.py
index 914c118ed..c5ec2e9e5 100644
--- a/passbook/stages/email/models.py
+++ b/passbook/stages/email/models.py
@@ -1,5 +1,6 @@
"""email stage models"""
-from django.core.mail.backends.smtp import EmailBackend
+from django.core.mail import get_connection
+from django.core.mail.backends.base import BaseEmailBackend
from django.db import models
from django.utils.translation import gettext as _
@@ -27,9 +28,9 @@ class EmailStage(Stage):
form = "passbook.stages.email.forms.EmailStageForm"
@property
- def backend(self) -> EmailBackend:
+ def backend(self) -> BaseEmailBackend:
"""Get fully configured EMail Backend instance"""
- return EmailBackend(
+ return get_connection(
host=self.host,
port=self.port,
username=self.username,
diff --git a/passbook/stages/email/stage.py b/passbook/stages/email/stage.py
index b19bc02a5..0bb49e5b3 100644
--- a/passbook/stages/email/stage.py
+++ b/passbook/stages/email/stage.py
@@ -65,9 +65,4 @@ class EmailStageView(FormView, AuthenticationStage):
send_mails(self.executor.current_stage, message)
# We can't call stage_ok yet, as we're still waiting
# for the user to click the link in the email
- # return self.executor.stage_ok()
return super().form_invalid(form)
-
- # def post(self, request: HttpRequest):
- # """Just redirect to next stage"""
- # return self.executor.()
diff --git a/passbook/stages/email/tasks.py b/passbook/stages/email/tasks.py
index 9ff45eb17..750a5326e 100644
--- a/passbook/stages/email/tasks.py
+++ b/passbook/stages/email/tasks.py
@@ -25,6 +25,7 @@ def send_mails(stage: EmailStage, *messages: List[EmailMultiAlternatives]):
@CELERY_APP.task(
bind=True, autoretry_for=(SMTPException, ConnectionError,), retry_backoff=True
)
+# pylint: disable=unused-argument
def _send_mail_task(self, email_stage_pk: int, message: Dict[Any, Any]):
"""Send E-Mail according to EmailStage parameters from background worker.
Automatically retries if message couldn't be sent."""
@@ -38,6 +39,4 @@ def _send_mail_task(self, email_stage_pk: int, message: Dict[Any, Any]):
setattr(message_object, key, value)
message_object.from_email = stage.from_address
LOGGER.debug("Sending mail", to=message_object.to)
- num_sent = stage.backend.send_messages([message_object])
- if num_sent != 1:
- raise self.retry()
+ stage.backend.send_messages([message_object])
diff --git a/passbook/stages/email/templates/stages/email/for_email/base.html b/passbook/stages/email/templates/stages/email/for_email/base.html
index 8677d592a..21f4f9db1 100644
--- a/passbook/stages/email/templates/stages/email/for_email/base.html
+++ b/passbook/stages/email/templates/stages/email/for_email/base.html
@@ -9,7 +9,7 @@
- Simple Transactional Email
+
diff --git a/passbook/stages/email/templatetags/passbook_stages_email.py b/passbook/stages/email/templatetags/passbook_stages_email.py
index d39668eff..48cd26949 100644
--- a/passbook/stages/email/templatetags/passbook_stages_email.py
+++ b/passbook/stages/email/templatetags/passbook_stages_email.py
@@ -1,7 +1,5 @@
"""passbook core inlining template tags"""
-import os
from pathlib import Path
-from typing import Optional
from django import template
from django.contrib.staticfiles import finders
@@ -10,21 +8,17 @@ register = template.Library()
@register.simple_tag()
-def inline_static_ascii(path: str) -> Optional[str]:
+def inline_static_ascii(path: str) -> str:
"""Inline static asset. Doesn't check file contents, plain text is assumed"""
result = finders.find(path)
- if os.path.exists(result):
- with open(result) as _file:
- return _file.read()
- return None
+ with open(result) as _file:
+ return _file.read()
@register.simple_tag()
-def inline_static_binary(path: str) -> Optional[str]:
+def inline_static_binary(path: str) -> str:
"""Inline static asset. Uses file extension for base64 block"""
result = finders.find(path)
suffix = Path(path).suffix
- if os.path.exists(result):
- with open(result) as _file:
- return f"data:image/{suffix};base64," + _file.read()
- return None
+ with open(result) as _file:
+ return f"data:image/{suffix};base64," + _file.read()
diff --git a/passbook/stages/email/tests.py b/passbook/stages/email/tests.py
new file mode 100644
index 000000000..345316c18
--- /dev/null
+++ b/passbook/stages/email/tests.py
@@ -0,0 +1,88 @@
+"""email tests"""
+from unittest.mock import MagicMock, patch
+
+from django.core import mail
+from django.shortcuts import reverse
+from django.test import Client, TestCase
+
+from passbook.core.models import Nonce, User
+from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding
+from passbook.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
+from passbook.flows.views import SESSION_KEY_PLAN
+from passbook.stages.email.models import EmailStage
+from passbook.stages.email.stage import QS_KEY_TOKEN
+
+
+class TestEmailStage(TestCase):
+ """Email tests"""
+
+ def setUp(self):
+ super().setUp()
+ self.user = User.objects.create_user(
+ username="unittest", email="test@beryju.org"
+ )
+ self.client = Client()
+
+ self.flow = Flow.objects.create(
+ name="test-email",
+ slug="test-email",
+ designation=FlowDesignation.AUTHENTICATION,
+ )
+ self.stage = EmailStage.objects.create(name="email",)
+ FlowStageBinding.objects.create(flow=self.flow, stage=self.stage, order=2)
+
+ def test_rendering(self):
+ """Test with pending user"""
+ plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
+ plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
+ session = self.client.session
+ session[SESSION_KEY_PLAN] = plan
+ session.save()
+
+ url = reverse(
+ "passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
+ )
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+
+ def test_pending_user(self):
+ """Test with pending user"""
+ plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
+ plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
+ session = self.client.session
+ session[SESSION_KEY_PLAN] = plan
+ session.save()
+
+ url = reverse(
+ "passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
+ )
+ with self.settings(
+ EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend"
+ ):
+ response = self.client.post(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(mail.outbox), 1)
+ self.assertEqual(mail.outbox[0].subject, "passbook - Password Recovery")
+
+ def test_token(self):
+ """Test with token"""
+ # Make sure token exists
+ self.test_pending_user()
+ plan = FlowPlan(flow_pk=self.flow.pk.hex, stages=[self.stage])
+ session = self.client.session
+ session[SESSION_KEY_PLAN] = plan
+ session.save()
+
+ with patch("passbook.flows.views.FlowExecutorView.cancel", MagicMock()):
+ url = reverse(
+ "passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug}
+ )
+ token = Nonce.objects.get(user=self.user)
+ url += f"?{QS_KEY_TOKEN}={token.pk.hex}"
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 302)
+ self.assertEqual(response.url, reverse("passbook_core:overview"))
+
+ session = self.client.session
+ plan: FlowPlan = session[SESSION_KEY_PLAN]
+ self.assertEqual(plan.context[PLAN_CONTEXT_PENDING_USER], self.user)