From 36161081076de97d59a37d656a061d8611910bd5 Mon Sep 17 00:00:00 2001 From: Troy Sankey Date: Wed, 8 Jan 2025 13:16:26 -0800 Subject: [PATCH] fix: add data object serialization support for reversal writing At some point during initial development I assumed the serialized data objects from openedx_events supported dict-like access patterns. I was wrong. This commit generalizes access patterns to support both dicts and objects so that we can continue to support both event-driven and cron-driven reversal writing. --- .../apps/transaction/signals/handlers.py | 16 +++---- .../transaction/tests/test_signal_handlers.py | 47 ++++++++++++++----- enterprise_subsidy/apps/transaction/utils.py | 9 ++-- 3 files changed, 49 insertions(+), 23 deletions(-) diff --git a/enterprise_subsidy/apps/transaction/signals/handlers.py b/enterprise_subsidy/apps/transaction/signals/handlers.py index a51a175..65f3eff 100644 --- a/enterprise_subsidy/apps/transaction/signals/handlers.py +++ b/enterprise_subsidy/apps/transaction/signals/handlers.py @@ -104,14 +104,14 @@ def handle_lc_enrollment_revoked(**kwargs): ) return revoked_enrollment_data = kwargs.get('learner_credit_course_enrollment') - fulfillment_uuid = revoked_enrollment_data.get("uuid") - enterprise_course_enrollment = revoked_enrollment_data.get("enterprise_course_enrollment") - enrollment_course_run_key = enterprise_course_enrollment.get("course_id") - enrollment_unenrolled_at = enterprise_course_enrollment.get("unenrolled_at") + fulfillment_uuid = revoked_enrollment_data.uuid + enterprise_course_enrollment = revoked_enrollment_data.enterprise_course_enrollment + enrollment_course_run_key = enterprise_course_enrollment.course_id + enrollment_unenrolled_at = enterprise_course_enrollment.unenrolled_at # Look for a transaction related to the unenrollment related_transaction = Transaction.objects.filter( - uuid=revoked_enrollment_data.get('transaction_id') + uuid=revoked_enrollment_data.transaction_id ).first() if not related_transaction: logger.info( @@ -151,16 +151,16 @@ def handle_lc_enrollment_revoked(**kwargs): ) # Check if the OCM unenrollment is refundable - if not unenrollment_can_be_refunded(content_metadata, enterprise_course_enrollment): + if not unenrollment_can_be_refunded(content_metadata, enterprise_course_enrollment.__dict__): logger.info( f"Unenrollment from course: {enrollment_course_run_key} by user: " - f"{enterprise_course_enrollment.get('enterprise_customer_user')} is not refundable." + f"{enterprise_course_enrollment.enterprise_customer_user} is not refundable." ) return logger.info( f"Course run: {enrollment_course_run_key} is refundable for enterprise " - f"customer user: {enterprise_course_enrollment.get('enterprise_customer_user')}. Writing " + f"customer user: {enterprise_course_enrollment.enterprise_customer_user}. Writing " "Reversal record." ) diff --git a/enterprise_subsidy/apps/transaction/tests/test_signal_handlers.py b/enterprise_subsidy/apps/transaction/tests/test_signal_handlers.py index b2fe6a6..d4159cd 100644 --- a/enterprise_subsidy/apps/transaction/tests/test_signal_handlers.py +++ b/enterprise_subsidy/apps/transaction/tests/test_signal_handlers.py @@ -10,6 +10,11 @@ import pytest from django.test import TestCase from django.test.utils import override_settings +from openedx_events.enterprise.data import ( + EnterpriseCourseEnrollment, + EnterpriseCustomerUser, + LearnerCreditEnterpriseCourseEnrollment +) from openedx_ledger.models import TransactionStateChoices from openedx_ledger.signals.signals import TRANSACTION_REVERSED from openedx_ledger.test_utils.factories import ( @@ -163,17 +168,37 @@ def test_handle_lc_enrollment_revoked( quantity=-transaction.quantity, ) enrollment_unenrolled_at = datetime(2020, 1, 1) - test_lc_course_enrollment = { - "uuid": uuid4(), - "transaction_id": transaction.uuid if transaction else uuid4(), - "enterprise_course_enrollment": { - "course_id": "course-v1:bin+bar+baz", - "unenrolled_at": enrollment_unenrolled_at, - "enterprise_customer_user": { - "unused": "unused", - }, - } - } + test_lc_course_enrollment = LearnerCreditEnterpriseCourseEnrollment( + uuid=uuid4(), + created=datetime(2020, 1, 1, 12, 0), + modified=datetime(2020, 1, 1, 12, 0), + fulfillment_type="learner_credit", + is_revoked=True, + enterprise_course_entitlement_uuid=None, + transaction_id=(transaction.uuid if transaction else uuid4()), + enterprise_course_enrollment=EnterpriseCourseEnrollment( + id=1, + created=datetime(2020, 1, 1, 12, 0), + modified=datetime(2020, 1, 1, 12, 0), + course_id="course-v1:bin+bar+baz", + saved_for_later=False, + source_slug=None, + unenrolled=True, + unenrolled_at=enrollment_unenrolled_at, + enterprise_customer_user=EnterpriseCustomerUser( + id=1, + created=datetime(2020, 1, 1, 12, 0), + modified=datetime(2020, 1, 1, 12, 0), + enterprise_customer_uuid=uuid4(), + user_id=1, + active=True, + linked=True, + is_relinkable=True, + should_inactivate_other_customers=True, + invite_key=None, + ), + ), + ) with self.assertLogs(level='INFO') as logs: handle_lc_enrollment_revoked(learner_credit_course_enrollment=test_lc_course_enrollment) if expected_log_regex: diff --git a/enterprise_subsidy/apps/transaction/utils.py b/enterprise_subsidy/apps/transaction/utils.py index 8faeeac..7ff10b7 100644 --- a/enterprise_subsidy/apps/transaction/utils.py +++ b/enterprise_subsidy/apps/transaction/utils.py @@ -60,10 +60,11 @@ def unenrollment_can_be_refunded( Args: content_metadata (dict): Metadata for course from which the learner has been unenrolled. - enterprise_course_enrollment: (dict-like): - Serialized ECE object. This supports two possible serialization formats: - 1. Serialized via the DRF when calling the unenrolled API. - 2. Serialized via openedx-events when edx-enterprise emits a learner credit lifecycle event. + enterprise_course_enrollment: (dict): + Serialized ECE object. If the caller has an instance of + openedx_events.enterprise.data.EnterpriseCourseEnrollment, coerce to + data object first: + ece_record.__dict__ """ # Retrieve the course start date from the content metadata