Skip to content

Commit acf9d0e

Browse files
authored
Move calls to create_user to earlier in the ecommerce process (#3337)
1 parent 49f21d2 commit acf9d0e

7 files changed

Lines changed: 89 additions & 24 deletions

File tree

b2b/api.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
)
5151
from main import constants as main_constants
5252
from main.utils import date_to_datetime
53-
from openedx.api import create_user
5453
from openedx.tasks import clone_courserun
5554

5655
log = logging.getLogger(__name__)
@@ -957,9 +956,9 @@ def _prepare_basket_for_b2b_enrollment(request, product: Product) -> Basket:
957956
"""
958957
from ecommerce.api import establish_basket # noqa: PLC0415
959958

960-
basket = establish_basket(request)
961-
# Clear the basket. For Unified Ecommerce, we may want to change this.
962-
# But MITx Online only allows one item per cart and not clearing it is confusing.
959+
# Establish and clear the basket. If the user doesn't have an edX username,
960+
# don't wait to create one.
961+
basket = establish_basket(request, no_delay=True)
963962
basket.basket_items.all().delete()
964963
basket.discounts.all().delete()
965964

@@ -1024,11 +1023,6 @@ def create_b2b_enrollment(request, product: Product):
10241023
if validation_error:
10251024
return validation_error
10261025

1027-
# Check for an edX user, and create one if there's not one
1028-
if not request.user.edx_username:
1029-
create_user(request.user)
1030-
request.user.refresh_from_db()
1031-
10321026
# Prepare the basket for enrollment
10331027
basket = _prepare_basket_for_b2b_enrollment(request, product)
10341028

b2b/views/v0/views_test.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,8 +337,9 @@ def test_b2b_contract_attachment_full_contract_with_used_code(mocker):
337337
def test_b2b_enroll(mocker, settings, user_has_edx_user, has_price, run_is_enrollable):
338338
"""Make sure that hitting the enroll endpoint actually results in enrollments"""
339339

340+
mocked_create_from_id = mocker.patch("openedx.tasks.create_user_from_id")
340341
mocker.patch("openedx.tasks.clone_courserun.delay")
341-
mocker.patch("openedx.api._create_edx_user_request")
342+
mocked_create_user_request = mocker.patch("openedx.api._create_edx_user_request")
342343
settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "a token" # noqa: S105
343344

344345
contract = ContractPageFactory.create(
@@ -354,15 +355,22 @@ def test_b2b_enroll(mocker, settings, user_has_edx_user, has_price, run_is_enrol
354355
courserun.live = False
355356
courserun.save()
356357

357-
user = UserFactory.create()
358-
user.b2b_contracts.add(contract)
358+
user = UserFactory.create(no_openedx_user=(not user_has_edx_user))
359359

360-
if not user_has_edx_user:
361-
user.openedx_users.all().delete()
360+
if user_has_edx_user:
361+
assert user.openedx_user
362+
else:
363+
assert not user.openedx_user
362364

365+
user.b2b_contracts.add(contract)
363366
user.save()
364367
user.refresh_from_db()
365368

369+
if user_has_edx_user:
370+
assert user.openedx_user
371+
else:
372+
assert not user.openedx_user
373+
366374
client = APIClient()
367375
client.force_login(user)
368376

@@ -384,5 +392,10 @@ def test_b2b_enroll(mocker, settings, user_has_edx_user, has_price, run_is_enrol
384392

385393
user.refresh_from_db()
386394

395+
if not user_has_edx_user:
396+
mocked_create_user_request.assert_called()
397+
mocked_create_from_id.assert_not_called()
398+
399+
del user.openedx_user
387400
assert user.edx_username
388401
assert CourseRunEnrollment.objects.filter(user=user, run=courserun).exists()

ecommerce/api.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@
7171
)
7272
from main.settings import ECOMMERCE_DEFAULT_PAYMENT_GATEWAY
7373
from main.utils import parse_supplied_date, redirect_with_user_message
74+
from openedx.api import create_user
7475
from openedx.constants import EDX_ENROLLMENT_AUDIT_MODE, EDX_ENROLLMENT_VERIFIED_MODE
76+
from openedx.tasks import create_user_from_id
7577

7678
log = logging.getLogger(__name__)
7779

@@ -445,11 +447,23 @@ def process_cybersource_payment_response(request, order):
445447
return return_message
446448

447449

448-
def establish_basket(request):
450+
def establish_basket(request, *, no_delay=False):
449451
"""
450-
Gets or creates the user's basket. (This may get some more logic later.)
452+
Gets or creates the user's basket. Queue creation of their edX user if necessary.
453+
454+
Kwargs:
455+
no_delay (bool): don't queue the edX user creation
451456
"""
452457
user = request.user
458+
459+
if not user.edx_username:
460+
if no_delay:
461+
create_user(user)
462+
# The openedx_user is a cached property, so delete the cached value
463+
del user.openedx_user
464+
else:
465+
create_user_from_id.delay(user.id)
466+
453467
(basket, is_new) = Basket.objects.get_or_create(user=user)
454468

455469
if is_new:

ecommerce/api_test.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
check_for_duplicate_discount_redemptions,
3030
create_verified_program_course_run_enrollment,
3131
create_verified_program_discount,
32+
establish_basket,
3233
get_auto_apply_discounts_for_basket,
3334
process_cybersource_payment_response,
3435
refund_order,
@@ -69,6 +70,7 @@
6970
from flexiblepricing.constants import FlexiblePriceStatus
7071
from flexiblepricing.factories import FlexiblePriceFactory, FlexiblePriceTierFactory
7172
from openedx.constants import EDX_ENROLLMENT_VERIFIED_MODE
73+
from openedx.factories import OpenEdxUserFactory
7274
from users.factories import UserFactory
7375

7476
pytestmark = [pytest.mark.django_db]
@@ -1139,3 +1141,46 @@ def test_get_auto_apply_discounts_respects_dates(user):
11391141

11401142
discounts = get_auto_apply_discounts_for_basket(basket.id)
11411143
assert discounts.count() == 0
1144+
1145+
1146+
@pytest.mark.parametrize(
1147+
"no_delay",
1148+
[
1149+
True,
1150+
False,
1151+
],
1152+
)
1153+
def test_establish_basket_calls_create_user(mocker, no_delay):
1154+
"""Test that establish_basket calls create_user if there's no edX username."""
1155+
1156+
if no_delay:
1157+
expected_run_mock = mocker.patch("ecommerce.api.create_user")
1158+
expected_skip_mock = mocker.patch("openedx.tasks.create_user_from_id.delay")
1159+
else:
1160+
expected_run_mock = mocker.patch("openedx.tasks.create_user_from_id.delay")
1161+
expected_skip_mock = mocker.patch("ecommerce.api.create_user")
1162+
1163+
user = UserFactory.create(no_openedx_api_auth=True, no_openedx_user=True)
1164+
request = RequestFactory().get("/")
1165+
request.user = user
1166+
1167+
basket = establish_basket(request, no_delay=no_delay)
1168+
1169+
expected_run_mock.assert_called_once()
1170+
expected_run_mock.reset_mock()
1171+
expected_skip_mock.assert_not_called()
1172+
1173+
OpenEdxUserFactory.create(user=user)
1174+
if not no_delay:
1175+
del user.openedx_user
1176+
1177+
basket.delete()
1178+
1179+
user.refresh_from_db()
1180+
request.user = user
1181+
1182+
assert user.edx_username
1183+
1184+
establish_basket(request)
1185+
1186+
assert not expected_run_mock.called

ecommerce/hooks/process_transaction_line.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import pluggy
66

77
from courses.models import CourseRun, PaidCourseRun, PaidProgram, Program
8-
from openedx.api import create_user
98
from openedx.constants import EDX_ENROLLMENT_VERIFIED_MODE
109

1110
hookimpl = pluggy.HookimplMarker("mitxonline")
@@ -26,11 +25,6 @@ def _create_courserun_enrollment(line) -> str | None:
2625
log.debug("Item purchased %s is not a course run, skipping", purchased_run)
2726
return None
2827

29-
# Check for an edX user, and create one if there's not one
30-
if not line.order.purchaser.edx_username:
31-
create_user(line.order.purchaser)
32-
line.order.purchaser.refresh_from_db()
33-
3428
create_run_enrollments(
3529
line.order.purchaser,
3630
[purchased_run],

ecommerce/mail_api_test.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from decimal import Decimal
22

33
import pytest
4+
import responses
45
import reversion
56

67
from ecommerce.factories import ProductFactory
@@ -38,6 +39,7 @@ def test_mail_api_task_called( # noqa: PLR0913
3839

3940

4041
@pytest.mark.skip_nplusone_check
42+
@responses.activate
4143
def test_mail_api_receipt_generation( # noqa: PLR0913
4244
settings, mocker, user, products, user_client, django_capture_on_commit_callbacks
4345
):
@@ -48,6 +50,7 @@ def test_mail_api_receipt_generation( # noqa: PLR0913
4850
"""
4951
settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" # noqa: S105
5052
mock_send_message = mocker.patch("mitol.mail.api.send_message")
53+
responses.get("https://fonts.googleapis.com/css")
5154

5255
with django_capture_on_commit_callbacks(execute=True):
5356
order = create_order_receipt(mocker, user, products, user_client)
@@ -64,6 +67,7 @@ def test_mail_api_receipt_generation( # noqa: PLR0913
6467

6568

6669
@pytest.mark.skip_nplusone_check
70+
@responses.activate
6771
def test_mail_api_refund_email_generation(
6872
settings, mocker, user, products, user_client
6973
):
@@ -73,6 +77,7 @@ def test_mail_api_refund_email_generation(
7377
"""
7478
settings.OPENEDX_SERVICE_WORKER_API_TOKEN = "mock_api_token" # noqa: S105
7579
order = create_order_receipt(mocker, user, products, user_client)
80+
responses.get("https://fonts.googleapis.com/css")
7681

7782
mock_send_message = mocker.patch("mitol.mail.api.send_message")
7883

ecommerce/views/legacy/views_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,7 +1180,7 @@ def test_start_checkout_and_ensure_edx_username_created(mocker, settings, produc
11801180
Check that checking out with a user that doesn't have an edx username
11811181
creates them an edx username
11821182
"""
1183-
mocked_create_user = mocker.patch("openedx.api.create_edx_user")
1183+
mocked_create_user = mocker.patch("openedx.tasks.create_user_from_id.delay")
11841184
mocker.patch("openedx.api.create_edx_auth_token")
11851185
user = UserFactory.create()
11861186
user.openedx_users.all().delete()
@@ -1204,7 +1204,7 @@ def test_start_checkout_and_ensure_edx_username_created(mocker, settings, produc
12041204
"run": order.lines.first().purchased_object.course.title,
12051205
}
12061206
)
1207-
mocked_create_user.assert_called_once()
1207+
mocked_create_user.assert_called()
12081208

12091209

12101210
@pytest.mark.parametrize("use_redemption_type_flags", [True, False])

0 commit comments

Comments
 (0)