diff --git a/passbook/stages/prompt/models.py b/passbook/stages/prompt/models.py index f0c85b6f9..28df19fb0 100644 --- a/passbook/stages/prompt/models.py +++ b/passbook/stages/prompt/models.py @@ -14,6 +14,7 @@ class FieldTypes(models.TextChoices): EMAIL = "e-mail" PASSWORD = "password" # noqa # nosec NUMBER = "number" + HIDDEN = "hidden" class Prompt(UUIDModel): @@ -55,7 +56,26 @@ class Prompt(UUIDModel): widget=forms.NumberInput(attrs=attrs), required=self.required, ) - raise ValueError + if self.type == FieldTypes.HIDDEN: + return forms.CharField( + widget=forms.HiddenInput(attrs=attrs), + required=False, + initial=self.placeholder, + ) + raise ValueError("field_type is not valid, not one of FieldTypes.") + + def save(self, *args, **kwargs): + if self.type not in FieldTypes: + raise ValueError + return super().save(*args, **kwargs) + + def __str__(self): + return f"Prompt '{self.field_key}' type={self.type}'" + + class Meta: + + verbose_name = _("Prompt") + verbose_name_plural = _("Prompts") class PromptStage(Stage): diff --git a/passbook/stages/prompt/stage.py b/passbook/stages/prompt/stage.py index f25be58d6..08c2601a7 100644 --- a/passbook/stages/prompt/stage.py +++ b/passbook/stages/prompt/stage.py @@ -1,4 +1,4 @@ -"""Enrollment Stage Logic""" +"""Prompt Stage Logic""" from django.http import HttpResponse from django.utils.translation import gettext_lazy as _ from django.views.generic import FormView @@ -11,8 +11,8 @@ LOGGER = get_logger() PLAN_CONTEXT_PROMPT = "prompt_data" -class EnrollmentStageView(FormView, AuthenticationStage): - """Enrollment Stage, save form data in plan context.""" +class PromptStageView(FormView, AuthenticationStage): + """Prompt Stage, save form data in plan context.""" template_name = "login/form.html" form_class = PromptForm diff --git a/passbook/stages/prompt/tests.py b/passbook/stages/prompt/tests.py new file mode 100644 index 000000000..f94decf4a --- /dev/null +++ b/passbook/stages/prompt/tests.py @@ -0,0 +1,147 @@ +"""Prompt tests""" +from unittest.mock import MagicMock, patch + +from django.shortcuts import reverse +from django.test import Client, TestCase + +from passbook.core.models import User +from passbook.flows.models import Flow, FlowDesignation, FlowStageBinding +from passbook.flows.planner import FlowPlan +from passbook.flows.views import SESSION_KEY_PLAN +from passbook.stages.prompt.forms import PromptForm +from passbook.stages.prompt.models import FieldTypes, Prompt, PromptStage +from passbook.stages.prompt.stage import PLAN_CONTEXT_PROMPT + + +class TestPromptStage(TestCase): + """Prompt tests""" + + def setUp(self): + super().setUp() + self.user = User.objects.create(username="unittest", email="test@beryju.org") + self.client = Client() + + self.flow = Flow.objects.create( + name="test-prompt", + slug="test-prompt", + designation=FlowDesignation.AUTHENTICATION, + ) + text_prompt = Prompt.objects.create( + field_key="text_prompt", + label="TEXT_LABEL", + type=FieldTypes.TEXT, + required=True, + placeholder="TEXT_PLACEHOLDER", + ) + email_prompt = Prompt.objects.create( + field_key="email_prompt", + label="EMAIL_LABEL", + type=FieldTypes.EMAIL, + required=True, + placeholder="EMAIL_PLACEHOLDER", + ) + password_prompt = Prompt.objects.create( + field_key="password_prompt", + label="PASSWORD_LABEL", + type=FieldTypes.PASSWORD, + required=True, + placeholder="PASSWORD_PLACEHOLDER", + ) + number_prompt = Prompt.objects.create( + field_key="number_prompt", + label="NUMBER_LABEL", + type=FieldTypes.NUMBER, + required=True, + placeholder="NUMBER_PLACEHOLDER", + ) + hidden_prompt = Prompt.objects.create( + field_key="hidden_prompt", + type=FieldTypes.HIDDEN, + required=True, + placeholder="HIDDEN_PLACEHOLDER", + ) + self.stage = PromptStage.objects.create(name="prompt-stage") + self.stage.fields.set( + [text_prompt, email_prompt, password_prompt, number_prompt, hidden_prompt,] + ) + self.stage.save() + + self.prompt_data = { + text_prompt.field_key: "test-input", + email_prompt.field_key: "test@test.test", + password_prompt.field_key: "test", + number_prompt.field_key: 3, + hidden_prompt.field_key: hidden_prompt.placeholder, + } + + FlowStageBinding.objects.create(flow=self.flow, stage=self.stage, order=2) + + def test_invalid_type(self): + """Test that invalid form type raises an error""" + with self.assertRaises(ValueError): + _ = Prompt.objects.create( + field_key="hidden_prompt", + type="invalid", + required=True, + placeholder="HIDDEN_PLACEHOLDER", + ) + with self.assertRaises(ValueError): + prompt = Prompt.objects.create( + field_key="hidden_prompt", + type=FieldTypes.HIDDEN, + required=True, + placeholder="HIDDEN_PLACEHOLDER", + ) + with patch.object(prompt, "type", MagicMock(return_value="invalid")): + _ = prompt.field + + def test_render(self): + """Test render of form, check if all prompts are rendered correctly""" + plan = FlowPlan(stages=[self.stage]) + session = self.client.session + session[SESSION_KEY_PLAN] = plan + session.save() + + response = self.client.get( + reverse( + "passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug} + ) + ) + self.assertEqual(response.status_code, 200) + for prompt in self.stage.fields.all(): + self.assertIn(prompt.field_key, response.rendered_content) + self.assertIn(prompt.label, response.rendered_content) + self.assertIn(prompt.placeholder, response.rendered_content) + + def test_valid_form(self) -> PromptForm: + """Test form validation""" + form = PromptForm(stage=self.stage, data=self.prompt_data) + self.assertEqual(form.is_valid(), True) + return form + + def test_valid_form_request(self): + """Test a request with valid form data""" + plan = FlowPlan(stages=[self.stage]) + session = self.client.session + session[SESSION_KEY_PLAN] = plan + session.save() + + form = self.test_valid_form() + + with patch("passbook.flows.views.FlowExecutorView.cancel", MagicMock()): + response = self.client.post( + reverse( + "passbook_flows:flow-executor", kwargs={"flow_slug": self.flow.slug} + ), + form.cleaned_data, + ) + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, reverse("passbook_core:overview")) + + # Check that valid data has been saved + session = self.client.session + plan: FlowPlan = session[SESSION_KEY_PLAN] + data = plan.context[PLAN_CONTEXT_PROMPT] + for prompt in self.stage.fields.all(): + prompt: Prompt + self.assertEqual(data[prompt.field_key], self.prompt_data[prompt.field_key])