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

Feat: CalFresh re-enrollment error #1992

Merged
merged 4 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.3 on 2024-05-01 19:30

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("core", "0008_eligibilityverifier_unverified_template"),
]

operations = [
migrations.AddField(
model_name="eligibilitytype",
name="reenrollment_error_template",
field=models.TextField(blank=True, null=True),
),
]
4 changes: 4 additions & 0 deletions benefits/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ class EligibilityType(models.Model):
expiration_days = models.PositiveSmallIntegerField(null=True, blank=True)
expiration_reenrollment_days = models.PositiveSmallIntegerField(null=True, blank=True)
enrollment_index_template = models.TextField(default="enrollment/index.html")
reenrollment_error_template = models.TextField(null=True, blank=True)

def __str__(self):
return self.label
Expand Down Expand Up @@ -141,6 +142,7 @@ def clean(self):
supports_expiration = self.supports_expiration
expiration_days = self.expiration_days
expiration_reenrollment_days = self.expiration_reenrollment_days
reenrollment_error_template = self.reenrollment_error_template

if supports_expiration:
errors = {}
Expand All @@ -149,6 +151,8 @@ def clean(self):
errors.update(expiration_days=ValidationError(message))
if expiration_reenrollment_days is None or expiration_reenrollment_days <= 0:
errors.update(expiration_reenrollment_days=ValidationError(message))
if reenrollment_error_template is None:
errors.update(reenrollment_error_template=ValidationError("Required when supports expiration is True."))

if errors:
raise ValidationError(errors)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{% extends "enrollment/reenrollment-error-base.html" %}
{% load i18n %}

{% block error-message %}
<p>
{% translate "Your CalFresh Cardholder transit benefit does not expire until" %} {{ enrollment.expires|date }}.
{% translate "You can re-enroll for this benefit beginning on" %} {{ enrollment.reenrollment|date }}.
{% translate "Please try again then." %}
</p>
{% endblock error-message %}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{% extends "core/base.html" %}
{% load i18n %}

{% block page-title %}
{% translate "Enrollment error" %}
{% endblock page-title %}

{% block main-content %}
{% if authentication and authentication.sign_out_link_template %}
{% include authentication.sign_out_link_template %}
{% endif %}

<div class="container">
<h1 class="h2 text-center">
<span class="icon d-block pb-5">{% include "core/includes/icon.html" with name="calendarcheck" %}</span>
{% translate "You are still enrolled in this benefit" %}
</h1>

<div class="row justify-content-center">
<div class="col-lg-8 pt-4">
{% block error-message %}
{% endblock error-message %}
</div>
</div>

<div class="row pt-8 justify-content-center">
<div class="col-lg-3 col-8">{% include "core/includes/button--index.html" %}</div>
</div>

</div>
{% endblock main-content %}
1 change: 1 addition & 0 deletions benefits/enrollment/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# /enrollment
path("", views.index, name="index"),
path("token", views.token, name="token"),
path("reenrollment-error", views.reenrollment_error, name="reenrollment-error"),
path("retry", views.retry, name="retry"),
path("success", views.success, name="success"),
]
28 changes: 22 additions & 6 deletions benefits/enrollment/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,13 @@
from requests.exceptions import HTTPError

from benefits.core import session
from benefits.core.middleware import (
EligibleSessionRequired,
VerifierSessionRequired,
pageview_decorator,
)
from benefits.core.middleware import EligibleSessionRequired, VerifierSessionRequired, pageview_decorator
from benefits.core.views import ROUTE_LOGGED_OUT
from . import analytics, forms

from . import analytics, forms

ROUTE_INDEX = "enrollment:index"
ROUTE_REENROLLMENT_ERROR = "enrollment:reenrollment-error"
ROUTE_RETRY = "enrollment:retry"
ROUTE_SUCCESS = "enrollment:success"
ROUTE_TOKEN = "enrollment:token"
Expand Down Expand Up @@ -122,6 +119,25 @@ def index(request):
return TemplateResponse(request, eligibility.enrollment_index_template, context)


@decorator_from_middleware(EligibleSessionRequired)
def reenrollment_error(request):
"""View handler for a re-enrollment attempt that is not yet within the re-enrollment window."""
eligibility = session.eligibility(request)
verifier = session.verifier(request)

if eligibility.reenrollment_error_template is None:
raise Exception(f"Re-enrollment error with null template on: {eligibility.label}")

if session.logged_in(request) and verifier.auth_provider.supports_sign_out:
# overwrite origin for a logged in user
# if they click the logout button, they are taken to the new route
session.update(request, origin=reverse(ROUTE_LOGGED_OUT))

analytics.returned_error(request, "Re-enrollment error.")

return TemplateResponse(request, eligibility.reenrollment_error_template)


@decorator_from_middleware(EligibleSessionRequired)
def retry(request):
"""View handler for a recoverable failure condition."""
Expand Down
15 changes: 15 additions & 0 deletions benefits/locale/en/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,21 @@ msgstr ""
msgid "Enroll"
msgstr ""

msgid "Your CalFresh Cardholder transit benefit does not expire until"
msgstr ""

msgid "You can re-enroll for this benefit beginning on"
msgstr ""

msgid "Please try again then."
msgstr ""

msgid "Enrollment error"
msgstr ""

msgid "You are still enrolled in this benefit"
msgstr ""

msgid "Unable to register card"
msgstr ""

Expand Down
15 changes: 15 additions & 0 deletions benefits/locale/es/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,21 @@ msgstr "Espere por favor..."
msgid "Enroll"
msgstr "Inscribir"

msgid "Your CalFresh Cardholder transit benefit does not expire until"
msgstr ""

msgid "You can re-enroll for this benefit beginning on"
msgstr ""

msgid "Please try again then."
msgstr ""

msgid "Enrollment error"
msgstr ""

msgid "You are still enrolled in this benefit"
msgstr ""

msgid "Unable to register card"
msgstr "No se pudo registrar la tarjeta"

Expand Down
1 change: 1 addition & 0 deletions tests/pytest/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ def model_EligibilityType_supports_expiration(model_EligibilityType):
model_EligibilityType.supports_expiration = True
model_EligibilityType.expiration_days = 365
model_EligibilityType.expiration_reenrollment_days = 14
model_EligibilityType.reenrollment_error_template = "enrollment/reenrollment-error--calfresh.html"
model_EligibilityType.save()

return model_EligibilityType
Expand Down
13 changes: 13 additions & 0 deletions tests/pytest/core/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,19 @@ def test_EligibilityType_zero_expiration_reenrollment_days(model_EligibilityType
)


@pytest.mark.django_db
def test_EligibilityType_missing_reenrollment_template(model_EligibilityType_supports_expiration):
model_EligibilityType_supports_expiration.reenrollment_error_template = None
model_EligibilityType_supports_expiration.save()

with pytest.raises(ValidationError) as exception_info:
model_EligibilityType_supports_expiration.full_clean()

error_dict = exception_info.value.error_dict
assert len(error_dict["reenrollment_error_template"]) == 1
assert error_dict["reenrollment_error_template"][0].message == "Required when supports expiration is True."


@pytest.mark.django_db
def test_EligibilityType_supports_expiration(model_EligibilityType_supports_expiration):
# test will fail if any error is raised
Expand Down
43 changes: 37 additions & 6 deletions tests/pytest/enrollment/test_views.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
import time

import pytest
from django.urls import reverse

from littlepay.api.funding_sources import FundingSourceResponse
from requests import HTTPError
import pytest

import benefits.enrollment.views
from benefits.core.middleware import TEMPLATE_USER_ERROR
from benefits.core.views import ROUTE_LOGGED_OUT
from benefits.enrollment.views import (
ROUTE_INDEX,
ROUTE_TOKEN,
ROUTE_SUCCESS,
ROUTE_REENROLLMENT_ERROR,
ROUTE_RETRY,
ROUTE_SUCCESS,
ROUTE_TOKEN,
TEMPLATE_SUCCESS,
TEMPLATE_RETRY,
)

import benefits.enrollment.views


@pytest.fixture
def card_tokenize_form_data():
Expand Down Expand Up @@ -211,6 +210,38 @@ def test_index_ineligible(client):
assert response.template_name == TEMPLATE_USER_ERROR


@pytest.mark.django_db
def test_reenrollment_error_ineligible(client):
path = reverse(ROUTE_REENROLLMENT_ERROR)

response = client.get(path)

assert response.status_code == 200
assert response.template_name == TEMPLATE_USER_ERROR


@pytest.mark.django_db
@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility")
def test_reenrollment_error_eligibility_no_error_template(client):
path = reverse(ROUTE_REENROLLMENT_ERROR)

with pytest.raises(Exception, match="Re-enrollment error with null template"):
client.get(path)


@pytest.mark.django_db
@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier")
def test_reenrollment_error(client, model_EligibilityType_supports_expiration, mocked_session_eligibility):
mocked_session_eligibility.return_value = model_EligibilityType_supports_expiration

path = reverse(ROUTE_REENROLLMENT_ERROR)

response = client.get(path)

assert response.status_code == 200
assert response.template_name == model_EligibilityType_supports_expiration.reenrollment_error_template


@pytest.mark.django_db
def test_retry_ineligible(client):
path = reverse(ROUTE_RETRY)
Expand Down
Loading