From d27a53e995573b6213412f8f63711b5d102c3ebf Mon Sep 17 00:00:00 2001 From: Angela Tran Date: Fri, 21 Feb 2025 21:00:57 +0000 Subject: [PATCH] feat(migration): enforce that in-person flows have an eligibility policy data migration handles removing "in_person" from flows that do not have a policy in the `in_person.context.eligibility_index` dict. `EnrollmentFlow.clean` handles enforcing this rule going forward when creating or editing flows. --- .../0036_in_person_enrollmentflows.py | 23 +++++++++++++++++++ benefits/core/models/enrollment.py | 17 ++++++++++---- tests/pytest/core/models/test_enrollment.py | 10 ++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 benefits/core/migrations/0036_in_person_enrollmentflows.py diff --git a/benefits/core/migrations/0036_in_person_enrollmentflows.py b/benefits/core/migrations/0036_in_person_enrollmentflows.py new file mode 100644 index 000000000..b206a1f55 --- /dev/null +++ b/benefits/core/migrations/0036_in_person_enrollmentflows.py @@ -0,0 +1,23 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0035_enrollmentflow_system_name_choices"), + ] + + def migrate_in_person_flows(apps, schema_editor): + EnrollmentFlow = apps.get_model("core", "EnrollmentFlow") + for flow in EnrollmentFlow.objects.all(): + in_person = "in_person" # value of EnrollmentMethods.IN_PERSON as of this migration + if in_person in flow.supported_enrollment_methods: + if flow.system_name not in [ + "senior", + "medicare", + "courtesy_card", + ]: # the keys in `in_person.context.eligibility_index` as of this migration + flow.supported_enrollment_methods.remove(in_person) + flow.save() + + operations = [migrations.RunPython(migrate_in_person_flows)] diff --git a/benefits/core/models/enrollment.py b/benefits/core/models/enrollment.py index 69e627fdd..70ace89f8 100644 --- a/benefits/core/models/enrollment.py +++ b/benefits/core/models/enrollment.py @@ -263,7 +263,7 @@ def in_person_eligibility_context(self): return in_person_context.eligibility_index[self.system_name].dict() def clean(self): - template_errors = [] + errors = [] if self.transit_agency: templates = [ @@ -281,10 +281,19 @@ def clean(self): # so just create directly for a missing template for t in templates: if not template_path(t): - template_errors.append(ValidationError(f"Template not found: {t}")) + errors.append(ValidationError(f"Template not found: {t}")) - if template_errors: - raise ValidationError(template_errors) + if EnrollmentMethods.IN_PERSON in self.supported_enrollment_methods: + try: + in_person_eligibility_context = self.in_person_eligibility_context + except KeyError: + in_person_eligibility_context = None + + if not in_person_eligibility_context: + errors.append(ValidationError(f"In-person eligibility context not found for: {self.system_name}")) + + if errors: + raise ValidationError(errors) def eligibility_form_instance(self, *args, **kwargs): """Return an instance of this flow's EligibilityForm, or None.""" diff --git a/tests/pytest/core/models/test_enrollment.py b/tests/pytest/core/models/test_enrollment.py index 2ee3b5927..ef12d79c8 100644 --- a/tests/pytest/core/models/test_enrollment.py +++ b/tests/pytest/core/models/test_enrollment.py @@ -255,6 +255,16 @@ def test_EnrollmentFlow_clean_templates(model_EnrollmentFlow_with_scope_and_clai model_EnrollmentFlow_with_scope_and_claim.clean() +@pytest.mark.django_db +def test_EnrollmentFlow_clean_in_person_eligibility_context_not_found(model_EnrollmentFlow): + model_EnrollmentFlow.system_name = "nonexistent_system_name" + + with pytest.raises( + ValidationError, match=f"In-person eligibility context not found for: {model_EnrollmentFlow.system_name}" + ): + model_EnrollmentFlow.clean() + + @pytest.mark.django_db def test_EnrollmentEvent_create(model_TransitAgency, model_EnrollmentFlow): ts = timezone.now()