diff --git a/RELEASE.rst b/RELEASE.rst index 8d11216825..88e0c6f795 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,11 @@ Release Notes ============= +Version 0.70.6 +-------------- + +- feat: force all enrollments (#1761) + Version 0.70.5 (Released August 08, 2023) -------------- diff --git a/courses/api.py b/courses/api.py index 08112fa431..23756b560c 100644 --- a/courses/api.py +++ b/courses/api.py @@ -52,8 +52,8 @@ ) from openedx.constants import ( EDX_DEFAULT_ENROLLMENT_MODE, - EDX_ENROLLMENT_VERIFIED_MODE, EDX_ENROLLMENT_AUDIT_MODE, + EDX_ENROLLMENT_VERIFIED_MODE, ) from openedx.exceptions import ( EdxApiEnrollErrorException, @@ -219,7 +219,6 @@ def create_run_enrollments( *, keep_failed_enrollments=False, mode=EDX_DEFAULT_ENROLLMENT_MODE, - force_enrollment=False, ): """ Creates local records of a user's enrollment in course runs, and attempts to enroll them @@ -230,8 +229,7 @@ def create_run_enrollments( runs (iterable of CourseRun): The course runs to enroll in keep_failed_enrollments: (boolean): If True, keeps the local enrollment record in the database even if the enrollment fails in edX. - force_enrollment (bool): If True, Enforces Enrollment after the enrollment end date - has been passed or upgrade_deadline is ended + mode (str): The course mode Returns: (list of CourseRunEnrollment, bool): A list of enrollment objects that were successfully @@ -265,8 +263,6 @@ def _enroll_learner_into_associated_programs(): user, runs, mode=mode, - force_enrollment=force_enrollment, - regen_auth_tokens=False, ) except ( UnknownEdxApiEnrollException, @@ -488,7 +484,6 @@ def defer_enrollment( runs=[from_enrollment.run], keep_failed_enrollments=True, mode=EDX_ENROLLMENT_AUDIT_MODE, - force_enrollment=True, ) return downgraded_enrollments, None diff --git a/courses/api_test.py b/courses/api_test.py index db6e8930f1..9e8f32dca9 100644 --- a/courses/api_test.py +++ b/courses/api_test.py @@ -344,14 +344,12 @@ def test_create_run_enrollments( mocker.patch("courses.tasks.subscribe_edx_course_emails.delay") successful_enrollments, edx_request_success = create_run_enrollments( - user, runs, mode=enrollment_mode, force_enrollment=False + user, runs, mode=enrollment_mode ) patched_edx_enroll.assert_called_once_with( user, runs, mode=enrollment_mode, - force_enrollment=False, - regen_auth_tokens=False, ) with django_capture_on_commit_callbacks(execute=True): @@ -399,8 +397,6 @@ def test_create_run_enrollments_upgrade( user, [test_enrollment.run], mode=EDX_ENROLLMENT_VERIFIED_MODE, - force_enrollment=False, - regen_auth_tokens=False, ) patched_send_enrollment_email.assert_called_once() @@ -486,8 +482,6 @@ def test_create_run_enrollments_api_fail(mocker, user, exception_cls): user, [run], mode=EDX_DEFAULT_ENROLLMENT_MODE, - force_enrollment=False, - regen_auth_tokens=False, ) patched_log_exception.assert_called_once() patched_send_enrollment_email.assert_not_called() @@ -535,8 +529,6 @@ def test_create_run_enrollments_enroll_api_fail( user, runs, mode=EDX_DEFAULT_ENROLLMENT_MODE, - force_enrollment=False, - regen_auth_tokens=False, ) patched_log_exception.assert_called_once() patched_send_enrollment_email.assert_not_called() @@ -568,8 +560,6 @@ def test_create_run_enrollments_creation_fail(mocker, user): user, runs, mode=EDX_DEFAULT_ENROLLMENT_MODE, - force_enrollment=False, - regen_auth_tokens=False, ) patched_log_exception.assert_called_once() patched_mail_api.send_course_run_enrollment_email.assert_not_called() @@ -1319,8 +1309,6 @@ def test_create_run_enrollments_upgrade_edx_request_failure(mocker, user): user, [test_course_run], mode=EDX_ENROLLMENT_AUDIT_MODE, - force_enrollment=False, - regen_auth_tokens=False, ) patched_send_enrollment_email.assert_called_once() @@ -1343,8 +1331,6 @@ def test_create_run_enrollments_upgrade_edx_request_failure(mocker, user): user, [test_course_run], mode=EDX_ENROLLMENT_VERIFIED_MODE, - force_enrollment=False, - regen_auth_tokens=False, ) assert edx_request_success is False diff --git a/courses/management/commands/create_verified_enrollment.py b/courses/management/commands/create_verified_enrollment.py index cacdfd818f..6e7748db13 100644 --- a/courses/management/commands/create_verified_enrollment.py +++ b/courses/management/commands/create_verified_enrollment.py @@ -143,7 +143,6 @@ def handle(self, *args, **options): [run], keep_failed_enrollments=options["keep_failed_enrollments"], mode=EDX_ENROLLMENT_VERIFIED_MODE, - force_enrollment=force_enrollment, ) if not successful_enrollments: raise CommandError("Failed to create the enrollment record") diff --git a/ecommerce/api.py b/ecommerce/api.py index 61497be0bf..f71e20425f 100644 --- a/ecommerce/api.py +++ b/ecommerce/api.py @@ -8,7 +8,6 @@ from django.db import transaction from django.db.models import Q from django.urls import reverse -from hubspot_sync.task_helpers import sync_hubspot_deal from ipware import get_client_ip from mitol.common.utils.datetime import now_in_utc from mitol.payment_gateway.api import CartItem as GatewayCartItem @@ -42,6 +41,7 @@ ) from ecommerce.tasks import perform_downgrade_from_order from flexiblepricing.api import determine_courseware_flexible_price_discount +from hubspot_sync.task_helpers import sync_hubspot_deal from main.constants import ( USER_MSG_TYPE_COURSE_NON_UPGRADABLE, USER_MSG_TYPE_DISCOUNT_INVALID, @@ -479,7 +479,6 @@ def downgrade_learner_from_order(order_id): runs=order.purchased_runs, keep_failed_enrollments=True, mode=EDX_ENROLLMENT_AUDIT_MODE, - force_enrollment=True, ) diff --git a/ecommerce/api_test.py b/ecommerce/api_test.py index e40edcb4d1..1119e5c451 100644 --- a/ecommerce/api_test.py +++ b/ecommerce/api_test.py @@ -414,9 +414,12 @@ def test_unenrollment_unenrolls_learner(mocker, user): unenroll_mock.assert_called() -def test_process_cybersource_payment_response(rf, mocker, user_client, user, products): +def test_process_cybersource_payment_response( + settings, rf, mocker, user_client, user, products +): """Test that ensures the response from Cybersource for an ACCEPTed payment updates the orders state""" + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" mocker.patch( "mitol.payment_gateway.api.PaymentGateway.validate_processor_response", return_value=True, diff --git a/ecommerce/mail_api_test.py b/ecommerce/mail_api_test.py index 2c17811ae2..010df368ad 100644 --- a/ecommerce/mail_api_test.py +++ b/ecommerce/mail_api_test.py @@ -18,7 +18,7 @@ def products(): def test_mail_api_task_called( - mocker, user, products, user_client, django_capture_on_commit_callbacks + settings, mocker, user, products, user_client, django_capture_on_commit_callbacks ): """ Tests that the Order model is properly calling the send email receipt task @@ -26,6 +26,7 @@ def test_mail_api_task_called( function should create a basket and process the order through to the point where the Order model itself will send the receipt email. """ + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" mock_delayed_send_ecommerce_order_receipt = mocker.patch( "ecommerce.tasks.send_ecommerce_order_receipt.delay" ) @@ -38,13 +39,14 @@ def test_mail_api_task_called( def test_mail_api_receipt_generation( - mocker, user, products, user_client, django_capture_on_commit_callbacks + settings, mocker, user, products, user_client, django_capture_on_commit_callbacks ): """ Tests email generation. Mocks actual message sending and then looks for some key data in the rendered template body (name from legal address, order ID, and line item unit price). """ + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" mock_send_message = mocker.patch("mitol.mail.api.send_message") with django_capture_on_commit_callbacks(execute=True): @@ -67,11 +69,14 @@ def test_mail_api_receipt_generation( assert str(lines[0].unit_price) in rendered_template.body -def test_mail_api_refund_email_generation(mocker, user, products, user_client): +def test_mail_api_refund_email_generation( + settings, mocker, user, products, user_client +): """ Tests email generation for the refund message. Generates a fulfilled order, then attemps to refund it after mocking the mail sender. """ + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" order = create_order_receipt(mocker, user, products, user_client) mock_send_message = mocker.patch("mitol.mail.api.send_message") diff --git a/ecommerce/models_test.py b/ecommerce/models_test.py index 4af345cc11..18b924cd67 100644 --- a/ecommerce/models_test.py +++ b/ecommerce/models_test.py @@ -200,12 +200,12 @@ def test_basket_discount_conversion(user, unlimited_discount): assert converted_discount == reconverted_discount -def test_order_refund(): +def test_order_refund(settings): """ Tests state change from fulfilled to refund. There should be a new Transaction record after the order has been refunded. """ - + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" with reversion.create_revision(): basket_item = BasketItemFactory.create() diff --git a/ecommerce/serializers_test.py b/ecommerce/serializers_test.py index 8e5b18089b..dc7f5bc907 100644 --- a/ecommerce/serializers_test.py +++ b/ecommerce/serializers_test.py @@ -348,7 +348,10 @@ def get_receipt_serializer_test_data(mocker, user, products, user_client): return (order, test_data) -def test_order_receipt_purchase_serializer(mocker, user, products, user_client): +def test_order_receipt_purchase_serializer( + settings, mocker, user, products, user_client +): + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" (order, test_data) = get_receipt_serializer_test_data( mocker, user, products, user_client ) @@ -358,7 +361,10 @@ def test_order_receipt_purchase_serializer(mocker, user, products, user_client): assert serialized_data == test_data["receipt"] -def test_order_receipt_purchaser_serializer(mocker, user, products, user_client): +def test_order_receipt_purchaser_serializer( + settings, mocker, user, products, user_client +): + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" (order, test_data) = get_receipt_serializer_test_data( mocker, user, products, user_client ) @@ -368,7 +374,8 @@ def test_order_receipt_purchaser_serializer(mocker, user, products, user_client) assert serialized_data == test_data["purchaser"] -def test_order_receipt_order_serializer(mocker, user, products, user_client): +def test_order_receipt_order_serializer(settings, mocker, user, products, user_client): + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" (order, test_data) = get_receipt_serializer_test_data( mocker, user, products, user_client ) @@ -379,7 +386,8 @@ def test_order_receipt_order_serializer(mocker, user, products, user_client): assert serialized_data == test_data["order"] -def test_order_receipt_lines_serializer(mocker, user, products, user_client): +def test_order_receipt_lines_serializer(settings, mocker, user, products, user_client): + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" (order, test_data) = get_receipt_serializer_test_data( mocker, user, products, user_client ) diff --git a/ecommerce/tasks_test.py b/ecommerce/tasks_test.py index d29a0ef7f9..3720a05632 100644 --- a/ecommerce/tasks_test.py +++ b/ecommerce/tasks_test.py @@ -16,7 +16,7 @@ def products(): def test_delayed_order_receipt_sends_email( - mocker, user, products, user_client, django_capture_on_commit_callbacks + settings, mocker, user, products, user_client, django_capture_on_commit_callbacks ): """ Tests that the Order model is properly calling the send email receipt task @@ -24,7 +24,7 @@ def test_delayed_order_receipt_sends_email( function should create a basket and process the order through to the point where the Order model itself will send the receipt email. """ - + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" mock_send_ecommerce_order_receipt = mocker.patch( "ecommerce.mail_api.send_ecommerce_order_receipt" ) @@ -35,14 +35,16 @@ def test_delayed_order_receipt_sends_email( mock_send_ecommerce_order_receipt.assert_called() -def test_delayed_order_refund_sends_email(mocker, user, products, user_client): +def test_delayed_order_refund_sends_email( + settings, mocker, user, products, user_client +): """ Tests that the Order model is properly calling the order refund email task rather than calling the mail_api version directly. The create_order_receipt function creates a fulfilled order, then we refund it and make sure the right task got called. """ - + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" mock_send_refund_email = mocker.patch( "ecommerce.mail_api.send_ecommerce_refund_message" ) diff --git a/ecommerce/views_test.py b/ecommerce/views_test.py index 9bdd8b1cd5..d0695beb1d 100644 --- a/ecommerce/views_test.py +++ b/ecommerce/views_test.py @@ -505,6 +505,7 @@ def test_start_checkout_with_invalid_discounts(user, user_client, products, disc ], ) def test_checkout_result( + settings, user, user_client, api_client, @@ -519,6 +520,7 @@ def test_checkout_result( Generates an order (using the API endpoint) and then cancels it using the endpoint. There shouldn't be any PendingOrders after that happens. """ + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" mocker.patch( "mitol.payment_gateway.api.PaymentGateway.validate_processor_response", return_value=True, @@ -830,13 +832,14 @@ def test_user_discounts_api(user_drf_client, admin_drf_client, discounts, user): def test_paid_and_unpaid_courserun_checkout( - user, user_client, user_drf_client, products + settings, user, user_client, user_drf_client, products ): """ Tests checking out a paid or unpaid course run: - If a course run is already paid, it should redirect to cart with 302 status code including a user message in the response cookies - Otherwise, it should be successful with 200 status code """ + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" product = products[0] basket = create_basket_with_product(user, product) order = PendingOrder.create_from_basket(basket) @@ -875,6 +878,7 @@ def test_paid_and_unpaid_courserun_checkout( ], ) def test_checkout_api_result( + settings, user, user_client, api_client, @@ -887,6 +891,7 @@ def test_checkout_api_result( """ Tests the proper handling of an order after receiving a valid Cybersource payment response. """ + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" mocker.patch( "mitol.payment_gateway.api.PaymentGateway.validate_processor_response", return_value=True, @@ -1008,10 +1013,11 @@ def test_non_upgradable_courserun_checkout( ) -def test_start_checkout_with_zero_value(user, user_client, products): +def test_start_checkout_with_zero_value(settings, user, user_client, products): """ Check that the checkout redirects the user to dashboard when basket price is zero """ + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" discount = DiscountFactory.create( discount_type=DISCOUNT_TYPE_PERCENT_OFF, amount=100 ) diff --git a/main/settings.py b/main/settings.py index 484429916f..20cb57d117 100644 --- a/main/settings.py +++ b/main/settings.py @@ -28,7 +28,7 @@ from main.celery_utils import OffsettingSchedule from main.sentry import init_sentry -VERSION = "0.70.5" +VERSION = "0.70.6" log = logging.getLogger() diff --git a/openedx/api.py b/openedx/api.py index a1b1d33d0d..50dabfc96e 100644 --- a/openedx/api.py +++ b/openedx/api.py @@ -608,8 +608,7 @@ def enroll_in_edx_course_runs( course_runs, *, mode=EDX_DEFAULT_ENROLLMENT_MODE, - force_enrollment=False, - regen_auth_tokens=True, + force_enrollment=True, ): """ Enrolls a user in edx course runs. If the user doesn't have a valid @@ -621,7 +620,6 @@ def enroll_in_edx_course_runs( mode (str): The course mode to enroll the user with force_enrollment (bool): If True, Enforces Enrollment after the enrollment end date has been passed or upgrade_deadline is ended - regen_auth_token (bool): Regenerate the auth tokens if the learner's are invalid Returns: list of edx_api.enrollments.models.Enrollment: @@ -631,48 +629,8 @@ def enroll_in_edx_course_runs( EdxApiEnrollErrorException: Raised if the underlying edX API HTTP request fails UnknownEdxApiEnrollException: Raised if an unknown error was encountered during the edX API request """ - username = None - if force_enrollment: - edx_client = get_edx_api_service_client() - username = user.username - else: - try: - edx_client = get_edx_api_client(user) - except (HTTPError, NoEdxApiAuthError) as exc: - log.exception( - "enroll_in_edx_course_runs got exception getting API client: %s %s", - type(exc), - exc, - ) - - if regen_auth_tokens and ( - "Bad Request" in str(exc) or type(exc) == NoEdxApiAuthError - ): - if OpenEdxApiAuth.objects.filter(user=user).count(): - OpenEdxApiAuth.objects.filter(user=user).delete() - user.refresh_from_db() - - try: - log.exception( - "enroll_in_edx_course_runs: creating new auth tokens for %s", - user, - ) - create_edx_auth_token(user) - user.refresh_from_db() - edx_client = get_edx_api_client(user) - except Exception as auth_exc: - log.exception( - "enroll_in_edx_course_runs: got exception creating new auth token: %s", - auth_exc, - ) - raise auth_exc - elif not regen_auth_tokens and ( - "Bad Request" in str(exc) or type(exc) == NoEdxApiAuthError - ): - regenerate_openedx_auth_tokens.delay(user.id) - raise exc - else: - raise exc + edx_client = get_edx_api_service_client() + username = user.username results = [] for course_run in course_runs: diff --git a/openedx/api_test.py b/openedx/api_test.py index b827a11c84..239b21fb90 100644 --- a/openedx/api_test.py +++ b/openedx/api_test.py @@ -421,8 +421,9 @@ def test_get_edx_api_client(mocker, settings, user): ) -def test_enroll_in_edx_course_runs(mocker, user): +def test_enroll_in_edx_course_runs(settings, mocker, user): """Tests that enroll_in_edx_course_runs uses the EdxApi client to enroll in course runs""" + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" mock_client = mocker.MagicMock() enroll_return_values = ["result1", "result2"] mock_client.enrollments.create_student_enrollment = mocker.Mock( @@ -435,14 +436,14 @@ def test_enroll_in_edx_course_runs(mocker, user): mock_client.enrollments.create_student_enrollment.assert_any_call( course_runs[0].courseware_id, mode=EDX_DEFAULT_ENROLLMENT_MODE, - username=None, - force_enrollment=False, + username=user.username, + force_enrollment=True, ) mock_client.enrollments.create_student_enrollment.assert_any_call( course_runs[1].courseware_id, mode=EDX_DEFAULT_ENROLLMENT_MODE, - username=None, - force_enrollment=False, + username=user.username, + force_enrollment=True, ) assert enroll_results == enroll_return_values @@ -464,11 +465,12 @@ def test_enroll_api_fail(mocker, user): enroll_in_edx_course_runs(user, [course_run]) -def test_enroll_pro_unknown_fail(mocker, user): +def test_enroll_pro_unknown_fail(settings, mocker, user): """ Tests that enroll_in_edx_course_runs raises an UnknownEdxApiEnrollException if an unexpected exception is encountered """ + settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" mock_client = mocker.MagicMock() mock_client.enrollments.create_student_enrollment = mocker.Mock( side_effect=ValueError("Unexpected error")