From 352bca9724bc731161853eac68c4e466b63d834d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:06:17 +0000 Subject: [PATCH 01/14] chore(pre-commit): autoupdate hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/flake8: 7.0.0 → 7.1.0](https://github.com/PyCQA/flake8/compare/7.0.0...7.1.0) - [github.com/pycqa/bandit: 1.7.8 → 1.7.9](https://github.com/pycqa/bandit/compare/1.7.8...1.7.9) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7b4940767..5ee2343bb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,14 +41,14 @@ repos: - python - repo: https://github.com/PyCQA/flake8 - rev: 7.0.0 + rev: 7.1.0 hooks: - id: flake8 types: - python - repo: https://github.com/pycqa/bandit - rev: 1.7.8 + rev: 1.7.9 hooks: - id: bandit args: ["-ll"] From 3791eff85c40733b4bec3a10fd5f61ea8b5729b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:25:43 +0000 Subject: [PATCH 02/14] chore(deps): bump docker/build-push-action from 5 to 6 Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v5...v6) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 790c295a3..e71153d54 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -52,7 +52,7 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Build, tag, and push image to GitHub Container Registry - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: builder: ${{ steps.buildx.outputs.name }} build-args: GIT-SHA=${{ github.sha }} From 52c8e934359ab41fe310efeac8001fe504039a0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 21:27:15 +0000 Subject: [PATCH 03/14] chore(deps): bump mkdocs-material from 9.5.26 to 9.5.27 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.26 to 9.5.27. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.26...9.5.27) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 67c855732..6da6cbb1e 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,5 +2,5 @@ mdx_truly_sane_lists mkdocs==1.6.0 mkdocs-awesome-pages-plugin mkdocs-macros-plugin -mkdocs-material==9.5.26 +mkdocs-material==9.5.27 mkdocs-redirects From 20a7016c23224ac880e17b417730dec86d4611dd Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 13 Jun 2024 19:56:41 +0000 Subject: [PATCH 04/14] chore: Revert "Revert "Feat: enrollments can expire (#1989)" (#2052)" This reverts commit ea0dbeac2e7beff6edfee2a34b829dc54f75309a, reversing changes made to dd3bd7a2bf65662985149be7ad566cd31d5c2f98. --- benefits/enrollment/views.py | 107 ++++++- benefits/settings.py | 6 +- tests/pytest/conftest.py | 10 + tests/pytest/enrollment/test_views.py | 445 ++++++++++++++++++++++++-- 4 files changed, 536 insertions(+), 32 deletions(-) diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index 916d942fb..f95308646 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -3,10 +3,12 @@ """ import logging +from datetime import timedelta from django.http import JsonResponse from django.template.response import TemplateResponse from django.urls import reverse +from django.utils import timezone from django.utils.decorators import decorator_from_middleware from littlepay.api.client import Client from requests.exceptions import HTTPError @@ -92,7 +94,6 @@ def index(request): if not form.is_valid(): raise Exception("Invalid card token form") - logger.debug("Read tokenized card") card_token = form.cleaned_data.get("card_token") client = Client( @@ -104,17 +105,69 @@ def index(request): client.oauth.ensure_active_token(client.token) funding_source = client.get_funding_source_by_token(card_token) + group_id = eligibility.group_id try: - client.link_concession_group_funding_source(funding_source_id=funding_source.id, group_id=eligibility.group_id) + group_funding_source = _get_group_funding_source( + client=client, group_id=group_id, funding_source_id=funding_source.id + ) + + already_enrolled = group_funding_source is not None + + if eligibility.supports_expiration: + # set expiry on session + if already_enrolled and group_funding_source.expiry_date is not None: + session.update(request, enrollment_expiry=group_funding_source.expiry_date) + else: + session.update(request, enrollment_expiry=_calculate_expiry(eligibility.expiration_days)) + + if not already_enrolled: + # enroll user with an expiration date, return success + client.link_concession_group_funding_source( + group_id=group_id, funding_source_id=funding_source.id, expiry=session.enrollment_expiry(request) + ) + return success(request) + else: # already_enrolled + if group_funding_source.expiry_date is None: + # update expiration of existing enrollment, return success + client.update_concession_group_funding_source_expiry( + group_id=group_id, + funding_source_id=funding_source.id, + expiry=session.enrollment_expiry(request), + ) + return success(request) + else: + is_expired = _is_expired(group_funding_source.expiry_date) + is_within_reenrollment_window = _is_within_reenrollment_window( + group_funding_source.expiry_date, session.enrollment_reenrollment(request) + ) + + if is_expired or is_within_reenrollment_window: + # update expiration of existing enrollment, return success + client.update_concession_group_funding_source_expiry( + group_id=group_id, + funding_source_id=funding_source.id, + expiry=session.enrollment_expiry(request), + ) + return success(request) + else: + # re-enrollment error, return enrollment error with expiration and reenrollment_date + return reenrollment_error(request) + else: # eligibility does not support expiration + if not already_enrolled: + # enroll user with no expiration date, return success + client.link_concession_group_funding_source(group_id=group_id, funding_source_id=funding_source.id) + return success(request) + else: # already_enrolled + if group_funding_source.expiry_date is None: + # no action, return success + return success(request) + else: + # remove expiration date, return success + raise NotImplementedError("Removing expiration date is currently not supported") + except HTTPError as e: - # 409 means that customer already belongs to a concession group. - # the response JSON will look like: - # {"errors":[{"detail":"Conflict (409) - Customer already belongs to a concession group."}]} - if e.response.status_code == 409: - analytics.returned_success(request, eligibility.group_id) - return success(request) - elif e.response.status_code >= 500: + if e.response.status_code >= 500: analytics.returned_error(request, str(e)) sentry_sdk.capture_exception(e) @@ -125,9 +178,6 @@ def index(request): except Exception as e: analytics.returned_error(request, str(e)) raise e - else: - analytics.returned_success(request, eligibility.group_id) - return success(request) # GET enrollment index else: @@ -154,6 +204,37 @@ def index(request): return TemplateResponse(request, eligibility.enrollment_index_template, context) +def _get_group_funding_source(client: Client, group_id, funding_source_id): + group_funding_sources = client.get_concession_group_linked_funding_sources(group_id) + matching_group_funding_source = None + for group_funding_source in group_funding_sources: + if group_funding_source.id == funding_source_id: + matching_group_funding_source = group_funding_source + break + + return matching_group_funding_source + + +def _is_expired(expiry_date): + """Returns whether the passed in datetime is expired or not.""" + return expiry_date <= timezone.now() + + +def _is_within_reenrollment_window(expiry_date, enrollment_reenrollment_date): + """Returns if we are currently within the reenrollment window.""" + return enrollment_reenrollment_date <= timezone.now() < expiry_date + + +def _calculate_expiry(expiration_days): + """Returns the expiry datetime, which should be midnight in our configured timezone of the (N + 1)th day from now, + where N is expiration_days.""" + default_time_zone = timezone.get_default_timezone() + expiry_date = timezone.localtime(timezone=default_time_zone) + timedelta(days=expiration_days + 1) + expiry_datetime = expiry_date.replace(hour=0, minute=0, second=0, microsecond=0) + + return expiry_datetime + + @decorator_from_middleware(EligibleSessionRequired) def reenrollment_error(request): """View handler for a re-enrollment attempt that is not yet within the re-enrollment window.""" @@ -192,6 +273,7 @@ def system_error(request): @pageview_decorator +@decorator_from_middleware(EligibleSessionRequired) @decorator_from_middleware(VerifierSessionRequired) def success(request): """View handler for the final success page.""" @@ -206,4 +288,5 @@ def success(request): # if they click the logout button, they are taken to the new route session.update(request, origin=reverse(ROUTE_LOGGED_OUT)) + analytics.returned_success(request, eligibility.group_id) return TemplateResponse(request, eligibility.enrollment_success_template) diff --git a/benefits/settings.py b/benefits/settings.py index 2d72dc8d2..829eb3b12 100644 --- a/benefits/settings.py +++ b/benefits/settings.py @@ -204,7 +204,11 @@ def RUNTIME_ENVIRONMENT(): USE_I18N = True -TIME_ZONE = "UTC" +# See https://docs.djangoproject.com/en/5.0/ref/settings/#std-setting-TIME_ZONE +# > Note that this isn’t necessarily the time zone of the server. +# > When USE_TZ is True, this is the default time zone that Django will use to display datetimes in templates +# > and to interpret datetimes entered in forms. +TIME_ZONE = "America/Los_Angeles" USE_TZ = True # https://docs.djangoproject.com/en/5.0/topics/i18n/formatting/#creating-custom-format-files diff --git a/tests/pytest/conftest.py b/tests/pytest/conftest.py index 60e12ef7c..aaddb4ba3 100644 --- a/tests/pytest/conftest.py +++ b/tests/pytest/conftest.py @@ -1,6 +1,7 @@ from unittest.mock import create_autospec from django.contrib.sessions.middleware import SessionMiddleware from django.middleware.locale import LocaleMiddleware +from django.utils import timezone import pytest from pytest_socket import disable_socket @@ -261,6 +262,15 @@ def mocked_session_oauth_token(mocker): return mocker.patch("benefits.core.session.oauth_token", autospec=True, return_value="token") +@pytest.fixture +def mocked_session_enrollment_expiry(mocker): + return mocker.patch( + "benefits.core.session.enrollment_expiry", + autospec=True, + return_value=timezone.make_aware(timezone.datetime(2024, 1, 1), timezone=timezone.get_default_timezone()), + ) + + @pytest.fixture def mocked_session_verifier(mocker, model_EligibilityVerifier): return mocker.patch("benefits.core.session.verifier", autospec=True, return_value=model_EligibilityVerifier) diff --git a/tests/pytest/enrollment/test_views.py b/tests/pytest/enrollment/test_views.py index 625fb5d40..d93a92936 100644 --- a/tests/pytest/enrollment/test_views.py +++ b/tests/pytest/enrollment/test_views.py @@ -1,9 +1,13 @@ +from datetime import timedelta import time import pytest from authlib.integrations.base_client.errors import UnsupportedTokenTypeError from django.urls import reverse +from django.utils import timezone + from littlepay.api.funding_sources import FundingSourceResponse +from littlepay.api.groups import GroupFundingSourceResponse from requests import HTTPError import benefits.enrollment.views @@ -19,6 +23,10 @@ ROUTE_TOKEN, TEMPLATE_SYSTEM_ERROR, TEMPLATE_RETRY, + _get_group_funding_source, + _calculate_expiry, + _is_expired, + _is_within_reenrollment_window, ) @@ -58,6 +66,26 @@ def mocked_funding_source(): ) +@pytest.fixture +def mocked_group_funding_source_no_expiry(mocked_funding_source): + return GroupFundingSourceResponse( + id=mocked_funding_source.id, + created_date=None, + updated_date=None, + expiry_date=None, + ) + + +@pytest.fixture +def mocked_group_funding_source_with_expiry(mocked_funding_source): + return GroupFundingSourceResponse( + id=mocked_funding_source.id, + created_date="2023-01-01T00:00:00Z", + updated_date="2021-01-01T00:00:00Z", + expiry_date="2021-01-01T00:00:00Z", + ) + + @pytest.mark.django_db def test_token_ineligible(client): path = reverse(ROUTE_TOKEN) @@ -239,6 +267,7 @@ def test_index_eligible_post_valid_form_http_error_500( mocker, client, mocked_session_agency, + model_EligibilityType, mocked_analytics_module, mocked_sentry_sdk_module, card_tokenize_form_data, @@ -246,6 +275,7 @@ def test_index_eligible_post_valid_form_http_error_500( ): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.agency.return_value = mocked_session_agency.return_value + mock_session.eligibility.return_value = model_EligibilityType mock_client_cls = mocker.patch("benefits.enrollment.views.Client") mock_client = mock_client_cls.return_value @@ -273,7 +303,6 @@ def test_index_eligible_post_valid_form_http_error_400(mocker, client, card_toke mock_client_cls = mocker.patch("benefits.enrollment.views.Client") mock_client = mock_client_cls.return_value - # 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 @@ -299,33 +328,122 @@ def test_index_eligible_post_valid_form_failure(mocker, client, card_tokenize_fo client.post(path, card_tokenize_form_data) +@pytest.mark.django_db +@pytest.mark.usefixtures("model_EligibilityType") +def test_get_group_funding_sources_funding_source_not_enrolled_yet(mocker, mocked_funding_source): + mock_client = mocker.Mock() + mock_client.get_concession_group_linked_funding_sources.return_value = [] + + matching_group_funding_source = _get_group_funding_source(mock_client, "group123", mocked_funding_source.id) + + assert matching_group_funding_source is None + + +@pytest.mark.django_db +@pytest.mark.usefixtures("model_EligibilityType") +def test_get_group_funding_sources_funding_source_already_enrolled( + mocker, mocked_funding_source, mocked_group_funding_source_no_expiry +): + mock_client = mocker.Mock() + mock_client.get_concession_group_linked_funding_sources.return_value = [mocked_group_funding_source_no_expiry] + + matching_group_funding_source = _get_group_funding_source(mock_client, "group123", mocked_funding_source.id) + + assert matching_group_funding_source == mocked_group_funding_source_no_expiry + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") +def test_index_eligible_post_valid_form_success_does_not_support_expiration_customer_already_enrolled_no_expiry( + mocker, + client, + card_tokenize_form_data, + mocked_analytics_module, + model_EligibilityType_does_not_support_expiration, + mocked_funding_source, + mocked_group_funding_source_no_expiry, +): + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + mock_client.get_funding_source_by_token.return_value = mocked_funding_source + + mocker.patch("benefits.enrollment.views._get_group_funding_source", return_value=mocked_group_funding_source_no_expiry) + + path = reverse(ROUTE_INDEX) + response = client.post(path, card_tokenize_form_data) + + assert response.status_code == 200 + assert response.template_name == model_EligibilityType_does_not_support_expiration.enrollment_success_template + mocked_analytics_module.returned_success.assert_called_once() + assert ( + model_EligibilityType_does_not_support_expiration.group_id in mocked_analytics_module.returned_success.call_args.args + ) + + @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") -def test_index_eligible_post_valid_form_customer_already_enrolled( - mocker, client, card_tokenize_form_data, mocked_analytics_module, model_EligibilityType, mocked_funding_source +def test_index_eligible_post_valid_form_success_does_not_support_expiration_no_expiry( + mocker, + client, + card_tokenize_form_data, + mocked_analytics_module, + model_EligibilityType_does_not_support_expiration, + mocked_funding_source, ): mock_client_cls = mocker.patch("benefits.enrollment.views.Client") mock_client = mock_client_cls.return_value mock_client.get_funding_source_by_token.return_value = mocked_funding_source - mock_error_response = mocker.Mock(status_code=409) - 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) mock_client.link_concession_group_funding_source.assert_called_once_with( - funding_source_id=mocked_funding_source.id, group_id=model_EligibilityType.group_id + funding_source_id=mocked_funding_source.id, group_id=model_EligibilityType_does_not_support_expiration.group_id ) assert response.status_code == 200 - assert response.template_name == model_EligibilityType.enrollment_success_template + assert response.template_name == model_EligibilityType_does_not_support_expiration.enrollment_success_template mocked_analytics_module.returned_success.assert_called_once() - assert model_EligibilityType.group_id in mocked_analytics_module.returned_success.call_args.args + assert ( + model_EligibilityType_does_not_support_expiration.group_id in mocked_analytics_module.returned_success.call_args.args + ) + + +def test_calculate_expiry(): + expiration_days = 365 + + expiry_date = _calculate_expiry(expiration_days) + + assert expiry_date == ( + timezone.localtime(timezone=timezone.get_default_timezone()) + timedelta(days=expiration_days + 1) + ).replace(hour=0, minute=0, second=0, microsecond=0) + + +def test_calculate_expiry_specific_date(mocker): + expiration_days = 14 + mocker.patch( + "benefits.enrollment.views.timezone.now", + return_value=timezone.make_aware( + value=timezone.datetime(2024, 3, 1, 13, 37, 11, 5), timezone=timezone.get_fixed_timezone(offset=0) + ), + ) + + expiry_date = _calculate_expiry(expiration_days) + + assert expiry_date == timezone.make_aware( + value=timezone.datetime(2024, 3, 16, 0, 0, 0, 0), timezone=timezone.get_default_timezone() + ) @pytest.mark.django_db @pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") -def test_index_eligible_post_valid_form_success( - mocker, client, card_tokenize_form_data, mocked_analytics_module, model_EligibilityType, mocked_funding_source +def test_index_eligible_post_valid_form_success_supports_expiration( + mocker, + client, + card_tokenize_form_data, + mocked_analytics_module, + model_EligibilityType_supports_expiration, + mocked_funding_source, + mocked_session_enrollment_expiry, ): mock_client_cls = mocker.patch("benefits.enrollment.views.Client") mock_client = mock_client_cls.return_value @@ -335,12 +453,293 @@ def test_index_eligible_post_valid_form_success( response = client.post(path, card_tokenize_form_data) mock_client.link_concession_group_funding_source.assert_called_once_with( - funding_source_id=mocked_funding_source.id, group_id=model_EligibilityType.group_id + funding_source_id=mocked_funding_source.id, + group_id=model_EligibilityType_supports_expiration.group_id, + expiry=mocked_session_enrollment_expiry.return_value, ) assert response.status_code == 200 - assert response.template_name == model_EligibilityType.enrollment_success_template + assert response.template_name == model_EligibilityType_supports_expiration.enrollment_success_template mocked_analytics_module.returned_success.assert_called_once() - assert model_EligibilityType.group_id in mocked_analytics_module.returned_success.call_args.args + assert model_EligibilityType_supports_expiration.group_id in mocked_analytics_module.returned_success.call_args.args + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") +def test_index_eligible_post_valid_form_success_supports_expiration_no_expiry( + mocker, + client, + card_tokenize_form_data, + mocked_analytics_module, + model_EligibilityType_supports_expiration, + mocked_funding_source, + mocked_group_funding_source_no_expiry, + mocked_session_enrollment_expiry, +): + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + mock_client.get_funding_source_by_token.return_value = mocked_funding_source + + mocker.patch("benefits.enrollment.views._get_group_funding_source", return_value=mocked_group_funding_source_no_expiry) + + path = reverse(ROUTE_INDEX) + response = client.post(path, card_tokenize_form_data) + + mock_client.update_concession_group_funding_source_expiry.assert_called_once_with( + funding_source_id=mocked_funding_source.id, + group_id=model_EligibilityType_supports_expiration.group_id, + expiry=mocked_session_enrollment_expiry.return_value, + ) + assert response.status_code == 200 + assert response.template_name == model_EligibilityType_supports_expiration.enrollment_success_template + mocked_analytics_module.returned_success.assert_called_once() + assert model_EligibilityType_supports_expiration.group_id in mocked_analytics_module.returned_success.call_args.args + + +def test_is_expired_expiry_date_is_in_the_past(mocker): + expiry_date = timezone.make_aware(timezone.datetime(2023, 12, 31), timezone.get_default_timezone()) + + # mock datetime of "now" to be specific date for testing + mocker.patch( + "benefits.enrollment.views.timezone.now", + return_value=timezone.make_aware(timezone.datetime(2024, 1, 1, 10, 30), timezone.get_default_timezone()), + ) + + assert _is_expired(expiry_date) + + +def test_is_expired_expiry_date_is_in_the_future(mocker): + expiry_date = timezone.make_aware(timezone.datetime(2024, 1, 1, 17, 34), timezone.get_default_timezone()) + + # mock datetime of "now" to be specific date for testing + mocker.patch( + "benefits.enrollment.views.timezone.now", + return_value=timezone.make_aware(timezone.datetime(2024, 1, 1, 11, 5), timezone.get_default_timezone()), + ) + + assert not _is_expired(expiry_date) + + +def test_is_expired_expiry_date_equals_now(mocker): + expiry_date = timezone.make_aware(timezone.datetime(2024, 1, 1, 13, 37), timezone.get_default_timezone()) + + # mock datetime of "now" to be specific date for testing + mocker.patch( + "benefits.enrollment.views.timezone.now", + return_value=timezone.make_aware(timezone.datetime(2024, 1, 1, 13, 37), timezone.get_default_timezone()), + ) + + assert _is_expired(expiry_date) + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") +def test_index_eligible_post_valid_form_success_supports_expiration_is_expired( + mocker, + client, + card_tokenize_form_data, + mocked_analytics_module, + model_EligibilityType_supports_expiration, + mocked_funding_source, + mocked_group_funding_source_with_expiry, + mocked_session_enrollment_expiry, +): + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + mock_client.get_funding_source_by_token.return_value = mocked_funding_source + + # mock that a funding source already exists, doesn't matter what expiry_date is + mocker.patch("benefits.enrollment.views._get_group_funding_source", return_value=mocked_group_funding_source_with_expiry) + + mocker.patch("benefits.enrollment.views._is_expired", return_value=True) + + path = reverse(ROUTE_INDEX) + response = client.post(path, card_tokenize_form_data) + + mock_client.update_concession_group_funding_source_expiry.assert_called_once_with( + funding_source_id=mocked_funding_source.id, + group_id=model_EligibilityType_supports_expiration.group_id, + expiry=mocked_session_enrollment_expiry.return_value, + ) + assert response.status_code == 200 + assert response.template_name == model_EligibilityType_supports_expiration.enrollment_success_template + mocked_analytics_module.returned_success.assert_called_once() + assert model_EligibilityType_supports_expiration.group_id in mocked_analytics_module.returned_success.call_args.args + + +def test_is_within_enrollment_window_True(mocker): + enrollment_reenrollment_date = timezone.make_aware(timezone.datetime(2023, 2, 1), timezone=timezone.get_default_timezone()) + expiry_date = timezone.make_aware(timezone.datetime(2023, 3, 1), timezone=timezone.get_default_timezone()) + + # mock datetime of "now" to be specific date for testing + mocker.patch( + "benefits.enrollment.views.timezone.now", + return_value=timezone.make_aware(timezone.datetime(2023, 2, 15, 15, 30), timezone=timezone.get_default_timezone()), + ) + + is_within_reenrollment_window = _is_within_reenrollment_window(expiry_date, enrollment_reenrollment_date) + + assert is_within_reenrollment_window + + +def test_is_within_enrollment_window_before_window(mocker): + enrollment_reenrollment_date = timezone.make_aware(timezone.datetime(2023, 2, 1), timezone=timezone.get_default_timezone()) + expiry_date = timezone.make_aware(timezone.datetime(2023, 3, 1), timezone=timezone.get_default_timezone()) + + # mock datetime of "now" to be specific date for testing + mocker.patch( + "benefits.enrollment.views.timezone.now", + return_value=timezone.make_aware(timezone.datetime(2023, 1, 15, 15, 30), timezone=timezone.get_default_timezone()), + ) + + is_within_reenrollment_window = _is_within_reenrollment_window(expiry_date, enrollment_reenrollment_date) + + assert not is_within_reenrollment_window + + +def test_is_within_enrollment_window_after_window(mocker): + enrollment_reenrollment_date = timezone.make_aware(timezone.datetime(2023, 2, 1), timezone=timezone.get_default_timezone()) + expiry_date = timezone.make_aware(timezone.datetime(2023, 3, 1), timezone=timezone.get_default_timezone()) + + # mock datetime of "now" to be specific date for testing + mocker.patch( + "benefits.enrollment.views.timezone.now", + return_value=timezone.make_aware(timezone.datetime(2023, 3, 15, 15, 30), timezone=timezone.get_default_timezone()), + ) + + is_within_reenrollment_window = _is_within_reenrollment_window(expiry_date, enrollment_reenrollment_date) + + assert not is_within_reenrollment_window + + +def test_is_within_enrollment_window_equal_reenrollment_date(mocker): + enrollment_reenrollment_date = timezone.make_aware(timezone.datetime(2023, 2, 1), timezone=timezone.get_default_timezone()) + expiry_date = timezone.make_aware(timezone.datetime(2023, 3, 1), timezone=timezone.get_default_timezone()) + + # mock datetime of "now" to be specific date for testing + mocker.patch( + "benefits.enrollment.views.timezone.now", + return_value=enrollment_reenrollment_date, + ) + + is_within_reenrollment_window = _is_within_reenrollment_window(expiry_date, enrollment_reenrollment_date) + + assert is_within_reenrollment_window + + +def test_is_within_enrollment_window_equal_expiry_date(mocker): + enrollment_reenrollment_date = timezone.make_aware(timezone.datetime(2023, 2, 1), timezone=timezone.get_default_timezone()) + expiry_date = timezone.make_aware(timezone.datetime(2023, 3, 1), timezone=timezone.get_default_timezone()) + + # mock datetime of "now" to be specific date for testing + mocker.patch( + "benefits.enrollment.views.timezone.now", + return_value=expiry_date, + ) + + is_within_reenrollment_window = _is_within_reenrollment_window(expiry_date, enrollment_reenrollment_date) + + assert not is_within_reenrollment_window + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") +def test_index_eligible_post_valid_form_success_supports_expiration_is_within_reenrollment_window( + mocker, + client, + card_tokenize_form_data, + mocked_analytics_module, + model_EligibilityType_supports_expiration, + mocked_funding_source, + mocked_group_funding_source_with_expiry, + mocked_session_enrollment_expiry, +): + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + mock_client.get_funding_source_by_token.return_value = mocked_funding_source + + # mock that a funding source already exists, doesn't matter what expiry_date is + mocker.patch("benefits.enrollment.views._get_group_funding_source", return_value=mocked_group_funding_source_with_expiry) + + mocker.patch("benefits.enrollment.views._is_within_reenrollment_window", return_value=True) + + path = reverse(ROUTE_INDEX) + response = client.post(path, card_tokenize_form_data) + + mock_client.update_concession_group_funding_source_expiry.assert_called_once_with( + funding_source_id=mocked_funding_source.id, + group_id=model_EligibilityType_supports_expiration.group_id, + expiry=mocked_session_enrollment_expiry.return_value, + ) + assert response.status_code == 200 + assert response.template_name == model_EligibilityType_supports_expiration.enrollment_success_template + mocked_analytics_module.returned_success.assert_called_once() + assert model_EligibilityType_supports_expiration.group_id in mocked_analytics_module.returned_success.call_args.args + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") +def test_index_eligible_post_valid_form_success_supports_expiration_is_not_expired_yet( + mocker, + client, + card_tokenize_form_data, + mocked_analytics_module, + mocked_funding_source, + mocked_group_funding_source_with_expiry, + model_EligibilityType_supports_expiration, +): + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + mock_client.get_funding_source_by_token.return_value = mocked_funding_source + + # mock that a funding source already exists, doesn't matter what expiry_date is + mocker.patch("benefits.enrollment.views._get_group_funding_source", return_value=mocked_group_funding_source_with_expiry) + + mocker.patch("benefits.enrollment.views._is_expired", return_value=False) + mocker.patch("benefits.enrollment.views._is_within_reenrollment_window", return_value=False) + + path = reverse(ROUTE_INDEX) + response = client.post(path, card_tokenize_form_data) + + assert response.status_code == 200 + assert response.template_name == model_EligibilityType_supports_expiration.reenrollment_error_template + mocked_analytics_module.returned_error.assert_called_once() + + +@pytest.mark.django_db +@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier", "mocked_session_eligibility") +def test_index_eligible_post_valid_form_success_does_not_support_expiration_has_expiration_date( + mocker, + client, + card_tokenize_form_data, + mocked_analytics_module, + model_EligibilityType_does_not_support_expiration, + mocked_funding_source, + mocked_group_funding_source_with_expiry, +): + mock_client_cls = mocker.patch("benefits.enrollment.views.Client") + mock_client = mock_client_cls.return_value + mock_client.get_funding_source_by_token.return_value = mocked_funding_source + + # mock that a funding source already exists, doesn't matter what expiry_date is + mocker.patch("benefits.enrollment.views._get_group_funding_source", return_value=mocked_group_funding_source_with_expiry) + + path = reverse(ROUTE_INDEX) + with pytest.raises(NotImplementedError): + client.post(path, card_tokenize_form_data) + + # this is what we would assert if removing expiration were supported + # + # mock_client.link_concession_group_funding_source.assert_called_once_with( + # funding_source_id=mocked_funding_source.id, + # group_id=model_EligibilityType_does_not_support_expiration.group_id, + # expiry_date=None, + # ) + # assert response.status_code == 200 + # assert response.template_name == model_EligibilityType_does_not_support_expiration.enrollment_success_template + # mocked_analytics_module.returned_success.assert_called_once() + # assert ( + # model_EligibilityType_does_not_support_expiration.group_id in mocked_analytics_module.returned_success.call_args.args + # ) @pytest.mark.django_db @@ -428,8 +827,8 @@ def test_success_no_verifier(client): @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") -def test_success_authentication_logged_in(mocker, client, model_TransitAgency, model_EligibilityType): +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification", "mocked_session_eligibility") +def test_success_authentication_logged_in(mocker, client, model_TransitAgency, model_EligibilityType, mocked_analytics_module): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.logged_in.return_value = True mock_session.agency.return_value = model_TransitAgency @@ -441,11 +840,14 @@ def test_success_authentication_logged_in(mocker, client, model_TransitAgency, m assert response.status_code == 200 assert response.template_name == model_EligibilityType.enrollment_success_template assert {"origin": reverse(ROUTE_LOGGED_OUT)} in mock_session.update.call_args + mocked_analytics_module.returned_success.assert_called_once() @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification") -def test_success_authentication_not_logged_in(mocker, client, model_TransitAgency, model_EligibilityType): +@pytest.mark.usefixtures("mocked_session_verifier_uses_auth_verification", "mocked_session_eligibility") +def test_success_authentication_not_logged_in( + mocker, client, model_TransitAgency, model_EligibilityType, mocked_analytics_module +): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.logged_in.return_value = False mock_session.agency.return_value = model_TransitAgency @@ -456,15 +858,20 @@ def test_success_authentication_not_logged_in(mocker, client, model_TransitAgenc assert response.status_code == 200 assert response.template_name == model_EligibilityType.enrollment_success_template + mocked_analytics_module.returned_success.assert_called_once() @pytest.mark.django_db -@pytest.mark.usefixtures("mocked_session_agency", "mocked_session_verifier_does_not_use_auth_verification") -def test_success_no_authentication(mocker, client, model_EligibilityType): +@pytest.mark.usefixtures( + "mocked_session_agency", "mocked_session_verifier_does_not_use_auth_verification", "mocked_session_eligibility" +) +def test_success_no_authentication(mocker, client, model_EligibilityType, mocked_analytics_module): mock_session = mocker.patch("benefits.enrollment.views.session") mock_session.eligibility.return_value = model_EligibilityType + path = reverse(ROUTE_SUCCESS) response = client.get(path) assert response.status_code == 200 assert response.template_name == model_EligibilityType.enrollment_success_template + mocked_analytics_module.returned_success.assert_called_once() From 50579680ece6b569a41907fac3e5d4971cb30987 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 13 Jun 2024 23:16:05 +0000 Subject: [PATCH 05/14] chore: update sample fixtures to show expiry example --- benefits/core/migrations/local_fixtures.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/benefits/core/migrations/local_fixtures.json b/benefits/core/migrations/local_fixtures.json index c66dfd8b2..a6910b280 100644 --- a/benefits/core/migrations/local_fixtures.json +++ b/benefits/core/migrations/local_fixtures.json @@ -131,7 +131,12 @@ "fields": { "name": "calfresh", "label": "CalFresh", - "group_id": "group123" + "group_id": "group123", + "supports_expiration": "True", + "expiration_days": 5, + "expiration_reenrollment_days": 3, + "reenrollment_error_template": "enrollment/reenrollment-error--calfresh.html", + "enrollment_success_template": "enrollment/success--mst.html" } }, { From 6088407c8833fdd271e5d5140932c9907fe3ec70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 21:46:29 +0000 Subject: [PATCH 06/14] chore(deps-dev): bump cypress from 13.11.0 to 13.12.0 in /tests/cypress Bumps [cypress](https://github.com/cypress-io/cypress) from 13.11.0 to 13.12.0. - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v13.11.0...v13.12.0) --- updated-dependencies: - dependency-name: cypress dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- tests/cypress/package-lock.json | 14 +++++++------- tests/cypress/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/cypress/package-lock.json b/tests/cypress/package-lock.json index 90de5d31c..b9ff5ca92 100644 --- a/tests/cypress/package-lock.json +++ b/tests/cypress/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "AGPL-3.0-or-later", "devDependencies": { - "cypress": "^13.11.0" + "cypress": "^13.12.0" } }, "node_modules/@colors/colors": { @@ -537,9 +537,9 @@ } }, "node_modules/cypress": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.11.0.tgz", - "integrity": "sha512-NXXogbAxVlVje4XHX+Cx5eMFZv4Dho/2rIcdBHg9CNPFUGZdM4cRdgIgM7USmNYsC12XY0bZENEQ+KBk72fl+A==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", + "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2358,9 +2358,9 @@ } }, "cypress": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.11.0.tgz", - "integrity": "sha512-NXXogbAxVlVje4XHX+Cx5eMFZv4Dho/2rIcdBHg9CNPFUGZdM4cRdgIgM7USmNYsC12XY0bZENEQ+KBk72fl+A==", + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", + "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", "dev": true, "requires": { "@cypress/request": "^3.0.0", diff --git a/tests/cypress/package.json b/tests/cypress/package.json index 254c8c948..5483da928 100644 --- a/tests/cypress/package.json +++ b/tests/cypress/package.json @@ -12,6 +12,6 @@ "license": "AGPL-3.0-or-later", "private": true, "devDependencies": { - "cypress": "^13.11.0" + "cypress": "^13.12.0" } } From fa8bd677e766b79b6bf4a5f0ab65a9b087a88b43 Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Thu, 20 Jun 2024 17:56:26 +0000 Subject: [PATCH 07/14] refactor(templates): consolidate login.gov media item into one template --- ...-item--idcardcheck--start--login-gov.html} | 0 ...edia-item--idcardcheck--start--senior.html | 27 ------------------- ...dia-item--idcardcheck--start--veteran.html | 27 ------------------- .../eligibility/start--calfresh.html | 2 +- .../templates/eligibility/start--senior.html | 2 +- .../templates/eligibility/start--veteran.html | 2 +- 6 files changed, 3 insertions(+), 57 deletions(-) rename benefits/eligibility/templates/eligibility/includes/{media-item--idcardcheck--start--calfresh.html => media-item--idcardcheck--start--login-gov.html} (100%) delete mode 100644 benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--senior.html delete mode 100644 benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--veteran.html diff --git a/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--calfresh.html b/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--login-gov.html similarity index 100% rename from benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--calfresh.html rename to benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--login-gov.html diff --git a/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--senior.html b/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--senior.html deleted file mode 100644 index 635c397de..000000000 --- a/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--senior.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "eligibility/includes/media-item--idcardcheck.html" %} - -{% load i18n %} - -{% block heading %} - {% translate "A Login.gov account with identity verification" %} -{% endblock heading %} - -{% block body %} -
-

- {% translate "You will be able to create an account using your email address if you do not already have one. We use your Login.gov account to verify your identity." %} - {% translate "Learn more about identity verification" as text %} - {% include "core/includes/modal-trigger.html" with modal="modal--identity-verification" text=text period=True %} -

-
-

{% translate "For this process you will need:" %}

-
    -
  • {% translate "Your state-issued ID card" %}
  • -
  • {% translate "Your Social Security number" %}
  • -
  • {% translate "A phone number with a phone plan associated with your name" %}
  • -
-
-
- - {% include "eligibility/includes/modal--login-gov-start-help.html" with size="modal-lg" id="modal--identity-verification" header="p-md-2 p-3" body="pb-md-3 mb-md-3 mx-md-3 py-0 pt-0 absolute-top" %} -{% endblock body %} diff --git a/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--veteran.html b/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--veteran.html deleted file mode 100644 index 635c397de..000000000 --- a/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--veteran.html +++ /dev/null @@ -1,27 +0,0 @@ -{% extends "eligibility/includes/media-item--idcardcheck.html" %} - -{% load i18n %} - -{% block heading %} - {% translate "A Login.gov account with identity verification" %} -{% endblock heading %} - -{% block body %} -
-

- {% translate "You will be able to create an account using your email address if you do not already have one. We use your Login.gov account to verify your identity." %} - {% translate "Learn more about identity verification" as text %} - {% include "core/includes/modal-trigger.html" with modal="modal--identity-verification" text=text period=True %} -

-
-

{% translate "For this process you will need:" %}

-
    -
  • {% translate "Your state-issued ID card" %}
  • -
  • {% translate "Your Social Security number" %}
  • -
  • {% translate "A phone number with a phone plan associated with your name" %}
  • -
-
-
- - {% include "eligibility/includes/modal--login-gov-start-help.html" with size="modal-lg" id="modal--identity-verification" header="p-md-2 p-3" body="pb-md-3 mb-md-3 mx-md-3 py-0 pt-0 absolute-top" %} -{% endblock body %} diff --git a/benefits/eligibility/templates/eligibility/start--calfresh.html b/benefits/eligibility/templates/eligibility/start--calfresh.html index f82468b6c..d968f9295 100644 --- a/benefits/eligibility/templates/eligibility/start--calfresh.html +++ b/benefits/eligibility/templates/eligibility/start--calfresh.html @@ -12,7 +12,7 @@

{% translate "You selected a CalFresh Cardholder transit benefit." %}

{% endblock headline %} {% block media-item %} - {% include "eligibility/includes/media-item--idcardcheck--start--calfresh.html" %} + {% include "eligibility/includes/media-item--idcardcheck--start--login-gov.html" %} {% endblock media-item %} {% block call-to-action %} diff --git a/benefits/eligibility/templates/eligibility/start--senior.html b/benefits/eligibility/templates/eligibility/start--senior.html index 485ec8905..a1e2ce765 100644 --- a/benefits/eligibility/templates/eligibility/start--senior.html +++ b/benefits/eligibility/templates/eligibility/start--senior.html @@ -12,7 +12,7 @@

{% translate "You selected an Older Adult transit benefit." %}

{% endblock headline %} {% block media-item %} - {% include "eligibility/includes/media-item--idcardcheck--start--senior.html" %} + {% include "eligibility/includes/media-item--idcardcheck--start--login-gov.html" %} {% endblock media-item %} {% block call-to-action %} diff --git a/benefits/eligibility/templates/eligibility/start--veteran.html b/benefits/eligibility/templates/eligibility/start--veteran.html index 0ad69d6b4..f398aa4de 100644 --- a/benefits/eligibility/templates/eligibility/start--veteran.html +++ b/benefits/eligibility/templates/eligibility/start--veteran.html @@ -12,7 +12,7 @@

{% translate "You selected a Veteran transit benefit." %}

{% endblock headline %} {% block media-item %} - {% include "eligibility/includes/media-item--idcardcheck--start--veteran.html" %} + {% include "eligibility/includes/media-item--idcardcheck--start--login-gov.html" %} {% endblock media-item %} {% block call-to-action %} From 7a18294119fe66f9856ba981b7ba1a9f78e649e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 21:54:56 +0000 Subject: [PATCH 08/14] chore(deps): bump treosh/lighthouse-ci-action from 11.4.0 to 12.1.0 Bumps [treosh/lighthouse-ci-action](https://github.com/treosh/lighthouse-ci-action) from 11.4.0 to 12.1.0. - [Release notes](https://github.com/treosh/lighthouse-ci-action/releases) - [Commits](https://github.com/treosh/lighthouse-ci-action/compare/11.4.0...12.1.0) --- updated-dependencies: - dependency-name: treosh/lighthouse-ci-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/tests-ui.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-ui.yml b/.github/workflows/tests-ui.yml index f7020e1be..409c9e8c1 100644 --- a/.github/workflows/tests-ui.yml +++ b/.github/workflows/tests-ui.yml @@ -22,7 +22,7 @@ jobs: docker compose up --detach client - name: Run Lighthouse tests for a11y - uses: treosh/lighthouse-ci-action@11.4.0 + uses: treosh/lighthouse-ci-action@12.1.0 with: urls: | http://localhost:8000 From 64e424857b581a575790e82de7aa3079597dd492 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Thu, 20 Jun 2024 16:50:14 +0000 Subject: [PATCH 09/14] fix(enrollment): fix changing languages on enrollment success --- benefits/core/templates/core/includes/lang-selector.html | 1 + benefits/enrollment/views.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/benefits/core/templates/core/includes/lang-selector.html b/benefits/core/templates/core/includes/lang-selector.html index b652d2e39..82fc08b07 100644 --- a/benefits/core/templates/core/includes/lang-selector.html +++ b/benefits/core/templates/core/includes/lang-selector.html @@ -14,6 +14,7 @@
{% csrf_token %} {% get_language_info for code as langinfo %} +
diff --git a/benefits/enrollment/views.py b/benefits/enrollment/views.py index f95308646..77a0a7a71 100644 --- a/benefits/enrollment/views.py +++ b/benefits/enrollment/views.py @@ -289,4 +289,5 @@ def success(request): session.update(request, origin=reverse(ROUTE_LOGGED_OUT)) analytics.returned_success(request, eligibility.group_id) - return TemplateResponse(request, eligibility.enrollment_success_template) + context = {"redirect_to": request.path} + return TemplateResponse(request, eligibility.enrollment_success_template, context) From 3f1296ca72dda66154541c6de6fd0237bae43ae3 Mon Sep 17 00:00:00 2001 From: Luis Alvergue Date: Tue, 25 Jun 2024 16:25:41 +0000 Subject: [PATCH 10/14] fix(oauth): fix client not registered exception in logout --- benefits/oauth/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benefits/oauth/views.py b/benefits/oauth/views.py index 9ce8297bf..678fd08ab 100644 --- a/benefits/oauth/views.py +++ b/benefits/oauth/views.py @@ -101,7 +101,7 @@ def cancel(request): def logout(request): """View implementing OIDC and application sign out.""" verifier = session.verifier(request) - oauth_client = oauth.create_client(verifier.auth_provider.client_name) + oauth_client = create_client(oauth, verifier.auth_provider) if not oauth_client: raise Exception(f"oauth_client not registered: {verifier.auth_provider.client_name}") From 0391d63ebf1781f0d8009da733b7b671f69f0cec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:43:54 +0000 Subject: [PATCH 11/14] chore(deps): bump calitp-littlepay from 2024.6.1 to 2024.7.1 Bumps [calitp-littlepay](https://github.com/cal-itp/littlepay) from 2024.6.1 to 2024.7.1. - [Release notes](https://github.com/cal-itp/littlepay/releases) - [Commits](https://github.com/cal-itp/littlepay/compare/2024.06.1...2024.07.1) --- updated-dependencies: - dependency-name: calitp-littlepay dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e8bff0969..da5eac6c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ dependencies = [ "django-admin-sortable2==2.2.1", "django-google-sso==6.2.1", "eligibility-api==2023.9.1", - "calitp-littlepay==2024.6.1", + "calitp-littlepay==2024.7.1", "requests==2.32.3", "sentry-sdk==2.5.1", "six==1.16.0", From 186cfb1c93ea7927bf5ba8c17c69a48ea77e377d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 21:43:20 +0000 Subject: [PATCH 12/14] chore(deps): bump azure-identity from 1.16.1 to 1.17.1 Bumps [azure-identity](https://github.com/Azure/azure-sdk-for-python) from 1.16.1 to 1.17.1. - [Release notes](https://github.com/Azure/azure-sdk-for-python/releases) - [Changelog](https://github.com/Azure/azure-sdk-for-python/blob/main/doc/esrp_release.md) - [Commits](https://github.com/Azure/azure-sdk-for-python/compare/azure-identity_1.16.1...azure-identity_1.17.1) --- updated-dependencies: - dependency-name: azure-identity dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e8bff0969..197d2e597 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ requires-python = ">=3.9" dependencies = [ "Authlib==1.3.1", "azure-keyvault-secrets==4.8.0", - "azure-identity==1.16.1", + "azure-identity==1.17.1", "Django==5.0.6", "django-csp==3.8", "django-admin-sortable2==2.2.1", From 8e6b9280890e5250cac20b9ab6d5d33e7a663a02 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 16:51:09 +0000 Subject: [PATCH 13/14] chore(deps): bump sentry-sdk from 2.5.1 to 2.7.1 Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 2.5.1 to 2.7.1. - [Release notes](https://github.com/getsentry/sentry-python/releases) - [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-python/compare/2.5.1...2.7.1) --- updated-dependencies: - dependency-name: sentry-sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 050e14d6b..2f625f76e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "eligibility-api==2023.9.1", "calitp-littlepay==2024.7.1", "requests==2.32.3", - "sentry-sdk==2.5.1", + "sentry-sdk==2.7.1", "six==1.16.0", ] From 7d193dfa60398dacb0d9097ba84ced7a37303cbf Mon Sep 17 00:00:00 2001 From: Kegan Maher Date: Wed, 3 Jul 2024 09:47:25 -0700 Subject: [PATCH 14/14] chore(pyproject): bump version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2f625f76e..a2c673360 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "benefits" -version = "2024.06.1" +version = "2024.07.1" description = "Cal-ITP Benefits is an application that enables automated eligibility verification and enrollment for transit benefits onto customers’ existing contactless bank (credit/debit) cards." readme = "README.md" license = { file = "LICENSE" }