diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 4918aaf8081cb97b94e94f860cd3b084375c971c..541d387f5db8d3b91ebff1bd2f9766ee63983f27 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -55,6 +55,13 @@ from simple_history.models import HistoricalRecords from slumber.exceptions import HttpClientError, HttpServerError from user_util import user_util +from openedx_events.learning.data import ( + CourseData, + CourseEnrollmentData, + UserData, + UserPersonalData, +) +from openedx_events.learning.signals import COURSE_ENROLLMENT_CREATED import openedx.core.djangoapps.django_comment_common.comment_client as cc from common.djangoapps.course_modes.models import CourseMode, get_cosmetic_verified_display_price from common.djangoapps.student.emails import send_proctoring_requirements_email @@ -1569,9 +1576,16 @@ class CourseEnrollment(models.Model): # All the server-side checks for whether a user is allowed to enroll. try: course = CourseOverview.get_from_id(course_key) + course_data = CourseData( + course_key=course.id, + display_name=course.display_name, + ) except CourseOverview.DoesNotExist: # This is here to preserve legacy behavior which allowed enrollment in courses # announced before the start of content creation. + course_data = CourseData( + course_key=course_key, + ) if check_access: log.warning("User %s failed to enroll in non-existent course %s", user.username, str(course_key)) raise NonExistentCourseError # lint-amnesty, pylint: disable=raise-missing-from @@ -1607,6 +1621,25 @@ class CourseEnrollment(models.Model): enrollment.update_enrollment(is_active=True, mode=mode, enterprise_uuid=enterprise_uuid) enrollment.send_signal(EnrollStatusChange.enroll) + # Announce user's enrollment + COURSE_ENROLLMENT_CREATED.send_event( + enrollment=CourseEnrollmentData( + user=UserData( + pii=UserPersonalData( + username=user.username, + email=user.email, + name=user.profile.name, + ), + id=user.id, + is_active=user.is_active, + ), + course=course_data, + mode=enrollment.mode, + is_active=enrollment.is_active, + creation_date=enrollment.created, + ) + ) + return enrollment @classmethod diff --git a/common/djangoapps/student/tests/test_enrollment.py b/common/djangoapps/student/tests/test_enrollment.py index 28fe2a9f786c08b0094ddfa73d8bf38939feff7d..5165ca57d2e8f742d11691d99122672014c149f2 100644 --- a/common/djangoapps/student/tests/test_enrollment.py +++ b/common/djangoapps/student/tests/test_enrollment.py @@ -10,6 +10,7 @@ import ddt import pytest from django.conf import settings from django.urls import reverse +from openedx_events.tests.utils import OpenEdxEventsTestMixin from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory @@ -30,11 +31,13 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @ddt.ddt @patch.dict('django.conf.settings.FEATURES', {'ENABLE_SPECIAL_EXAMS': True}) @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') -class EnrollmentTest(UrlResetMixin, SharedModuleStoreTestCase): +class EnrollmentTest(UrlResetMixin, SharedModuleStoreTestCase, OpenEdxEventsTestMixin): """ Test student enrollment, especially with different course modes. """ + ENABLED_OPENEDX_EVENTS = [] + USERNAME = "Bob" EMAIL = "bob@example.com" PASSWORD = "edx" @@ -42,7 +45,14 @@ class EnrollmentTest(UrlResetMixin, SharedModuleStoreTestCase): @classmethod def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ super().setUpClass() + cls.start_events_isolation() cls.course = CourseFactory.create() cls.course_limited = CourseFactory.create() cls.proctored_course = CourseFactory( diff --git a/common/djangoapps/student/tests/test_events.py b/common/djangoapps/student/tests/test_events.py index 938952f571fce4cd37e9a3ba7c4f5650576bdc7b..00b0b62784cb58eb0ccbd2a0e8e5e0219f5ed93e 100644 --- a/common/djangoapps/student/tests/test_events.py +++ b/common/djangoapps/student/tests/test_events.py @@ -10,10 +10,23 @@ from django.db.utils import IntegrityError from django.test import TestCase from django_countries.fields import Country -from common.djangoapps.student.models import CourseEnrollmentAllowed -from common.djangoapps.student.tests.factories import CourseEnrollmentAllowedFactory, UserFactory +from common.djangoapps.student.models import CourseEnrollmentAllowed, CourseEnrollment +from common.djangoapps.student.tests.factories import CourseEnrollmentAllowedFactory, UserFactory, UserProfileFactory from common.djangoapps.student.tests.tests import UserSettingsEventTestMixin +from openedx_events.learning.data import ( + CourseData, + CourseEnrollmentData, + UserData, + UserPersonalData, +) +from openedx_events.learning.signals import COURSE_ENROLLMENT_CREATED +from openedx_events.tests.utils import OpenEdxEventsTestMixin +from openedx.core.djangolib.testing.utils import skip_unless_lms + +from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory + class TestUserProfileEvents(UserSettingsEventTestMixin, TestCase): """ @@ -179,3 +192,87 @@ class TestUserEvents(UserSettingsEventTestMixin, TestCase): # CEAs shouldn't have been affected assert CourseEnrollmentAllowed.objects.count() == 1 assert CourseEnrollmentAllowed.objects.filter(email='test@edx.org').count() == 1 + + +@skip_unless_lms +class EnrollmentEventsTest(SharedModuleStoreTestCase, OpenEdxEventsTestMixin): + """ + Tests for the Open edX Events associated with the enrollment process through the enroll method. + + This class guarantees that the following events are sent during the user's enrollment, with + the exact Data Attributes as the event definition stated: + + - COURSE_ENROLLMENT_CREATED: sent after the user's enrollment. + """ + + ENABLED_OPENEDX_EVENTS = ["org.openedx.learning.course.enrollment.created.v1"] + + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + + def setUp(self): # pylint: disable=arguments-differ + super().setUp() + self.course = CourseFactory.create() + self.user = UserFactory.create( + username="test", + email="test@example.com", + password="password", + ) + self.user_profile = UserProfileFactory.create(user=self.user, name="Test Example") + self.receiver_called = False + + def _event_receiver_side_effect(self, **kwargs): # pylint: disable=unused-argument + """ + Used show that the Open edX Event was called by the Django signal handler. + """ + self.receiver_called = True + + def test_enrollment_created_event_emitted(self): + """ + Test whether the student enrollment event is sent after the user's + enrollment process. + + Expected result: + - COURSE_ENROLLMENT_CREATED is sent and received by the mocked receiver. + - The arguments that the receiver gets are the arguments sent by the event + except the metadata generated on the fly. + """ + event_receiver = mock.Mock(side_effect=self._event_receiver_side_effect) + COURSE_ENROLLMENT_CREATED.connect(event_receiver) + + enrollment = CourseEnrollment.enroll(self.user, self.course.id) + + self.assertTrue(self.receiver_called) + self.assertDictContainsSubset( + { + "signal": COURSE_ENROLLMENT_CREATED, + "sender": None, + "enrollment": CourseEnrollmentData( + user=UserData( + pii=UserPersonalData( + username=self.user.username, + email=self.user.email, + name=self.user.profile.name, + ), + id=self.user.id, + is_active=self.user.is_active, + ), + course=CourseData( + course_key=self.course.id, + display_name=self.course.display_name, + ), + mode=enrollment.mode, + is_active=enrollment.is_active, + creation_date=enrollment.created, + ), + }, + event_receiver.call_args.kwargs + ) diff --git a/openedx/core/djangoapps/user_authn/views/login.py b/openedx/core/djangoapps/user_authn/views/login.py index 6b31252c17fc5c174e4148409e50c81ad68cba79..a92cad9e05a4746066fe233c6c7d6a2920086814 100644 --- a/openedx/core/djangoapps/user_authn/views/login.py +++ b/openedx/core/djangoapps/user_authn/views/login.py @@ -29,6 +29,8 @@ from edx_django_utils.monitoring import set_custom_attribute from ratelimit.decorators import ratelimit from rest_framework.views import APIView +from openedx_events.learning.data import UserData, UserPersonalData +from openedx_events.learning.signals import SESSION_LOGIN_COMPLETED from common.djangoapps.edxmako.shortcuts import render_to_response from openedx.core.djangoapps.password_policy import compliance as password_policy_compliance from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers @@ -298,6 +300,19 @@ def _handle_successful_authentication_and_login(user, request): django_login(request, user) request.session.set_expiry(604800 * 4) log.debug("Setting user session expiry to 4 weeks") + + # Announce user's login + SESSION_LOGIN_COMPLETED.send_event( + user=UserData( + pii=UserPersonalData( + username=user.username, + email=user.email, + name=user.profile.name, + ), + id=user.id, + is_active=user.is_active, + ), + ) except Exception as exc: AUDIT_LOG.critical("Login failed - Could not create session. Is memcached running?") log.critical("Login failed - Could not create session. Is memcached running?") diff --git a/openedx/core/djangoapps/user_authn/views/register.py b/openedx/core/djangoapps/user_authn/views/register.py index c7ce726b7cad9b983c5dc6eadcfba905874c07e8..23e0c413b5006a49d998c865e7537629b031f7cc 100644 --- a/openedx/core/djangoapps/user_authn/views/register.py +++ b/openedx/core/djangoapps/user_authn/views/register.py @@ -23,6 +23,8 @@ from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie from django.views.decorators.debug import sensitive_post_parameters from edx_django_utils.monitoring import set_custom_attribute from edx_toggles.toggles import LegacyWaffleFlag, LegacyWaffleFlagNamespace +from openedx_events.learning.data import UserData, UserPersonalData +from openedx_events.learning.signals import STUDENT_REGISTRATION_COMPLETED from pytz import UTC from ratelimit.decorators import ratelimit from requests import HTTPError @@ -252,6 +254,18 @@ def create_account_with_params(request, params): # Announce registration REGISTER_USER.send(sender=None, user=user, registration=registration) + STUDENT_REGISTRATION_COMPLETED.send_event( + user=UserData( + pii=UserPersonalData( + username=user.username, + email=user.email, + name=user.profile.name, + ), + id=user.id, + is_active=user.is_active, + ), + ) + create_comments_service_user(user) try: diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_events.py b/openedx/core/djangoapps/user_authn/views/tests/test_events.py new file mode 100644 index 0000000000000000000000000000000000000000..acec4a935d6032bec88a8b1919c1f327a56cba02 --- /dev/null +++ b/openedx/core/djangoapps/user_authn/views/tests/test_events.py @@ -0,0 +1,183 @@ +""" +Test classes for the events sent in the registration process. + +Classes: + RegistrationEventTest: Test event sent after registering a user through the + user API. + LoginSessionEventTest: Test event sent after creating the user's login session + user through the user API. +""" +from unittest.mock import Mock + +from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user +from django.urls import reverse +from openedx_events.learning.data import UserData, UserPersonalData +from openedx_events.learning.signals import SESSION_LOGIN_COMPLETED, STUDENT_REGISTRATION_COMPLETED +from openedx_events.tests.utils import OpenEdxEventsTestMixin + +from common.djangoapps.student.tests.factories import UserFactory, UserProfileFactory +from openedx.core.djangoapps.user_api.tests.test_views import UserAPITestCase +from openedx.core.djangolib.testing.utils import skip_unless_lms + + +@skip_unless_lms +class RegistrationEventTest(UserAPITestCase, OpenEdxEventsTestMixin): + """ + Tests for the Open edX Events associated with the registration process through + the registration view. + + This class guarantees that the following events are sent after registering + a user, with the exact Data Attributes as the event definition stated: + + - STUDENT_REGISTRATION_COMPLETED: after the user's registration has been + completed. + """ + + ENABLED_OPENEDX_EVENTS = ["org.openedx.learning.student.registration.completed.v1"] + + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + So the Open edX Events Isolation starts, the setUpClass must be explicitly + called with the method that executes the isolation. We do this to avoid + MRO resolution conflicts with other sibling classes while ensuring the + isolation process begins. + """ + super().setUpClass() + cls.start_events_isolation() + + def setUp(self): # pylint: disable=arguments-differ + super().setUp() + self.url = reverse("user_api_registration") + self.user_info = { + "email": "user@example.com", + "name": "Test User", + "username": "test", + "password": "password", + "honor_code": "true", + } + self.receiver_called = False + + def _event_receiver_side_effect(self, **kwargs): # pylint: disable=unused-argument + """ + Used show that the Open edX Event was called by the Django signal handler. + """ + self.receiver_called = True + + def test_send_registration_event(self): + """ + Test whether the student registration event is sent during the user's + registration process. + + Expected result: + - STUDENT_REGISTRATION_COMPLETED is sent and received by the mocked receiver. + - The arguments that the receiver gets are the arguments sent by the event + except the metadata generated on the fly. + """ + event_receiver = Mock(side_effect=self._event_receiver_side_effect) + STUDENT_REGISTRATION_COMPLETED.connect(event_receiver) + + self.client.post(self.url, self.user_info) + + user = User.objects.get(username=self.user_info.get("username")) + self.assertTrue(self.receiver_called) + self.assertDictContainsSubset( + { + "signal": STUDENT_REGISTRATION_COMPLETED, + "sender": None, + "user": UserData( + pii=UserPersonalData( + username=user.username, + email=user.email, + name=user.profile.name, + ), + id=user.id, + is_active=user.is_active, + ), + }, + event_receiver.call_args.kwargs + ) + + +@skip_unless_lms +class LoginSessionEventTest(UserAPITestCase, OpenEdxEventsTestMixin): + """ + Tests for the Open edX Events associated with the login process through the + login_user view. + + This class guarantees that the following events are sent after the user's + session creation, with the exact Data Attributes as the event definition + stated: + + - SESSION_LOGIN_COMPLETED: after login has been completed. + """ + + ENABLED_OPENEDX_EVENTS = ["org.openedx.learning.auth.session.login.completed.v1"] + + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + + def setUp(self): # pylint: disable=arguments-differ + super().setUp() + self.url = reverse("user_api_login_session", kwargs={"api_version": "v1"}) + self.user = UserFactory.create( + username="test", + email="test@example.com", + password="password", + ) + self.user_profile = UserProfileFactory.create(user=self.user, name="Test Example") + self.receiver_called = True + + def _event_receiver_side_effect(self, **kwargs): # pylint: disable=unused-argument + """ + Used show that the Open edX Event was called by the Django signal handler. + """ + self.receiver_called = True + + def test_send_login_event(self): + """ + Test whether the student login event is sent after the user's + login process. + + Expected result: + - SESSION_LOGIN_COMPLETED is sent and received by the mocked receiver. + - The arguments that the receiver gets are the arguments sent by the event + except the metadata generated on the fly. + """ + event_receiver = Mock(side_effect=self._event_receiver_side_effect) + SESSION_LOGIN_COMPLETED.connect(event_receiver) + data = { + "email": "test@example.com", + "password": "password", + } + + self.client.post(self.url, data) + + user = User.objects.get(username=self.user.username) + self.assertTrue(self.receiver_called) + self.assertDictContainsSubset( + { + "signal": SESSION_LOGIN_COMPLETED, + "sender": None, + "user": UserData( + pii=UserPersonalData( + username=user.username, + email=user.email, + name=user.profile.name, + ), + id=user.id, + is_active=user.is_active, + ), + }, + event_receiver.call_args.kwargs + ) diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_login.py b/openedx/core/djangoapps/user_authn/views/tests/test_login.py index 50827e65404da699575e6cb677eff40157af87b3..8c5a08521514b76070a8f78ef9680a1ee0896310 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_login.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_login.py @@ -21,6 +21,7 @@ from django.urls import NoReverseMatch, reverse from edx_toggles.toggles.testutils import override_waffle_flag, override_waffle_switch from freezegun import freeze_time from common.djangoapps.student.tests.factories import RegistrationFactory, UserFactory, UserProfileFactory +from openedx_events.tests.utils import OpenEdxEventsTestMixin from openedx.core.djangoapps.password_policy.compliance import ( NonCompliantPasswordException, @@ -44,11 +45,13 @@ from common.djangoapps.util.password_policy_validators import DEFAULT_MAX_PASSWO @ddt.ddt -class LoginTest(SiteMixin, CacheIsolationTestCase): +class LoginTest(SiteMixin, CacheIsolationTestCase, OpenEdxEventsTestMixin): """ Test login_user() view """ + ENABLED_OPENEDX_EVENTS = [] + ENABLED_CACHES = ['default'] LOGIN_FAILED_WARNING = 'Email or password is incorrect' ACTIVATE_ACCOUNT_WARNING = 'In order to sign in, you need to activate your account' @@ -56,6 +59,17 @@ class LoginTest(SiteMixin, CacheIsolationTestCase): user_email = 'test@edx.org' password = 'test_password' + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + def setUp(self): """Setup a test user along with its registration and profile""" super().setUp() @@ -954,13 +968,26 @@ class LoginTest(SiteMixin, CacheIsolationTestCase): @ddt.ddt @skip_unless_lms -class LoginSessionViewTest(ApiTestCase): +class LoginSessionViewTest(ApiTestCase, OpenEdxEventsTestMixin): """Tests for the login end-points of the user API. """ + ENABLED_OPENEDX_EVENTS = [] + USERNAME = "bob" EMAIL = "bob@example.com" PASSWORD = "password" + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + def setUp(self): super().setUp() self.url = reverse("user_api_login_session", kwargs={'api_version': 'v1'}) diff --git a/openedx/core/djangoapps/user_authn/views/tests/test_register.py b/openedx/core/djangoapps/user_authn/views/tests/test_register.py index 869a270c399e35eae24109ef48557e41eb659b40..fa1933d53bb486c0b5addff701d1b7a71092c069 100644 --- a/openedx/core/djangoapps/user_authn/views/tests/test_register.py +++ b/openedx/core/djangoapps/user_authn/views/tests/test_register.py @@ -17,6 +17,7 @@ from django.test.utils import override_settings from django.urls import reverse from pytz import UTC from social_django.models import Partial, UserSocialAuth +from openedx_events.tests.utils import OpenEdxEventsTestMixin from edx_toggles.toggles.testutils import override_waffle_flag from openedx.core.djangoapps.site_configuration.helpers import get_value @@ -69,12 +70,16 @@ from common.djangoapps.util.password_policy_validators import ( @ddt.ddt @skip_unless_lms -class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCase, RetirementTestCase): +class RegistrationViewValidationErrorTest( + ThirdPartyAuthTestMixin, UserAPITestCase, RetirementTestCase, OpenEdxEventsTestMixin +): """ Tests for catching duplicate email and username validation errors within the registration end-points of the User API. """ + ENABLED_OPENEDX_EVENTS = [] + maxDiff = None USERNAME = "bob" @@ -88,6 +93,17 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa COUNTRY = "us" GOALS = "Learn all the things!" + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + def setUp(self): # pylint: disable=arguments-differ super().setUp() self.url = reverse("user_api_registration") @@ -423,9 +439,13 @@ class RegistrationViewValidationErrorTest(ThirdPartyAuthTestMixin, UserAPITestCa @ddt.ddt @skip_unless_lms -class RegistrationViewTestV1(ThirdPartyAuthTestMixin, UserAPITestCase): +class RegistrationViewTestV1( + ThirdPartyAuthTestMixin, UserAPITestCase, OpenEdxEventsTestMixin +): """Tests for the registration end-points of the User API. """ + ENABLED_OPENEDX_EVENTS = [] + maxDiff = None USERNAME = "bob" @@ -486,6 +506,17 @@ class RegistrationViewTestV1(ThirdPartyAuthTestMixin, UserAPITestCase): ] link_template = "<a href='/honor' rel='noopener' target='_blank'>{link_label}</a>" + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + def setUp(self): # pylint: disable=arguments-differ super().setUp() self.url = reverse("user_api_registration") @@ -1815,6 +1846,17 @@ class RegistrationViewTestV2(RegistrationViewTestV1): # pylint: disable=test-inherits-tests + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + def setUp(self): # pylint: disable=arguments-differ super(RegistrationViewTestV1, self).setUp() # lint-amnesty, pylint: disable=bad-super-call self.url = reverse("user_api_registration_v2") @@ -2043,16 +2085,31 @@ class RegistrationViewTestV2(RegistrationViewTestV1): @httpretty.activate @ddt.ddt -class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTestCase): +class ThirdPartyRegistrationTestMixin( + ThirdPartyOAuthTestMixin, CacheIsolationTestCase, OpenEdxEventsTestMixin +): """ Tests for the User API registration endpoint with 3rd party authentication. """ CREATE_USER = False + ENABLED_OPENEDX_EVENTS = [] + ENABLED_CACHES = ['default'] __test__ = False + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + def setUp(self): super().setUp() self.url = reverse('user_api_registration') @@ -2209,11 +2266,25 @@ class ThirdPartyRegistrationTestMixin(ThirdPartyOAuthTestMixin, CacheIsolationTe @skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled") class TestFacebookRegistrationView( - ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinFacebook, TransactionTestCase + ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinFacebook, TransactionTestCase, OpenEdxEventsTestMixin ): """Tests the User API registration endpoint with Facebook authentication.""" + + ENABLED_OPENEDX_EVENTS = [] + __test__ = True + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + def test_social_auth_exception(self): """ According to the do_auth method in social_core.backends.facebook.py, @@ -2227,21 +2298,48 @@ class TestFacebookRegistrationView( @skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled") class TestGoogleRegistrationView( - ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase + ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase, OpenEdxEventsTestMixin ): """Tests the User API registration endpoint with Google authentication.""" + + ENABLED_OPENEDX_EVENTS = [] + __test__ = True + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + @ddt.ddt -class RegistrationValidationViewTests(test_utils.ApiTestCase): +class RegistrationValidationViewTests(test_utils.ApiTestCase, OpenEdxEventsTestMixin): """ Tests for validity of user data in registration forms. """ + ENABLED_OPENEDX_EVENTS = [] + endpoint_name = 'registration_validation' path = reverse(endpoint_name) + @classmethod + def setUpClass(cls): + """ + Set up class method for the Test class. + + This method starts manually events isolation. Explanation here: + openedx/core/djangoapps/user_authn/views/tests/test_events.py#L44 + """ + super().setUpClass() + cls.start_events_isolation() + def setUp(self): super().setUp() cache.clear() diff --git a/requirements/edx/base.in b/requirements/edx/base.in index 1c3d9af99fec30620ff1e712b99996da65267cf8..4c3d1897d72b1a793ac997ab1da4a1e237f1db5e 100644 --- a/requirements/edx/base.in +++ b/requirements/edx/base.in @@ -122,6 +122,7 @@ nltk # Natural language processing; used by the c nodeenv # Utility for managing Node.js environments; we use this for deployments and testing oauthlib # OAuth specification support for authenticating via LTI or other Open edX services openedx-calc # Library supporting mathematical calculations for Open edX +openedx-events # Open edX Events from Hooks Extension Framework (OEP-50) ora2 piexif # Exif image metadata manipulation, used in the profile_images app Pillow # Image manipulation library; used for course assets, profile images, invoice PDFs, etc. diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index e64164e8ca0e475c0d9ab134e1f3f847e5717332..b9b52e0c74a6e5ce92c9915d9543af7327a3b5ca 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -53,6 +53,7 @@ attrs==21.2.0 # -r requirements/edx/base.in # aiohttp # edx-ace + # openedx-events babel==2.9.1 # via # -r requirements/edx/base.in @@ -220,6 +221,7 @@ django==2.2.24 # help-tokens # jsonfield2 # lti-consumer-xblock + # openedx-events # ora2 # rest-condition # super-csv @@ -469,6 +471,7 @@ edx-opaque-keys[django]==2.2.2 # edx-user-state-client # edx-when # lti-consumer-xblock + # openedx-events # ora2 # xmodule edx-organizations==6.10.0 @@ -702,6 +705,8 @@ oauthlib==3.0.1 # social-auth-core openedx-calc==2.0.1 # via -r requirements/edx/base.in +openedx-events==0.5.1 + # via -r requirements/edx/base.in ora2==3.6.20 # via -r requirements/edx/base.in packaging==21.0 diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index dbf6234ee7519714a4ec21d3b1ea955560c56e8f..6ace173a4082b756a083c425f859b3a1e2f75b3c 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -74,6 +74,7 @@ attrs==21.2.0 # aiohttp # edx-ace # jsonschema + # openedx-events # pytest babel==2.9.1 # via @@ -298,6 +299,7 @@ django==2.2.24 # help-tokens # jsonfield2 # lti-consumer-xblock + # openedx-events # ora2 # rest-condition # super-csv @@ -574,6 +576,7 @@ edx-opaque-keys[django]==2.2.2 # edx-user-state-client # edx-when # lti-consumer-xblock + # openedx-events # ora2 # xmodule edx-organizations==6.10.0 @@ -929,6 +932,8 @@ oauthlib==3.0.1 # social-auth-core openedx-calc==2.0.1 # via -r requirements/edx/testing.txt +openedx-events==0.5.1 + # via -r requirements/edx/testing.txt ora2==3.6.20 # via -r requirements/edx/testing.txt packaging==21.0 diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index 66b6f71350e0e72ae2d8595749782ef21748a4dc..533b39582e4e5bb3ed81f2e6ee199cef42b3beb3 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -68,6 +68,7 @@ attrs==21.2.0 # -r requirements/edx/base.txt # aiohttp # edx-ace + # openedx-events # pytest babel==2.9.1 # via @@ -283,6 +284,7 @@ distlib==0.3.2 # help-tokens # jsonfield2 # lti-consumer-xblock + # openedx-events # ora2 # rest-condition # super-csv @@ -556,6 +558,7 @@ edx-opaque-keys[django]==2.2.2 # edx-user-state-client # edx-when # lti-consumer-xblock + # openedx-events # ora2 # xmodule edx-organizations==6.10.0 @@ -877,6 +880,8 @@ oauthlib==3.0.1 # social-auth-core openedx-calc==2.0.1 # via -r requirements/edx/base.txt +openedx-events==0.5.1 + # via -r requirements/edx/base.txt ora2==3.6.20 # via -r requirements/edx/base.txt packaging==21.0