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: system enrollment error #2066

Closed
wants to merge 2 commits into from
Closed
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
6 changes: 6 additions & 0 deletions benefits/enrollment/templates/enrollment/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ <h1 class="pb-lg-8 pb-4">
$.ajax({ dataType: "script", attrs: { nonce: "{{ request.csp_nonce }}"}, url: "{{ card_tokenize_url }}" })
.done(function() {
$.get("{{ access_token_url }}", function(data) {
if (data.redirect) {
// https://stackoverflow.com/a/42469170
// use 'assign' because 'replace' was giving strange Back button behavior
window.location.assign(data.redirect);
}

$(".loading").remove();
// remove invisible and add back visible, so we aren't left with
// a div with an empty class attribute
Expand Down
Empty file.
1 change: 1 addition & 0 deletions benefits/enrollment/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
path("token", views.token, name="token"),
path("retry", views.retry, name="retry"),
path("success", views.success, name="success"),
path("system_error", views.system_error, name="system_error"),
]
28 changes: 26 additions & 2 deletions benefits/enrollment/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from django.utils.decorators import decorator_from_middleware
from littlepay.api.client import Client
from requests.exceptions import HTTPError
import sentry_sdk

from benefits.core import session
from benefits.core.middleware import (
Expand All @@ -24,10 +25,12 @@
ROUTE_INDEX = "enrollment:index"
ROUTE_RETRY = "enrollment:retry"
ROUTE_SUCCESS = "enrollment:success"
ROUTE_SYSTEM_ERROR = "enrollment:system_error"
ROUTE_TOKEN = "enrollment:token"

TEMPLATE_RETRY = "enrollment/retry.html"
TEMPLATE_SUCCESS = "enrollment/success.html"
TEMPLATE_SYSTEM_ERROR = "enrollment/system_error.html"


logger = logging.getLogger(__name__)
Expand All @@ -46,8 +49,20 @@ def token(request):
audience=payment_processor.audience,
)
client.oauth.ensure_active_token(client.token)
response = client.request_card_tokenization_access()
session.update(request, enrollment_token=response.get("access_token"), enrollment_token_exp=response.get("expires_at"))

try:
response = client.request_card_tokenization_access()
except Exception as e:
if isinstance(e, HTTPError) and e.response.status_code >= 500:
sentry_sdk.capture_exception(e)
data = {"redirect": reverse(ROUTE_SYSTEM_ERROR)}
return JsonResponse(data)
else:
raise e
else:
session.update(
request, enrollment_token=response.get("access_token"), enrollment_token_exp=response.get("expires_at")
)

data = {"token": session.enrollment_token(request)}

Expand Down Expand Up @@ -91,6 +106,9 @@ def index(request):
if e.response.status_code == 409:
analytics.returned_success(request, eligibility.group_id)
return success(request)
elif e.response.status_code >= 500:
analytics.returned_error(request, str(e))
return system_error(request)
else:
analytics.returned_error(request, str(e))
raise Exception(f"{e}: {e.response.json()}")
Expand Down Expand Up @@ -129,6 +147,12 @@ def retry(request):
return TemplateResponse(request, TEMPLATE_RETRY)


@decorator_from_middleware(EligibleSessionRequired)
def system_error(request):
"""View handler for an enrollment system error."""
return TemplateResponse(request, TEMPLATE_SYSTEM_ERROR)


@pageview_decorator
@decorator_from_middleware(VerifierSessionRequired)
def success(request):
Expand Down
55 changes: 53 additions & 2 deletions tests/pytest/enrollment/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@
ROUTE_INDEX,
ROUTE_TOKEN,
ROUTE_SUCCESS,
ROUTE_SYSTEM_ERROR,
ROUTE_RETRY,
TEMPLATE_SUCCESS,
TEMPLATE_SYSTEM_ERROR,
TEMPLATE_RETRY,
)

Expand Down Expand Up @@ -98,6 +100,31 @@ def test_token_valid(mocker, client):
assert data["token"] == "enrollment_token"


@pytest.mark.django_db
@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility")
def test_token_http_error_500(mocker, client):
mocker.patch("benefits.core.session.enrollment_token_valid", return_value=False)

mock_client_cls = mocker.patch("benefits.enrollment.views.Client")
mock_client = mock_client_cls.return_value

mock_error = {"message": "Mock error message"}
mock_error_response = mocker.Mock(status_code=500, **mock_error)
mock_error_response.json.return_value = mock_error
mock_client.request_card_tokenization_access.side_effect = HTTPError(
response=mock_error_response,
)

path = reverse(ROUTE_TOKEN)
response = client.get(path)

assert response.status_code == 200
data = response.json()
assert "token" not in data
assert "redirect" in data
assert data["redirect"] == reverse(ROUTE_SYSTEM_ERROR)


@pytest.mark.django_db
@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility")
def test_index_eligible_get(client, model_EligibilityType):
Expand Down Expand Up @@ -125,13 +152,37 @@ def test_index_eligible_post_invalid_form(client, invalid_form_data):
client.post(path, invalid_form_data)


@pytest.mark.django_db
@pytest.mark.parametrize("status_code", [500, 501, 502, 503, 504])
@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility")
def test_index_eligible_post_valid_form_http_error_500(
mocker, client, mocked_analytics_module, card_tokenize_form_data, status_code
):
mock_client_cls = mocker.patch("benefits.enrollment.views.Client")
mock_client = mock_client_cls.return_value

mock_error = {"message": "Mock error message"}
mock_error_response = mocker.Mock(status_code=status_code, **mock_error)
mock_error_response.json.return_value = mock_error
mock_client.link_concession_group_funding_source.side_effect = HTTPError(
response=mock_error_response,
)

path = reverse(ROUTE_INDEX)
response = client.post(path, card_tokenize_form_data)

assert response.status_code == 200
assert response.template_name == TEMPLATE_SYSTEM_ERROR
mocked_analytics_module.returned_error.assert_called_once()


@pytest.mark.django_db
@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_eligibility")
def test_index_eligible_post_valid_form_http_error(mocker, client, card_tokenize_form_data):
def test_index_eligible_post_valid_form_http_error_400(mocker, client, card_tokenize_form_data):
mock_client_cls = mocker.patch("benefits.enrollment.views.Client")
mock_client = mock_client_cls.return_value

# any status_code that isn't 409 is considered an error
# any 400 level status_code that isn't 409 is considered an error
mock_error = {"message": "Mock error message"}
mock_error_response = mocker.Mock(status_code=400, **mock_error)
mock_error_response.json.return_value = mock_error
Expand Down
Loading